From da015defc4290058785e894df4e90a3be33e0047 Mon Sep 17 00:00:00 2001 From: Dai Ha Date: Tue, 4 Aug 2026 16:00:46 +0200 Subject: [PATCH] Use Cases: as-built CLAUDE.md bridge charter + bridge_whoami MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- 7-Use-Cases.md | 246 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 229 insertions(+), 17 deletions(-) diff --git a/7-Use-Cases.md b/7-Use-Cases.md index 3ff7d96..be534d7 100644 --- a/7-Use-Cases.md +++ b/7-Use-Cases.md @@ -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: , kind: , 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 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)