CB-560/562/563: correct the pages the architect fix invalidated

Three entries described behaviour that CB-548 and today's repairs changed:

* "A lead can be delivered to" said MemberPresence is populated only for a
  worker. It is populated for every spawned member — worker or architect, never
  a lead. That sentence was the exact assumption that left every architect
  undeliverable for a day.
* Its gotcha said the readiness-gate failure is mute. CB-562 gave it a WARN that
  names the target, the counts and the grace, so a readiness failure no longer
  reads as a turn stall.
* "Advisory architect slots" claimed an architect resolves as `architect` and
  can send. True, and it could not receive anything, which the entry did not
  say. It now records the delivery condition and the lesson: shipping the
  binding is not shipping the role.

New entry for CB-563, since nothing documented the completion fallback's 4000
character cap — a clipped report used to be indistinguishable from a whole one.

7-Use-Cases.md carries the portable CLAUDE.md block, re-synced byte-identical
with the repo copy.
Dai Ha
2026-08-15 04:38:08 +02:00
parent 7c50cce52e
commit 05124a2c71
2 changed files with 81 additions and 35 deletions
+42 -4
@@ -31,6 +31,7 @@ six weeks, and the table alone will not carry it.
| [Re-read the config without a restart](#re-read-the-config-without-a-restart) | `configReload.enabled: true` | CB-559 | `config/ConfigRef` |
| [Leads talk to each other](#leads-talk-to-each-other) | (always on, two leads) | CB-532 | `auth/Principal` |
| [A lead can be delivered to](#a-lead-can-be-delivered-to) | automatic | CB-534 | `Bridged.deliverableTo` |
| [Know when a completion fallback is partial](#know-when-a-completion-fallback-is-partial) | automatic | CB-563 | `inject/CompletionResolver` |
| [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) | `fleet.architects:` | CB-548 | `auth/MemberRegistry` |
@@ -166,7 +167,9 @@ the placement policy gains an exclusion setting.
**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.
and discards them, rather than creating a supervisor above the lead. Like every spawned member, an
architect becomes deliverable once it mounts the bridge MCP — until then a brief sent to it is held at
the readiness gate and never typed into its pane (CB-560).
**On.** Declare each slot against a configured worker profile:
@@ -189,6 +192,12 @@ an architect until the lifecycle binds a live terminal to a slot. The profile na
as are duplicate slot names. An architect has delegation authority but no lifecycle authority, by
design.
Shipping the binding is not the same as shipping the role. For one day an architect bound correctly,
resolved as `architect`, and could not receive a single message: the presence map that opens the
readiness gate was keyed on `Role.WORKER`, so an architect was never marked available. It built clean
and passed two reviewers. When a change adds a role, check every place that assumes a member *is* a
worker — `bridge_status` tells you whether a member ever became ready.
## Keep a worker conversation
**What.** Carries a bridge logical session name and a peer-owned resume id through `SpawnRequest`, then
@@ -537,9 +546,9 @@ addressed to a lead is actually typed into its pane.
**Why.** The gate (CB-113) holds a delivery out of a *spawned* worker's boot window: herdr reports
`idle` while the agent is still starting, and a paste into that window is lost. Membership in it
comes from `WorkerPresence`, which `BridgeMcp` populates **only for a worker** — deliberately, since
that map doubles as the roster's availability signal and a lead counted there would appear as an
available worker. The two rules composed into a dead end: a lead is never marked present, so the
comes from `MemberPresence`, which `BridgeMcp` populates **for every spawned member** — worker or
architect, but never a lead, since that map doubles as the roster's availability signal and a lead
counted there would appear as an available member. The two rules composed into a dead end: a lead is never marked present, so the
gate never opened for one, so [lead-to-lead messaging](#leads-talk-to-each-other) — shipped and
authorized in CB-532 — still could not deliver a single keystroke. The gate's premise simply does not
apply to a lead: a lead is never spawned, so it has no boot window to guard.
@@ -549,6 +558,12 @@ another form: the send was accepted, the pane stayed `idle`, nothing was ever ty
(`READINESS_GRACE_POLLS`, 240 × 250ms) it failed through the same path as a stalled turn — so the log
said *turn-stall fallback* while the truth was that delivery had never been attempted. A repeating
62-second gap between send and failure is the signature of the gate, not of a peer that ignored you.
The gate is no longer mute (CB-562). When the grace expires it logs a WARN naming the target, the
poll count, the grace in seconds and how many queued messages it is failing because the target never
became deliverable — so a readiness failure now reads differently from a turn stall. The seconds are
derived from `Injector.POLL_INTERVAL_MILLIS`, the single source the poller is also built from, so a
cadence change cannot leave the log confidently stating a wrong duration.
It was also intermittently masked: presence is a sticky set, so a pane that was seen as a worker
*before* being recognised as a lead stayed deliverable until the next restart cleared the set.
@@ -741,6 +756,29 @@ earns a fresh attempt.
---
## Know when a completion fallback is partial
**What.** If a member ends a turn without `bridge_reply`, bridged scrapes its pane and resolves the
waiting send with that tail, so the sender is not left hanging. The tail is capped at
`MAX_SCRAPE_CHARS` (4000). When the cap bites, the returned text now ends with
`[Pane tail clipped: member did not call bridge_reply.]` and bridged logs a WARN with the original
length and the cap.
**On.** Automatic, whenever the completion fallback reads more than 4000 characters.
**Why.** A clipped transcript used to be indistinguishable from a complete report. A delegating lead
could act on an engineering report whose end had been cut off and never know — the only trace was a
DEBUG line reading `(4000 chars scraped)`, which reads like a size, not a warning. The cap itself is
deliberate and unchanged; the defect was silence, not the number.
**Gotcha.** The marker does not recover the missing text, and it is not a licence to skip the reply.
The fallback is a liveness net, not a channel: it returns only the pane tail, stripped to the last
assistant block. A member must still end every delegated turn with exactly one `bridge_reply`. The
marker is appended **after** the CB-115 misattribution guard compares the scrape to its baseline, so
marking cannot make an unchanged pane look like new output.
---
## Backfill status
This page was started after the fact, so it is **not yet complete**. Entries above are written from
+39 -31
@@ -299,41 +299,45 @@ notes after the block). Improvements land *here* first, then propagate to each p
If no `bridge_*` MCP tools are mounted in this session, this section does not apply — skip it.
`bridged` is the **sole communication gateway** between agents here. The orchestrating session (the
**primary**) and every delegated peer (a **worker**) mount the *same* MCP server and talk only
**primary**) and every delegated peer (a **member**) mount the *same* MCP server and talk only
through its `bridge_*` tools. No session addresses a peer, a broker, or the network directly.
### Which role am I? — settle this before acting
**Both roles read this file.** A worker runs in a git worktree of this same repo, so it inherits
**Every role reads this file.** A member runs in a git worktree of this same repo, so it inherits
this `CLAUDE.md` verbatim, and every rule below is role-conditional.
**Call `bridge_whoami`.** It returns `{"role":"primary"}` or `{"role":"worker","sessionId":…,
"profile":…,"worktree":…,"branch":…}`, resolved by the daemon from your connection — unforgeable,
and the same resolution its authorization gate uses. Don't infer what you can ask.
**Call `bridge_whoami`.** It returns `primary`, `worker`, or `architect`, resolved by the daemon from
your connection — unforgeable, and the same resolution its authorization gate uses. A worker also
carries its `sessionId`, `profile`, `worktree` and `branch`; an architect carries the slot name it
was bound to. Don't infer what you can ask.
Only if that call is unavailable, fall back to these — each is one-way, so keep reading until one
fires: the reply charter in your system prompt (*"You are an off-subscription worker in the
claude-bridge fleet"*) ⇒ **worker**; bridge tools prefixed `mcp__bridge__*` ⇒ **worker** (the
launcher fixes that mount name; a primary's mount is named by whoever wrote its `.mcp.json`, so it
varies); `ANTHROPIC_BASE_URL` set ⇒ **worker** (Claude-model workers run on a clean env, so its
*absence* proves nothing). **Still unsure ⇒ act as a worker.** The two mistakes are not symmetric: a
primary acting as a worker is refused by the authorization gate — loud and self-correcting — while a
worker acting as the primary ends its turn with no `bridge_reply`, and the sender silently receives
nothing. Fail toward the recoverable error.
claude-bridge fleet"*) ⇒ **spawned member**; bridge tools prefixed `mcp__bridge__*` ⇒ **spawned
member** (the launcher fixes that mount name; a primary's mount is named by whoever wrote its
`.mcp.json`, so it varies); `ANTHROPIC_BASE_URL` set ⇒ **spawned member** (Claude-model members run
on a clean env, so its *absence* proves nothing). None of these separate a worker from an architect —
only `bridge_whoami` does. **Still unsure ⇒ act as a worker**, the most restricted member role. The
two mistakes are not symmetric: a primary acting as a worker is refused by the authorization gate —
loud and self-correcting — while a member acting as the primary ends its turn with no `bridge_reply`,
and the sender silently receives nothing. Fail toward the recoverable error.
### Invariants — both roles, no exceptions
1. **Never set, export, or forward `ANTHROPIC_BASE_URL`** (or `ANTHROPIC_AUTH_TOKEN`). The primary
stays on subscription; only the bridge puts a worker off it, at spawn. Mounting the bridge must
stays on subscription; only the bridge puts a member off it, at spawn. Mounting the bridge must
never move a session across that boundary.
2. **The bridge is the only channel.** Text you print in your terminal reaches nobody — the other
side cannot see your screen. An answer that isn't in a `bridge_*` call is silently discarded.
3. **Identity comes from the connection, never an argument.** Workers never pass a target; you
cannot act as another session. Spawn/stop/send/drain are lead-only; reply/ask are
only-as-itself — any peer may answer for its own pane, and for no other. A call outside your
role is refused, not queued.
cannot act as another session. Spawn/stop/drain are lead-only; **send is lead or architect**;
reply/ask are only-as-itself — any peer may answer for its own pane, and for no other. A call
outside your role is refused, not queued.
4. **Delivery is status-gated: one message per turn.** Don't busy-poll a peer's terminal and don't
re-send because a call looks slow — the bridge delivers when the peer is `idle`/`blocked`.
re-send because a call looks slow — the bridge delivers when the peer is `idle`, `blocked` or
`done`. A spawned member must **also** have mounted the bridge MCP: until it has, it is not
deliverable, and a send waits on that gate for ~60s and then fails without ever reaching its pane.
5. **Never drive the terminal multiplexer directly** (no `herdr` CLI, no socket). The bridge owns
policy; the multiplexer owns PTYs. Going around the bridge bypasses every rule above.
@@ -389,26 +393,26 @@ the merge — and merging on a reviewer's word is delegating it by proxy.
|---|---|
| Confirm your own role | `bridge_whoami` |
| See backends available | `bridge_profiles` |
| Start a worker | `bridge_spawn{profile?, cwd?, worktree?, ticket?}` → `sessionId` + `paneId` |
| See the fleet | `bridge_list` → `leads` (your peers) + `workers` · one peer's state: `bridge_status{sessionId}` |
| Start a member | `bridge_spawn{role?, profile?, cwd?, worktree?, ticket?}` → `sessionId` + `paneId` |
| See the fleet | `bridge_list` → `leads` (your peers) + `members` · one peer's state: `bridge_status{sessionId}` |
| Delegate (blocking) | `bridge_send{sessionId, content}` |
| Delegate (long task) | `bridge_send{sessionId, content, wait:false}` → ticket → `bridge_poll{ticket}` |
| Answer a worker's `bridge_ask` | `bridge_send{turnId, content}` — **not** `sessionId` |
| Answer a member's `bridge_ask` | `bridge_send{turnId, content}` — **not** `sessionId` |
| Message a **peer lead** | `bridge_send{sessionId: <their terminal>, content}` — `bridge_list` → `leads` reports it. Coordination only, **never** a task |
| Answer a peer lead that messaged you | `bridge_reply{content}` — the one case a lead replies |
| Collect a held reply | `bridge_poll{target}` · then `bridge_ack{target, msgId}` |
| Tear down | `bridge_stop{paneId}` |
| Tear down a member | `bridge_stop{paneId}` |
### Lead ↔ lead — coordinate, never delegate
`bridge_list` returns `leads` alongside `workers`; your own row carries `self: true`. Every other row
is a peer — an orchestrator with its own context, its own workers, and its own judgment. An empty
`workers` array means no workers are spawned; it says nothing about peers.
`bridge_list` returns `leads` alongside `members`; your own row carries `self: true`. Every other row
is a peer — an orchestrator with its own context, its own members, and its own judgment. An empty
`members` array means no members are spawned; it says nothing about peers.
**A lead never assigns a task to another lead.** Work goes to workers — only ever downward, never
sideways. Sending a peer a brief with acceptance criteria is a category error: a brief is a worker's
**A lead never assigns a task to another lead.** Work goes to members — only ever downward, never
sideways. Sending a peer a brief with acceptance criteria is a category error: a brief is a member's
artefact, and a peer is not yours to task. If a unit needs doing and it falls in your area, spawn a
worker and delegate it yourself; if it falls in the peer's area, say so and let the peer assign it.
member and delegate it yourself; if it falls in the peer's area, say so and let the peer assign it.
The traffic between leads is coordination and nothing else:
1. **Divide the map, not the work.** Agree who owns which area, then each of you assigns inside your
@@ -426,7 +430,7 @@ Being messaged by a peer does not make you its worker: answer with `bridge_reply
the substance if it is wrong. A peer that simply complies has thrown away the reason there are two of
you.
### Worker — the turn contract
### Member (worker or architect) — the turn contract
1. **Load the playbook skill the lead named** before doing anything else.
2. **Do the assigned scope only.** Note anything you spot outside it in one line; don't go hunt it.
@@ -435,6 +439,10 @@ you.
Don't ask what you could decide yourself.
4. **End the turn with exactly one `bridge_reply{content}`**, carrying your complete answer. This is
the whole handoff. No `bridge_reply` ⇒ the sender gets nothing and the exchange stalls.
Do **not** lean on the completion fallback to carry your answer for you: when you end a turn
without replying, the bridge scrapes your pane, and it can return only the last 4000 characters.
A clipped scrape is marked as partial, but the missing text is gone — your report reaches the
lead with its end cut off.
5. **Report honestly.** State only what you actually ran and its real output, including failures.
You mount **only** the bridge MCP — the primary's other servers (IDE, forge, docs) are not yours,
so never claim the result of a check you had no way to run.
@@ -445,9 +453,9 @@ you.
| Layer | Scope | Reaches |
|---|---|---|
| the launcher's reply charter | the one rule that must survive with no repo: *end every turn with `bridge_reply`* | every worker, at launch, every peer kind |
| **this section** | protocol + orchestration policy | primary **and** every Claude worker — tracked in git, so worktrees inherit it |
| role playbook skills | per-job procedure (commit/PR recipe, finding format) | a worker told to load one |
| the launcher's reply charter | the one rule that must survive with no repo: *end every turn with `bridge_reply`* | every spawned member, at launch, every peer kind — never a lead |
| **this section** | protocol + orchestration policy | primary **and** every member that reads the repo — tracked in git, so worktrees inherit it |
| role playbook skills | per-job procedure (commit/PR recipe, finding format) | a member told to load one |
| the bridge's own docs | design detail, flows, error model | on demand |
A rule belongs in **exactly one** layer — the outermost one that must obey it. Peers that don't read