---
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, Claude-only output styles including output-style files, output-style frontmatter, the outputStyle setting, and plugin-bundled output-styles directories, their Codex near-counterparts in personality, model_instructions_file, model_verbosity, and profile config layers, and the additive rules-file and instructions-file counterparts on Cursor, Antigravity, OpenCode, and GitHub Copilot in VS Code, 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.12
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. Include component types that only one harness implements — a Claude output style is the worked example in `<claude_output_styles>` — because the portability question there is which harnesses the component reaches and what carries the same intent on the rest.
    </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. GitHub Copilot in VS Code is a target on the instruction-and-style surface `<claude_output_styles>` covers, where its own customization tree and its adoption of Claude paths both matter; its hook, agent, and plugin specifics are not worked out in the sections below, so read that absence as unfinished coverage rather than as a support decision. 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>Treat a component type that only one harness implements as a portability surface of its own: name the harnesses it reaches, decide what carries the same intent on the others, and establish whether the mechanism adds to what the harness already supplies or replaces part of it. An additive carrier and a replacing one are not interchangeable, so moving the same text between them changes what it does even when the wording is identical.</rule>
    <rule>Deliver every supported artefact as a per-harness variant written into that harness's own native root, and treat cross-harness adoption as contamination to detect rather than as a delivery channel. A harness that loads skills, rules, or agents from a sibling's config tree will happily read a file shaped for the other tool, complete with frontmatter keys, activation flags, and conventions it does not implement, so adoption delivers a silently degraded artefact where a generated variant would have delivered a correct one. Confirm each target's discovery roots so the leak is visible, disable adoption wherever the harness offers an isolation switch, and where no switch exists, plan for the foreign file arriving and keep the native variant authoritative. Relying on adoption is defensible only as a deliberate, stated fallback for a harness with no native root for that artefact class.</rule>
  </policy>

  <cross_harness_artefact_discovery>
    <foreign_directory_adoption>
      Treat artefact discovery as a portability surface, and map these roots so adoption can be detected and switched off rather than depended on: 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. Every root catalogued below is a leak path first and a fallback only where a target has no native root for that artefact class, because the standard delivery is the per-harness variant the policy above requires. 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. Rules resolve by first match rather than by accumulation, which is the detail that turns a deploy into a silent suppression: globally the loader tests `~/.config/opencode/AGENTS.md` then `~/.claude/CLAUDE.md` and stops at the first that exists, and per project it tests `AGENTS.md`, then `CLAUDE.md`, then the deprecated `CONTEXT.md` walking up to the worktree and stops at the first filename with any match (verified 7 August 2026 against `packages/opencode/src/session/instruction.ts` on the `dev` branch of github.com/sst/opencode; re-verify before relying on it). So writing a global `~/.config/opencode/AGENTS.md` does not sit alongside a user's `~/.claude/CLAUDE.md`, it stops that file loading altogether, and a deploy that wants to add rules without displacing the user's own reaches for the separate `instructions` list in `<opencode_delivery_modes>` instead. VS Code Copilot is a third adopter and reaches further into the Claude tree than OpenCode does: with Agent Host enabled it reads user-level instructions from `~/.copilot/instructions` and `~/.claude/rules`, finds `CLAUDE.md` at a workspace root, in a `.claude` folder, or at `~/.claude/CLAUDE.md`, and loads custom agents from a workspace `.claude/agents` beside its own `.github/agents` (verified 7 August 2026 against code.visualstudio.com/docs/copilot/customization; re-verify before relying on it). Read the two adopters together and the conclusion for a style is the same one the policy reaches by principle. Neither list includes `~/.claude/output-styles`, so adoption would not carry a style even if it were the chosen route, and a Claude style file placed where either harness does read would arrive carrying `keep-coding-instructions` and a frontmatter schema neither implements. Deposit a generated variant in each target's own tree instead, and disable the adoption paths so the Claude originals stop competing with it. 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 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>

  <claude_output_styles>
    <claude_only_component>
      Treat a Claude output style as a single-harness component and plan its cross-harness counterpart separately. Verified 7 August 2026 against code.claude.com/docs/en/output-styles and /docs/en/plugins-reference, plus the installed Claude Code 2.1.215 build and the desktop application bundle of the same date; re-verify before relying on it. Codex's `.codex-plugin/plugin.json` carries no `outputStyles` key, and Cursor, OpenCode, and Antigravity document no equivalent component, so a style shipped through a dual- or triple-manifest plugin is live on Claude and inert everywhere else. That makes it the wrong single carrier for a prose or behavior rule the whole harness set must obey. Reach the other harnesses with a generated variant of the prose written into each one's own native root, never by letting the Claude file be adopted from `~/.claude`: a skill, or that harness's own instruction carrier, meaning `~/.codex/AGENTS.md` for Codex, the `instructions` list for OpenCode, `.agents/rules/` and `GEMINI.md` for Antigravity per `<antigravity_config_tree>`, and `~/.copilot/instructions` for VS Code. Treat the style as the Claude-side member of that set of siblings rather than as the source file every harness reads. A child per harness works that mapping out: `<codex_counterparts>` and `<codex_delivery_modes>`, then `<cursor_counterparts>`, `<antigravity_counterparts>`, `<opencode_counterparts>` with `<opencode_delivery_modes>`, and `<vscode_counterparts>` with `<vscode_delivery_modes>`. Two results from those children carry across any plan. Displacement is rarer than appending but not unique to Claude, since Codex replaces its built-in instructions through `model_instructions_file` and OpenCode replaces its per-model base prompt through an agent `prompt`, while Cursor, Antigravity, and VS Code Copilot only ever append — so a rule written to remove something silently does nothing on those three. And a plugin-integrated route for a standing style reaches three harnesses by three unlike mechanisms: Claude's `output-styles/` component, Antigravity's bundled `rules/` directory, and an OpenCode plugin mutating the assembled prompt through an experimental code hook. VS Code Copilot offers a partial fourth, bundling a selectable agent or an on-demand skill but no instructions component, while Codex and Cursor leave the global config-tree deposit as their only channel.
    </claude_only_component>
    <system_prompt_replacement>
      Establish which direction a change moves text before porting it, because a style subtracts where a rules file only adds. Claude appends the style body to the end of the system prompt and leaves out its own built-in software-engineering instructions — how to scope changes, write comments, and verify work — unless the frontmatter sets `keep-coding-instructions: true`. A rules file such as `CLAUDE.md` or `AGENTS.md` adds a message after the system prompt and removes nothing. So the same paragraph moved from a style into a rules file quietly stops displacing the coding instructions, and moved the other way it quietly starts displacing them. Name which of the two effects the change intends, and set `keep-coding-instructions: true` whenever the style governs voice while the agent still writes code. That flag then picks the channel when the style is ported, so read it before choosing one: a style carrying `keep-coding-instructions: true` is additive in effect and belongs in the target's additive channel, while a style that deliberately drops the coding instructions belongs in whatever the target offers that replaces, such as a Codex `model_instructions_file` or an OpenCode agent `prompt`. Porting across that line silently changes what the prose does, and the resulting mismatch shows up as behavior rather than as an error. A port that lands in a replacing channel also inherits the duty of supplying whatever the displaced base prompt used to provide, since no target offers a keep-the-original flag of its own. Two scope limits bound what a style can be trusted to carry. It applies to the main conversation only, since a subagent runs its own system prompt and only a fork inherits the parent's, so an agent definition's policy has to stand on its own exactly as `<readonly_agent_enforcement>` requires. And a custom style, whether user-level or plugin-supplied, is disabled under safe mode in the 2.1.215 build, which annotates the saved value as `<name> (disabled in safe mode)` — so a rule that must hold in every session belongs somewhere other than a style.
    </system_prompt_replacement>
    <two_delivery_modes>
      Pick one of exactly two delivery modes before authoring a style, because they differ in reach rather than only in file location, and neither substitutes for the other. A **global style** governs every session on the machine and is two placements rather than one: the file at `~/.claude/output-styles/<name>.md` in the user config tree, and `"outputStyle": "<name>"` in `~/.claude/settings.json`. Nothing in the plugin system writes either of them — a marketplace install, a `/plugin` enablement, and a plugin manifest all leave the user config tree untouched — so a component repository reaches this mode only through a deploy step that copies the file into the config tree and merges the settings key. That is the mirror image of the caution in `<native_fields_and_deploy_bridges>`, where a deploy-only convention is invisible on every native install path: here the effect is reachable by the deploy path alone, so the absence of a plugin channel is the design of the feature rather than a gap to work around. A **plugin-integrated style** ships inside the plugin under `output-styles/` and is live only while that plugin is loaded and enabled, which makes its coverage a property of the plugin rather than of the user. Declare which mode a style targets in its own documentation, since the two need different placements, different activation instructions, and different removal steps, and a style written for one mode does nothing when dropped into the other's channel.
    </two_delivery_modes>
    <style_locations_and_activation>
      Resolve the style file and the setting that selects it as two separate placements, each with its own scope. A style file lives at user level in `~/.claude/output-styles/`, at project level in `.claude/output-styles/`, or in the managed-settings directory, and the filename supplies the style name unless frontmatter `name:` overrides it. Project styles load from every `.claude/output-styles/` between the working directory and the repository root, and since v2.1.178 the directory nearest the working directory wins a name collision. Selection is the `outputStyle` settings key, and project and local settings outrank the user-level key that the global mode sets. Running `/config` and choosing **Output style** writes the pick to `.claude/settings.local.json` at local project scope, so a pick made that way binds one project rather than the machine, and an operator who selects a style there has not achieved the global mode. Keep activation prose current, because two documented routes are gone: the standalone `/output-style` command was deprecated in v2.1.73 and removed in v2.1.91, and `claude config` is no longer a CLI subcommand at all, so an instruction naming either fails for the operator who follows it. `/config` is an interactive terminal dialog, so a desktop-application session cannot reach the picker; on the desktop build inspected 7 August 2026 the settings file is the only route there, which is a further reason to document the file-and-key pair rather than a menu path. The style is read into the system prompt once at session start, so an edit to either half takes effect after `/clear` or in a new session rather than immediately.
    </style_locations_and_activation>
    <plugin_bundled_styles>
      Wire the plugin-integrated mode through the manifest fields the loader actually reads, and size its coverage against the enablement condition `<two_delivery_modes>` states. A plugin auto-discovers one Markdown file per style from `output-styles/` at the plugin root; an `outputStyles` entry in `.claude-plugin/plugin.json` takes a path or an array of paths to files or directories and replaces that default scan, so list `./output-styles/` explicitly alongside any custom path to keep both. A marketplace entry can declare `outputStyles` as well, resolving against the plugin's own declaration with append or replace semantics, and declaring components in both places at once is a conflict Claude reports as an error instead of merging. A plugin style is namespaced `<plugin>:<style>`, where the style half comes from frontmatter `name:` or the filename, so any settings value that selects it carries the prefix; without a `description:` the picker shows a generated line naming the source plugin. `force-for-plugin: true` is what makes a plugin style apply on its own, overriding the user's `outputStyle` for as long as the plugin is enabled, and the first style loaded wins when several enabled plugins force one. Weigh that flag against the four gaps it still leaves: an unforced style is merely offered in the picker and waits for someone to select it, a forced one is absent from every session where the plugin is not enabled, both are switched off under safe mode, and an edit to any plugin component other than a skill needs `/reload-plugins` or a restart before a session sees it. Those four are why forcing a plugin style is not a route to the global mode, however close it looks. The 2.1.215 loader also skips a plugin style that is not a regular file or exceeds 1,048,576 bytes, which is generous but finite for a generated or concatenated style.
    </plugin_bundled_styles>
    <codex_counterparts>
      Map an output style onto Codex by splitting it into the axes it combines, because Codex offers no single equivalent and each near-analogue covers a different one. Verified 7 August 2026 against learn.chatgpt.com/docs/config-file (`config-basic`, `config-advanced`, `config-reference`) and developers.openai.com/codex/plugins/build; re-verify before relying on it, and note that no local Codex build was available to confirm the keys against the installed loader. `model_instructions_file` is the closest match on the replacing axis: a config key naming a file the reference documents as a replacement for the built-in instructions rather than for `AGENTS.md`, so it displaces them wholesale with no `keep-coding-instructions` equivalent to retain the engineering half, and a relative path inside a project config resolves against the `.codex/` directory holding that `config.toml`. `personality` is the closest on the communication axis and the only one switchable inside a running session, through `/personality` or a per-thread or per-turn override in the app-server API, but it is a closed enum of `none`, `friendly`, and `pragmatic` honoured only by models that advertise `supportsPersonality`, so it selects a vendor-defined tone and cannot carry a repository's own prose conventions. A profile is the closest on the named-and-switchable axis: `~/.codex/<profile>.config.toml`, selected with `--profile <name>` and holding top-level keys rather than a `[profiles.<name>]` table, which lets one named layer set an instructions file, a personality, and `model_verbosity` (`low`, `medium`, `high`, Responses API providers only) together. A profile paired with an instructions file is therefore the nearest functional composite of a Claude output style, at the cost that selection is a launch flag rather than a persisted key every session reads. The additive counterpart remains `AGENTS.md`, global at `~/.codex/AGENTS.md` and discovered per project, bounded by `project_doc_max_bytes` with `project_doc_fallback_filenames` naming alternates when it is absent.
    </codex_counterparts>
    <codex_delivery_modes>
      Carry the split in `<two_delivery_modes>` over to Codex knowing that only one of the two modes exists there, and plan against the asymmetry rather than around it. The global mode maps directly and needs the same kind of config-tree deposit: a deploy step writes the instructions file or `AGENTS.md` into `~/.codex/` and merges `model_instructions_file`, `personality`, or `model_verbosity` into `~/.codex/config.toml`. That deposit lands in the middle of Codex's layer stack rather than on top of it, since each layer overrides the one before in the order system defaults, user `~/.codex/config.toml`, the `--profile` file when one is passed, project `.codex/config.toml` closest to the working directory, then CLI flags and `--config` overrides. A profile or a project config therefore outranks the global deposit, the same way project and local settings outrank the user-level `outputStyle` key in `<style_locations_and_activation>`, which makes the global mode a default rather than a guarantee on both harnesses. The plugin-integrated mode has no counterpart at all, since a Codex plugin ships skills, MCP servers and app connections, and lifecycle hooks, while its manifest carries no settings, instructions, personality, or style component. A Codex plugin therefore cannot set communication style at all except through a skill the model chooses to load, which is opt-in per turn rather than standing, so a plan that assumes the two harnesses mirror each other ships a style that is simply missing on the Codex side. One trust caveat belongs with any project-scoped placement: Codex loads project `.codex/` layers only for a trusted project, so an instructions file wired through `<repo>/.codex/config.toml` does nothing until the user trusts that project, and it stays silent rather than reporting the skip.
    </codex_delivery_modes>
    <cursor_counterparts>
      Read Cursor as additive-only, with nothing that displaces the system prompt and no plugin route to one. Verified 7 August 2026 against cursor.com/docs/context/rules and /docs/agent/modes; re-verify before relying on it. Rules are the whole mechanism: markdown under `.cursor/rules/` whose frontmatter selects one of four activation modes — Always Apply through `alwaysApply: true`, Apply Intelligently from a `description`, Apply to Specific Files from `globs`, and Apply Manually by `@`-mention — and an applied rule is included at the start of the model context rather than replacing anything already in it. Nested `AGENTS.md` files are the frontmatter-free alternative, with the more specific file taking precedence. The closest thing to a machine-wide voice is a User Rule, global to the Cursor installation but edited in its settings interface rather than in a file a deploy step can write, and it carries a limit worth stating in any plan that leans on it: User Rules reach Agent chat only, not Inline Edit or Tab, so a voice set there governs part of the product rather than all of it. Modes exist and switch from the picker or Shift+Tab, but the modes documentation sources no user-defined mode carrying its own instructions, so treat a mode as a tool-and-behavior preset rather than a style slot until that changes.
    </cursor_counterparts>
    <antigravity_counterparts>
      Treat Antigravity as additive-only on voice as well, and as the one non-Claude harness that offers a plugin-integrated route for the prose carrying it. Verified 7 August 2026 against antigravity.google/docs/rules-workflows; re-verify before relying on it. Its rules documentation describes no tone, persona, output-format, or system-prompt-replacement feature at all, presenting rules purely as constraints the agent follows, so the entire mapping runs through the rules surface `<antigravity_config_tree>` specifies: markdown under `.agents/rules/` or the single global `~/.gemini/GEMINI.md`, capped at 12,000 characters per file, with Always On as the activation mode corresponding to a standing voice. The delivery picture then diverges from every other non-Claude target, because `rules/` is one of the documented components of the plugin bundle in `<antigravity_plugin_bundle>`. Antigravity therefore supports both modes in the shape `<two_delivery_modes>` describes, a config-tree deposit globally and a bundled `rules/` directory inside a plugin, while still replacing nothing. Size the content for the cap in either mode, since 12,000 characters binds each rule file whatever ships it, so a long style splits across several rule files rather than travelling as one.
    </antigravity_counterparts>
    <opencode_counterparts>
      Read OpenCode as the one non-Claude harness that genuinely replaces part of its own system prompt, and read that fact off the source rather than the documentation, which states none of it. Verified 7 August 2026 against opencode.ai/docs/agents and /docs/rules for the surface, and against `packages/opencode/src/session/llm/request.ts`, `packages/opencode/src/session/prompt.ts`, and `packages/opencode/src/session/system.ts` on the `dev` branch of github.com/sst/opencode, cross-checked against the same expression in the installed OpenCode desktop 1.17.9 bundle; re-verify before relying on it, and expect the internals to move faster than a documented contract would. Its additive carrier is the widest of the set: `AGENTS.md` in the project and `~/.config/opencode/AGENTS.md` globally, plus an `instructions` array in `opencode.json` accepting file paths and remote URLs, with every discovered file combined rather than one winning. Its switchable carrier is a primary agent, whose `prompt` config names a custom system-prompt file and which the user cycles with Tab or the `switch_agent` keybind inside a running session, so switching lands mid-session where Codex's `--profile` needs a relaunch. The assembly answers the replace-versus-append question structurally, and splits it along the same seam Claude's `keep-coding-instructions` does: request preparation emits `agent.prompt ? [agent.prompt] : SystemPrompt.provider(model)`, so a set `prompt` **replaces** the per-model base prompt that `system.ts` selects — `anthropic.txt`, `gpt.txt`, `codex.txt`, `gemini.txt` and the rest — rather than joining it, while the environment block, the `AGENTS.md` and `instructions` content, the MCP instructions, and the skills catalogue are appended afterwards unconditionally and survive the swap, as does a user-supplied system string. The replaceable half is therefore the vendor base prompt and the kept half is project and environment context, which is a cleaner split than Claude's all-or-nothing flag while reaching only the vendor prose. One discovery point belongs with the global mode, as a reason to deploy natively rather than as an opportunity: the Claude-compatibility roots catalogued in `<foreign_directory_adoption>` cover `~/.claude/CLAUDE.md` and `.claude/skills/` and list no output-style directory, so adoption would not carry a style here even before the isolation switches in `<opencode_isolation_switches>` turn it off.
    </opencode_counterparts>
    <opencode_delivery_modes>
      Reach both delivery modes on OpenCode through channels that are neither declarative nor stable, and price that in before choosing it. The global mode is the ordinary config-tree deposit, and the `instructions` array is the member of it that a deploy step should target, because it adds to the rules the user already has where a written `AGENTS.md` would suppress them per `<foreign_directory_adoption>`. Its resolution rules are what make it deployable, and they are worth knowing exactly (verified 7 August 2026 against `packages/opencode/src/session/instruction.ts` on the `dev` branch of github.com/sst/opencode; re-verify before relying on it). An entry beginning `http://` or `https://` is fetched at prompt-assembly time under a five-second timeout and contributes nothing on failure, which makes a remote entry a runtime dependency rather than a deployed artefact. An entry beginning `~/` expands against the home directory. An absolute path is globbed by its basename within its own directory, and a relative entry globs upward from the working directory to the worktree. Every match resolves to an absolute path collected in a set, so a repeated deploy of the same entry is idempotent and needs no duplicate check. Each file is injected as `Instructions from: <path>` followed by its content, so the prose arrives labelled with its own path rather than anonymously, and the injected block sits in the appended tail after the environment section rather than at the very end of the prompt. Reach is wider than a Claude style: the tail is appended to every request whatever agent is running, so instructions deployed this way govern subagents too, where a Claude style covers the main conversation only. An agent markdown file under `~/.config/opencode/agents/` carrying a `prompt` path is the other global-mode placement, and belongs to the replacing route rather than this one. A plugin-integrated route does exist, contrary to what the absence of a style component suggests, but it lives in code: request preparation fires the `experimental.chat.system.transform` plugin hook on the assembled system array and lets a plugin mutate it in place, which is the executable-plugin model `<opencode_plugin_hooks>` describes applied to the prompt rather than to a tool call. Weigh two costs against that reach. The hook carries `experimental` in its own name, so pin any dependency on it to a tested version and expect the contract to move. And a code hook is a different review and trust proposition from a markdown component, so a prose convention delivered this way is harder for a reader to audit than the same words in a rules file.
    </opencode_delivery_modes>
    <vscode_counterparts>
      Read VS Code Copilot as append-only like Cursor and Antigravity, while noting it has the most direct global deposit of any non-Claude target and a third injection position that changes how prose reads. Verified 7 August 2026 against code.visualstudio.com/docs/copilot/customization (custom-instructions, custom-chat-modes, overview) and code.visualstudio.com/docs/agent-customization/agent-plugins; re-verify before relying on it, since the plugin surface is documented as preview and the mode file was renamed from `*.chatmode.md` to `*.agent.md`. Instructions are the always-on carrier and they combine rather than compete. Its own roots are `.github/copilot-instructions.md` at the workspace root, `*.instructions.md` files whose `applyTo` glob scopes them to matching files, an auto-detected workspace `AGENTS.md`, and `~/.copilot/instructions` for the user profile, with `chat.instructionsFilesLocations` adding further workspace locations. It additionally reads `CLAUDE.md` from a workspace root, a `.claude` folder, or `~/.claude/CLAUDE.md`, and `~/.claude/rules` at user level, but those are the adoption roots `<foreign_directory_adoption>` catalogues rather than places a deploy writes. A custom agent is the switchable form: a `*.agent.md` file carrying `description`, `name`, `tools`, `model`, `handoffs`, and `agents` frontmatter, kept in `.github/agents` for a workspace and `~/.copilot/agents` for the user, with a workspace `.claude/agents` read by adoption on the same terms, and picked from a dropdown in the chat view. Its body is prepended to the user chat prompt rather than merged into the system prompt, which is a third position beside a Claude style's system-prompt tail and OpenCode's appended system section, so prose written to read as standing system instruction arrives inside the user turn instead and any wording that assumes a system voice reads oddly there. Nothing documented replaces the base prompt, so VS Code sits on the append-only side with Cursor and Antigravity.
    </vscode_counterparts>
    <vscode_delivery_modes>
      Split the two modes on VS Code by which form of the style is shipping, because the plugin route covers one form and not the other. The global mode is the strongest of the non-Claude set: a deploy step writes the generated VS Code variant into the user profile at `~/.copilot/instructions`, its own native root, from where it applies across every workspace at the top of the documented precedence above repository and organization instructions. The `~/.claude/rules` path the documentation pairs with it reaches the same place through adoption and is not a deploy target, so writing there would put one file in front of two harnesses and hand VS Code a Claude-shaped artefact. No settings key activates it, which is one moving part fewer than the Claude, Codex, and OpenCode global modes each need, and it makes VS Code the cheapest target to reach. The plugin-integrated mode is partial. An agent plugin bundles MCP servers, skills, agents, hooks, and slash commands under a `plugin.json`, and the documented component list carries no instructions entry, so a standing style has no plugin channel. Two indirect routes exist and both change the activation model rather than merely the packaging: a bundled `agents/<name>.agent.md` delivers the prose as a persona the user has to select, and a bundled skill carries instructions that load on demand when the model judges the skill relevant. Neither is standing, so a plugin here buys availability rather than effect, which is the same distinction an unforced Claude plugin style carries in `<plugin_bundled_styles>`.
    </vscode_delivery_modes>
    <style_frontmatter_schema>
      Keep output-style frontmatter to the four accepted keys, and treat the frontmatter as metadata that never reaches the model: Claude strips it and injects the body alone, so a port to another harness carries the body and drops the block rather than shipping the file whole. Note also that this surface is strict where Claude's agent frontmatter is tolerant. The keys are `name`, `description`, `keep-coding-instructions`, and `force-for-plugin`, and the schema rejects any fifth key outright rather than ignoring it, unlike the ignore-unknown behavior `<frontmatter_schema_tolerance>` records for Claude agent files. The difference decides what a shared source can do: the union-of-native-fields pattern that lets one agent file carry several harnesses' keys side by side has no equivalent here, so a style file stays single-harness in its metadata as well as its effect. `force-for-plugin` is meaningful only on a plugin-bundled style; set on a user-level file it is ignored and logged as a warning on every load, so leave it out until the file actually ships inside a plugin.
    </style_frontmatter_schema>
  </claude_output_styles>

  <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>Every supported artefact reaches each target as a generated variant in that target's own native root, with adoption from a sibling harness's tree treated as a leak to disable where a switch exists rather than as the delivery path, and with any unavoidable adoption stated as a deliberate fallback. 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>
    <output_styles>A Claude output style declares which of the two delivery modes it targets: global, meaning the file in the user config tree plus the `outputStyle` key in `~/.claude/settings.json`, which only a deploy step into that tree can achieve; or plugin-integrated, meaning `output-styles/` inside the plugin and live only while that plugin is loaded and enabled. The style also sets `keep-coding-instructions: true` wherever the agent still writes code, keeps its frontmatter to the four accepted keys, and describes activation through `/config` or the settings file rather than through the removed `/output-style` command or the retired `claude config` subcommand. Behavior that must hold in every session, and inside subagents, lives somewhere other than a style, since a style reaches the main conversation only and is disabled under safe mode. Where the same intent has to reach Codex, Cursor, OpenCode, Antigravity, or VS Code Copilot, a skill or each harness's own rules file carries it, and the additive-versus-replacing difference is accounted for rather than assumed away. A Codex plan in particular names which near-analogue it uses — `model_instructions_file`, `personality`, a profile, or `AGENTS.md` — and every plan states per harness whether its chosen mechanism displaces or appends, and whether a plugin-integrated route exists there at all, rather than generalizing either property from Claude.</output_styles>
    <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>
