Use Cases: as-built CLAUDE.md bridge charter + bridge_whoami
Replace the design-era 'suggested CLAUDE.md snippet' (built on a bridge_send(to:,kind:,body:) envelope that never shipped) with what is actually in the tree: - the four instruction layers and the rule that keeps them from drifting (a rule lives in exactly one layer — the outermost that must obey it) - the portable CLAUDE.md block, verbatim, as a copy-as-is template - bridge_whoami: why guessing your own role failed silently, and the two properties to preserve (same resolution as the authz gate; degrade toward the useful answer)
+229
-17
@@ -197,29 +197,241 @@ flowchart TD
|
||||
*Figure: an env property (bridge available) flips the default from "do it myself" to "delegate unless
|
||||
it needs my judgment."*
|
||||
|
||||
Suggested snippet to drop into the primary's `CLAUDE.md`:
|
||||
This is a *suggestion*, not wiring: `bridged` never edits an agent's `CLAUDE.md` (that would cross
|
||||
the subscription boundary in the wrong direction). The operator writes it.
|
||||
|
||||
```markdown
|
||||
## Delegating off-subscription work (claude-bridge)
|
||||
### As-built — the `CLAUDE.md` **Bridge communication** section
|
||||
|
||||
If the `bridged` MCP tools are connected (env marker `BRIDGED_MCP_URL` is set), you are a **bridge
|
||||
primary** on a metered subscription. Default to pushing work that does not need your own judgment to
|
||||
a cheaper worker instead of spending subscription tokens on it:
|
||||
**Shipped** in the repo's own `CLAUDE.md` as the first section, ahead of the IDE workflow. The
|
||||
design-era sketch above imagined a primary-only reminder keyed on an env marker; what shipped is
|
||||
broader, because a worker runs in a **git worktree of the same repo** and therefore inherits the
|
||||
same tracked `CLAUDE.md` verbatim. One file, both roles — so the section is **role-split**, and the
|
||||
first thing it does is make the reader establish which role it is.
|
||||
|
||||
- **Delegate** bulk, mechanical, or parallelizable work — test writing, log triage, wide-area or
|
||||
per-file reviews, codegen/refactors behind a clear spec — with
|
||||
`bridge_send(to: <role@profile>, kind: <verb.noun>, body: {...})`. Fan several out and reduce.
|
||||
- **Keep on the primary** the conversation with the user, final judgment, merge/commit decisions,
|
||||
and anything that needs your on-subscription model's reasoning.
|
||||
- **Never set `ANTHROPIC_BASE_URL` yourself.** Routing to an off-subscription model is the worker's
|
||||
job (its ccs profile carries it); you stay env-clean — that boundary is the whole point of the bridge.
|
||||
Why in `CLAUDE.md` and not somewhere else — the four layers, each with a different reach:
|
||||
|
||||
Rule of thumb: if you could hand the task to a junior with a written brief, `bridge_send` it.
|
||||
| Layer | Carries | Reaches | Cost to the reader |
|
||||
|---|---|---|---|
|
||||
| `REPLY_CHARTER` (`ClaudeCodeLauncher` / `OpenCodeLauncher`) | the one rule that must survive with no repo: *end every turn with `bridge_reply`* | every worker, at launch, both peer kinds | always in the system prompt |
|
||||
| **`CLAUDE.md` → Bridge communication** | protocol invariants + orchestration policy | primary **and** every claude-code worker — tracked in git, so worktrees get it free | always in context |
|
||||
| `.claude/skills/{implementer,reviewer}` | per-job procedure: commit/push/PR recipe, finding format | a worker told to load it | on demand |
|
||||
| `docs/MCP-Contract.md` | design detail, flows, error model | anyone who goes looking | on demand |
|
||||
|
||||
The rule that keeps them from drifting: **a rule lives in exactly one layer — the outermost one that
|
||||
must obey it.** Duplicating a rule into a skill is how the skill and the charter end up disagreeing.
|
||||
|
||||
What each part of the section pins down:
|
||||
|
||||
- **Role identification** — **`bridge_whoami`** (below) answers it authoritatively; the section
|
||||
tells the reader to call it rather than infer. A fallback ladder remains for when it is
|
||||
unreachable: the charter in the system prompt (reliable — the launcher appends it in the same
|
||||
branch that mounts the MCP, so bridge tools without a charter is not a reachable state); the mount
|
||||
name (`mcp__bridged__*` for the primary's `.mcp.json` vs `mcp__bridge__*` for a worker's inline
|
||||
config); `ANTHROPIC_BASE_URL` (one-way — Claude-model workers run clean, so absence proves
|
||||
nothing); then **fail toward worker**. The two errors are asymmetric: a primary acting as a worker
|
||||
gets refused by the authz gate — loud and self-correcting — while a worker acting as the primary
|
||||
ends its turn silently and the sender receives nothing.
|
||||
- **Invariants (both roles)** — never set/forward `ANTHROPIC_BASE_URL`; the bridge is the only
|
||||
channel (terminal text reaches nobody); identity comes from the connection, never an argument
|
||||
(mirroring [`Authz`](9-Implementation)); delivery is status-gated, one message per turn; never
|
||||
touch herdr directly.
|
||||
- **Primary** — **delegate-by-default**, then an intent→tool table over the shipped tools. The
|
||||
default answer to "who does this?" is a worker: the test is not *"could I do this faster myself?"*
|
||||
(usually yes) but *"can I write a brief good enough for a worker to succeed?"* — a wasted worker
|
||||
turn costs a worker turn, while doing it yourself costs the primary's context and subscription.
|
||||
Independent units fan out (one worktree worker each, all dispatched `wait:false`, then poll)
|
||||
rather than serializing. Plus the policy the tool descriptions can't carry: pass `profile:`
|
||||
explicitly; prefer `wait:false` + `bridge_poll`, since a blocking `bridge_send` is capped by the
|
||||
*caller's own* MCP client timeout (~60s) long before a real task finishes; make every delegation
|
||||
self-contained; **name the worker's skill in the first line of `content`** — that instruction is
|
||||
what turns an opt-in skill into a reliable one; you are the merge gate; verify what a worker
|
||||
claims rather than trusting a "clean" report. Delegating work never delegates responsibility.
|
||||
- **Worker** — the turn contract: load the named skill, stay in scope, `bridge_ask` only for a
|
||||
decision that is genuinely the lead's, end with exactly one `bridge_reply`, report only what you
|
||||
actually ran, never merge, never commit `.mcp.json` or `wiki/`.
|
||||
|
||||
**Known gap:** *opencode* workers never read `CLAUDE.md` — they receive `REPLY_CHARTER` as an
|
||||
instructions file and nothing else. Any rule a non-Claude peer must obey belongs in the charter, not
|
||||
in this section. The charter currently carries only the reply rule.
|
||||
|
||||
### `bridge_whoami` — asking instead of guessing
|
||||
|
||||
**Shipped.** The daemon always knew the answer: `ConnectionIdentity` maps a call's loopback peer PID
|
||||
to a herdr pane, and every tool call is already gated on the `Principal` it yields. What was missing
|
||||
was any way for an agent to *ask* — so an agent's own role had to be inferred from side channels the
|
||||
daemon does not control, with a silent failure mode when the inference went the wrong way.
|
||||
|
||||
`bridge_whoami` (no params, `READ` in the [authz table](9-Implementation)) returns that same
|
||||
resolved identity as data:
|
||||
|
||||
```json
|
||||
{"role":"worker","sessionId":"term_a7","paneId":"w9:pW","profile":"ollama",
|
||||
"state":"ready","worktree":"/wt/cb-517","branch":"worker/cb-517-3f2a","owner":"term_primary"}
|
||||
```
|
||||
|
||||
This is a *suggestion*, not wiring: `bridged` never edits the primary's `CLAUDE.md` (that would cross
|
||||
the subscription boundary in the wrong direction). The operator pastes it; the env property is what
|
||||
makes the reminder fire only in sessions where a bridge actually exists.
|
||||
The primary gets `{"role":"primary"}` and nothing more — deliberately: handing it a `sessionId` it
|
||||
does not own would invite exactly the forged `bridge_reply` that `Authz` refuses. A worker the
|
||||
session registry has no record of — one that outlived a daemon restart — still gets `role` and
|
||||
`sessionId`, which is the load-bearing part; the registry fields are simply absent rather than
|
||||
invented.
|
||||
|
||||
Two properties worth keeping if this is ever reimplemented:
|
||||
|
||||
- **It reports, it does not decide.** The value comes from being the *same* resolution the
|
||||
authorization gate uses, not a parallel one that could disagree with it.
|
||||
- **It degrades toward the useful answer.** Never "unknown" when the role is known.
|
||||
|
||||
### The portable `CLAUDE.md` block — copy as-is
|
||||
|
||||
This is the **canonical text**, verbatim. Drop it into any project whose agents mount the bridge MCP;
|
||||
it needs no editing — every project-specific detail was deliberately pushed out of it (see the two
|
||||
notes after the block). Improvements land *here* first, then propagate to each project's `CLAUDE.md`.
|
||||
|
||||
```markdown
|
||||
## Bridge communication (enforced — read this first)
|
||||
|
||||
> **Canonical block.** Everything down to §Layering is the portable bridge charter, copied verbatim
|
||||
> into every project that mounts the bridge MCP. Keep it byte-identical with the template in the
|
||||
> wiki ([Use Cases](https://git.ltms.dev/lms/claude-bridge/wiki/7-Use-Cases) → *The portable
|
||||
> CLAUDE.md block*); improvements go to the template first, then out to each project. Anything
|
||||
> specific to *this* repo lives under §Project addendum below, never inline above it.
|
||||
|
||||
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
|
||||
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
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
### 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
|
||||
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 primary-only; reply/ask are
|
||||
worker-only-and-only-as-itself. 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`.
|
||||
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.
|
||||
|
||||
### Primary (lead) — orchestration
|
||||
|
||||
**Delegate by default — that is the job.** With the bridge mounted you are an orchestrator on a
|
||||
metered subscription, and workers are cheap, parallel, and disposable. The default answer to "who
|
||||
does this?" is **a worker**, not you. Reach for `bridge_send` before you reach for `Edit`.
|
||||
|
||||
- **Delegate**: implementation behind a clear spec, test writing, per-file or wide-area review, log
|
||||
and failure triage, mechanical refactors, doc passes, and any investigation with a stated
|
||||
question. If the unit of work is independent, fan out — one worker per file, area, or dimension —
|
||||
and reduce the replies yourself.
|
||||
- **Keep**: the conversation with the user, decomposition and planning, the final judgment call,
|
||||
merges, and anything that depends on context only you hold.
|
||||
- **The bar is not "could I do this faster myself?"** — usually you could. It is **"can I write a
|
||||
brief good enough for a worker to succeed?"** If yes, write the brief and send it. A wasted worker
|
||||
turn costs a worker turn; doing it yourself costs your context and your subscription.
|
||||
- **Parallelize instead of serializing.** For independent units, spawn one worktree worker each
|
||||
(`bridge_spawn{worktree:true, ticket:…}`), dispatch every one with `wait:false`, then poll the
|
||||
tickets. Waiting for worker A before briefing worker B is the most common way this layer is wasted.
|
||||
- **Delegating does not delegate responsibility.** You still review, verify, and merge.
|
||||
|
||||
| Intent | Tool |
|
||||
|---|---|
|
||||
| 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` · one worker'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` |
|
||||
| Collect a held reply | `bridge_poll{target}` · then `bridge_ack{target, msgId}` |
|
||||
| Tear down | `bridge_stop{paneId}` |
|
||||
|
||||
- **Pass `profile:` explicitly.** Profiles differ in model and cost, not in tier — don't assume the
|
||||
default is what you want.
|
||||
- **Prefer `wait:false` + `bridge_poll` for anything non-trivial.** A blocking `bridge_send` is
|
||||
capped by *your own* MCP client call timeout (~60s), well below the task's real runtime; the ticket
|
||||
path is what survives a long task.
|
||||
- **Every delegation names the worker's playbook.** If this project ships role skills, make the
|
||||
first line of `content` `Load the <name> skill.` — those skills are opt-in, and that line is what
|
||||
makes them reliable. With no such skill, spell the procedure out in the brief instead.
|
||||
- **A delegation must be self-contained**: scope, the files or PR in question, acceptance criteria,
|
||||
and exactly what to report back. The worker sees your message and the repo — nothing of your
|
||||
context, your plan, or your screen.
|
||||
- **You are the gate.** Workers open PRs; you review and merge. Never delegate the merge.
|
||||
- **Verify what a worker claims.** A worker mounts only the bridge MCP and cannot run your other
|
||||
tooling, and a piped build command (`… | tail`) hides failures behind a zero exit — re-run the
|
||||
build and the checks yourself before you believe "clean".
|
||||
|
||||
### Worker — 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.
|
||||
3. **`bridge_ask{question}`** when a decision is genuinely the lead's (ambiguous requirement, two
|
||||
defensible fixes, "bug or intended?"). It blocks and you resume the *same* turn with the answer.
|
||||
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.
|
||||
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.
|
||||
6. **Never merge.** Stage files explicitly — never `git add -A` — and leave alone anything the
|
||||
project marks as not-yours-to-commit.
|
||||
|
||||
### Where each rule lives (don't duplicate — extend the right layer)
|
||||
|
||||
| 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 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
|
||||
`CLAUDE.md` (non-Claude adapters) get the charter only, so any rule *they* must obey belongs in the
|
||||
charter, not here.
|
||||
```
|
||||
|
||||
Two rules keep this portable, and both were learned by getting them wrong first:
|
||||
|
||||
1. **Nothing repo-local inside the block.** The first draft named `auth/Authz.java`, the
|
||||
`.mcp.json`/`wiki/` commit exclusions, and the `implementer`/`reviewer` skills by name — all
|
||||
meaningless in another project. Each moved to a **Project addendum** section that sits *below*
|
||||
the block and never interleaves with it, so the block can be replaced wholesale without reading it.
|
||||
2. **Every fallback signal must be one-way.** The role ladder originally read the MCP mount name in
|
||||
both directions — `mcp__bridged__*` ⇒ primary, `mcp__bridge__*` ⇒ worker. Only the second half is
|
||||
real: the launcher hard-codes `bridge` for a worker's inline config, while a primary's mount is
|
||||
named by whoever wrote that project's `.mcp.json`. A two-way reading of a one-way signal is a
|
||||
confident wrong answer, so the block states only the direction that holds.
|
||||
|
||||
In the `claude-bridge` repo itself the block is treated as **shipped surface, not documentation**:
|
||||
its `CLAUDE.md` carries a change-checklist mapping each part of the code (tool catalog, `Authz`,
|
||||
`ConnectionIdentity`, `REPLY_CHARTER`, injector, worktree overlay, skills) to the part of the block
|
||||
that change can invalidate, plus a sync check that fails if this template and that copy have drifted.
|
||||
A code change that silently falsifies the block is an incomplete change — the agents reading it have
|
||||
no other source.
|
||||
|
||||
|
||||
## More use cases (catalogue)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user