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.
+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
|
||||
|
||||
Reference in New Issue
Block a user