harness_portability · diff

v1.0.11 to v1.0.11

1 added, 1 removed. Audit A to A.

---
name: harness_portability
description: Cross-agent-harness and cross-OS portability review for runtime artefacts bundled inside skills and plugins. Use when creating, editing, reviewing, or troubleshooting shell scripts, Bash scripts, Python helpers, Node helpers, hooks, Codex hook layering, Claude hook configuration, shared plugin hook scripts, MCP servers, command wrappers, setup or install flows, plugin wiring, agent and subagent definitions, agent frontmatter fields, tool allowlists, read-only agent enforcement, agent TOML generation, skill wording, execution instructions, initialization instructions, configuration instructions, path handling, environment variables, permissions, or provider-specific OpenAI Codex, Anthropic Claude, Cursor, Google Antigravity, and SST OpenCode behavior (including Antigravity's native .agents/ workspace tree, its per-product ~/.gemini global roots, its declarative hooks.json and plugin.json bundles, and OpenCode plugin hooks, the tool.execute.before block model, its ~/.config/opencode config tree, and its discovery of Claude/agents skill directories) that must work across agent harnesses, macOS, and Linux.
version: 1.0.11
author: Andreas F. Hoffmann
license: MIT
---
# harness_portability
<harness_portability>
<objective>
Keep bundled skill and plugin runtime artefacts portable across agent harnesses and operating systems. Apply this skill whenever a change creates or edits scripts, hooks, agent definitions, MCP helpers, command wrappers, setup flows, or the skill/plugin wording that tells future agents how to execute, initialize, or configure those artefacts.
</objective>
<scope>
<included_artefacts>
Apply these rules to Bash, POSIX shell, Python, JavaScript, TypeScript, Node.js, and other executable files shipped inside skills, agents, commands, hooks, MCP servers, plugin directories, and their supporting resources. Apply them equally to prose that wires those files into agent workflows, and to agent and subagent definition files themselves, whose frontmatter and body each harness parses under its own schema.
</included_artefacts>
<target_harnesses>
Treat OpenAI Codex and Anthropic Claude as the primary provider targets for every surface, and include Cursor and SST OpenCode for the agent-definition surface their native loaders exercise. OpenCode is a full target across skills, agents, commands, and hooks whose model diverges enough — a `~/.config/opencode` tree instead of `~/.<tool>`, discovery of other harnesses' skill directories, and code-only hooks — that its specifics live in the sections below. Google Antigravity is a full target on the same footing across skills, agents, rules and command workflows, hooks, plugin bundles, and supervised background sidecars, diverging in its own way: a native `.agents/` workspace tree, global roots that differ by artefact class, declarative `hooks.json` and `plugin.json` bundles, and a separate Python SDK whose lifecycle hooks are a second hook surface — so its specifics also live in the sections below. Gemini CLI is no longer a supported target: Google retired that consumer surface on 18 June 2026, and Antigravity is the Google harness this skill targets in its place. That is why no section below carries Gemini CLI paths, frontmatter schemas, tool names, or deploy steps — those details are dropped deliberately rather than missing, so read their absence as the support decision and route Google-surface work to Antigravity instead of restoring them. Standard/Enterprise and paid API-key access to Gemini models sit outside this consumer-target scope rather than being deploy targets of their own. Add further harnesses as concrete practice reveals new compatibility requirements.
</target_harnesses>
<target_operating_systems>
Support macOS and Linux by default. Use portable APIs and runtime feature detection when behavior differs between the two systems.
</target_operating_systems>
</scope>
<policy>
<rule>Design scripts and wiring for the harness that will run the published skill or plugin, not only for the harness currently doing the implementation work.</rule>
<rule>Use official provider documentation before encoding provider-specific behavior for OpenAI Codex, Anthropic Claude, or another targeted harness. Prefer current official docs over memory, observed behavior in one session, or assumptions from another agent surface.</rule>
<rule>State the provider documentation source or the verification gap when a change depends on harness-specific execution, initialization, configuration, filesystem, environment, permission, or tool-discovery behavior.</rule>
<rule>Compose cross-harness behavior as a union of native fields: when one harness reads a field the others ignore — Claude reads a `tools:` allowlist, Codex reads `sandbox_mode` — carry each harness's native field side by side in the shared artefact and let every other harness ignore the foreign ones. Verify unknown-field tolerance on every target first, and reach any target that cannot safely read a shared multi-harness file — whether it rejects an unknown key outright or passes it through to its provider — through a generated variant instead of the shared file.</rule>
<rule>Scope a behavior or prose rule to a single harness as a sanctioned cross-harness-compatibility mechanism — the behavior-and-prose sibling of the rule that composes cross-harness behavior as a union of native fields. Where that rule carries each harness's native config fields side by side, this one scopes an instruction to the one harness needing special attention — a missing tool, a divergent execution model, a different default — so every other harness keeps the shared instruction and its UX while the special harness avoids an error or degraded behavior. The carve-out serves both compatibility goals at once: roughly equivalent UX across the supported harnesses, and error-or-degradation avoidance in the single harness that needs the special handling. Key the carve-out on the agent's actual capability rather than the harness identity when the triggering harness property is itself changing across versions — a tool being added or removed — so the clause stays correct as the harness evolves; keying on the harness identity is acceptable when that property is stable.</rule>
<rule>Treat agent and subagent definition files as their own portability surface: confirm each target harness's frontmatter schema tolerance, tool naming, and agent registration mechanism before shipping a shared definition, and keep role-critical policy in the agent body so it survives harnesses that run the role inline.</rule>
<rule>Model hook wiring as a layered runtime surface. Identify every hook source the target harness can load, then choose one intentional activation path for each hook behavior unless duplicate execution is deliberate.</rule>
<rule>Use provider-specific hook configuration files for provider-specific schemas, and keep the executable hook script shared when the same policy should run in multiple harnesses.</rule>
<rule>Confirm a target harness loads plugin-bundled hooks at runtime before shipping a blocking or lifecycle hook inside a plugin, and document the trust, enablement, or reload step that makes the hook active.</rule>
<rule>Use POSIX shell features for shell scripts unless the script declares and checks for a stronger shell requirement such as Bash. Use Python standard-library APIs for path, JSON, subprocess, temporary-file, and filesystem operations when they are more portable than shell pipelines.</rule>
<rule>Resolve paths relative to the script, skill, plugin, or explicit user-provided root. Use environment variables and documented harness inputs for configuration; keep user-specific absolute paths out of published artefacts.</rule>
<rule>Handle spaces, quotes, newlines, and special characters in file paths and user-provided values. Quote shell expansions, pass subprocess arguments as arrays where the language supports it, and keep data separate from command strings.</rule>
<rule>Use feature detection for external commands, optional tools, shells, package managers, and OS-specific utilities. Provide a clear error message or documented fallback when a required dependency is unavailable.</rule>
<rule>Treat GNU-only flags, BSD-only flags, macOS-only commands, Linux-only paths, current-user paths, and current-harness internals as portability risks. Add guards, alternative implementations, or documentation when the implementation truly depends on one of them.</rule>
<rule>Keep non-interactive automation paths deterministic. Accept parameters, environment variables, or documented config files for inputs that scripts need in agent-driven workflows.</rule>
<rule>Write skill and plugin instructions so future agents can execute the artefact in Codex and Claude without relying on hidden thread state, local shell aliases, current working-directory accidents, or undocumented plugin-cache layout.</rule>
<rule>Account for harnesses that auto-discover artefacts from another harness's directories: a tool may load skills, rules, or other components from a sibling harness's config tree, so a component deployed for one harness can surface unbidden in another. Confirm each target's discovery roots, and where a harness offers an isolation switch, document it so an operator can scope discovery to the intended source.</rule>
</policy>
<cross_harness_artefact_discovery>
<foreign_directory_adoption>
Treat artefact discovery as a portability surface: a harness may load components from another harness's directories, so a skill, agent, or rule file deployed for one tool can appear unbidden in another. Verified July 2026 against OpenCode's official docs (opencode.ai/docs) and its issue tracker; re-verify against current docs before relying on it. OpenCode discovers skills from its own `.opencode/skills/` and, in the same walk up to the git worktree, from `.claude/skills/` and `.agents/skills/`; globally it reads `~/.config/opencode/skills/`, `~/.claude/skills/`, and `~/.agents/skills/`, plus any directory or HTTP catalog named in the `skills` array of `opencode.json`, with the later-scanned source winning on a duplicate skill id. It also reads rules from `AGENTS.md` then `CLAUDE.md` in a project and `~/.config/opencode/AGENTS.md` then `~/.claude/CLAUDE.md` globally. Agents and commands, by contrast, load only from OpenCode's own directories — never `.claude/agents` or `.claude/commands` — so those artefacts reach OpenCode only through an OpenCode-native deploy. Antigravity looks like the opposite case, though by absence of evidence rather than an explicit statement: no page reviewed in July 2026 — settings, CLI settings, permissions, CLI reference, skills, subagents, or plugins — mentions discovering artefacts from `.claude`, `.cursor`, or `.codex`, and none documents an environment variable or config key that scopes or disables such discovery. Plan on Antigravity loading only its own roots, which means a deploy has to place artefacts there rather than relying on a `.claude` deposit surfacing, and read the missing isolation switch as unnecessary rather than absent. Confirm on the installed build before relying on the negative.
</foreign_directory_adoption>
<opencode_config_tree>
Resolve OpenCode paths against its own convention rather than the `~/.<tool>` shape the other harnesses share: the global tree is `~/.config/opencode/` and the per-project tree is `.opencode/`, each holding plural subdirectories `skills/`, `agents/`, `commands/`, and `plugins/` (singular names are tolerated for back-compat; write the plural canonical form). A deploy step that hardcodes `~/.opencode` misses the real global tree.
</opencode_config_tree>
<opencode_isolation_switches>
Document the isolation switch when a harness can be scoped away from foreign directories. OpenCode's are environment variables rather than config keys (a config-file toggle is an open feature request): `OPENCODE_DISABLE_CLAUDE_CODE=1` disables Claude Code compatibility broadly and, since v1.1.50, also stops `.agents/skills` discovery; `OPENCODE_DISABLE_CLAUDE_CODE_PROMPT=1` stops only the `~/.claude/CLAUDE.md` fallback; `OPENCODE_DISABLE_CLAUDE_CODE_SKILLS=1` stops only `.claude` skill discovery. Setting the broad switch is how an operator makes OpenCode surface only its own curated artefacts — but weigh its reach against `<antigravity_config_tree>` before recommending it, because `.agents/skills/` is Antigravity's own native skill root rather than a Claude-compatibility deposit. The broad switch therefore scopes away both a sibling tool's `~/.claude` artefacts and the Google harness's native workspace skills in one stroke, a materially different trade for an operator running both tools than a Claude-only compatibility toggle would be.
</opencode_isolation_switches>
<antigravity_config_tree>
Split Antigravity's global roots by artefact class, not by product wholesale, and keep config roots separate from output paths. Verified July 2026 against antigravity.google/docs (`/skills`, `/subagents`, `/hooks`, `/plugins`, `/mcp`, `/sidecars`, `/rules-workflows`, `/ide/skills`, `/ide/plugins`, `/ide/hooks`, `/cli/plugins`, `/cli/settings`, `/cli/gcli-migration`); re-verify before relying on it. The workspace tree is uniform across products: `.agents/{skills,agents,rules,plugins}/` at the workspace root plus `.agents/hooks.json` and `.agents/mcp_config.json`, with `.agent/` named as back-compat by the skills and rules pages and `_agents/` by the plugins pages. A skill there is a `SKILL.md` under the Agent Skills open standard — `description` required, `name` optional and defaulting to the folder name — model-invoked through a progressive-disclosure sequence of discovery, activation, then execution, which makes this repo's own skills near drop-in for the directory rather than a format port. Globally, exactly two artefact classes diverge by product and the rest converge, so resolve the question per class rather than per product. Skills split three ways — `~/.gemini/config/skills/<skill-folder>/` on `/docs/skills`, `~/.gemini/antigravity/skills/<skill-folder>/` on `/docs/ide/skills`, and `~/.gemini/antigravity-cli/skills/` on `/docs/cli/plugins` — and plugins split two, `~/.gemini/config/plugins/` for 2.0 and the IDE against `~/.gemini/antigravity-cli/plugins/<plugin_name>/` for the CLI. Everything else is single-rooted under `~/.gemini/config/`: `agents/`, `hooks.json` (given identically by the 2.0 and IDE hook pages), `mcp_config.json`, and `sidecars/`, with global rules as the single file `~/.gemini/GEMINI.md` one level above `config/` and the CLI's own preferences at `~/.gemini/antigravity-cli/settings.json`. So "one global deploy reaches IDE, CLI, and 2.0" holds for agents, hooks, MCP, and sidecars and fails for skills and plugins: deploy those two to the root the target product's own page confirms instead of assuming a shared tree. Distinguish every one of these config roots from the transcript and artifact output paths that share the same per-product trees; the hook payload examples show those outputs landing under a `brain/<conversationId>/` subtree — `~/.gemini/antigravity/brain/…` on the 2.0 page and `~/.gemini/antigravity-ide/brain/…` on the IDE page, which is also the only place a fourth `antigravity-ide` tree appears — so a deploy step that reads a whole per-product tree as config, or as output, gets one of the two wrong. Two workspace paths collide with other harnesses, which is why this block sits beside the OpenCode ones: `.agents/skills/` is where Codex project-level skill deployment and OpenCode discovery converge on Antigravity's native root, so one directory serves three harnesses and a duplicate skill id resolves by whichever tool scans last; and `.agents/plugins/` holds Antigravity's `plugin.json` bundles while a Codex marketplace registration also lands at `.agents/plugins/marketplace.json`, so one directory carries two harnesses' differently-schemad manifests. Rules and command workflows are documented rather than inferred: workspace rules are markdown under `.agents/rules/` capped at 12,000 characters each with four activation modes — Manual, Always On, Model Decision, and Glob — global rules are the single `~/.gemini/GEMINI.md`, and workflows are markdown files in either scope, capped at the same 12,000 characters and invoked as `/workflow-name` slash commands. Both a project's `GEMINI.md` and its `AGENTS.md` are parsed for rule constraints; no page reviewed states which wins when the two disagree, so treat that precedence as unresolved and avoid splitting one rule set across the pair.
</antigravity_config_tree>
</cross_harness_artefact_discovery>
<hook_portability>
<codex_hook_layers>
Treat Codex hook sources as additive layers. Codex can load user hooks from `~/.codex/hooks.json` or inline `[hooks]` in `~/.codex/config.toml`, project hooks from `<repo>/.codex/hooks.json` or inline `[hooks]` in `<repo>/.codex/config.toml`, managed hooks from managed configuration, and plugin-bundled hooks from enabled plugins. Higher-precedence configuration layers do not replace lower-precedence hooks; all matching hooks run. If the same command is present in a plugin hook and a project or user hook, expect duplicate reviews and duplicate execution.
</codex_hook_layers>
<codex_project_hooks>
Use project `.codex/` hooks for repository-local activation without plugin installation, for quick experiments before packaging, or for a project that intentionally owns its own hook policy. Keep project `.codex/` hooks out of plugin source repositories when the normal goal is to install the plugin; the installed plugin already contributes the hook and a committed project hook usually creates a second active source.
</codex_project_hooks>
<codex_plugin_hooks>
Treat plugin-root `hooks/hooks.json` as Codex's default plugin hook file. Use a `.codex-plugin/plugin.json` `hooks` entry to override that default with a Codex-native hook file, an empty hook file containing only `{"hooks": {}}`, multiple hook files, or inline hook objects. Resolve manifest hook paths relative to the plugin root, keep them inside the plugin root, and start file paths with `./`.
</codex_plugin_hooks>
<codex_hook_schema>
Keep Codex-consumed hook JSON strict and minimal: the top-level key is `hooks`, event names map to matcher groups, and matcher groups contain one or more handlers. Put descriptive prose in the plugin manifest, README, skill prose, or provider-specific documentation instead of adding custom top-level fields such as `description` to Codex hook JSON. Use one matcher group with a regex such as `^(apply_patch|Bash)$` when the same handler, timeout, and status message apply to multiple tools; use separate matcher groups when behavior, command arguments, timeout, status text, or policy differs.
</codex_hook_schema>
<codex_trust_and_cache>
Account for Codex trust and cache behavior when validating hooks. Non-managed command hooks are listed but skipped until the user reviews and trusts the current hook definition. Trust is tied to the current hook definition, so edits require review again. After editing a marketplace-installed plugin, refresh the installed Codex plugin cache with `codex plugin add <plugin>@<marketplace>` or the matching marketplace workflow, then inspect `~/.codex/plugins/cache/<marketplace>/<plugin>/<version>/`; restarting Codex alone can reuse stale cached plugin files.
</codex_trust_and_cache>
<claude_plugin_hooks>
Treat Claude plugin hooks as plugin-root components that live at `hooks/hooks.json` or inline in the Claude plugin manifest. Claude command hooks receive event JSON on stdin and commonly resolve plugin files through `${CLAUDE_PLUGIN_ROOT}`. Reload or restart the Claude plugin surface after changing plugin hooks so the active session sees hook, MCP, agent, and other plugin component changes.
</claude_plugin_hooks>
<dual_harness_layout>
Keep Claude and Codex hook configuration parallel rather than shared when their schemas, event coverage, matcher names, trust model, or command environment differ. A practical dual-harness plugin can use `hooks/hooks.json` for Claude, `.codex-plugin/plugin.json` pointing to `./hooks/codex-plugin-hooks.json` for Codex, and an optional separate hook JSON such as `./hooks/codex-custom-deploy-hooks.json`, kept outside the manifest, for users who explicitly merge it into their user- or project-level Codex config layers. Use an empty Codex hook file when a Claude hook exists but no Codex-equivalent behavior is ready yet. Antigravity adds a third declarative slot rather than sharing either of those: its own `hooks.json`, either bundled inside an Antigravity `plugin.json` package per `<antigravity_plugin_bundle>` or placed loose in `.agents/` or `~/.gemini/config/`, carrying the event names, camelCase envelope, and stdout `decision` contract that `<antigravity_hooks>` defines. Keep it a separate file from the Claude and Codex hook config for the same reason those two stay separate — the schemas, event names, and signalling mechanism differ — so a three-harness plugin ships three hook configs over one shared script.
</dual_harness_layout>
<shared_hook_scripts>
Share executable hook scripts across Codex and Claude when the policy is the same, but make the script harness-neutral. Read event input from stdin when available, tolerate missing or extra JSON fields, detect the active harness through documented environment variables, and resolve the plugin root from `${PLUGIN_ROOT}` or `${CLAUDE_PLUGIN_ROOT}` with a script-relative fallback. Emit blocking decisions and diagnostics in the format expected by the harness that invoked the script. Budget for three signalling contracts rather than one: Claude and Codex block by exit code on a snake_case envelope, Antigravity reads a camelCase envelope and expects a JSON `decision` on stdout per `<antigravity_hooks>` — so a script that only ever exits non-zero silently fails open there — and OpenCode's bridge plugin blocks by throwing. Parse the envelope by the field names the invoking harness actually sends instead of assuming one casing, and branch the response shape the same way.
</shared_hook_scripts>
<opencode_plugin_hooks>
Treat OpenCode as having no declarative hook configuration: unlike Claude's `settings.json` `hooks` key or Codex's `hooks.json`, OpenCode expresses hooks only as code — a JavaScript/TypeScript plugin loaded from `.opencode/plugins/` or `~/.config/opencode/plugins/` at startup and run by OpenCode's embedded Bun runtime, so a local `.ts` plugin needs no npm publish and no build step. Verified July 2026 against opencode.ai/docs/plugins and /permissions plus the issue tracker; re-verify before relying on it. A plugin exports an async function returning a hooks object; `tool.execute.before` fires before a tool runs, may mutate the tool arguments, and aborts the call by throwing — that throw is OpenCode's block mechanism. Because Bun's `$` shell is available to the handler, a shared policy script — a Claude/Codex `PreToolUse` guard, for instance — can be reused through a thin bridge plugin that builds the script's stdin envelope, runs it, and throws on a non-zero exit, keeping one source of truth for the policy. Encode three OpenCode-specific limits: `tool.execute.before` does not intercept tool calls made by task-tool subagents (issue #5894), so a before-hook guard is bypassable by delegation and gives a weaker guarantee than the same guard on Claude/Codex; the `permission.ask` plugin hook is defined but never fired (issue #7006), so interception must use `tool.execute.before`; and OpenCode's tools are lowercase (`edit`, `write`, `bash`) with no `apply_patch`, so port matcher sets to those names. OpenCode's native `permission` config (allow/ask/deny by tool and path) is a declarative alternative for simple path protection, but cannot express conditional logic such as a branch check, which still needs the plugin.
</opencode_plugin_hooks>
<antigravity_hooks>
Treat Antigravity as declarative on hooks, closer to the Claude/Codex JSON-config model than to OpenCode's code-only bridge, while reading its resemblance to Claude as structural rather than behavioural. Verified July 2026 against antigravity.google/docs/hooks; re-verify before relying on it. Configuration is a `hooks.json` in `.agents/` for a workspace and `~/.gemini/config/` globally, with five events — `PreToolUse`, `PostToolUse`, `PreInvocation`, `PostInvocation`, and `Stop`. The stdin/stdout contract carries **two casings in one payload**, which is the trap for a script shared with Claude or Codex. The envelope is camelCase — `toolCall`, `stepIdx`, `conversationId`, `workspacePaths`, `transcriptPath`, `artifactDirectoryPath` — but the per-tool arguments nested under `toolCall.args` are **PascalCase**, so a `run_command` call arrives as `toolCall.args.CommandLine` and `toolCall.args.Cwd`. Address the tool by `toolCall.name` and its parameters by `toolCall.args`; there is no top-level `cwd`, so take the working directory from `toolCall.args.Cwd` or `workspacePaths[0]`. Only `run_command`'s argument keys are documented, so a guard that must inspect a file path across the file-writing tools is more robust scanning every string value under `toolCall.args` than guessing a per-tool key name. On stdout a `PreToolUse` handler returns `decision` (`allow`, `deny`, `ask`, or `force_ask`) alongside an optional human-readable `reason` and an optional `permissionOverrides` array of entries shaped like `command(npm test)` — a decision value, not an exit code — while a `Stop` handler returns `continue` to keep going or any other value to allow the stop. The config file's own shape diverges too: where Claude and Codex nest event names under a top-level `hooks` key, Antigravity keys the file by a **named hook** first and the event second — `{"my-linter-hook": {"PostToolUse": [{"matcher": "run_command", "hooks": [{"type": "command", "command": "./scripts/lint.sh", "timeout": 10}]}]}}` — with `matcher` selecting tools by exact name, `""` or `"*"` for all, `"a|b"` alternatives, or a regex such as `"browser_.*"`. The docs cover Antigravity 2.0 and the CLI and point to a separate IDE hooks page, so confirm the surface you are targeting rather than assuming one config reaches all three, consistent with the per-product roots in `<antigravity_config_tree>`. **Divergence trap:** the event names are Claude's while nothing behind them is. A `PreToolUse` hook that works under Claude reads a snake_case envelope and blocks by exit code; deployed unchanged to Antigravity it parses the wrong field names and exits into a handler that wanted a `decision` on stdout, so it fails open silently rather than erroring. Matching event names are the reason to check the envelope, not a reason to skip the check. Treat the declarative `hooks.json` as one of two distinct hook surfaces, not the only one: the Antigravity SDK (`pip install google-antigravity`, Python only) carries its own programmatic **lifecycle hooks**, documented as three categories — inspect (read-only, non-blocking), decide (read-only, blocking), and transform (modifying, blocking) — across nine lifecycle points. Those are in-process Python callbacks for an agent the SDK builds, not a config file the IDE or CLI reads, so they neither replace nor register a `hooks.json`. Name which surface a hook targets before porting it: a shared shell policy reaches the IDE and CLI through `hooks.json`, while an SDK-hosted agent needs the policy called from a Python hook instead.
</antigravity_hooks>
<duplicate_diagnosis>
Diagnose duplicate or surprising hook counts by enumerating active sources before changing scripts. Check the plugin manifest hook path, plugin-root `hooks/hooks.json`, user and project `hooks.json`, inline `[hooks]` tables, managed policy, installed plugin cache, and the hook review UI. A displayed count can reflect separate sources, separate matcher groups, parse errors plus valid hooks, or stale cached plugin files.
</duplicate_diagnosis>
</hook_portability>
<agent_portability>
<agent_definition_surfaces>
Treat agent and subagent definitions as a per-harness surface with distinct formats, locations, and loaders; the facts below were verified against provider documentation, loader source, and on-disk harness state in July 2026 — re-verify against current official docs before encoding new behavior. Anthropic Claude reads markdown agents with YAML frontmatter, loads plugin-bundled `agents/*.md` natively from installed plugins, and silently ignores unknown frontmatter keys; plugin-delivered agents additionally have `hooks`, `mcpServers`, and `permissionMode` ignored for security. Cursor reads markdown agents from `.cursor/agents/`, `.claude/agents/`, and `.codex/agents/` in both project and user variants (`.cursor` wins name conflicts) and recognizes `name`, `description`, `model`, `readonly`, and `is_background`. OpenAI Codex registers spawnable roles only from standalone TOML files under `~/.codex/agents/` or `<repo>/.codex/agents/` (`name`, `description`, `developer_instructions` required; `model`, `model_reasoning_effort`, `sandbox_mode`, `mcp_servers` optional); the Codex plugin schema carries no agent component, so plugin-bundled markdown agents land in the plugin cache without ever becoming spawnable roles. Google Antigravity reads markdown subagents from `.agents/agents/<name>.md` or `.agents/agents/<name>/agent.md` in a workspace, from `~/.gemini/config/agents/<name>.md` or `.../agents/<name>/agent.md` globally, and from `plugins/<plugin_name>/agents/` inside a plugin bundle (verified July 2026 against antigravity.google/docs/subagents; re-verify before relying on it). Its documented frontmatter is `name` and `description` (both required), `tools` as a string array defaulting to `[]`, `mainAgent` and `subagent` as booleans both defaulting to `true` — so a definition registers as a spawnable subagent unless `subagent: false` says otherwise — `model` defaulting to `inherit`, `commandExecutionPolicy` defaulting to `sandbox`, `mcpServers` as an object array, and `skills`/`plugins` as string arrays of dependency paths. The documented `model` tier list is exactly `inherit`, `flash`, and `pro`; a `flash_lite` tier circulates in secondary sources but appears neither in the subagents frontmatter table nor among the models `/docs/models` lists, and `hidden` and `inheritMcp` are likewise community-sourced only — treat all three as unsupported rather than native fields, since an unrecognized value here carries the same silent-failure risk as an unrecognized tool name. The CLI reads the same definitions from the same two roots and refers back to this one specification rather than defining a variant, so treat the subagent schema as unified across products even where skill and plugin roots diverge. SST OpenCode reads markdown agents from `.opencode/agents/` and `~/.config/opencode/agents/` (plural canonical, singular tolerated) and never from `.claude/agents`; its frontmatter uses `mode` (`primary` | `subagent` | `all`, where only `subagent` or `all` registers a spawnable subagent), `description`, `model` as `provider/model-id` inheriting the session model when omitted, `temperature`, `prompt`, and a `permission` object keyed by capability with `allow` | `ask` | `deny` values — which supersedes the now-deprecated (OpenCode v1.1.1) boolean `tools` object — rather than Claude's comma-separated `tools:` string.
</agent_definition_surfaces>
<codex_model_inheritance>
Express Codex custom-agent model inheritance by omitting the `model` key from the generated TOML. Codex inherits optional fields such as `model`, `model_reasoning_effort`, and `sandbox_mode` from the parent session or default configuration when the key is absent. When a shared markdown agent source expresses inheritance with a sentinel value such as `model: inherit` — a value Claude and Cursor understand natively, and Antigravity's documented default for the `model` tier field listed in `<agent_definition_surfaces>` — translate that sentinel to key omission while generating the Codex TOML; explicit concrete model pins still pass through as `model = "..."`. Treat `model = "inherit"` in generated Codex TOML as invalid output because Codex interprets it as a literal model name, and ChatGPT-backed Codex sessions can reject it before the agent starts.
</codex_model_inheritance>
<reasoning_effort_portability>
Let sub-agents inherit the spawning session's reasoning depth by omitting the effort keys from shared agent definitions — inheritance by omission works on both major harnesses and keeps the user's session setting as the single knob. Claude Code subagents inherit the parent session's extended-thinking state and effort level since 2.1.198, with frontmatter `effort` (`low` | `medium` | `high` | `xhigh` | `max`) acting as a per-agent override when present; releases before 2.1.198 spawn subagents without extended thinking regardless of frontmatter. Codex inherits `model_reasoning_effort`, like `model`, from the parent session when the agent TOML omits the key, and a role-pinned effort is locked against per-spawn changes. Antigravity documents no separate reasoning-effort key at all: its `model` tier field, whose canonical values `<agent_definition_surfaces>` lists, is the only depth control sourced on its subagents page, so express Antigravity depth through that tier rather than looking for an effort equivalent. Reserve a pin for an agent that must run at a fixed depth regardless of session settings, and express it as a union of native keys in the shared source: Claude reads frontmatter `effort`, the Codex TOML key is `model_reasoning_effort`, and the two names are disjoint, so both sit side by side in one markdown file — each tolerant harness ignores the foreign key, whatever generates the Codex TOML carries the `model_reasoning_effort` value through, and a generated OpenCode variant strips both, since OpenCode would otherwise pass each unknown key to its provider as a bogus model option. Pin Codex at `xhigh` at most — the ceiling every current first-party Codex model advertises (verified July 2026): the `max` and `ultra` tiers above it are accepted only by preview models, and Codex applies an agent-TOML effort pin without spawn-time validation, so an unsupported pin surfaces as API errors on the child agent's turns rather than a clamp or fallback.
</reasoning_effort_portability>
<frontmatter_schema_tolerance>
Classify every target harness as ignore-unknown or strict-schema before sharing one agent file across harnesses. Claude and Cursor tolerate foreign keys, which is what lets one markdown file carry several harnesses' native fields side by side. A strict target rejects a single unrecognized key — `version:`, `background:`, `effort:`, or any vendor-prefixed field — and leaves that agent unloaded while the harness keeps running, which is why the classification has to happen before a shared file ships rather than after an agent silently fails to appear. Antigravity's tolerance is **unconfirmed**, and the available evidence leans tolerant: its subagents and skills pages say nothing about unknown keys either way, the one documented validation gotcha is a bad *tool* name causing a runtime hang rather than a load rejection, and the fixed-allowlist "unexpected fields" behaviour that circulates traces to a SKILL.md editor linter in VS Code, which can raise a cosmetic editor warning without being a runtime rejection and never names Antigravity. Until someone confirms it against the loader or empirically, classify the Google target as pending verification rather than strict, and let that pending state — not an assumed strictness — decide how aggressively a generated Antigravity variant filters keys. Confirm tolerance from the docs or the loader source, and treat "the other harness will ignore it" as a claim to verify per harness rather than a default assumption. OpenCode is a third category beyond ignore-unknown and strict-schema: its agents docs (opencode.ai/docs/agents, **Additional**, verified July 2026; re-verify before relying on it) state that any unrecognized frontmatter option is passed through directly to the provider as a model option, so a foreign key such as `version:`, `background:`, or `effort:` is neither ignored nor rejected but injected into OpenCode's provider call as a bogus model parameter. Like a strict-schema target, then, OpenCode cannot safely read a shared multi-harness file; reach it through a generated variant that whitelists its `mode`/`description`/`model`/`temperature`/`prompt`/`permission` keys and drops the rest. Its legacy boolean `tools` object is separately deprecated as of v1.1.1, merged into `permission`.
</frontmatter_schema_tolerance>
<tool_name_namespaces>
Treat tool allowlists as harness-specific in both value shape and tool naming. Claude's `tools:` takes a comma-separated string of capitalized names (`Read, Grep, Glob, Bash`), and omitting the field inherits every tool. Antigravity's `tools` is a YAML array of lowercase snake_case names, and its failure mode is the one to plan around: an unmapped or misspelled name may cause the subagent process to **hang during execution** rather than fail validation at load, so a typo surfaces as a stalled agent instead of an error message (verified July 2026 against antigravity.google/docs/subagents and /docs/hooks; re-verify before relying on it). Treat its two documented surfaces as two partial views of one vocabulary rather than as two namespaces: the subagents page's example allowlist names `view_file`, `grep_search`, and `run_command`, while the hooks page's matcher list adds `write_to_file`, `replace_file_content`, `multi_replace_file_content`, `list_dir`, `find_by_name`, `search_web`, `read_url_content`, `manage_task`, `schedule`, `list_permissions`, `ask_permission`, `invoke_subagent`, `define_subagent`, `send_message`, `manage_subagents`, `ask_question`, and `generate_image`. Nothing on either page says the allowlist and matcher vocabularies differ, and no canonical tool list is published anywhere in the docs — the permissions and CLI reference pages carry none either — which is exactly why the hang matters, since there is no registry to validate a name against before shipping. Copy a name from one of those two lists verbatim rather than inferring it from another harness's vocabulary. The `read_file` and `edit_file` names that circulate for Antigravity allowlists appear on neither page; prefer a name one of the two lists actually sources. Cursor exposes no tools field at all — its current identifiers are `Shell`, `Read`, `Grep`, `Glob`, `LS`, `StrReplace`, and `Write`, with the shell tool named `Shell` rather than `Bash` — and Codex offers no per-agent tool allowlist. OpenCode is a distinct shape again — its current model is a `permission` object mapping lowercase capability keys to `allow`/`ask`/`deny` (keys include `read`, `edit`, `glob`, `grep`, `bash`, `task`, `skill`, `lsp`, `question`, `webfetch`, `websearch`, `external_directory`, and `doom_loop`, where the `edit` key covers the `write`, `edit`, and `patch` tools), so it too needs its own generated value rather than any other harness's `tools:`. Verified July 2026 against opencode.ai/docs/agents and /docs/permissions; re-verify before relying on it: the older boolean `tools` object that mapped tool names to `true`/`false` is deprecated as of OpenCode v1.1.1, merged into `permission` and kept only for backwards compatibility, so generate the `permission` object rather than the legacy `tools` map. Because the namespaces are disjoint, one `tools:` value cannot satisfy Claude and Antigravity simultaneously — a capitalized comma string against a lowercase snake_case array: a shared file carries at most one harness's `tools:` and reaches the others through their own fields or a generated per-harness variant.
</tool_name_namespaces>
<readonly_agent_enforcement>
Enforce a read-only agent role with each harness's native lever, unioned in one source file where key tolerance allows: a `tools:` allowlist of read-only tools for Claude, `readonly: true` for Cursor (restricted write permissions), `sandbox_mode = "read-only"` in the generated TOML for Codex, a two-part lever for Antigravity — a `tools` array holding only read-only names from the vocabulary `<tool_name_namespaces>` sources (`view_file`, `grep_search`) paired with `commandExecutionPolicy` set to `off`, or to `sandbox` where the role still needs contained command execution, since leaving that key at its `sandbox` default already denies unsandboxed commands while `off` denies them outright — and, for OpenCode, a `permission` object with `edit: deny` plus `bash: deny` where the agent needs no shell, because OpenCode's `edit` key gates all file modification: it covers the `write`, `edit`, and `patch` tools, so no separate `write` permission key exists. Verified July 2026 against opencode.ai/docs/agents and /docs/permissions; re-verify before relying on it: the older boolean `tools` object that disabled tools individually is deprecated as of OpenCode v1.1.1 and merged into this unified `permission` object, so enforce OpenCode read-only through `permission`, not a `tools` map. Account for Codex read-only semantics gating command execution behind approval, so a read-only agent that needs `git log`-style commands may pause for confirmation there. Keep the prompt-level prohibition — edit no files, stamp no frontmatter — stated in the agent body as the universal floor: frontmatter enforcement binds only where the agent actually spawns as a separate agent, while the body contract also governs harnesses that degrade the role to inline execution.
</readonly_agent_enforcement>
<spawn_registration_and_inline_degradation>
Confirm where a named agent actually registers before relying on its definition file for any guarantee. A harness without the registered role usually keeps the orchestrating skill working — the main agent performs the role inline, where agent-file enforcement never applies. Verify registration on the target itself: the harness agent directory, the installed plugin cache, and the spawnable-role list the harness advertises (Codex builds its `spawn_agent` roles from the TOML agent directory plus built-ins such as `default`, `explorer`, and `worker`). Write orchestrating skills so their policy survives inline degradation instead of assuming a sub-agent boundary exists everywhere.
</spawn_registration_and_inline_degradation>
<native_fields_and_deploy_bridges>
- Author shared agent sources with each harness's native field names, and reserve deploy-time transforms for format bridges. A native plugin or marketplace install reads the raw file, so a deploy-only convention — a vendor-prefixed key such as `CLAUDE_tools:`, or a `readonly:` to `sandbox_mode` mapping — takes effect only on machines where that deployment step ran and is invisible on every native install path. Use a generation step in two situations: where the target reads a different format entirely — markdown to Codex TOML is the required bridge, and it is also the only path by which a plugin-distributed agent reaches Codex as a spawnable role — and where the target cannot safely read a shared multi-harness file, which the shared file can never satisfy. That second trigger is broader than strict validation alone: a target qualifies when it rejects unknown keys outright, and equally when it accepts them and does something harmful with them, which is why OpenCode is the live exemplar rather than any strict-schema target. OpenCode passes every unrecognized frontmatter key straight through to the provider as a model option per `<frontmatter_schema_tolerance>`, so a shared file's foreign keys arrive as bogus model parameters; its generated variant whitelists `mode`, `description`, `model`, `temperature`, `prompt`, and `permission`, maps a read-only role onto `permission` values rather than any other harness's `tools:`, and drops every remaining key. Generate an Antigravity variant on the same pattern, preserving its native fields — the agent `tools` array in Antigravity's own tool vocabulary, `commandExecutionPolicy`, `mainAgent`/`subagent`, `mcpServers`, `skills`/`plugins`, and the `model` tier field whose canonical values `<agent_definition_surfaces>` lists — and dropping foreign effort and tool fields; how aggressively it must drop the rest follows the pending tolerance finding rather than an assumption. Whatever the target, a variant generator that meets a value it cannot map — a tool name with no equivalent in the target's vocabulary — drops that field rather than emitting a guess, since a dropped allowlist degrades to the harness default while a wrong one fails at load or, on Antigravity, hangs at runtime.
+ Author shared agent sources with each harness's native field names, and reserve deploy-time transforms for format bridges. A native plugin or marketplace install reads the raw file, so a deploy-only convention — a vendor-prefixed key such as `CLAUDE_tools:`, or a `readonly:` to `sandbox_mode` mapping — takes effect only on machines where that deployment step ran and is invisible on every native install path. Use a generation step in two situations: where the target reads a different format entirely — markdown to Codex TOML is the required bridge, and it is also the only path by which a plugin-distributed agent reaches Codex as a spawnable role — and where the target cannot safely read a shared multi-harness file, which the shared file can never satisfy. That second trigger is broader than strict validation alone: a target qualifies when it rejects unknown keys outright, and equally when it accepts them and does something harmful with them, which is why OpenCode is the live exemplar rather than any strict-schema target. OpenCode passes every unrecognized frontmatter key straight through to the provider as a model option per `<frontmatter_schema_tolerance>`, so a shared file's foreign keys arrive as bogus model parameters; its generated variant whitelists `mode`, `description`, `model`, `temperature`, `prompt`, and `permission`, maps a read-only role onto `permission` values rather than any other harness's `tools:`, and drops every remaining key. Generate an Antigravity variant on the same pattern, preserving its native fields — the agent `tools` array in Antigravity's own tool vocabulary, `commandExecutionPolicy`, `mainAgent`/`subagent`, `mcpServers`, `skills`/`plugins`, and the `model` tier field whose canonical values `<agent_definition_surfaces>` lists — and dropping foreign effort and tool fields; how aggressively it must drop the rest follows the pending tolerance finding rather than an assumption. Whatever the target, a variant generator that meets a value it cannot map — a tool name with no equivalent in the target's vocabulary — drops it rather than emitting a guess, since a wrong name fails at load or, on Antigravity, hangs at runtime. Drop at the granularity the field has: from a structured allowlist drop only the unmappable entry and keep every name that did map, and reserve the whole-field drop for a value carrying no per-entry structure. Dropping a whole array also drops the enforcement its mapped names carry — on Antigravity the `tools` array is half the read-only lever `<readonly_agent_enforcement>` defines, so a whole-field drop un-restricts exactly the agents that section protects. When no entry maps at all the field falls back to its own documented default, and that default's meaning is per-harness rather than uniform: omitting Claude's `tools:` inherits every tool, while Antigravity documents `tools` as defaulting to `[]` without stating what an empty array grants.
</native_fields_and_deploy_bridges>
</agent_portability>
<antigravity_plugin_bundle>
Treat Antigravity's plugin as its own bundling unit, distinct from the Claude and Codex plugin manifests and from an OpenCode plugin, which is executable code rather than a package. Verified July 2026 against antigravity.google/docs/plugins; re-verify before relying on it. The manifest is a `plugin.json`, and bundles live at `.agents/plugins/` (with `_agents/plugins/` accepted as back-compat) in a workspace and `~/.gemini/config/plugins/` globally, alongside the CLI's own `~/.gemini/antigravity-cli/plugins/<plugin_name>/` root that `<antigravity_config_tree>` records. The documented bundle carries four component types: skills under `skills/`, rules under `rules/`, MCP servers through `mcp_config.json`, and hooks through the `hooks.json` whose contract `<antigravity_hooks>` defines. A bundle can also carry **sidecars** — long-running background processes the harness supervises and restarts, each its own directory holding a `sidecar.json` that names the command, restart behavior, and environment — discovered at `~/.gemini/config/plugins/<pluginName>/sidecars/` for a plugin and `~/.gemini/config/sidecars/` standalone, and addressed as `<pluginName>/<sidecarName>`. A sidecar is the one Antigravity component that keeps running between turns, so treat it as a process-lifecycle surface rather than a config file: it needs the portable-executable, dependency-detection, and path-resolution discipline this skill applies to hook scripts, plus a plan for what happens when the supervisor restarts it. One documented inconsistency remains, and it belongs in any plan that depends on the bundle: whether agents ship inside a bundle is contradictory across official pages, since the subagents page names `plugins/<plugin_name>/agents/` as a subagent location while both the 2.0 and IDE plugins pages omit agents from the component list entirely — so confirm on the target product before shipping a plugin-bundled agent and expecting it to register, the same registration check `<spawn_registration_and_inline_degradation>` requires of every harness.
</antigravity_plugin_bundle>
<workflow>
<inventory_runtime_surface>
List every script, hook, MCP helper, command wrapper, setup flow, and prose instruction affected by the change. Include both directly edited files and callers that execute them.
</inventory_runtime_surface>
<identify_targets>
Identify the harnesses, operating systems, shells, language runtimes, and dependency assumptions that the artefact must support.
</identify_targets>
<check_official_sources>
Consult official provider documentation for any Codex, Claude, MCP, hook, skill, plugin, or command behavior that affects execution. Use local official docs when they are bundled with the environment; browse official provider domains when current online details are needed.
</check_official_sources>
<map_hook_layers>
For hook work, map every provider-specific source that can load the hook before editing: plugin default files, manifest hook overrides, user config, project config, managed policy, deploy-generated config, and installed plugin caches.
</map_hook_layers>
<apply_portable_patterns>
Implement the change with portable path handling, argument handling, dependency detection, error reporting, and documented configuration. Prefer small explicit compatibility checks over implicit reliance on the current machine.
</apply_portable_patterns>
<verify_across_surfaces>
Run the narrowest useful verification for the touched artefact. Include macOS/Linux or Codex/Claude coverage when available; otherwise report the untested surface and why it could not be exercised in the current session. For agent definitions, confirm on the target harness that the definition actually loads and registers — inspect the harness agent directory, the installed plugin cache, and the advertised spawnable-role list — rather than inferring from file placement.
</verify_across_surfaces>
<refresh_installed_plugin_cache>
After editing a marketplace-installed plugin, refresh the installed Codex plugin cache with `codex plugin add <plugin>@<marketplace>` or the matching marketplace workflow, then inspect `~/.codex/plugins/cache/<marketplace>/<plugin>/<version>/` to confirm the cached manifest and hook files match the source. Restarting Codex alone can reuse stale cached plugin files.
</refresh_installed_plugin_cache>
</workflow>
<review_checklist>
<paths_and_locations>Paths resolve from documented roots and support spaces or special characters.</paths_and_locations>
<shell_portability>Shell syntax, utilities, and flags work on macOS and Linux or are guarded with fallbacks.</shell_portability>
<language_runtime>Python, Node.js, or other runtime code uses portable standard APIs for filesystem, process, encoding, and temporary-file behavior.</language_runtime>
<harness_wiring>Skill/plugin prose explains execution and configuration through documented harness behavior rather than current-session implementation details.</harness_wiring>
<artefact_discovery>Each target's artefact-discovery roots are known, including any harness that adopts another harness's directories — OpenCode loads skills from `.claude`/`.agents` and reads `CLAUDE.md` alongside its own, while taking agents and commands only from its own `~/.config/opencode` tree — and any available isolation switch is documented. Antigravity's native `.agents/skills/` is named as its own root rather than a foreign directory, with the Codex and OpenCode convergence on that same path called out; its global roots are resolved per artefact class rather than per product, so a deploy targets `~/.gemini/config/` for agents, hooks, MCP, and sidecars but picks the product-specific root for skills and plugins; and its own foreign-directory posture is stated with the evidence behind it rather than assumed.</artefact_discovery>
<behavior_carveouts>A behavior or prose rule that special-cases one harness is scoped to the harness or capability that needs the special attention and leaves every other harness on the shared instruction and its UX; where the triggering harness property changes across versions, the carve-out keys on the agent's capability rather than the harness identity.</behavior_carveouts>
<plugin_hook_runtime>A blocking or lifecycle hook shipped inside a plugin is verified to load at runtime on each target harness, with explicit Codex manifest overrides when `hooks/hooks.json` is Claude-only and with config-layer registration where the harness does not execute plugin-bundled hooks.</plugin_hook_runtime>
<hook_layering>Codex hook behavior has exactly the intended active sources across plugin, user, project, managed, and deploy-generated layers; duplicate execution is either removed or documented as intentional.</hook_layering>
<dual_harness_hooks>Claude and Codex hook configuration files are separated when schemas differ, while shared shell scripts remain portable across both harnesses. Antigravity's declarative `hooks.json` counts as a third separate schema beside them rather than a variant of either, and a shared script invoked from it reads the camelCase envelope and returns a stdout `decision` instead of blocking by exit code.</dual_harness_hooks>
<agent_definitions>Shared agent frontmatter carries only keys every target harness tolerates, tool allowlists use each harness's exact tool names and value shape, read-only roles pair frontmatter enforcement with a body-level policy that survives inline execution, and the definition is verified to register on each harness expected to spawn it.</agent_definitions>
<provider_docs>Provider-specific claims are checked against official OpenAI Codex, Anthropic Claude, or other targeted provider documentation.</provider_docs>
<failure_modes>Missing dependency, unsupported OS, unsupported shell, and missing config errors are actionable for a future agent.</failure_modes>
<verification>Verification covers the touched runtime path, or the remaining unverified surfaces are named explicitly.</verification>
</review_checklist>
<output_contract>
<format>
When reporting the work, summarize the portability surface touched, the official provider documentation checked when applicable, the verification performed, and any remaining harness or OS gaps.
</format>
<validation>
The final artefact can be understood and executed by a future Codex or Claude agent from the published skill/plugin files without relying on the implementation session's private state.
</validation>
</output_contract>
</harness_portability>