CB-617: role agent definitions + re-sync the canonical CLAUDE.md block

Dai Ha
2026-08-22 12:24:37 +02:00
parent 073dea5504
commit 48b30d1baf
2 changed files with 36 additions and 3 deletions
+31
@@ -1983,6 +1983,37 @@ Still to catalogue:
consequence to write up: the cap bounds nudges *about one item*, so a lead with a steady arrival of
new work keeps being nudged — which is correct, but is not what the knob's name suggests
### Role agent definitions — how a member learns its role (CB-617)
**What it does.** The `architect`, `dev` and `reviewer` contracts live as files in the repo, at both
`.claude/agents/<role>.md` and `.opencode/agent/<role>.md`. A member's worktree is a checkout of this
repo, so it arrives with its own contract. At spawn the launcher passes `--agent <role>` when the
matching file exists.
**The knob.** Nothing to turn on. The role you pass to `bridge_spawn` selects the file. Absent file ⇒
no `--agent` flag and the member still spawns, so this degrades rather than breaks.
**Why it exists.** The contract used to travel as an inline argv flag,
`--append-system-prompt <text>`. herdr refuses to shell-encode a multi-line argument, so **every
claude-code member with a configured role charter failed to spawn** (CB-616) — measured as an
architect that started on `sol` and died on `opus`. opencode escaped only because it already wrote
its charter to a file. Files fix that for both backends and make the contract reviewable in git.
**The gotchas.**
- **Both directories, every time.** Neither tool reads the other's: a `.claude/agents/` file is
invisible to opencode. Measured — a probe agent in `.claude/agents/` never appeared in
`opencode agent list`. The two copies of a role must carry identical bodies or the backends work
from different contracts.
- **Never put `model:` in an agent file.** Both CLIs honour it *only* when no launch flag is passed,
and bridged always passes one (`--model` for claude-code, `-m` for opencode). Measured both ways:
an agent pinned to `openai/gpt-5.6-terra` run with `-m opencode/x-preview-f-free` reported
`> pin · x-preview-f-free`. A `model:` here is silently overridden on every spawn. The model stays
in `bridged.yaml`, which also keeps role and backend as the separate axes the role pools need.
- **The reply charter is still injected, not filed.** "End every turn with `bridge_reply`" must reach
a member that has no repo checkout, so it keeps its inline delivery. Verified that `--agent` and
`--append-system-prompt` compose: both the agent body and the appended rule are in force.
### Found while cataloguing, not by looking for bugs
Two of these are filed as their own tickets. They are recorded here because both are the same shape:
+5 -3
@@ -461,12 +461,14 @@ you.
|---|---|---|
| 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 agent definition files | role contract and per-job procedure | a member whose launcher binds its role to the matching file in its worktree |
| 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
`CLAUDE.md` (non-Claude adapters) get the charter only, so any rule *they* must obey belongs in the
charter, not here.
A rule belongs in **exactly one** layer — the outermost one that must obey it. A member without a
repo checkout still gets the launcher's reply charter, which is why that one rule stays there.
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: