191 lines
10 KiB
Markdown
191 lines
10 KiB
Markdown
# Authoring portable plugin content
|
||
|
||
Plugin content in this repo ships to **five** harnesses: OpenAI Codex CLI, Cursor, OpenCode, Gemini CLI, and GitHub Copilot. Claude Code is the source-of-truth. The adapter framework handles per-harness
|
||
mechanics (frontmatter rewrites, format transforms, output paths) so you author one set of
|
||
markdown files. But content choices still affect portability — this guide tells you what to
|
||
do, and what to avoid, so the work you do for Claude Code translates cleanly everywhere.
|
||
|
||
## The principles (from OpenAI's harness-engineering post)
|
||
|
||
1. **Context file is a table of contents, not an encyclopedia.** Keep `AGENTS.md`,
|
||
`CLAUDE.md`, and `GEMINI.md` under ~150 lines / ~500 tokens. Detail belongs in
|
||
`docs/` or in a skill's `references/`.
|
||
2. **Repository is the system of record.** If it's not in `plugins/` or `docs/`, the
|
||
agent can't see it. No Slack threads, no Google Docs, no Notion. Push knowledge into
|
||
the repo so every harness can ground on it.
|
||
3. **Enforce invariants, not implementation.** Frontmatter shape, file naming, and
|
||
trigger-phrase conventions are mechanically enforced by `plugin-eval`. Style and
|
||
tone within those bounds are your call.
|
||
4. **Boring tech preference.** Markdown + YAML frontmatter + small Python adapters. No
|
||
templating engines, no DSLs, no harness-specific markup.
|
||
|
||
> **Native-install registries are generated and committed.** The per-harness install
|
||
> manifests (Codex `.agents/plugins/marketplace.json` + `plugins/*/.codex-plugin/plugin.json`,
|
||
> `.cursor-plugin/`, `gemini-extension.json`) point at the source `plugins/` and are checked in.
|
||
> Run `make generate-all` before committing source changes — CI gates registry drift.
|
||
|
||
## Frontmatter
|
||
|
||
| File | Required | Recommended | Notes |
|
||
|---|---|---|---|
|
||
| `agents/<name>.md` | `name`, `description` | `model`, optional `tools:`, optional `color:` | `tools:` allowlist becomes a per-harness permission block where supported, dropped otherwise. |
|
||
| `skills/<name>/SKILL.md` | `name`, `description` | (none) | Other Anthropic SKILL.md fields work on Claude Code only. |
|
||
| `commands/<name>.md` | `description` | `argument-hint:` | Codex converts these to skills (it deprecated `~/.codex/prompts/`). Copilot emits `.copilot/commands/<plugin>/<name>.md` slash-command prompts. |
|
||
|
||
**Description triggers.** Include a recognized phrase: `Use when …`, `Use this skill when …`,
|
||
`Use PROACTIVELY when …`, `Use after …`, `Trigger when …`, `Auto-loads when …`. The
|
||
`MISSING_TRIGGER` lint fires without one. The phrase is what the model uses to decide whether
|
||
to invoke your skill/agent.
|
||
|
||
## Body content
|
||
|
||
### Talk about actions, not tools
|
||
|
||
Codex's underlying GPT-5.x models don't have a `Read`/`Edit`/`Bash` vocabulary — the model picks
|
||
the native tool from the action you describe. OpenCode is strict about lowercase
|
||
(`read`, `bash`). Cursor's agent has its own vocabulary.
|
||
|
||
| Don't write | Write instead |
|
||
|---|---|
|
||
| "Use the `Read` tool to open the file." | "Open the file." |
|
||
| "Use the `Bash` tool to run `npm test`." | "Run `npm test`." |
|
||
| "Call the `Grep` tool with pattern X." | "Search for pattern X." |
|
||
| "Use `TodoWrite` to track progress." | "Track progress as you go." (No equivalent in Codex/Cursor.) |
|
||
| "Spawn a subagent via the `Task` tool." | "Delegate to a subagent." (Codex: name the agent in prose.) |
|
||
|
||
The `harness_portability` lint surfaces `CLAUDE_TOOL_REFS` and `CLAUDE_TOOL_PROSE` findings
|
||
with concrete fix suggestions. The adapter does a conservative rewrite at generation time
|
||
but explicit phrasing produces cleaner output.
|
||
|
||
### Respect the Codex 8 KB skill body cap
|
||
|
||
Codex hard-truncates `SKILL.md` bodies at 8 KB and warns. Push detail into
|
||
`skills/<name>/references/` files — agents load them on demand. The `SKILL_OVER_CODEX_CAP`
|
||
lint fires for any skill above 8 KB that has no `references/` directory.
|
||
|
||
```
|
||
skills/my-skill/
|
||
├── SKILL.md # navigation + quick-start, ≤ 8 KB
|
||
└── references/
|
||
├── details.md # deep implementation notes
|
||
├── api-reference.md
|
||
└── examples/
|
||
```
|
||
|
||
Link from `SKILL.md` like ``See `references/details.md` for the full algorithm.`` — keep the
|
||
link target as backticked path text so the gardener's dead-link checker doesn't false-positive
|
||
on illustrative examples.
|
||
|
||
### Use globally unique agent names
|
||
|
||
Claude Code keys installed agents by the YAML frontmatter `name`, so two plugins that
|
||
ship the same agent name can silently overwrite each other when installed together. Use
|
||
plugin-scoped names for common roles using `<plugin-directory>-<agent-file-stem>`
|
||
(`backend-development-test-automator`, not `test-automator`) and update any bundled
|
||
command `subagent_type` references to match.
|
||
CI runs `tools/check_agent_name_collisions.py --fail-on-duplicates` to keep the source
|
||
tree collision-free.
|
||
|
||
### Don't collide with Codex built-in agent names
|
||
|
||
`default`, `worker`, and `explorer` are built-in Codex subagent roles. If you name a custom
|
||
agent any of those, the Codex adapter namespaces it (`<plugin>__worker`) and the
|
||
`AGENT_NAME_COLLISION` lint fires. Prefer plugin-scoped names from the start.
|
||
|
||
### Same-name command and skill collisions (Codex)
|
||
|
||
Codex deprecated `~/.codex/prompts/` in favor of skills, so the adapter synthesizes a skill
|
||
from every command. If your plugin has a skill **and** a command sharing the same name (say
|
||
`review`), the adapter would otherwise produce two entries at
|
||
`.codex/skills/<plugin>__review/SKILL.md` — the second clobbering the first.
|
||
|
||
To prevent silent overwrite, the adapter detects this collision and namespaces the
|
||
command-derived skill with a `__command` suffix:
|
||
|
||
- `plugins/<p>/skills/review/SKILL.md` → `.codex/skills/<plugin>__review/SKILL.md`
|
||
- `plugins/<p>/commands/review.md` → `.codex/skills/<plugin>__review__command/SKILL.md`
|
||
|
||
A warning is emitted whenever this happens. Avoid the collision in source if you want
|
||
clean naming — pick distinct names for skill/command pairs within a plugin.
|
||
|
||
### Model aliases
|
||
|
||
| Source field | Codex | Cursor | OpenCode | Gemini | Copilot |
|
||
|---|---|---|---|---|---|---|
|
||
| `model: fable` | `gpt-5.5` | `inherit` | `anthropic/claude-fable-5` | `gemini-2.5-pro` | `claude-fable-5` |
|
||
| `model: opus` | `gpt-5.5` | `inherit` | `anthropic/claude-opus-4-8` | `gemini-2.5-pro` | `claude-opus-4.8` |
|
||
| `model: sonnet` | `gpt-5.4-mini` | `inherit` | `anthropic/claude-sonnet-5` | `gemini-2.5-pro` | `claude-sonnet-5` |
|
||
| `model: haiku` | `gpt-5.4-mini` | `inherit` | `anthropic/claude-haiku-4-5` | `gemini-2.5-flash` | `claude-haiku-4.5` |
|
||
| `model: inherit` | `gpt-5.5` | `inherit` | `anthropic/claude-sonnet-5` | `gemini-2.5-pro` | `claude-sonnet-5` |
|
||
|
||
The adapter handles mapping. The `BARE_MODEL_ALIAS` lint is informational — it just notes
|
||
that the mapping is implicit. If you want explicit, use `inherit`.
|
||
|
||
Mapping targets live in `tools/adapters/capabilities.py` (`MODEL_ALIASES`) and track each
|
||
harness's published catalog (last verified July 2026). Copilot CLI serves Claude models
|
||
natively — including Fable 5 and Sonnet 5 since late June 2026 — so its aliases map
|
||
Claude → Claude using Copilot's IDs (dotted for minor-versioned models). Gemini stays on
|
||
the GA `gemini-2.5-*` family because Gemini 3.x ships only access-gated `-preview` IDs.
|
||
|
||
`fable` (Claude Fable 5) is the tier above `opus`, reserved for the longest-horizon
|
||
autonomous work. It is native in Claude Code (v2.1.170+, opt-in, ~2.6× Opus effective
|
||
cost); other harnesses map it to their top available model. Tag an agent `fable` only
|
||
when Opus demonstrably needs multiple attempts at the task. Avoid it for
|
||
security-analysis agents — Fable 5's cyber/bio classifiers fall back to Opus there
|
||
anyway. Prefer stating goals over step-by-step scaffolding in fable-tier agent bodies,
|
||
and never instruct the model to echo its reasoning (triggers `reasoning_extraction`
|
||
refusals).
|
||
|
||
## Skills layout for progressive disclosure
|
||
|
||
The OpenAI harness-engineering post argues that "agents start with a small, stable entry
|
||
point and are taught where to look next." Apply this within each skill:
|
||
|
||
- `SKILL.md` body: navigation + quick-start. What this is, when it fires, the one-paragraph
|
||
decision tree, links into `references/`.
|
||
- `references/`: deep material. `details.md`, `api-reference.md`, `examples/`. Load only
|
||
when the navigation tier is insufficient.
|
||
- `assets/`: templates, configs, scaffolding. Loaded by name when the skill says "scaffold
|
||
from `assets/config.template.ts`".
|
||
|
||
This is the canonical Anthropic SKILL.md pattern. Codex, Cursor, OpenCode, and Gemini all
|
||
honor `references/`.
|
||
|
||
## What translates poorly
|
||
|
||
Things that work in Claude Code but degrade across harnesses:
|
||
|
||
| Source pattern | Why it degrades |
|
||
|---|---|
|
||
| `TodoWrite` references | Only Claude Code and OpenCode support it. |
|
||
| Hooks (`hooks:` frontmatter) | Only Claude Code and OpenCode (via TS plugins). |
|
||
| `color:` on agents | Cosmetic; dropped everywhere except Claude Code. |
|
||
| Per-agent tool allowlist | Honored only on Claude Code/Gemini/OpenCode. Cursor and Codex have coarser models. |
|
||
| Slash commands | Codex converts to skills. Gemini transpiles to TOML. Copilot emits `.copilot/commands/` prompt files. |
|
||
| Marketplace registry | Only Claude Code and Cursor have one. Gemini installs by URL; Codex/OpenCode have no marketplace. |
|
||
|
||
When you must use a feature with no equivalent, the `harness_portability` lint won't fire
|
||
(it's not a portability problem — it's a capability gap). Just document the constraint in
|
||
the skill body so users running on a non-supporting harness know.
|
||
|
||
## Verifying portability locally
|
||
|
||
```bash
|
||
# Lint one plugin against the portability dimension
|
||
cd plugins/plugin-eval
|
||
uv run plugin-eval score ../my-plugin/skills/my-skill --depth quick
|
||
|
||
# Regenerate artifacts for one harness and inspect
|
||
cd ../..
|
||
make generate HARNESS=codex PLUGIN=my-plugin
|
||
diff -ru .codex/skills/my-plugin__my-skill plugins/my-plugin/skills/my-skill
|
||
```
|
||
|
||
The `plugin-eval` static layer runs in <2s and is free. Use it before sending a PR.
|
||
|
||
## See also
|
||
|
||
- [`harnesses.md`](harnesses.md) — full capability matrix per harness
|
||
- [`plugin-eval.md`](plugin-eval.md) — scoring framework and the `harness_portability` dimension
|
||
- [`architecture.md`](architecture.md) — overall design principles
|