diff --git a/10-Cross-Host-Messaging.md b/10-Cross-Host-Messaging.md
index 3c329e7..3118653 100644
--- a/10-Cross-Host-Messaging.md
+++ b/10-Cross-Host-Messaging.md
@@ -25,13 +25,14 @@ Every cross-host interaction is one of these. Each is a message flow between two
| U3 | **Stranded / late reply** — B replies after A's blocking send timed out (or was never open); held durably until A pulls | B → A (held) | `REPLY` (deferred) |
| U4 | **Cross-host spawn** — A requests a new agent *on host B*; gateway B launches it locally and announces it | A → gateway B (control) | `SPAWN` |
| U5 | **Roster / presence** — every gateway announces its local agents so all build one union "who/where/status" view | each gateway → all | `PRESENCE` |
-| U6 | **Main-pair** — the two mains (Opus + cloud, CB-500 B) message each other | A ↔ A' | `SEND`/`REPLY` |
-| U7 | **Orchestrator ↔ mains** — the orchestrator tier (CB-500 C) drives/monitors its main sessions | O ↔ main | `SEND`/`REPLY` |
-| U8 | **Broadcast / group announce** — a main publishes **once** to all workers (or a named group); the broker copies the message into every bound inbox | A → all | `BROADCAST` |
+| U6 | **Lead ↔ architect** — the human-driven lead engages either independent advisory architect | L ↔ A | `SEND`/`REPLY` |
+| U7 | **Parallel architect advice** — the lead sends the same brief to Claude Sonnet 5 and GPT-5.6/opencode, then compares independent results | L → A1, A2 | `SEND`/`REPLY` |
+| U8 | **Broadcast / group announce** — a lead publishes **once** to all workers (or a named group); the broker copies the message into every bound inbox | L → all | `BROADCAST` |
U1–U3 are the [CB-307 rendezvous](9-Implementation) semantics, now spanning hosts. U4–U5 are the
-net-new federation control plane. U6–U7 reuse the *same* per-entity inbox as U1 — a main and an
-orchestrator are just message-addressable entities with their own inbox. U8 is for **identical
+net-new federation control plane. U6–U7 reuse the *same* per-entity inbox as U1 — a lead and an
+architect are just message-addressable entities with their own inbox. The two architect model families
+are deliberate: independent agreement is evidence rather than correlated echo. U8 is for **identical
announcements** ("everyone: stop", "everyone: report status") — tailored task briefs keep the
per-worker fan-out pattern ([Team](6-Team#parallel-fan-out-map--reduce)), which works unchanged
across hosts because each send routes to its recipient's inbox wherever it lives.
@@ -41,12 +42,12 @@ across hosts because each send routes to its recipient's inbox wherever it lives
```mermaid
flowchart LR
subgraph e["Message-addressable entities (each owns ONE inbox)"]
- orch["orchestrator
(MCP client)"]
- main["primary / main
(MCP client)"]
+ arch["architect
(MCP client)"]
+ main["human-driven lead
(MCP client)"]
wrk["worker / sandboxed agent
(herdr pane)"]
end
gw["gateway = bridged
(one per host)"]
- orch -->|"delivered by local MCP pull"| gw
+ arch -->|"delivered by local MCP pull"| gw
main -->|"delivered by local MCP pull"| gw
wrk -->|"delivered by local herdr inject"| gw
gw -->|"consumes its local entities' inboxes,
publishes to remote inboxes"| broker["BROKER (AMQP)"]
diff --git a/11-Features.md b/11-Features.md
index 4870c7a..430d07a 100644
--- a/11-Features.md
+++ b/11-Features.md
@@ -28,14 +28,18 @@ six weeks, and the table alone will not carry it.
| [A lead can be delivered to](#a-lead-can-be-delivered-to) | automatic | CB-534 | `Bridged.deliverableTo` |
| [Leads are visible in bridge_list](#leads-are-visible-in-bridge_list) | automatic | CB-535 | `mcp/BridgeMcp.listFleet` |
| [Reply nudges follow the delegating lead](#reply-nudges-follow-the-delegating-lead) | automatic (retires `primary:`) | CB-532 | `mcp/PrimaryRegistry` |
+| [Advisory architect slots](#advisory-architect-slots) | `architects:` | CB-548 | `auth/ArchitectRegistry` |
| [Weighted worker placement](#weighted-worker-placement) | `placement: weighted` + `weight` / `maxLoad` | CB-518 | `placement/` |
| [Give workers a toolchain](#give-workers-a-toolchain) | per-profile `env:` | CB-511 | `worker/HerdrPeerLauncher` |
| [Run a worker on the subscription](#run-a-worker-on-the-subscription) | profile `subscription: true` | CB-539 | `worker/ClaudeCodeLauncher` |
+| [Keep a worker conversation](#keep-a-worker-conversation) | session name + resume id on spawn | CB-547 | `peer/SpawnRequest` |
| [Isolated worktree per worker](#isolated-worktree-per-worker) | `bridge_spawn{worktree, ticket}` | CB-301-ext | `session/GitWorktrees` |
| [Worker tool-surface isolation](#worker-tool-surface-isolation) | automatic | CB-525 | `session/GitWorktrees` |
+| [Worktree-hostile config isolation](#worktree-hostile-config-isolation) | automatic | CB-543 | `session/GitWorktrees` |
| [Worker opens its own PR](#worker-opens-its-own-pr) | `gitTokenEnv:` / `gitHostEnv:` | CB-302 | `worker/HerdrPeerLauncher` |
| [Session lifecycle caps](#session-lifecycle-caps) | `lifecycle:` | CB-303 | `session/SessionManager` |
| [Durable reply inbox](#durable-reply-inbox) | `broker:` | CB-307 | `msg/AmqpReplyInbox` |
+| [Reject overlapping rendezvous](#reject-overlapping-rendezvous) | automatic | CB-548 | `msg/Rendezvous` |
| [Pin an opencode endpoint](#pin-an-opencode-endpoint) | profile `baseUrl:` | CB-508 | `worker/OpenCodeLauncher` |
| [Onboard a project with the plugin](#onboard-a-project-with-the-plugin) | `/plugin install claude-bridge` → `/claude-bridge:setup` | CB-527 | `plugin/` |
| [Port a workspace to OpenCode](#port-a-workspace-to-opencode) | `port-to-opencode` skill + `opencode.json` | CB-529 | `.claude/skills/port-to-opencode` |
@@ -44,7 +48,7 @@ Nearly every knob above lives in one file, on one profile:
```mermaid
flowchart LR
- Y["bridged.yaml"] --> G["bind / auth / primary.terminal"]
+ Y["bridged.yaml"] --> G["bind / auth / leaders / architects"]
Y --> B["broker"]
Y --> W["workers:"]
W --> P1["profile: gx10"]
@@ -121,6 +125,15 @@ requirement *for that profile only* — every other profile keeps the hard refus
boundary: a claude-code profile with no base_url may not spawn, because doing so would bill the
subscription.
+```yaml
+workers:
+ sonnet:
+ kind: claude-code
+ subscription: true
+ model: claude-sonnet-5
+ argv: ["ccs", "sonnet"]
+```
+
**Why.** Some model families (e.g. `sonnet` on `ccs`) have no off-subscription endpoint to point a
worker at. Rather than leave those profiles unspawnable, this is an explicit, visible opt-in — a
spawn under it logs a WARN naming the profile, so billing the subscription is never an accident.
@@ -130,6 +143,57 @@ refused at spawn if both are set. The same contradiction is refused at config lo
map: on the subscription path the guard is skipped and the adapter writes neither Anthropic key, so
an `ANTHROPIC_BASE_URL`/`ANTHROPIC_AUTH_TOKEN` in `env:` would reach the worker having passed no
guard at all (CB-542). A subscription profile may still carry `env:` — just not those two keys.
+`weight: 0` cannot reserve this profile from unqualified weighted placement: worker-config
+normalization changes non-positive weights to `1.0`. Use an explicit `profile: sonnet` spawn until
+the placement policy gains an exclusion setting.
+
+## Advisory architect slots
+
+**What.** Declares named, strong-model advisory slots. A bound architect resolves as `architect`, can
+send, reply, ask, and read, but cannot spawn, stop, or drain the fleet. The operating shape is one
+human-driven lead, two short-lived architects, and N workers: the lead consults the architects sideways
+and discards them, rather than creating a supervisor above the lead.
+
+**On.** Declare each slot against a configured worker profile:
+
+```yaml
+architects:
+ sonnet-adviser:
+ profile: sonnet
+ gpt-adviser:
+ profile: gpt
+```
+
+**Why.** The rejected alternative put an orchestrator above the lead and made the lead a managed,
+resumable session. That breaks the actual control boundary: the human drives a lead that pre-exists,
+is recognised, and cannot be resumed by bridged. Two advisory model families, Claude Sonnet 5 and
+GPT-5.6 through opencode, receive the same brief independently so agreement is evidence rather than
+correlated echo.
+
+**Gotcha.** `architects:` declares slots, not sessions: no terminal is configured and nothing becomes
+an architect until the lifecycle binds a live terminal to a slot. The profile name is validated at boot,
+as are duplicate slot names. An architect has delegation authority but no lifecycle authority, by
+design.
+
+## Keep a worker conversation
+
+**What.** Carries a bridge logical session name and a peer-owned resume id through `SpawnRequest`, then
+returns them from the peer handle where the adapter supports them. Claude Code mints a UUID for a fresh
+named session, passes it as `--session-id`, exposes the logical name with `-n`, and resumes with `-r`.
+OpenCode resumes with `-s` and discovers its id after launch from its on-disk session record by matching
+the worker's unique worktree cwd.
+
+**On.** Supply a session name and/or resume id when the spawn lifecycle has one to carry. Adapter
+capabilities state the asymmetry: Claude Code offers `SESSION_NAME` and `SESSION_RESUME`; OpenCode
+offers `SESSION_RESUME` only.
+
+**Why.** A bridge session name is an operator-facing roster label, while a provider session id is the
+only handle that can resume the actual conversation. Treating them as peer-neutral values prevents the
+core from assuming Claude Code's flags are a universal protocol.
+
+**Gotcha.** OpenCode has no name flag, and it writes its session record only after it persists a
+conversation. Its id is therefore discovered lazily and may be absent immediately after spawn; its slot
+name remains only in bridged's roster.
## Give workers a toolchain
@@ -183,6 +247,22 @@ build passed.
is deliberate — the bridge is a message bus, and the primary is the gate. Never accept a worker's
claim about a check it had no way to run.
+## Worktree-hostile config isolation
+
+**What.** Every provisioned worktree neutralizes tracked `.mcp.json`, `opencode.json`, and `.autoenv`
+with a valid format-specific stub, then marks a tracked replacement `--skip-worktree`.
+
+**On.** Automatic at worktree provisioning; no configuration.
+
+**Why.** The tracked `opencode.json` can reference gitignored `.secrets/` files that a worktree never
+contains, and OpenCode refuses to start with that dangling reference. The same isolation rule also
+prevents a primary-only MCP configuration or an autoenv authorization prompt from entering a worker's
+tool surface.
+
+**Gotcha.** The replacement is deliberately valid, not deleted: `{}` for `opencode.json`, an empty
+server map for `.mcp.json`, and an empty `.autoenv`. A deletion could be undone by a later checkout; the
+skip-worktree bit keeps the safe local replacement from looking like work for a worker to commit.
+
## Worker opens its own PR
**What.** Injects a repo-scoped forge token so a worker can commit, push over SSH, and open its own
@@ -228,6 +308,20 @@ real task's runtime. Without a durable inbox, a reply arriving after that window
writing into someone else's broker. Also see the known hole: an async ticket that times out while
the session is still BUSY currently discards the later completion rather than parking it.
+## Reject overlapping rendezvous
+
+**What.** A second attempt to open a reply waiter for the same worker session fails atomically instead
+of replacing the first waiter.
+
+**On.** Always on; no configuration.
+
+**Why.** One session has one outstanding delegated turn. Replacing its waiter silently would strand the
+first caller and let a reply resolve the wrong request. `MessageService` serializes normal sends, but
+the atomic rejection is the tripwire that makes a future violation loud rather than corrupt.
+
+**Gotcha.** This is not concurrent-turn support. A terminal send closes its own waiter before the next
+turn can open one; a double-open is an invariant failure that must be investigated.
+
## Pin an opencode endpoint
**What.** An opencode profile can target its own OpenAI-compatible endpoint.