Teemu Piirainen

One Source, Multiple Harnesses: Building a Portable Agent Marketplace

Sep 27, 2026 · ai · agents · claude · codex · workflow

A Claude Code terminal with /plugin install aw-review@awave-agents typed at the prompt, with arrows fanning out from it to the Codex, GitHub Copilot and OpenCode logos.

When I started using Codex after more than a year with Claude Code, I wanted to bring my agents, skills, and review workflows with me. I now generate and run Copilot CLI and Codex exports from the same Claude Code source.

One repository serves as the marketplace for all three. Each installs aw-review@awave-agents, with its own catalog pointing at the appropriate package.

The first article covered the plugin itself. This article covers the implementation that lets a workflow correction reach every supported platform from one maintained source.

A harness is the application around the model: tools, permissions, context handling, agent lifecycle, and plugin loading. Porting a workflow means adapting to those behaviors as well as converting files.

1. Keep one source and generate deployments

The Claude Code plugin layout already expresses our skills, agents, hooks, and scripts, so it remains the authoring format. Fixes go into that source or the build adapter. Generated output is never edited manually.

The Agent Skills specification (opens in a new tab) gives us a shared SKILL.md format with metadata and supporting resources. Agent Plugins (opens in a new tab) adds a portable root plugin.json, skills, and MCP configuration. Agent roles and hooks still require host-specific integration.

Our build separates capability tables, platform translation, and shared helpers:

awave-agents/ ├── .claude-plugin/marketplace.json # hand-written ├── .github/plugin/marketplace.json # generated ├── .agents/plugins/marketplace.json # generated ├── plugins/aw-review/ # canonical source ├── build.mjs # exporter CLI ├── build/ │ ├── harnesses.mjs # models, efforts, tools, dropped keys │ ├── harness/ │ │ ├── copilot.mjs │ │ └── codex.mjs │ └── lib/ # shared helpers and adjacent tests ├── scripts/ # repository checks; not shipped │ ├── check-contract.mjs │ ├── check-pipeline.mjs │ └── check-node-launcher.mjs ├── release.sh └── dist/ ├── copilot/aw-review/ └── codex/aw-review/

This tree shows implemented targets. OpenCode remains planned.

build/harnesses.mjs centralizes the mappings and harness-only script lists. Each module under build/harness/ translates declarations and generates its deployment. Runtime scripts come from plugins/aw-review/scripts/ and ship inside the applicable package.

The build must report unsupported fields, unknown tools, and fallback models. Silent translation losses become runtime surprises.

2. Route each harness to its own package

Each loader reads a different marketplace file at the repository root:

HarnessMarketplace filePackage path
Claude Code.claude-plugin/marketplace.json./plugins/aw-review
Copilot CLI.github/plugin/marketplace.json./dist/copilot/aw-review
Codex.agents/plugins/marketplace.json./dist/codex/aw-review

Both Copilot and Codex fall back to the Claude file when their own is missing. Before our Codex catalog existed, installation succeeded but loaded the Claude source, including Claude tool names and an unresolved plugin-root variable.

The build writes both generated catalogs on every run, taking name and owner from the Claude marketplace manifest and version from the plugin. Before writing the Codex catalog, it validates that entry and plugin names agree and that local paths start with ./ and remain inside the repository.

Package loading differs too:

ComponentClaude sourceCopilot exportCodex export
Manifest.claude-plugin/plugin.jsonRoot plugin.json.codex-plugin/plugin.json
Agentsagents/*.mdcom.github.copilot/agents/*.mdTOML roles installed separately
Skillsskills/skills/skills/
Hookshooks/hooks.jsoncom.github.copilot/hooks/hooks.jsonhooks/hooks.json

The source carries both Claude and Agent Plugins manifests; each export selects its format. See the Copilot plugin reference (opens in a new tab) for its layout.

Our Codex observations below are from CLI 0.156.1 and 0.157.1, tested on September 26, 2026. OpenAI’s packaging documentation (opens in a new tab) describes root plugin.json with extensions.com.openai, but in those versions it took precedence without loading our hooks correctly. Our export uses only .codex-plugin/plugin.json and rejects a root manifest, a workaround tracked in the upstream issue (opens in a new tab).

3. Translate behavior explicitly

Agent instructions remain Markdown in Copilot and become TOML developer_instructions in Codex. Model fields also change: Claude’s effort becomes reasoning-effort in Copilot and model_reasoning_effort in Codex.

Our Copilot exporter maps Read to view, Grep to rg, and Agent to task, including references in instruction text. I used names enumerated by the CLI after encountering rejected names copied from VS Code configurations. Copilot documents compatible names and aliases (opens in a new tab), but the executable still needs testing.

Model mappings belong in the capability tables. Mapping several source tiers to one target model is a cost decision; review quality needs separate evaluation.

Some differences affect execution more directly:

  • Permissions: in our Codex tests, role-level sandbox_mode = "read-only" did not stop children inheriting workspace-write permissions. A generated setting cannot serve as proof of enforcement. Our Copilot export also drops disable-model-invocation with a warning; carrying the restriction in instructions is weaker than host enforcement.
  • Context: Codex reviewers initially inherited the orchestrator’s full history. We use fork_turns: "none", role instructions, and an explicit task message. Project instructions and other harness context still remain.
  • Instruction size: Codex truncated plugin skill bodies at 8,000 bytes. Our build warns about oversized skills; one still needs splitting into reference files that the workflow explicitly reads.

These are behavioral differences to expose and test alongside syntax conversion.

4. Separate build adapters from runtime integration

Shared Node scripts stay byte-identical across our exports. They normalize supported payload differences, such as agent_type versus agentType, before applying review logic.

The build translates hook declarations. Copilot uses entries with bash, cwd, and timeoutSec; Claude uses nested matcher-and-hook structures. The Copilot hook reference (opens in a new tab) defines its contract.

Codex-specific runtime work lives in review-codex.mjs. It performs coverage checks and collects metrics, then forwards the payload to the shared review-output.mjs, preserving its output and exit status. Only the Codex export ships this wrapper and its role installer. The exporter never patches shared JavaScript.

Installation is part of the adapter’s responsibility. Codex did not load agent TOML files from inside the plugin, so the review skill calls codex-agents.mjs ensure on first use. It installs changed roles under ~/.codex/agents/aw-review/, potentially requiring approval to write outside the workspace. New role names require a new thread; existing role files were reread at spawn in our tests. See custom subagents (opens in a new tab).

Paths also depend on execution context. Codex supplies PLUGIN_ROOT and CLAUDE_PLUGIN_ROOT to hook commands, but did not expand the latter in skill text. Our build substitutes <plugin-root> and instructs the model to resolve it from the loaded skill’s absolute path. Test supporting scripts from the installed cache, away from the source checkout.

5. Verify inputs, activation, and results

In aw-review, each specialist must produce an artifact identifying its producer and current run and satisfying the expected schema. Claude’s SubagentStop supplies a capture point; compatible Codex fields let the shared capture logic run unchanged.

Build checks establish the foundation. check-contract.mjs, check-pipeline.mjs, and check-node-launcher.mjs check instruction-to-script contracts, pipeline execution, and Node discovery on source and exports. node --test 'build/**/*.test.mjs' checks mappings, one model family per harness, and rebuilding without orphaned files. Generated TOML and manifests are validated too.

Our first Codex review exposed what those checks missed. For a 17-file change, four of six reviewers read all patches in one command. Output exceeded the token budget and was truncated. All six returned empty findings, and the ledger accepted the run.

We added instructions to read patches separately with sufficient output budgets and a coverage check in the Codex wrapper. It looks for patch headers in untruncated output in each child’s transcript and asks the reviewer to continue when required patches are missing.

This check has limits: a visible header does not prove understanding. It blocks at most twice, then records remaining gaps and accepts the answer. Whether unresolved gaps should fail the run remains open.

Keep four levels of verification distinct:

LevelEvidence needed
PackageThe catalog resolves to the intended build and it installs
ActivationRequired skills, roles, and hooks execute
ExecutionRequired inputs are visible and valid artifacts are produced
QualityRepresentative reviews find known defects

Session logs reveal inherited context, truncated output, and effective settings. A passing artifact check alone cannot establish review quality.

6. Release and test the installed lifecycle

release.sh updates versions, runs build unit tests, and rebuilds every supported target with its checks. Commit source changes, generated dist/ trees, and both generated marketplace files together. A marketplace installed from Git sees only committed files.

All three harnesses install the same package from the same repository:

HarnessAdd marketplaceInstall plugin
Claude Code/plugin marketplace add <owner>/<repo>/plugin install aw-review@awave-agents
Copilot CLI/plugin marketplace add <owner>/<repo>/plugin install aw-review@awave-agents
Codexcodex plugin marketplace add <owner>/<repo>codex plugin add aw-review@awave-agents

Restart Copilot after installation: our first run captured no output because hooks had not registered. For Codex, trust plugin hooks in /hooks and start a new session; first-use role installation may then require another thread. Changing a hook command can require renewed hook trust (opens in a new tab).

Test installation, execution, update, and removal in a separate consumer project. Codex supports local marketplaces but copies installed plugins into a versioned cache. Rebuilding the source does not update that copy. Our development loop uses a cachebuster version suffix, CLI reinstallation, and a new thread; verify the cache contains the changed build.

Removal also needs testing. Deleting the Codex plugin leaves separately installed roles behind, so our role installer provides its own removal operation.

Start with one plugin and one additional harness. Implement its package mapping, installation, and runtime integration, then change the source and verify the update in a consumer project. That gives you a tested maintenance path before adding another platform.