diff --git a/12-Claude-to-Codex.md b/12-Claude-to-Codex.md
deleted file mode 100644
index b1c4271..0000000
--- a/12-Claude-to-Codex.md
+++ /dev/null
@@ -1,227 +0,0 @@
-# 12 — Porting a Claude Code workspace to Codex
-
-**What this page is for.** Taking a project that a Claude Code session already works in, and making
-an OpenAI Codex session a first-class participant in the same workspace — same instructions, same
-skills, same bridge mount. It is written as a procedure so it can become a plugin skill verbatim.
-
-**Why it exists.** The bridge is a provider-neutral message bus, but a peer is only useful if it
-knows the same rules. A Codex session with no ported config is not a peer — it is a stranger with
-shell access. Everything below was verified by hand against `codex-cli 0.147.0` and
-`ai-config-sync-manager 0.1.10`; nothing here is inferred from documentation.
-
-## The mapping
-
-Both hosts express the same four concepts in different files.
-
-| Concept | Claude Code | Codex |
-|---|---|---|
-| Instructions | `CLAUDE.md` | `AGENTS.md` |
-| Skills | `.claude/skills/` | `.agents/skills/` (project) · `$CODEX_HOME/skills/` (user) |
-| MCP servers | `.mcp.json` | `[mcp_servers.*]` in `config.toml` |
-| Settings | `.claude/settings.json` | `config.toml` |
-| Config isolation | `CLAUDE_CONFIG_DIR` | `CODEX_HOME` |
-
-```mermaid
-flowchart LR
- subgraph C["Claude Code workspace"]
- C1["CLAUDE.md"]
- C2[".claude/skills/"]
- C3[".mcp.json"]
- end
- subgraph X["Codex workspace"]
- X1["AGENTS.md"]
- X2[".agents/skills/"]
- X3[".codex/config.toml"]
- end
- C1 -->|"port"| X1
- C2 -->|"port"| X2
- C3 -->|"port — FILTERED"| X3
- C3 -.->|"machine-local servers
must NOT cross"| STOP["blocked on purpose"]
- classDef danger fill:#b7791f,stroke:#7b341e,color:#ffffff;
- class STOP danger
-```
-
-*Three of the four categories port wholesale. The MCP category is the one that must be filtered —
-see [What must never cross](#what-must-never-cross).*
-
-## Procedure
-
-### 1. Install the porting tool
-
-```bash
-npm install -g ai-config-sync-manager
-ai-config-sync --version
-```
-
-It performs a continuous bidirectional sync with backups and a diff-first workflow. For this
-procedure we use it **one-directional and project-scoped only**.
-
-### 2. Inspect before changing anything
-
-```bash
-cd
-ai-config-sync status --scope project --compact
-```
-
-Read every diff it reports. `--scope project` is deliberate: the global scope syncs your *user*
-configuration between the two CLIs, which is a different decision with different blast radius (see
-[Scope](#scope-project-vs-global)).
-
-### 3. Dry-run with an explicit include list
-
-Never sync "everything". Name what crosses:
-
-```bash
-ai-config-sync sync --scope project --from claude --to codex \
- --include instructions,skills,mcp: --dry-run
-```
-
-The include syntax is `category` or `category:item` — so `mcp:bridged` ports exactly one MCP
-server and leaves the rest behind. Read the change preview and the patch preview before applying.
-
-### 4. Apply
-
-```bash
-ai-config-sync sync --scope project --from claude --to codex \
- --include instructions,skills,mcp: --apply
-```
-
-Backups land under `~/.ai-config-sync-manager/backups//`.
-
-### 5. Review the generated instructions — every time
-
-**The terminology map is naive about file paths, and it will produce sentences that are wrong
-rather than merely awkward.** Diff the result and read the changed lines:
-
-```bash
-diff CLAUDE.md AGENTS.md
-```
-
-Two real examples from this repo's port:
-
-| Generated | Why it is wrong |
-|---|---|
-| `Never commit ~/.codex/config.toml [mcp_servers]` | The source rule was *"never commit `.mcp.json`"* — a repo file. You cannot "never commit" a path in `~/`, so the sentence lost both its meaning and its protection. |
-| skills row rewritten to `.codex/skills/**` | The tool had just written the skills to `.agents/skills/`. The document contradicted the tool's own output. |
-
-Fix these by hand. **`AGENTS.md` is a generated artifact that is not safe to blind-regenerate** —
-re-running the sync reintroduces the same errors, so the diff review is part of the procedure, not
-an optional polish step.
-
-### 6. Verify against the running agent, not the file
-
-A file on disk proves nothing about what the agent read. Ask it cold, in a fresh session:
-
-```bash
-codex exec -s read-only "In one line: what is the ONLY channel you may use to answer the sender?"
-```
-
-The answer must reflect your ported instructions. If it answers generically, the instructions did
-not reach the model and every later step is built on sand.
-
-Confirm the MCP mount separately:
-
-```bash
-codex mcp list # your bridge server, status `enabled`
-```
-
-## What must never cross
-
-**Machine-local MCP servers.** An IDE index, a language server, an editor bridge — anything bound
-to *your* checkout — must stay out of the ported config.
-
-The generated file lands at the **project root**, and Codex discovers it from the working directory
-— so its blast radius is decided by one thing: whether you commit it.
-
-- **Left untracked** (the default the sync leaves you in) it stays local to your own checkout. A
- git worktree cut from the project does *not* receive untracked files, so no peer sees it.
-- **Committed**, every worktree gets it, and a worker in its own worktree mounts servers whose every
- path points back into the primary's checkout.
-
-The second case is not hypothetical: it produced a worker that made all 59 of its edits in the
-primary's tree while running its build in its own — so every build passed, and every change went
-nowhere. See [Features](11-Features) → *Worker tool-surface isolation*.
-
-So the hazard arms on `git add`, not on `ai-config-sync --apply`, and an untracked file is one
-`git add -A` away from armed. Close it explicitly rather than relying on nobody doing that:
-
-```gitignore
-.codex/config.toml
-```
-
-Do this even if you filtered the sync correctly — the ignore rule protects the *next* person to run
-it without `--include`. Note that gitignore is the right tool only while the file is untracked; a
-config file already tracked for other reasons needs `git update-index --skip-worktree` instead,
-which is how this repo pins `.mcp.json`.
-
-Port the bridge server. Leave the rest.
-
-**`status` will report the gap forever, and that is correct.** Once you have filtered, every later
-`ai-config-sync status --scope project` shows the withheld servers as unresolved drift:
-
-```
-project/mcp [safe] missing-in-codex: intellij-index [exact]
- missing-in-codex: jetbrains [exact]
-```
-
-That is the steady state, not a to-do list. The failure mode is a later session reading it as one
-and "fixing" it with an unfiltered sync.
-
-## Scope: project vs global
-
-| Scope | What it touches | Use it for |
-|---|---|---|
-| `project` | `CLAUDE.md`/`AGENTS.md`, `.claude/skills`/`.agents/skills`, `.mcp.json`/`.codex/config.toml` | making one workspace bi-host — **the procedure on this page** |
-| `global` | `~/.claude/**` ↔ `~/.codex/**` | keeping the two CLIs *you* drive interactively in step |
-
-Keep global sync away from anything the bridge spawns. The bridge deliberately gives each peer an
-isolated tool surface; a user-level bidirectional sync works against that by design, not by bug.
-
-## What Codex does differently
-
-Porting config is not the whole story — Codex reaches the same outcomes through different seams.
-A launcher or skill that assumes Claude's flags will fail silently.
-
-| Need | Claude Code | Codex |
-|---|---|---|
-| Standing instruction | `--append-system-prompt` (inline) | **`AGENTS.md` in `$CODEX_HOME`** — a file, no flag exists |
-| Mount an MCP server | `--mcp-config ` | `-c mcp_servers..url="…"`, or `codex mcp add --url` |
-| MCP bearer token | server config | `--bearer-token-env-var ` |
-| Unattended tool calls | permission settings | **`--approve-for-me`** |
-| Resume a conversation | `--resume ` | `codex resume ` — **names work natively** |
-| Isolate config | `CLAUDE_CONFIG_DIR` | `CODEX_HOME` |
-| Pick a model | `--model` | `-m` |
-
-Three of these are load-bearing enough to call out:
-
-**`--approve-for-me` is mandatory for any unattended peer.** Without it, every MCP tool call
-returns `user cancelled MCP tool call` — even with `approval: never` and a read-only sandbox. MCP
-tools sit behind a separate trust gate. The flag routes approvals through automatic review and
-**keeps the sandbox on**; it is not the same as
-`--dangerously-bypass-approvals-and-sandbox`, which disables sandboxing entirely and should not be
-used to work around this.
-
-**`CODEX_HOME` is a complete boundary, and a fresh one has no credentials.** It holds config,
-sessions, skills, plugins, and state. Point a peer at a new empty home and Codex fails with an
-opaque `401 Unauthorized` mid-run rather than a startup error — so a launcher must provision
-credentials into the home, and should **copy rather than symlink** so a peer cannot write back
-through to the operator's real credential.
-
-**`AGENTS.md` in `$CODEX_HOME` is genuinely read and obeyed.** Verified by planting a distinctive
-codeword as a standing instruction and asking an unrelated question in a fresh session; the answer
-came back with the codeword. This is the only delivery mechanism for a reply charter on Codex.
-
-## Gotchas
-
-- **Identity comes from the terminal, not the runtime.** A Codex process started inside the
- primary's pane resolves as the **primary** and inherits its authority. A Codex *peer* must be
- spawned into its own pane — you cannot create one by running `codex` in a terminal.
-- **The subscription guard does not apply.** The guard is `ANTHROPIC_BASE_URL`-shaped; Codex has no
- such seam and authenticates through its own credentials. Codex peers bill their own provider, and
- `maxLoad` is the only spend control that reaches them.
-- **Model selection lives in the host's own config, not the bridge.** Porting instructions does not
- port model choice, and the bridge should not own it — see [Features](11-Features) for why a second
- source of truth hides drift instead of fixing it.
-- **`.codex/config.toml` is local config.** Treat it like `.mcp.json` and keep it out of commits —
- gitignore it, per [What must never cross](#what-must-never-cross). "It only has the bridge mount
- today" is not a reason to commit it; the file is what the *next* sync writes into.
diff --git a/12-Claude-to-OpenCode.md b/12-Claude-to-OpenCode.md
new file mode 100644
index 0000000..8e8d544
--- /dev/null
+++ b/12-Claude-to-OpenCode.md
@@ -0,0 +1,142 @@
+# 12 — Porting a Claude Code workspace to OpenCode
+
+**What this page is for.** Taking a project a Claude Code session already works in, and making an
+OpenCode session a first-class participant in the same workspace — same instructions, same MCP
+servers, same bridge mount.
+
+**The short version: there is almost nothing to port.** OpenCode reads `CLAUDE.md` natively. The
+only artifact you create is one `opencode.json` mapping MCP servers. Everything below was verified
+against `opencode 1.18.16`; the procedural form ships as the `port-to-opencode` skill.
+
+## What you get for free
+
+OpenCode's instruction search order:
+
+```
+1. walking up from cwd: AGENTS.md , then CLAUDE.md
+2. global: ~/.config/opencode/AGENTS.md
+3. Claude Code global: ~/.claude/CLAUDE.md (unless disabled)
+```
+
+> *"The first matching file wins in each category."*
+
+So a project `CLAUDE.md` **and** your user-level `~/.claude/CLAUDE.md` are already read. No
+translation step, no generated second copy, no sync tool — and therefore none of the drift that
+comes with them.
+
+```mermaid
+flowchart LR
+ subgraph W["Claude Code workspace"]
+ C1["CLAUDE.md"]
+ C2[".mcp.json"]
+ C3[".claude/skills/"]
+ end
+ subgraph O["OpenCode"]
+ O1["read natively"]
+ O2["opencode.json
(mcp)"]
+ O3["not read"]
+ end
+ C1 -->|"no port needed"| O1
+ C2 -->|"map — FILTERED"| O2
+ C3 -.->|"no equivalent"| O3
+ classDef warn fill:#b7791f,stroke:#7b341e,color:#ffffff;
+ class O3 warn
+```
+
+*Instructions cross for free; MCP servers need a small mapping; skills do not cross at all.*
+
+## The one file you write
+
+Project config is `opencode.json` in the repo root; the global one is
+`~/.config/opencode/opencode.json`. **Configs merge rather than replace**, so machine-local servers
+belong in the global file and shared ones in the project file.
+
+| `.mcp.json` | `opencode.json` |
+|---|---|
+| `"type": "http"` / `"sse"` | `"type": "remote"`, `"url"` |
+| `"type": "stdio"` | `"type": "local"`, `"command": ["bin", "arg"]` |
+| `"command"` + `"args"` | single `"command"` array |
+| `"env"` | `"environment"` |
+| `"headers"` | `"headers"` |
+
+```json
+{
+ "$schema": "https://opencode.ai/config.json",
+ "instructions": ["CLAUDE.md"],
+ "mcp": {
+ "bridged": { "type": "remote", "url": "http://127.0.0.1:8765/mcp", "enabled": true }
+ }
+}
+```
+
+Set `instructions` explicitly even though `CLAUDE.md` is found anyway — the fallback applies only
+while no `AGENTS.md` exists.
+
+## Secrets: reference, never embed
+
+OpenCode substitutes at load time, in both `headers` and `environment`:
+
+```
+{env:VARIABLE_NAME} from the environment
+{file:~/.secrets/token} from a file
+```
+
+This is what makes `opencode.json` safe to commit — and it **must** be committable, because a peer
+running in a git worktree receives tracked files only. No credential ever belongs in the file.
+
+## What must never cross
+
+Machine-local MCP servers — an IDE index, a language server, an editor bridge. A committed project
+config reaches every worktree, and a worker mounting servers whose paths point into the primary's
+checkout will edit the primary's files while building in its own: every build passes, every change
+lands in the wrong tree. See [Features](11-Features) → *Worker tool-surface isolation*.
+
+Keep those in the global config if you want them personally.
+
+## Verify against the running agent
+
+A file on disk proves nothing about what the agent loaded.
+
+```bash
+opencode run "In one line: state a rule from this project's instructions."
+```
+
+The answer must reflect the real `CLAUDE.md`. Then list the MCP servers and confirm each expected
+one is connected — and that nothing you withheld appears.
+
+## Gotchas
+
+- **A leftover `AGENTS.md` silently shadows `CLAUDE.md`.** If one exists from an earlier port,
+ delete it, or opencode reads the stale copy instead of the live rules. Check this first.
+- **Skills do not port.** OpenCode uses its own agent markdown under `.opencode/agents/`;
+ `.claude/skills/**` is not read. A delegation brief that says "load the *implementer* skill" has
+ no effect on an opencode peer — spell the procedure out in the brief instead.
+- **Identity comes from the terminal, not the runtime.** A peer must be spawned into its own pane;
+ running `opencode` in the primary's pane resolves as the primary.
+- **The subscription guard doesn't apply.** It is `ANTHROPIC_BASE_URL`-shaped; opencode
+ authenticates through its own credentials, so `maxLoad` is the only spend control that reaches it.
+
+---
+
+## Appendix — the Codex port (superseded)
+
+Chapter 12 previously documented porting to OpenAI Codex. That path was abandoned in favour of
+opencode, which the bridge already supports through `OpenCodeLauncher`. Four findings are kept
+because they were verified by hand and explain *why* the comparison went this way:
+
+- **Codex has no `CLAUDE.md` fallback.** Instructions had to be translated into `AGENTS.md` by a
+ third-party tool, whose terminology map rewrote file paths into sentences that were wrong rather
+ than merely awkward — turning "never commit `.mcp.json`" into a rule about a path in `~/` that
+ cannot be committed at all. The generated file was never safe to regenerate.
+- **Codex config has no variable interpolation.** `${VAR}` passes through literally, so a secret in
+ a config file had to be a literal value.
+- **Codex scrubs the environment for MCP servers.** A stdio MCP child receives only a core set
+ (`HOME LANG LOGNAME PATH PWD SHELL SHLVL TERM TMPDIR USER …`) — neither the parent environment nor
+ `shell_environment_policy.set` reaches it. A per-server `env_vars` whitelist exists to pass named
+ variables through; it was documented and accepted by the CLI, but never confirmed end-to-end here.
+- **Project config is gated on trust.** `.codex/config.toml` is read only when the checkout is
+ marked `trust_level = "trusted"` in the home config, and the two layers merge.
+
+Contrast with opencode's `{env:VAR}` and native `CLAUDE.md` reading: the same three problems — rules
+delivery, secret handling, and tool-surface control — cost one small JSON file instead of an adapter,
+a provisioner, and a translation tool.
diff --git a/_Sidebar.md b/_Sidebar.md
index 045ec32..9ec486f 100644
--- a/_Sidebar.md
+++ b/_Sidebar.md
@@ -15,7 +15,7 @@
9. [Implementation](9-Implementation) — as-built code map · classes · flows · state machines
10. [Cross-Host Messaging](10-Cross-Host-Messaging) — broker topology · exchanges · queues per entity
11. [Features](11-Features) — what it can do · the knob that turns it on · why · the gotcha
-12. [Claude → Codex](12-Claude-to-Codex) — porting a workspace to a second host
+12. [Claude → OpenCode](12-Claude-to-OpenCode) — porting a workspace to a second host
---
🟢 herdr-centric `bridged` · AgentAPI = fallback