One Profile, Four CLIs
One profile, four vendor CLIs, one predictable result. The adapter layer is where the quirks go to die.

Six issues on the Crew and the Council, all running on one vendor CLI. Your team does not all use one CLI. So the real test of a profile is whether it runs the same on Claude Code, Codex, Gemini, and Pi, or whether it quietly means something different on each.
The portability claim
Back in Issue 4 I made the case that a profile is a contract, not a config file. A contract that only holds on one runtime is not much of a contract. It is a script with a favorite vendor.
So here is the test that closes the Patterns arc. Take the same `strategic-council` profile the Council has used since Issue 3, the one that ran the auto-deploy decision on the platform-engineering domain, and point it at four different vendor CLIs. The claim is that you get the same shape of memo out of each: ranked recommendations, agent stances, documented dissent, next actions. The words differ. The contract does not.
If that holds, a Council is a reusable asset. If it does not, every profile is secretly four profiles that happen to share a filename.
The adapter is the runtime boundary
The thing that makes the claim testable is that the harness does not talk to any vendor CLI directly. It talks to an adapter, and the adapter talks to the CLI.
Since 0.6.0 the adapters are separate packages, versioned in lockstep with the harness release line, not with the vendor CLI they drive. All four sit at 0.10.0 today, and that is the harness version, not the CLI's: @aos-harness/claude-code-adapter drives claude, @aos-harness/codex-adapter drives codex, @aos-harness/gemini-adapter drives gemini, @aos-harness/pi-adapter drives pi. The vendor CLIs carry their own version numbers. An adapter does not ship a model or an account. It augments a vendor CLI you already installed and logged into. You bring the CLI, the adapter makes the harness speak its language.
Install is two lines. Pick the adapter that matches the CLI you already have.
bun add -g @aos-harness/codex-adapter # or claude-code / gemini / pi
aos init --adapter codexThe seam that makes one profile portable lives in `adapters/shared/src/`. That directory is the whole trick: adapter-contract.ts, base-agent-runtime.ts, base-event-bus.ts, base-workflow.ts, composite-runtime.ts, tool-policy.ts. The profile is written against that shared contract, and each vendor adapter extends the base classes to translate the contract into whatever the CLI actually wants. The README lays out the four layers: L1 agent runtime (spawn, message, destroy), L2 event bus (session and tool-call hooks), L3 user interface (rendering and confirmations), L4 workflow engine (parallel dispatch, code execution, artifacts).
Collapse those four layers to one idea: contract in, vendor-specific translation out. Vendor quirks stay inside the adapter where they belong, instead of leaking into the profile where they would fork it into four.
Model tier pinning is opt-in
Portable does not mean identical, and the first place that shows is which model runs. The harness lets you decide, per adapter, in .aos/config.yaml under adapter_defaults.
adapter_defaults:
pi:
use_vendor_default_model: false
models:
economy: anthropic/claude-haiku-4-5
standard: anthropic/claude-sonnet-4-6
premium: anthropic/claude-opus-4-7
claude-code:
use_vendor_default_model: true
codex:
use_vendor_default_model: true
gemini:
use_vendor_default_model: trueTwo modes, and only two. Set use_vendor_default_model: true and the adapter defers to whatever model the CLI would pick on its own. Set it to false and pin economy, standard, and premium tiers, and the adapter drives those instead. By default Pi pins explicit tiers, and Claude Code, Codex, and Gemini defer to their CLI's default. Nothing is pinned unless you say so.
Pin when you need reproducibility or cost control, when a run has to cost the same next month as it did today. Defer when you would rather let the CLI pick its own best model and not chase vendor model releases in your config. Both are correct. The point is that the choice is a config line, not a code change, and it is the same line on every adapter.
Where it holds and where it leaks
I have one real cross-adapter data point, not four.
The claude-code column below is a captured run: the Issue 3 auto-deploy deliberation, run as aos run strategic-council --domain platform-engineering --brief <auto-deploy brief>, on @aos-harness/claude-code-adapter@0.10.0, session 2026-07-01-strategic-council-mr1k0uaj. Four rounds, 15.6 minutes, a 28.5 KB memo written to .aos/sessions/2026-07-01-strategic-council-mr1k0uaj/memo.md. Metered cost was $0.00 because it ran on a Claude subscription, not a per-token key.
I did not capture live runs on Codex, Gemini, or Pi. So the other three columns are not transcripts. They are what the shared contract and the config are built to produce, read off the code, and labeled as expectation. I am not going to invent output from runs that did not happen.
| Adapter (CLI) | Model when deferred | Memo contract | This row is |
|---|---|---|---|
| claude-code (`claude`) | vendor default | ranked recs, stances, dissent, next actions | **real run** `...mr1k0uaj` |
| codex (`codex`) | vendor default | same contract, contract-normalized | expected from contract + config |
| gemini (`gemini`) | vendor default | same contract, contract-normalized | expected from contract + config |
| pi (`pi`) | pinned tiers | same contract, contract-normalized | expected from contract + config |The adapter layer normalizes three things: profile assembly, the delegation rounds, and the memo's shape. Those are driven by the shared contract, so they should not move between adapters.
Vendor reality still shows through in three places, and here is exactly where. Token streaming paces differently under each CLI, though the L2 event bus buffers it into one stream. Tool-call formats vary by vendor, and the adapter's onToolCall hook maps them onto the contract. Model defaults genuinely differ too: "same contract" does not mean "same model," because a deferred Codex run and a deferred Gemini run pick different models by definition. The reasoning inside the memo carries each model's fingerprint. The sections stay identical; only the voice inside them changes. A leak would be the sections themselves moving.
Account-level failures surface clearly
The other thing that separates a real runtime boundary from a hopeful one is what happens when the vendor CLI is not ready.
The failure mode I have watched sink agent systems is silent degradation. A CLI is not logged in, a stale flag somewhere still says "enabled," and the system limps on producing degraded output instead of stopping. You find out three runs later, from the memo, that it was never really working.
aos init scans for exactly this before you spend anything. It runs each vendor CLI's own auth check (claude auth status --json, codex auth status, gemini auth status, pi auth status) and resolves every adapter to one honest status: ready, needs-cli, needs-adapter, needs-login, or broken. A Codex CLI that is not logged in comes back needs-login with the fix printed next to it, "Run codex auth login", not a green light hiding a dead account. Install the adapter but not the CLI and you get needs-cli with the install URL. The check reads the account, not a cached boolean, so the answer cannot go stale behind your back.
That is the whole design goal from the thesis. When something is wrong at the account level, the tool says which adapter, what is wrong, and what to type. It does not pretend.
Portability is the payoff
This is where the Patterns arc has been heading since Issue 1. A profile is a coherent assembly of agents, evidence standards, and a contract between them. The reason to make it a first-class primitive instead of a pile of orchestration code is that a primitive travels. Write the Council once, run it on the CLI your team already pays for, move it to another CLI next quarter without rewriting the deliberation.
The adapter layer is what buys that. The profile talks to one contract, four adapters translate it, and the quirks that used to fork your system into vendor-specific variants stay boxed inside the adapter where they cannot spread.
A profile that only runs on one CLI is lock-in wearing a nicer name.
Next
That closes the patterns. Next we point the harness at real domains. Issue 9, Security Review: a Council built for finding what a threat model missed, run end to end. The full contract, and the four adapters, are at harness.aos.engineer.