catherd-cli 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +144 -42
- package/catalog/ATTRIBUTION.md +12 -4
- package/catalog/models.json +8 -0
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +11 -0
- package/plugin/.mcp-codex.json +10 -0
- package/plugin/bin/catherd-mcp +1 -1
- package/plugin/skills/catherd/SKILL.md +150 -79
- package/plugin/skills/catherd-setup/SKILL.md +24 -8
- package/src/adapters/access.ts +7 -1
- package/src/adapters/antigravity/index.ts +18 -3
- package/src/adapters/backend.ts +18 -2
- package/src/adapters/claude-code/index.ts +126 -14
- package/src/adapters/cli.ts +1 -64
- package/src/adapters/codex/events.ts +10 -0
- package/src/adapters/codex/index.ts +17 -5
- package/src/adapters/cursor/index.ts +7 -1
- package/src/adapters/grok/home.ts +75 -20
- package/src/adapters/grok/index.ts +27 -8
- package/src/adapters/opencode/index.ts +38 -1
- package/src/cli.ts +53 -19
- package/src/domain/agents.ts +3 -1
- package/src/domain/brief.ts +91 -0
- package/src/domain/calibration.ts +7 -0
- package/src/domain/catalog.ts +12 -1
- package/src/domain/changes.ts +23 -1
- package/src/domain/cost.ts +8 -3
- package/src/domain/errors.ts +4 -0
- package/src/domain/failover.ts +17 -5
- package/src/domain/gate-brief.ts +52 -0
- package/src/domain/hints.ts +13 -2
- package/src/domain/host.ts +17 -0
- package/src/domain/lane.ts +99 -3
- package/src/domain/notice.ts +14 -4
- package/src/domain/profile-rules.ts +129 -16
- package/src/domain/profile.ts +56 -5
- package/src/domain/record.ts +17 -2
- package/src/domain/reply.ts +29 -0
- package/src/domain/role-prompts.ts +56 -16
- package/src/domain/role-scope.ts +106 -0
- package/src/domain/role-tools.ts +34 -0
- package/src/domain/route.ts +73 -0
- package/src/domain/select.ts +177 -25
- package/src/domain/state.ts +5 -0
- package/src/domain/util.ts +22 -0
- package/src/domain/workspace.ts +130 -0
- package/src/entry/catalog-command.ts +18 -8
- package/src/entry/cli-kit.ts +17 -1
- package/src/entry/deps.ts +8 -3
- package/src/entry/doctor-command.ts +29 -2
- package/src/entry/host-arg.ts +56 -0
- package/src/entry/init-command.ts +20 -8
- package/src/entry/knowledge-command.ts +78 -3
- package/src/entry/lock-command.ts +111 -10
- package/src/entry/mcp/dispatch-tools.ts +3 -3
- package/src/entry/mcp/handshake.ts +8 -2
- package/src/entry/mcp/lane-tools.ts +55 -8
- package/src/entry/mcp/protocol-tools.ts +10 -4
- package/src/entry/mcp/push-tools.ts +16 -0
- package/src/entry/mcp/role-bin.ts +20 -0
- package/src/entry/mcp/role-server.ts +116 -0
- package/src/entry/mcp/run-tools.ts +25 -6
- package/src/entry/mcp/server.ts +224 -44
- package/src/entry/mcp/setup-tools.ts +11 -5
- package/src/entry/mcp/workspace-tools.ts +142 -0
- package/src/entry/pause-command.ts +67 -0
- package/src/entry/profile-command.ts +131 -16
- package/src/entry/role-cli-command.ts +115 -0
- package/src/entry/runs-command.ts +157 -11
- package/src/entry/supervise-command.ts +30 -3
- package/src/entry/tui/effects.ts +23 -8
- package/src/entry/tui/fixtures.ts +33 -10
- package/src/entry/tui/run.tsx +10 -4
- package/src/entry/tui/state.ts +56 -16
- package/src/entry/tui/stories.ts +11 -4
- package/src/entry/tui/views/profile-actions.ts +4 -2
- package/src/entry/tui/views/profiles.tsx +3 -3
- package/src/entry/tui/views/runs.tsx +66 -20
- package/src/entry/tui/views/save-dialog.tsx +8 -4
- package/src/entry/tui/views/status.tsx +6 -2
- package/src/entry/workspace-command.ts +44 -0
- package/src/infra/boot-lock.ts +25 -0
- package/src/infra/cli.ts +65 -0
- package/src/infra/codex-queue.ts +165 -0
- package/src/infra/delivery.ts +86 -0
- package/src/infra/dispatch-dir.ts +3 -0
- package/src/infra/env.ts +15 -2
- package/src/infra/git.ts +6 -0
- package/src/infra/heavy-lock.ts +93 -4
- package/src/infra/host-context.ts +58 -0
- package/src/infra/host-probe.ts +16 -0
- package/src/infra/lock-activity.ts +88 -0
- package/src/infra/login-env.ts +48 -0
- package/src/infra/paths.ts +35 -0
- package/src/infra/peer-inbox.ts +4 -1
- package/src/infra/role-mcp.ts +69 -0
- package/src/infra/sources/arena.ts +11 -3
- package/src/infra/sources/artificial-analysis.ts +8 -1
- package/src/infra/sources/cache.ts +2 -0
- package/src/infra/supervisor.ts +110 -18
- package/src/services/admission.ts +117 -26
- package/src/services/agent-links.ts +30 -5
- package/src/services/backends.ts +7 -0
- package/src/services/budget.ts +26 -6
- package/src/services/catalog-service.ts +20 -12
- package/src/services/credentials.ts +11 -6
- package/src/services/dispatch-service.ts +220 -22
- package/src/services/dispatches.ts +36 -3
- package/src/services/doctor-access.ts +8 -3
- package/src/services/doctor-backends.ts +30 -7
- package/src/services/doctor-checks.ts +20 -5
- package/src/services/doctor-docker.ts +221 -0
- package/src/services/doctor-push.ts +69 -90
- package/src/services/doctor-role-mcp.ts +65 -0
- package/src/services/doctor.ts +172 -38
- package/src/services/end-push.ts +157 -0
- package/src/services/finalize.ts +13 -3
- package/src/services/gate-env.ts +218 -0
- package/src/services/gate-service.ts +216 -45
- package/src/services/global-install.ts +46 -13
- package/src/services/lane-edit.ts +154 -0
- package/src/services/lane-service.ts +258 -52
- package/src/services/milestones.ts +137 -26
- package/src/services/notifier.ts +377 -72
- package/src/services/orchestrator-wait.ts +82 -0
- package/src/services/pause.ts +161 -0
- package/src/services/peek.ts +122 -23
- package/src/services/ports.ts +39 -4
- package/src/services/preflight.ts +153 -19
- package/src/services/profile-service.ts +117 -30
- package/src/services/profile-store.ts +31 -7
- package/src/services/protocol.ts +119 -25
- package/src/services/provenance.ts +16 -12
- package/src/services/questions.ts +15 -0
- package/src/services/reconcile.ts +22 -1
- package/src/services/reentry.ts +7 -6
- package/src/services/role-access.ts +27 -0
- package/src/services/routing-service.ts +150 -37
- package/src/services/run-debug.ts +98 -4
- package/src/services/run-pin.ts +125 -0
- package/src/services/run-service.ts +153 -19
- package/src/services/run-store.ts +119 -19
- package/src/services/runs-page.ts +45 -9
- package/src/services/scratch.ts +60 -0
- package/src/services/session-view.ts +18 -13
- package/src/services/sessions.ts +34 -16
- package/src/services/setup.ts +31 -10
- package/src/services/source-derive.ts +28 -5
- package/src/services/source-sync.ts +9 -2
- package/src/services/standins.ts +40 -6
- package/src/services/state.ts +21 -0
- package/src/services/summary.ts +86 -11
- package/src/services/treat-likes.ts +41 -12
- package/src/services/verifier-brief.ts +23 -0
- package/src/services/verifier-step.ts +48 -0
- package/src/services/workspace-admission.ts +267 -0
- package/src/services/workspace-service.ts +254 -0
- package/src/services/workspace-store.ts +95 -0
|
@@ -23,73 +23,124 @@ Your job:
|
|
|
23
23
|
|
|
24
24
|
The catherd server keeps `state.md` true: it rewrites it on every dispatch, climb and landing.
|
|
25
25
|
|
|
26
|
-
**Your first call is `status()`.** Its `version` must be the one this plugin pins, `catherd-cli@1.
|
|
26
|
+
**Your first call is `status()`.** Its `version` must be the one this plugin pins, `catherd-cli@1.5.0`. If it differs, stop and tell the user to restart their host session so the plugin and its server match. Read its host and capability diagnostics; unknown or conflicting identity never authorizes ownership or push.
|
|
27
27
|
|
|
28
28
|
**You never edit product files.** Every line of code, tests or docs comes from a role, including a one-line fix.
|
|
29
29
|
|
|
30
|
-
**You run in the user's own session, on purpose.** The main thread is the user's own model in Claude Code, with their plugins, hooks and memory beside it: the environment they decide in every day. Never propose a stripped launcher or a headless relay to save tokens.
|
|
30
|
+
**You run in the user's own session, on purpose.** The main thread is the user's own model in Claude Code or native Codex, with their plugins, hooks and memory beside it: the environment they decide in every day. Never propose a stripped launcher or a headless relay to save tokens.
|
|
31
31
|
|
|
32
32
|
**Each role runs in its vendor's own harness with the user's customizations — never isolate it.** Codex roles keep the user's config, hooks, MCP servers, skills and `AGENTS.md`. The one exception is the user's own choice, the profile's `harness.<name>.isolated`, and `dispatch` applies it for you.
|
|
33
33
|
|
|
34
34
|
## Tools
|
|
35
35
|
|
|
36
|
-
The catherd MCP tools ship with this plugin.
|
|
37
|
-
|
|
38
|
-
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
| `
|
|
45
|
-
| `
|
|
46
|
-
| `
|
|
47
|
-
| `
|
|
48
|
-
| `
|
|
49
|
-
| `
|
|
50
|
-
| `
|
|
51
|
-
| `
|
|
52
|
-
| `
|
|
53
|
-
| `
|
|
54
|
-
| `
|
|
55
|
-
| `
|
|
56
|
-
| `
|
|
57
|
-
| `
|
|
58
|
-
| `
|
|
36
|
+
The catherd MCP tools ship with this plugin. Discover them through the host's available capability mechanism: tool names and deferred loading vary by host. In Claude Code they appear as `mcp__plugin_catherd_catherd__<name>`; use `ToolSearch` there when deferred, and load `PushNotification` if available. Codex uses its own exposed tools and discovery facility; never invent a Claude `ToolSearch`, `Agent` or notification tool there.
|
|
37
|
+
|
|
38
|
+
Headless Codex and Claude Code roles receive a dedicated `catherd_role` MCP server (`mcp__catherd_role__<name>`), isolated or not: all roles can read run files and knowledge, architects/researchers can write run files, and verifiers can record gate checks and passes. Dispatch configures it for the role without editing the user's config or profiles. Native Claude subagents retain the plugin tool namespace above. Roles on other backends use the same operations from their shell: `catherd run-file read|write <run> <path>` and `catherd gate check|pass <run> …`, bound to their own run. A role is never refused for being isolated.
|
|
39
|
+
|
|
40
|
+
Pass the actual project `repo` explicitly to profile, setup and catalog tools that accept it. The native Codex MCP server starts in the installed plugin root, so its cwd is not evidence of the project. `run_start(repo, ...)` establishes the run's repository.
|
|
41
|
+
|
|
42
|
+
| Tool | Use |
|
|
43
|
+
| ----------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
44
|
+
| `status(run?)` | First, and whenever the user asks where it stands: `version`, then per run the open owner questions, the `state.md` tail, live roles, totals, Claude subagents, the verifier's step, budget, milestones |
|
|
45
|
+
| `run_start(repo, title, a_lines, from?)` | Once per project. Returns `run`, the id every other call takes, `dir`, the run folder `R`, and `protocol`: the next step of the milestone loop and its six-line checklist. `from`: the run this one takes over (a planning run handed to a worktree), closed with a pointer here. It pins the profile, each role's access and each backend's isolation for the run |
|
|
46
|
+
| `run_pin(run)` | Only on the owner's word: re-pins the run to what its repo runs on now. Until then dispatch keeps the pinned values, and `status` and `state.md` say what changed |
|
|
47
|
+
| `lane_set(run, lane, field, value)`, `owns_add(run, lane, paths, why)` | One lane header line (`owns`, `fast_check`, `kind`, `difficulty`, `after`, `allow`), validated, instead of editing the file by hand; `owns_add` grows a lane's Owns mid-lane, refused when a running lane owns a path |
|
|
48
|
+
| `peek(run?, name?)` | Never waits. Per run: the open owner questions first (the owner's to answer), each live role with its rung, time and last event, every finished record not yet read, the next step, the verifier's step, and `protocol` (the milestone loop's next step and the checklist). It marks nothing read: `result` does |
|
|
49
|
+
| `route(run, lane_file?, lanes?, role?)` | A lane's rung (from the lane file's `Kind:`/`Difficulty:` when it declares both, else Jev, else the profile), or a role's rung; `lanes` routes several lane files in one call (`routes`, one per lane). Returns `rung`, `ladder` (only rungs at least as strong as the start), `backend`, `agent` and a one-line `why`; the full provenance goes to `R/routes.jsonl` |
|
|
50
|
+
| `preflight(run, confirmed?, milestone?)` | Each lane's fast check once, before any lane runs. Outcomes below |
|
|
51
|
+
| `dispatch(run, role, name, brief, rung?, thread?, lane?, next?)` | Starts one process role (Codex, claude-code or opencode) and returns at launch, in about a second, with `dispatched`. It routes a lane not routed yet, runs a lane at its current rung when `rung` is left out (a role outside a lane needs `rung`), appends the role's reply contract to the brief, and its result arrives as a catherd message |
|
|
52
|
+
| `result(run, name)` | A role's latest reply, capped, its record and its `hints`; reading a finished record marks it read |
|
|
53
|
+
| `cancel(run, name)` | Stops a live role and returns its record, `cancelled`, and `hints` |
|
|
54
|
+
| `test_push()` | One labeled smoke message to this session through catherd's push; its receipt proves the host accepted it, never that you read it. From a shell: `catherd doctor --test-push --thread <uuid>` |
|
|
55
|
+
| `record_agent_run(run, name, role, rung, total_tokens, duration_ms?, cost_usd?, status?, lane?)` | Native Claude Agent results on Claude Code only. The budget counts them; `lane` counts their time toward that lane's kind; a verifier named `verifier-<M>` records FAIL with `status: "failed"` |
|
|
56
|
+
| `climb(run, lane, reason, evidence?, env?)` | The lane's next rung, with its `backend` and `agent`, or `top: true`. `env: true` when the environment, not the rung, caused it |
|
|
57
|
+
| `ask(run, question, state)` | Jev's `finding` or `same-defect` answer |
|
|
58
|
+
| `gate_check(run, item, command, paths, milestone?)`, `gate_pass(run, item, command, paths, evidence)` | The verifier's gate ledger: an item that passed on the same content is carried over, not run again; `milestone` scopes the digest's carried items |
|
|
59
|
+
| `park(run, milestone, question)`, `answer(run, milestone, answer)` | An owner question parks its milestone while the rest of the run goes on; the owner's answer unparks it |
|
|
60
|
+
| `land(run, milestone, what, commit, evidence, next, learned?, skip?)` | A landed milestone's ledger row, with the minutes it took, `state.md`, and `digest`, the milestone's digest. Refused until the milestone has its reviewer and its verifier. `learned` appends to this repo's `knowledge.md` |
|
|
61
|
+
| `read_knowledge(repo)` | What past runs of this repo learned. The dossier brief reads it |
|
|
62
|
+
| `write_run_file(run, path, content)`, `read_run_file(run, path)` | Files in `R`. Never your own Write or Read tools there: `R` is outside the repo |
|
|
63
|
+
| `set_next(run, next)` | The next step, when you pause or the plan changes. Returns `state` and, when git fails, `hints` |
|
|
64
|
+
| `runs_summary(run?, repo?, role?, since_days?)` | Time, tokens, refusals and climbs per role and rung, the Claude subagent runs, and the harness cost, for the report |
|
|
65
|
+
| `profile_get(repo?)` | The profile this repo runs on: each role's access, rungs and enforcement, failover, budget, timeouts, and the moments to push |
|
|
59
66
|
|
|
60
67
|
**A tool returns `hints` when it has any:** one line each, on what to do next. Read them before you move on.
|
|
61
68
|
|
|
62
69
|
**Every error is `{ code, message, fix }`.** Read the code, act on the fix, and never retry the same call blindly:
|
|
63
70
|
|
|
64
71
|
- `E_ADMIT_OVERLAP`: the lane shares an owned path with a running lane. Dispatch it when that one returns.
|
|
72
|
+
- `E_ADMIT_ORDER`: the lane's `After:` line names a lane that has not finished. Dispatch that one first.
|
|
73
|
+
- `E_ADMIT_PAUSED`: the machine (`catherd pause --machine`) or the run's workspace (`workspace_pause`) is paused on a blocker; the message gives the reason. Wait for the owner, or resume once the blocker is gone.
|
|
74
|
+
- `E_RUN_NOT_LIVE` on dispatch: the run is superseded; dispatch in the run the message names.
|
|
65
75
|
- `E_ADMIT_DUPLICATE`: that role name is already running. It reports through a catherd message when it finishes; `peek(run, name)` shows it now, and `cancel` stops it.
|
|
66
|
-
- `E_ADMIT_RUNG`: the rung is not on that role's ladder, it is a `claude:` rung, or the profile turns the role off. Use the rung `route` returned;
|
|
76
|
+
- `E_ADMIT_RUNG`: the rung is not on that role's ladder, it is a `claude:` rung, or the profile turns the role off. Use the rung `route` returned; native `claude:` requires Claude Code; skip a role that is off. On Codex, explain the exact `claude-code:` model/effort equivalent or the reviewed host-default reset instead of converting the rung yourself.
|
|
67
77
|
- `E_RUN_BUDGET`: the run's budget is spent (a soft cap: roles already running finish). Pause, report and push.
|
|
68
78
|
- `E_BACKEND_MISSING`, `E_BACKEND_NOT_LOGGED_IN`, `E_BACKEND_TOO_OLD`, `E_BACKEND_CANNOT_RUN`: tell the user the `fix`, word for word, then pause.
|
|
69
79
|
- `E_LANE_INVALID`: a lane file's `Kind:` or `Difficulty:` is missing or not one the catalog knows. Fix the header (the `fix` lists the values), or have the architect fix it, then call again.
|
|
70
80
|
- `E_LAND_GATE`: the milestone has no reviewer record or no verifier verdict since its lanes started, it is parked, or its `skip` does not hold. Run what the message names, then land again.
|
|
71
81
|
- `E_CLIMB_DESIGN`: the evidence points at the plan, not the rung. Send it to the architect (an `ask` finding, then an architect delta), not up the ladder.
|
|
82
|
+
- `E_CLIMB_ENV`: the lane's last reply named the environment (`ENV:`). A higher rung would stop the same way: fix the environment, or park the milestone.
|
|
83
|
+
|
|
84
|
+
## Workspaces with independent repositories
|
|
85
|
+
|
|
86
|
+
For a task spanning repositories, the workspace root may be a plain directory. Call
|
|
87
|
+
`workspace_inspect(root, repos?)` to resolve its explicit `catherd.workspace.json` members
|
|
88
|
+
(`{"schema":1,"repos":{"api":"./api","web":"./web"}}`) or a supplied repo map.
|
|
89
|
+
Never discover participants by scanning unrelated folders. Select only members the task needs.
|
|
90
|
+
|
|
91
|
+
Call `workspace_start(root, title, a_lines, steps, repos?, budget?)`. Each step names its `id`, member
|
|
92
|
+
`repo`, `title`, `a_lines`, optional `depends_on` step ids, and the `milestone` that satisfies downstream
|
|
93
|
+
dependencies (default `M1`). The response contains `workspace.id` and the parent artifact `dir`.
|
|
94
|
+
Keep each workspace within 100 members and 100 steps.
|
|
95
|
+
Steps reusing a member must be ordered by dependencies. For a joint check, add a final step in an
|
|
96
|
+
existing member with `depends_on` naming both producer steps; never start two steps in the same repo together.
|
|
97
|
+
Use `workspace_contract(workspace, content)` to prepare the shared interface and coordination agreement
|
|
98
|
+
before any child exists. The first child freezes the contract; subsequent reads omit `content`.
|
|
99
|
+
|
|
100
|
+
Use `workspace_status(workspace)` for ready, waiting, active and landed steps and aggregate spending.
|
|
101
|
+
Start only ready steps with `workspace_child_start(workspace, step)`; it returns the ordinary child `run`
|
|
102
|
+
id, `dir`, and frozen contract path. Repeating it returns the same child. Children are created lazily, and a
|
|
103
|
+
dependency becomes ready only after its predecessor's declared milestone is recorded by `land`.
|
|
104
|
+
Finish and collect every child dispatch before landing the step's completion milestone; live or uncollected
|
|
105
|
+
work blocks it, not the step's other milestones. A landed step still admits a post-land fix.
|
|
106
|
+
When the program's order is "after it is merged", give the step `release: "merge"` and a `base` ref
|
|
107
|
+
(`origin/main`): its dependents start once its landed commit is in that ref. Fetch, then ask again;
|
|
108
|
+
nothing is polled. The workspace budget has no cap unless you set one; `workspace_budget(workspace, …)`
|
|
109
|
+
raises it on the owner's word. One blocker for every child (a VPN, Docker down) is one
|
|
110
|
+
`workspace_pause(workspace, reason)` and one push, not a park per run; `workspace_resume` lifts it. For
|
|
111
|
+
every run on the machine, the owner runs `catherd pause --machine "<reason>"`.
|
|
112
|
+
With a cap set, repair corrupt accounting evidence before continuing rather than treating unknown usage as
|
|
113
|
+
zero; `workspace_status` names any run folder it skipped.
|
|
114
|
+
|
|
115
|
+
Follow the single-repo milestone sequence below independently for each child. Read each member's rules,
|
|
116
|
+
profile and knowledge separately. Include the copied `workspace-contract.md` in role briefs; do not give
|
|
117
|
+
workers sibling writable roots. The shared budget (`minutes`, `tokens`, `usd`) includes all child spending
|
|
118
|
+
and complements each child's profile budget. Keep cross-repo progress in the parent status view; do not
|
|
119
|
+
assume one successful member completes the workspace. Commits and pushes still require the user's
|
|
120
|
+
authorization and repository rules; there is no automatic commit, push or rollback.
|
|
72
121
|
|
|
73
122
|
## Roles
|
|
74
123
|
|
|
75
|
-
| Role | How to run it
|
|
76
|
-
| ----------- |
|
|
77
|
-
| architect | `
|
|
78
|
-
| verifier | `
|
|
79
|
-
| worker | `dispatch(run, "worker", "worker-<lane>", brief, rung, lane: "<lane>")`
|
|
80
|
-
| reviewer | `dispatch(run, "reviewer", "reviewer-<m>", brief, rung)`
|
|
81
|
-
| ui-reviewer | `dispatch(run, "ui-reviewer", "ui-<m>", brief, rung)`
|
|
82
|
-
| artist | `dispatch(run, "artist", "artist-<n>", brief, rung)`
|
|
83
|
-
| writer | `dispatch(run, "writer", "writer-<m>", brief, rung)`
|
|
84
|
-
| researcher | `dispatch(run, "researcher", "researcher-<n>", brief, rung)`
|
|
85
|
-
|
|
86
|
-
- **Every rung comes from `route`,** never from you. A rung names its backend: `backend:model#effort`, like `codex:gpt-6-sol#high` or `claude:claude-opus-5-5#high`.
|
|
87
|
-
- **
|
|
124
|
+
| Role | How to run it | Job |
|
|
125
|
+
| ----------- | ----------------------------------------------------------------------- | ------------------------------------------------------- |
|
|
126
|
+
| architect | `route(run, role: "architect")`, then the backend rule below | Decisions, milestones, lane files. No code. |
|
|
127
|
+
| verifier | `route(run, role: "verifier")`, then the backend rule below | Independent PASS/FAIL of a milestone, by running it |
|
|
128
|
+
| worker | `dispatch(run, "worker", "worker-<lane>", brief, rung, lane: "<lane>")` | One lane: its code and its tests |
|
|
129
|
+
| reviewer | `dispatch(run, "reviewer", "reviewer-<m>", brief, rung)` | Review of a milestone's diff |
|
|
130
|
+
| ui-reviewer | `dispatch(run, "ui-reviewer", "ui-<m>", brief, rung)` | Screenshots of the changed screens with `agent-browser` |
|
|
131
|
+
| artist | `dispatch(run, "artist", "artist-<n>", brief, rung)` | Images a screen needs, with Codex's built-in image tool |
|
|
132
|
+
| writer | `dispatch(run, "writer", "writer-<m>", brief, rung)` | README, docs, changelog, MR body |
|
|
133
|
+
| researcher | `dispatch(run, "researcher", "researcher-<n>", brief, rung)` | The dossier, or one factual question about the code |
|
|
134
|
+
|
|
135
|
+
- **Every rung comes from `route`,** never from you, for every role including architect and verifier. A rung names its backend: `backend:model#effort`, like `codex:gpt-6.1-sol#high` or `claude:claude-opus-5-5#high`. Only `backend: "claude"` on a Claude Code host uses `Agent(subagent_type: <agent>)`, with the brief and run id as its prompt. Every process backend uses `dispatch` followed by `result`, on either host. `dispatch` refuses native Claude rungs; Codex never fabricates native agents or silently changes `claude:` to `claude-code:`.
|
|
136
|
+
- **Only omitted architect/verifier rungs follow the host:** Codex uses exactly `codex:gpt-6.1-sol#high` and `codex:gpt-6.1-sol#low`; Claude Code retains its native Claude defaults. Explicit ladders, model IDs, efforts and existing materialized profiles stay unchanged. A deliberate reset removes only those two roles' `rungs` and `defaultRung` through the reviewed CLI workflow in the setup skill.
|
|
137
|
+
- **Verify the exact `gpt-6.1-sol` model ID against native catalog discovery and validation.** If the catalog spells it differently, report the mismatch and resolve it explicitly with the owner; never silently normalize or substitute a model. Retain architect high and verifier low effort.
|
|
138
|
+
- **After every native Claude subagent returns on Claude Code,** call `record_agent_run(run, name, role, rung, total_tokens, duration_ms, lane)` with the numbers its Agent result reports (`lane` when it worked one). Claude runs cost the budget too, and catherd cannot see them otherwise. Process roles already have dispatch records; do not report them as native agents.
|
|
88
139
|
- **A role the profile disables** (`profile_get`; every role but the worker can be off) is skipped, and the report says so.
|
|
89
140
|
- **Access:** catherd sets it per role. Worker, artist and writer write in the repo, the temp dir and catherd's lock dir, and reach the network, loopback ports and a local Docker, so a worker runs its installs and tests itself; reviewer, researcher and architect read; verifier and UI reviewer get full access for Docker, a browser, gate logs and screenshots. `catherd doctor` says per backend what a worker can reach.
|
|
90
141
|
- **A read-only role on claude-code or opencode has no shell** (no `git diff`, no `ls`), so its brief must list the files to read.
|
|
91
142
|
- **Cheap rungs hide broken tools.** The lowest Track A rung stays silent about a broken tool about a third of the time, so every lane on it is checked by your fast check, not by its own word.
|
|
92
|
-
- **Claude agents:**
|
|
143
|
+
- **Native Claude agents, only on Claude Code:**
|
|
93
144
|
- catherd generates them from the profile, and Claude Code registers them at session start.
|
|
94
145
|
- Pass no `model` to those Agent calls. Never let `Plan`, `general-purpose` or `Explore` stand in.
|
|
95
146
|
- `Agent type … not found` means the profile changed after this session started: tell the user to open a new session.
|
|
@@ -100,18 +151,20 @@ The catherd MCP tools ship with this plugin. They appear as `mcp__plugin_catherd
|
|
|
100
151
|
|
|
101
152
|
A lane starts on the lowest rung that can do it, and climbs one rung when it shows it cannot. The ladders come from the profile and the catalog. With the default profile:
|
|
102
153
|
|
|
103
|
-
| Track | Lanes | Rungs, low to high
|
|
104
|
-
| ----- | ----------------------------------------------------------------------------- |
|
|
105
|
-
| A | `copy` or `build`: an existing pattern, a clear fast check | `codex:gpt-6-luna#high` → `codex:gpt-6-sol#
|
|
106
|
-
| B | `logic` or `hard`, or `terminal` work: state, concurrency, ops, unclear cause | `codex:gpt-6-sol#medium` → `codex:gpt-6-sol#high` → `codex:gpt-6-sol#xhigh`
|
|
154
|
+
| Track | Lanes | Rungs, low to high |
|
|
155
|
+
| ----- | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
|
|
156
|
+
| A | `copy` or `build`: an existing pattern, a clear fast check | `codex:gpt-6-luna#high` → `codex:gpt-6-sol#xhigh` |
|
|
157
|
+
| B | `logic` or `hard`, or `terminal` work: state, concurrency, ops, unclear cause | `codex:gpt-6-sol#medium` → `codex:gpt-6-sol#high` → `codex:gpt-6-sol#xhigh` |
|
|
107
158
|
|
|
108
|
-
|
|
159
|
+
A ladder holds only rungs at least as strong as its start on the lane's bar, so a climb never lands lower. When no rung clears a lane's bar, `route`'s `why` says so and names the closest rung; when rungs on two quotas score the same, the start goes to the quota the run has used least, and `why` says a tie decided.
|
|
109
160
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
|
113
|
-
|
|
|
114
|
-
| A
|
|
161
|
+
**The lane's header decides; Jev fills in what it leaves out.** A lane file that declares both `Kind:` and `Difficulty:` routes on them (`source: "lane"`), and `why` says where Jev disagreed. Jev (TypeSafe) is a decision model: it answers a fixed set of questions about the lane with calibrated probabilities, usually in well under a second, for a fraction of a cent; `route` gives up on it after 25 s. It is optional. For a lane without both lines, `route` takes Jev's answer when its probabilities settle the track (`source: "jev"`), else the profile's default (`source: "default"`), so the run never waits on it. When Jev is sure of the kind but not the difficulty, `route` keeps the lane's kind if it has one, else keeps Jev's kind and takes the difficulty from the lane's `Difficulty:` line, else the role's default (`source: "jev-kind"`). Every Jev call is logged to `R/jev.jsonl`, without the lane's text, and every decision to `R/routes.jsonl`.
|
|
162
|
+
|
|
163
|
+
| When | Call | On the answer |
|
|
164
|
+
| ------------------------------------------------ | ----------------------------------------------------------------------------- | ------------------------------------------- |
|
|
165
|
+
| A milestone's lanes, before their first dispatch | `route(run, lanes: ["lanes/Mx.L1.md", …])` | Dispatch each at its `rung` |
|
|
166
|
+
| A review finding that may be the plan's fault | `ask(run, "finding", { lane_file: "lanes/Mx.Ly.md", finding: "<the line>" })` | `design` → the architect. Else → the worker |
|
|
167
|
+
| A finding back after its fix round | `ask(run, "same-defect", { before: "<old line>", after: "<new line>" })` | `yes` → climb one rung |
|
|
115
168
|
|
|
116
169
|
**Climb one rung** with `climb(run, lane, reason, evidence)` (add `env: true` when a missing service, a broken tool or a usage limit caused it, not the rung), then dispatch the lane at the new rung on a fresh thread whose brief is the lane file plus the path of the failing evidence, when:
|
|
117
170
|
|
|
@@ -126,14 +179,14 @@ A climb is for capability only. When the evidence says the lane cannot be done a
|
|
|
126
179
|
A failure on the top rung (`top: true`) goes to the architect when Jev calls it design, else to the report as open.
|
|
127
180
|
|
|
128
181
|
- Jev decides which model does the work. It never decides that the work is done: only a check, the reviewer or the verifier does.
|
|
129
|
-
- `R/routes.jsonl` records each lane's rung
|
|
182
|
+
- `R/routes.jsonl` records every role's decision (its why and provenance, a lane-less dispatch's rung too), each lane's rung, every climb with its reason, and each lane's outcome beside Jev's answer; `R/outcomes.jsonl` gets one row per lane when its milestone lands or it fails its top rung; a lane written again (landed after failing its top rung, or re-landed) keeps its rows, and the last row per lane wins.
|
|
130
183
|
- Never put a secret or a key into an `ask` state. Keep the state short and in English.
|
|
131
184
|
|
|
132
185
|
## Threads
|
|
133
186
|
|
|
134
187
|
A resumed thread replays its whole history on every tool call. In the first real run, one worker thread cost 20–25M input tokens per resume, and threads were 90% of the Codex spend.
|
|
135
188
|
|
|
136
|
-
- **Resume a thread only for its own fix round:** the reviewer's findings on that piece, or the verifier's FAIL, at the same rung. That is `dispatch(…, thread: <record.thread>)`, with the fix as the brief.
|
|
189
|
+
- **Resume a thread only for its own fix round:** the reviewer's findings on that piece, or the verifier's FAIL, at the same rung. That is `dispatch(…, thread: <record.thread>)`, with the fix as the brief: the `thread:` the first line of its catherd message names, or `thread: "latest"` for that name's last thread. A thread that name never ran on in the run is refused (`E_ADMIT_THREAD`).
|
|
137
190
|
- **A new piece of work gets a fresh thread,** even for the same worker role: a new bug, a cleanup slice, a docs pass, a re-run, a climb to the next rung. Its brief carries what it needs, from its lane file and the ledger.
|
|
138
191
|
- A `thread-heavy` hint means that thread is spent. Its next piece starts fresh.
|
|
139
192
|
|
|
@@ -153,15 +206,19 @@ Your context is re-read on every turn, and it is the run's most expensive token.
|
|
|
153
206
|
|
|
154
207
|
Call `dispatch` from your main thread only, never from a subagent: catherd messages the session that dispatched.
|
|
155
208
|
|
|
209
|
+
Only you, the orchestrator, call the coordinator tools: `peek` (of another role), `result`, `dispatch`, `run_start`, `climb`, `land`, `park`, `cancel`, `set_next`, `answer`, `profile_set`, `test_push`, `run_pin`, `lane_set`, `owns_add`, `record_agent_run` and the `workspace_*` writers (`workspace_budget`, `workspace_pause` and `workspace_resume` among them). A role never does: a native Claude subagent's agent file forbids them, and a process role's catherd server refuses them with `E_ROLE_SCOPE`, so a role can never take the run from you. A brief never asks a role to call one.
|
|
210
|
+
|
|
156
211
|
- **Dispatch every independent role one after another.** Each `dispatch` returns in about a second, once its role has started, so they all run side by side.
|
|
157
|
-
- **Then write one status line and end your turn.**
|
|
212
|
+
- **Then write one status line and end your turn.** Claude Code receives `<cross-session-message from-name="catherd">` through its peer inbox with its existing priorities. Codex receives queued next input through the existing native server (`--remote unix://`): idle sessions can wake; a busy session processes it after the active turn, without Claude's urgent next-tool-round promise. The notice names the run, dispatch and every event ID, including every coalesced event. Call `result(run, name)` for each stored record you will act on, then dispatch what follows.
|
|
158
213
|
- **A single role is `dispatch`, then end your turn.**
|
|
159
|
-
- **Never `sleep`, loop, or call `peek` again and again.** Call `peek(run)` when the user asks how it is going, when a decision needs the other roles' state, or once after `run_start` on a resumed run.
|
|
160
|
-
- **
|
|
214
|
+
- **Never `sleep`, loop, use `await_results`, or call `peek` again and again.** Call `peek(run)` when the user asks how it is going, when a decision needs the other roles' state, or once after `run_start` on a resumed run.
|
|
215
|
+
- **Queue acceptance is not processing or collection.** An unloaded, interrupted or restarted conversation may retain input without generating. Accepted and ambiguous events are never automatically resent; durable unread records remain recoverable with `peek`/`result`. Only `result` collects a finished record. `status` and `result` do not adopt ownership; explicit `peek(run)`, `run_start` and `dispatch` do.
|
|
216
|
+
- **Duplicate notices are idempotent reads.** Preserve every event ID, consult the actual stored record and protocol, and never redispatch work or land a milestone twice because input repeats. A message or transcript echo supplies neither delivery acknowledgement nor user approval; reviewer/verifier gates remain authoritative.
|
|
217
|
+
- **An ambiguous retry is an explicit decision:** after validating the current owner and exact stored event, use `catherd runs retry-push <run> <name> --event '<exact event ID>' --acknowledge-possible-duplicate` from that owner's host session. It may duplicate native input. Accepted receipts suppress another enqueue for that owner/event; no supported history reconciliation API means retain ambiguity until this deliberate decision. Unknown/conflicting hosts never push.
|
|
161
218
|
|
|
162
219
|
**When the user asks where it stands,** call `peek(run)` once and answer from it; `status(run)` adds the totals, budget and milestones.
|
|
163
220
|
|
|
164
|
-
**Push a notification**
|
|
221
|
+
**Push a notification through an available host facility** only at the moments the profile's `notify` lists (`profile_get({ repo })`), one line each. Claude Code may use `PushNotification`; if the host exposes none, report in the conversation without inventing a notification or scheduled follow-up:
|
|
165
222
|
|
|
166
223
|
- `milestone`: a milestone landed: its name, its commit, the time it took, and the path of its digest (`land`'s `digest`);
|
|
167
224
|
- `finish`: the run finished: verdict and total time;
|
|
@@ -169,6 +226,12 @@ Call `dispatch` from your main thread only, never from a subagent: catherd messa
|
|
|
169
226
|
|
|
170
227
|
Nothing else pushes: a phone that buzzes for progress teaches the user to ignore it.
|
|
171
228
|
|
|
229
|
+
### On Codex
|
|
230
|
+
|
|
231
|
+
- **Run inside tmux.** When `$TMUX` and `$STY` are both empty, tell the user once, at the run's start, to run the coordinator inside `tmux` (or `screen`): Codex's app-server stops a thread's MCP servers once no client is attached, so an SSH drop detaches you. Each role's supervisor also queues its result to your thread when it ends, and the queued input waits for the next attach.
|
|
232
|
+
- **Goal mode.** A goal continuation while only roles are live ends the turn with no tool call: no `peek`, no `sleep`, no `pidwait` or other wait cell, no role work of your own. When unsure, one `peek(run)` answers it: `actionable: false` and its `reason` mean nothing is yours to do until the next catherd message.
|
|
233
|
+
- **Test the push from inside the thread** with `test_push`, when the user asks whether results will reach you: a shell cannot name this thread, since Codex does not export its id to the commands it runs.
|
|
234
|
+
|
|
172
235
|
## The run folder
|
|
173
236
|
|
|
174
237
|
`run_start` creates `R` under catherd's data directory, outside the repo. It outlives the session. `R` holds:
|
|
@@ -180,7 +243,7 @@ Nothing else pushes: a phone that buzzes for progress teaches the user to ignore
|
|
|
180
243
|
- **`runs.jsonl`, `agents.jsonl`, `jev.jsonl`, `routes.jsonl`, `outcomes.jsonl`, `harness.jsonl`, `verifier.jsonl`, `questions.jsonl`:** the record.
|
|
181
244
|
- **`digests/<milestone>.md`:** each landed milestone's digest, written by `land`.
|
|
182
245
|
- **`roles/<name>/<dispatchId>/`** (each dispatch's brief, reply, events and stderr) and **`shots/`** (screenshots).
|
|
183
|
-
- **The repo's `knowledge.md`** (
|
|
246
|
+
- **The repo's `knowledge.md`** (per repo, not per run, keyed by its git origin, so every worktree and clone of it shares one): what past runs learned, beside the repo's gate environment (`catherd knowledge env`). `land`'s `learned` appends to it; `read_knowledge` reads it.
|
|
184
247
|
|
|
185
248
|
**Pause** (on the user's word, a usage limit, or `E_RUN_BUDGET`): dispatch nothing new, `cancel(run, name)` each live role the user wants stopped, and bring down only this run's stack. Leave the tree as it is, call `set_next(run, "paused: <why>; resume with <step>")`, then stop. On a usage limit with no stand-in, catherd has already written the pause.
|
|
186
249
|
|
|
@@ -188,7 +251,7 @@ Nothing else pushes: a phone that buzzes for progress teaches the user to ignore
|
|
|
188
251
|
|
|
189
252
|
**Resume** (a new session, or after your context was compacted): `status()` names the run, and `status(run)` shows it. Before dispatching anything, call `peek(run)` once: it lists the open owner questions first, the roles still running, the records not read yet (read each with `result`), and `protocol`, the step of the milestone loop the run is at, with the checklist. It also makes this session the run's owner, so catherd messages you from now on. Check HEAD and the dirty files against its `state.md`, whose last line is the same step. Continue each role on its own thread with `dispatch(…, thread, brief: "<where it stopped>")` (`dispatch` refuses a name that is still running).
|
|
190
253
|
|
|
191
|
-
**Owner questions:** a product question outside the A-lines never stops the run. `park(run, milestone, question)` parks that milestone (`land` refuses it until it is answered),
|
|
254
|
+
**Owner questions:** a product question outside the A-lines never stops the run. `park(run, milestone, question)` parks that milestone (`land` refuses it until it is answered), notify through the available host facility as above, and go on with the milestones and runs that do not depend on it. When the owner answers in this session, `answer(run, milestone, answer)`, then finish the milestone.
|
|
192
255
|
|
|
193
256
|
## The sequence
|
|
194
257
|
|
|
@@ -206,18 +269,21 @@ Nothing else pushes: a phone that buzzes for progress teaches the user to ignore
|
|
|
206
269
|
- the repo's rules (`CLAUDE.md`, `AGENTS.md`, conventions) that bind this work;
|
|
207
270
|
- risks: shared files, generated code, slow or flaky tests.
|
|
208
271
|
|
|
209
|
-
It may run to 200 lines; the 15-line cap does not apply to it. Its reply is `result(run, "researcher-dossier")`, at the record's `replyPath`. A Claude researcher writes it to `R/dossier.md` with `write_run_file` instead.
|
|
272
|
+
It may run to 200 lines; the 15-line cap does not apply to it. Its reply is `result(run, "researcher-dossier")`, at the record's `replyPath`. A native Claude researcher on Claude Code writes it to `R/dossier.md` with `write_run_file` instead.
|
|
210
273
|
|
|
211
|
-
3. **Architect,** once, with the A-lines, the run id and the dossier's path. It reads the dossier first, writes `plan.md` and every `lanes/Mx.Ly.md` itself with `write_run_file`, and replies with a short list of milestones and lanes. Each lane names its owned files and its **fast check**: its targeted tests plus the linter, and the type check when the repo has one, scoped to
|
|
274
|
+
3. **Architect,** once, with the A-lines, the run id and the dossier's path. It reads the dossier first, writes `plan.md` and every `lanes/Mx.Ly.md` itself with `write_run_file`, and replies with a short list of milestones and lanes. Each lane names its owned files and its **fast check**: its targeted tests plus the linter of every package the lane touches, and the type check when the repo has one, scoped to those packages (seconds to a minute or two). Each milestone names its **full check** (the whole suite).
|
|
212
275
|
- A full check slower than about five minutes is a problem to solve, not to live with. The architect makes speeding it up (parallel tests, a shared fixture) an early lane.
|
|
213
|
-
- Every lane file starts with these lines: `# Mx.Ly — <title>`, `Owns: <paths>` (repo-relative, a trailing `/` for a folder, never a glob), `Fast check: <command>`, `Kind: repo_code|terminal|ui|prose|research` and `Difficulty: copy|build|logic|hard`.
|
|
276
|
+
- Every lane file starts with these lines: `# Mx.Ly — <title>`, `Owns: <paths>` (repo-relative, a trailing `/` for a folder, never a glob), `Fast check: <command>`, `Kind: repo_code|terminal|ui|prose|research` and `Difficulty: copy|build|logic|hard`. Two optional lines follow: `After: Mx.Ly, …` when the lane compiles against another lane's changes (`protocol.next` and `dispatch` hold it until those finish ok), and `Allow: path[:line], …` under a fast check that greps for absence, naming the hits that are allowed exceptions. `write_run_file` refuses a lane whose header values are wrong; fix one line with `lane_set`.
|
|
214
277
|
- **Plan in hand** (a `plan:` A-line): no dossier. Brief the architect with the A-lines, the run id and the plan's paths, to translate, not design: each plan task becomes lanes (`Owns:`, `Fast check:`, `Kind:`, `Difficulty:`), each MR or phase a milestone with its full check. It copies the plan's decisions into `plan.md` and the lane files, redesigns only what the plan leaves undecided, and stays the target for `design` findings.
|
|
215
278
|
- **No dossier and no architect** for a polish or fix run (a list of known defects or tweaks to code that exists) or a single mechanical task. You write the lane files yourself with `write_run_file`, straight from the A-lines: one lane per cluster of defects that share files, with owned files found by `grep -n`, and the same header lines.
|
|
216
|
-
4. **Route and preflight.** `route(run, "lanes/Mx.
|
|
279
|
+
4. **Route and preflight.** `route(run, lanes: ["lanes/Mx.L1.md", …])` once for the milestone's lanes: Jev is asked about all of them at once, where one call per lane may wait up to 25 s each (`dispatch` routes a lane you missed, and starts it at the routed rung when yours is off its ladder). `route`, `preflight` and `dispatch` refuse a lane whose `Kind:` or `Difficulty:` the catalog does not know (`E_LANE_INVALID`). Then, once every lane file exists, `preflight(run)` once, before dispatching any lane. Each lane comes back as one of:
|
|
217
280
|
- `pass`: the check already passes on the base tree;
|
|
218
281
|
- `fails-as-expected`: it runs and fails, because the lane has not been done yet;
|
|
219
|
-
- `skipped`: it checks a file the lane creates;
|
|
220
|
-
- `cannot-start`: it could not even run (a missing command, exit 126 or 127, a 120 s timeout,
|
|
282
|
+
- `skipped`: it checks a file the lane creates, or a pnpm filter that matches no package yet;
|
|
283
|
+
- `cannot-start`: it could not even run (a missing command, exit 126 or 127, a 120 s timeout, no `Fast check:` line, or an environment error its note names: no Docker at `DOCKER_HOST`, DNS, a denied permission). Only this blocks: fix that lane's check, or the environment (`catherd knowledge env set` for the repo's gate environment), and run `preflight` again;
|
|
284
|
+
- `lock-busy`: other checks held every heavy slot for 60 s; run `preflight` again later.
|
|
285
|
+
|
|
286
|
+
Preflight runs in your login environment and only the lanes of milestones not landed yet (`milestone` names one). Its `warnings` name a lane whose fast check runs no linter while the repo has one: add the linter of every package the lane touches.
|
|
221
287
|
|
|
222
288
|
When it returns `needsConfirmation: true`, the profile wants the user to see the commands first: show them the `commands`, and on their yes call `preflight(run, confirmed: true)`.
|
|
223
289
|
|
|
@@ -225,27 +291,31 @@ Nothing else pushes: a phone that buzzes for progress teaches the user to ignore
|
|
|
225
291
|
|
|
226
292
|
5. **Lanes.** Dispatch every lane of the milestone, one after another, each at its rung, then end your turn: workers, the artist, and a researcher if needed. A worker's brief points at its lane file, and `dispatch` gets its `lane`. A worker runs its own fast check until it passes.
|
|
227
293
|
- When a worker's message arrives, read it with `result`: check its STATUS line, its `changedOwned` and its `hints`, then run its fast check yourself once. A fail goes back to the same thread with the failing output's path; a second fail climbs a rung.
|
|
294
|
+
- A hint `environment: <what>` (the reply had an `ENV:` line) is the machine, not the rung: `climb` refuses it (`E_CLIMB_ENV`). Fix the environment, or `park` the milestone when only the owner can. `STATUS: flaky` (a check failed, then passed alone) is the worker's evidence: rerun that check alone, accept it, or climb; the call is yours.
|
|
228
295
|
- A `violation: <paths>` hint means the role wrote outside its lane. Send those paths to the reviewer with the milestone; a lane that needs them gets an `Owns:` delta from the architect.
|
|
229
|
-
6. **writer,** when the milestone changes docs. It starts once the workers are done.
|
|
296
|
+
6. **writer,** when the milestone changes docs. It starts once the workers are done. Its brief names the files it may change on an `Owns:` line (default: `docs/**` and `*.md`), so its edits count as its own and never as a lane's violation.
|
|
230
297
|
7. **reviewer,** named `reviewer-<M>`, once, over the whole milestone diff on a frozen tree.
|
|
298
|
+
- A reply of `STATUS: partial` (it read only part of the diff) is not the milestone's review: `land` refuses it, and `protocol.next` names a scoped second pass, `reviewer-<M>-2`, over the files it did not read. For a native reviewer, pass `reply_status` to `record_agent_run`.
|
|
299
|
+
- While its BLOCKER or BUG lines are open, `protocol.next` names the fix round, not the verifier.
|
|
231
300
|
- **UI pass,** when the milestone touched a screen. List the changed files (`git diff --name-only <milestone base>`), map them to the screens that render them, and brief the UI reviewer on those screens only. You start the app first.
|
|
232
301
|
8. **One fix round.** Send each lane's findings, verbatim, to its own worker thread, at its rung. A BLOCKER climbs a rung instead, on a fresh thread. Dispatch every lane's fix, then end your turn. Then resume the same reviewer thread, and it re-checks only the BLOCKER and BUG lines.
|
|
233
|
-
- Before routing a finding that questions the plan, `ask(run, "finding", …)`. `design` goes to the architect
|
|
302
|
+
- Before routing a finding that questions the plan, `ask(run, "finding", …)`. `design` goes to the architect: `SendMessage` to the same native Claude agent on Claude Code, or `dispatch` with its recorded process `thread` for its own delta, followed by `result`. Its delta rewrites the lane files.
|
|
234
303
|
- A finding that comes back: `ask(run, "same-defect", …)`. `yes` gets one climb and one re-check of that line. Anything still open goes to the report, not into another round.
|
|
235
|
-
9. **verifier,** named `verifier-<M>`, with the run id, the milestone's A-lines and the **full check**, on a frozen tree
|
|
304
|
+
9. **verifier,** named `verifier-<M>`, with the run id, the milestone's A-lines and the **full check**, on a frozen tree: it owns the independent gate. Route it first. A native Claude verifier runs in the foreground on Claude Code; a Codex or other process verifier uses `dispatch`, end the turn, then `result`. A full check that starts while a role still edits proves nothing, and it has to run again. Never give it a worker's reply.
|
|
236
305
|
- It checks each gate item with `gate_check` first and skips an item that passed on the same content (carried over from its commit); it records each pass with `gate_pass`, runs independent items side by side within the lock's slots, and builds each commit's images once. It names git-ignored inputs (`.env`, generated files) in `paths` explicitly: `.` covers only HEAD and uncommitted not-ignored changes. It passes `gate_check` the milestone it verifies, so the digest lists only that milestone's carried items. `peek` and `status` show its current step.
|
|
237
|
-
-
|
|
238
|
-
-
|
|
306
|
+
- For a native Claude verifier only, call `record_agent_run(run, "verifier-<M>", "verifier", rung, …)`, with `status: "failed"` when its verdict is FAIL: only a PASS is recorded `ok`. Pass `verdict:`, its reply's first line. A process verifier on any backend counts through its dispatch record only when its reply opens `VERDICT: PASS`; collect it with `result` before landing.
|
|
307
|
+
- `VERDICT: BLOCKED: environment — <probe>` is the machine, not the work (a VPN filter, a Docker proxy, a dead registry): no fix round and no climb. `land` refuses it; surface it to the owner with `park`, and run a fresh verifier once it is fixed.
|
|
308
|
+
- On FAIL, the owning worker fixes it, then a **fresh** verifier re-checks (a resumed one can hang): a new `dispatch` or Agent with no `thread`, its brief naming the failed items (`gate_check(run, milestone)` lists them) and a 10-minute cap on each command; what passed is carried over.
|
|
239
309
|
- A second FAIL on the same line goes to the architect.
|
|
240
310
|
- A third one: pause, report and push.
|
|
241
|
-
10. **Land.** Commit the milestone path-scoped, then `land(run, milestone, what, commit, evidence, next, learned)`, passing `learned` when the milestone taught the next run something worth knowing (a slow suite, a flaky test, a pattern to copy); push if the profile's `notify` has `milestone`, with the digest's path, and move to the next milestone.
|
|
242
|
-
- `land` refuses (`E_LAND_GATE`) a milestone with no reviewer record (a `reviewer-<M>` dispatch record, or a `record_agent_run` row with role reviewer and that name, status `ok`) or no verifier verdict since its lanes started. A skip over an empty commit range is refused too: commit first. A milestone that changed only docs lands with `skip: "docs-only"`; one that changed no source file with `skip: "no-code"` and the
|
|
311
|
+
10. **Land.** Commit the milestone path-scoped, then `land(run, milestone, what, commit, evidence, next, learned)`, passing `learned` when the milestone taught the next run something worth knowing (a slow suite, a flaky test, a pattern to copy); push if the profile's `notify` has `milestone`, with `digestPath` (the digest's full path: its commits, findings and what is open), and move to the next milestone. Only a verifier named exactly `verifier-<M>` counts, and the minutes leave out parked and paused time.
|
|
312
|
+
- `land` refuses (`E_LAND_GATE`) a milestone with no reviewer record (a `reviewer-<M>` dispatch record, or a native Claude `record_agent_run` row with role reviewer and that name, status `ok`) or no verifier verdict since its lanes started. A skip over an empty commit range is refused too: commit first. A milestone that changed only docs lands with `skip: "docs-only"`; one that changed no source file with `skip: "no-code"` and the required local gate evidence in `evidence`. Follow the repo's CI policy; a skill never authorizes triggering checks.
|
|
243
313
|
- Commit only while no role is writing. A pre-commit hook may stash unstaged files, and a role's edits vanish under it.
|
|
244
314
|
- The next milestone's lanes can start in the same turn as the `land`.
|
|
245
315
|
|
|
246
316
|
**Finish:** run the project's final gate if it has one.
|
|
247
317
|
|
|
248
|
-
- The verifier is the gate owner,
|
|
318
|
+
- The verifier is the gate owner, with `gate_check` and `gate_pass`. On Claude Code a native Claude verifier runs in the **foreground** (`run_in_background: false`); a background subagent dies with the session. A process verifier on either host uses `dispatch`/`result` and its durable record; finish only after its PASS.
|
|
249
319
|
- Beside it, in the background:
|
|
250
320
|
- any independent review (a spec or milestone review by a fresh verifier);
|
|
251
321
|
- the writer's MR body;
|
|
@@ -266,7 +336,7 @@ Roles never commit. You commit, following the repo's rules.
|
|
|
266
336
|
|
|
267
337
|
## Brief for a role
|
|
268
338
|
|
|
269
|
-
The brief is the `brief` text you pass to `dispatch` (catherd writes it to the dispatch's own `brief.md` and hands it to the CLI on stdin), or the prompt of a Claude role's Agent call, with the run id. It has these parts, in order:
|
|
339
|
+
The brief is the `brief` text you pass to `dispatch` (catherd writes it to the dispatch's own `brief.md` and hands it to the CLI on stdin), or the prompt of a native Claude role's Agent call on Claude Code, with the run id. It has these parts, in order:
|
|
270
340
|
|
|
271
341
|
1. The role and the goal, in one line.
|
|
272
342
|
2. The A-lines this run must meet.
|
|
@@ -274,7 +344,7 @@ The brief is the `brief` text you pass to `dispatch` (catherd writes it to the d
|
|
|
274
344
|
4. For a worker:
|
|
275
345
|
- "Read `<R>/lanes/Mx.Ly.md`": decisions, signatures, data shapes;
|
|
276
346
|
- its fast check (targeted tests, lint and type check), to run until it passes;
|
|
277
|
-
- "Run the full suite only if this brief says so", and "Wrap any full build or full test suite in `bunx catherd-cli@1.
|
|
347
|
+
- "Run the full suite only if this brief says so", and "Wrap any full build or full test suite in `bunx catherd-cli@1.5.0 lock -- <command>`": other lanes share the machine.
|
|
278
348
|
5. For a reviewer: the A-lines and the changed files, with new files read in full. It reports every finding as `BLOCKER|BUG|NIT file:line — problem — fix`, covering:
|
|
279
349
|
- unmet A-lines and edge cases;
|
|
280
350
|
- code or abstractions nobody needs;
|
|
@@ -303,6 +373,7 @@ The brief is the `brief` text you pass to `dispatch` (catherd writes it to the d
|
|
|
303
373
|
The tool tops out around 1536×1024.
|
|
304
374
|
|
|
305
375
|
8. Not the reply shape: `dispatch` appends the role's reply contract to every brief ("Do not commit", at most 15 lines, and the last line `STATUS: complete|partial|blocked|refused — <one line why>`), and a native Claude role's agent carries it.
|
|
376
|
+
9. Not the lane file's text, the role's scratch folder or its catherd tools: with `lane`, `dispatch` inlines the lane file as it stands, and every brief names the role's `$TMPDIR` (`<run>/scratch/<name>/`) and how it reads its run's files.
|
|
306
377
|
|
|
307
378
|
## Reading results
|
|
308
379
|
|
|
@@ -318,7 +389,7 @@ The brief is the `brief` text you pass to `dispatch` (catherd writes it to the d
|
|
|
318
389
|
| ----------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
319
390
|
| A lane waits on another lane that shares none of its files | Dispatch it now, beside the others |
|
|
320
391
|
| A role dispatched, then waited on, before the next independent one is dispatched | Dispatch every independent role first, then end your turn |
|
|
321
|
-
| `sleep`, a loop, or repeated
|
|
392
|
+
| `sleep`, `await_results`, a loop, or repeated `peek` to see whether a role is done | End your turn; use the host's completion input and durable `result` recovery |
|
|
322
393
|
| A worker or fix loop runs the full suite to check one change | Its fast check. The full check is the verifier's, once per milestone |
|
|
323
394
|
| A reviewer or verifier runs after each lane | Once per milestone, over the whole milestone |
|
|
324
395
|
| A third review round | One fix round, one climb for a returning defect, one re-check. The rest goes to the report |
|
|
@@ -333,7 +404,7 @@ The brief is the `brief` text you pass to `dispatch` (catherd writes it to the d
|
|
|
333
404
|
| A dossier, or an architect designing afresh, with a plan in hand | No dossier. Brief the architect to translate the plan, and to design only what it leaves undecided |
|
|
334
405
|
| A dossier or an architect for a polish or fix run | Write the lane files from the A-lines yourself |
|
|
335
406
|
| A lane file without an `Owns:` line | Add it: `dispatch` refuses the lane without one |
|
|
336
|
-
| A Claude subagent returned and you moved on
|
|
407
|
+
| A native Claude subagent on Claude Code returned and you moved on | `record_agent_run` with its `total_tokens` and `duration_ms` first |
|
|
337
408
|
| A rung written as `model#effort` | `backend:model#effort`, exactly as `route` returned it |
|
|
338
409
|
| `dispatch` retried after an `E_*` error without doing what its `fix` says | Do the `fix`, or pause and tell the user |
|
|
339
410
|
| A `cannot-start` preflight lane dispatched anyway | Fix its fast check first, and run `preflight` again |
|
|
@@ -342,7 +413,7 @@ The brief is the `brief` text you pass to `dispatch` (catherd writes it to the d
|
|
|
342
413
|
| A reply from the lowest Track A rung trusted without your fast check | Run it. Cheap rungs hide broken tools |
|
|
343
414
|
| A reviewer, or a `logic`/`hard` lane, on a rung you chose yourself | The rung `route` returned |
|
|
344
415
|
| Exit 0 with the owned files unchanged, treated as done | A refusal: climb with reason `unchanged` |
|
|
345
|
-
|
|
|
416
|
+
| A native Claude gate verifier dispatched in the background | Foreground on Claude Code; process verifiers use `dispatch`/`result` on either host |
|
|
346
417
|
| A full check started while a worker still edits | Wait for a frozen tree. That run proves nothing |
|
|
347
418
|
| Lanes dispatched before `preflight` ran | Run `preflight(run)` first; a lane whose check cannot even start wastes a dispatch |
|
|
348
419
|
| An architect planning without reading what past runs learned | `read_knowledge(repo)` first, from the dossier brief |
|
|
@@ -350,12 +421,12 @@ The brief is the `brief` text you pass to `dispatch` (catherd writes it to the d
|
|
|
350
421
|
| You update catherd, this plugin or the profile while a run is in flight | After the run. A role mid-flight must see one version |
|
|
351
422
|
| You stop to ask the user something mid-run | Decide within the A-lines and note it for the report. A product question outside them parks its milestone (`park`), with a push; the rest goes on |
|
|
352
423
|
| A brief that spells out the reply shape and the STATUS line | `dispatch` appends the reply contract itself |
|
|
353
|
-
| A milestone landed without its reviewer and verifier | `reviewer-<M>`, then the verifier
|
|
424
|
+
| A milestone landed without its reviewer and verifier | `reviewer-<M>`, then the routed verifier's independent PASS, then `land` |
|
|
354
425
|
| A climb for a finding that questions the plan or the lane's owned files | `ask` finding, then the architect. `climb` refuses it (`E_CLIMB_DESIGN`) |
|
|
355
426
|
| A gate item run again on content that already passed it | The verifier's `gate_check` carries it over |
|
|
356
427
|
| A push for progress that is not a landed milestone, the finish or a block | No push. `status(run)` answers when the user asks |
|
|
357
428
|
| Your own decision changes behavior that already exists and no A-line asked for it (e.g. re-numbering `list` to match a new command) | Pick the option that keeps existing behavior, and fit the new code to it |
|
|
358
|
-
| `Agent(subagent_type: "Plan", model: "opus")` for the architect |
|
|
429
|
+
| `Agent(subagent_type: "Plan", model: "opus")` for the architect | Route first: native Claude's returned `agent` with no model on Claude Code, otherwise `dispatch`/`result` |
|
|
359
430
|
| `dispatch` called from a subagent | The main thread: catherd messages the session that dispatched |
|
|
360
431
|
| `codex exec` or `opencode run` called by hand | Always `dispatch`: it records the run, keeps `state.md` true and guards the lanes |
|
|
361
432
|
| You isolate a role's harness yourself, or tell a role to ignore the user's config | Never. Only the profile's `harness.<name>.isolated`, which the user sets |
|