catherd-cli 0.2.1 → 1.0.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.
Files changed (187) hide show
  1. package/README.md +114 -30
  2. package/THIRD_PARTY_NOTICES.md +37 -0
  3. package/catalog/jev.json +89 -0
  4. package/catalog/models.json +236 -0
  5. package/catalog/scores.json +366 -0
  6. package/package.json +24 -8
  7. package/plugin/.claude-plugin/plugin.json +2 -2
  8. package/plugin/.mcp.json +1 -1
  9. package/plugin/commands/catherd.md +1 -1
  10. package/plugin/skills/catherd/SKILL.md +97 -51
  11. package/plugin/skills/catherd-setup/SKILL.md +34 -24
  12. package/src/adapters/all.ts +8 -0
  13. package/src/adapters/backend.ts +144 -0
  14. package/src/adapters/claude-code/events.ts +86 -0
  15. package/src/adapters/claude-code/index.ts +223 -0
  16. package/src/adapters/claude-code/models-api.ts +65 -0
  17. package/src/adapters/claude-code/models.ts +26 -0
  18. package/src/adapters/cli.ts +74 -0
  19. package/src/adapters/codex/events.ts +65 -0
  20. package/src/adapters/codex/home.ts +47 -0
  21. package/src/adapters/codex/index.ts +227 -0
  22. package/src/adapters/discovery.ts +87 -0
  23. package/src/adapters/opencode/agents.ts +84 -0
  24. package/src/adapters/opencode/events.ts +109 -0
  25. package/src/adapters/opencode/index.ts +316 -0
  26. package/src/adapters/registry.ts +14 -0
  27. package/src/cli.ts +159 -19
  28. package/src/domain/agents.ts +60 -0
  29. package/src/domain/budget.ts +45 -0
  30. package/src/domain/catalog.ts +262 -0
  31. package/src/domain/changes.ts +52 -0
  32. package/src/domain/cost.ts +89 -0
  33. package/src/domain/errors.ts +58 -0
  34. package/src/domain/hints.ts +22 -0
  35. package/src/domain/ids.ts +97 -0
  36. package/src/domain/jev.ts +283 -0
  37. package/src/domain/lane.ts +69 -0
  38. package/src/domain/profile-rules.ts +205 -0
  39. package/src/domain/profile.ts +383 -0
  40. package/src/domain/record.ts +84 -0
  41. package/src/{profile → domain}/role-prompts.ts +42 -26
  42. package/src/domain/roles.ts +25 -0
  43. package/src/domain/route.ts +126 -0
  44. package/src/domain/runtime.ts +21 -0
  45. package/src/domain/sanitize.ts +29 -0
  46. package/src/domain/secrets.ts +57 -0
  47. package/src/domain/select.ts +136 -0
  48. package/src/domain/state.ts +33 -0
  49. package/src/domain/util.ts +18 -0
  50. package/src/entry/capture-fixtures-command.ts +57 -0
  51. package/src/entry/catalog-command.ts +106 -0
  52. package/src/entry/cli-kit.ts +36 -0
  53. package/src/entry/deps.ts +16 -0
  54. package/src/entry/doctor-command.ts +45 -0
  55. package/src/entry/glyphs.ts +33 -0
  56. package/src/entry/init-command.ts +142 -0
  57. package/src/entry/lock-command.ts +123 -0
  58. package/src/entry/mcp/dispatch-tools.ts +82 -0
  59. package/src/entry/mcp/handshake.ts +48 -0
  60. package/src/entry/mcp/lane-tools.ts +83 -0
  61. package/src/entry/mcp/result.ts +51 -0
  62. package/src/entry/mcp/run-tools.ts +144 -0
  63. package/src/entry/mcp/server.ts +72 -0
  64. package/src/entry/mcp/setup-tools.ts +81 -0
  65. package/src/entry/profile-command.ts +320 -0
  66. package/src/entry/prompt.ts +103 -0
  67. package/src/entry/runs-command.ts +217 -0
  68. package/src/entry/supervise-bin.ts +18 -0
  69. package/src/entry/supervise-command.ts +37 -0
  70. package/src/entry/tui/commands.ts +545 -0
  71. package/src/entry/tui/effects.ts +270 -0
  72. package/src/entry/tui/fixtures.ts +318 -0
  73. package/src/entry/tui/profile-edits.ts +138 -0
  74. package/src/entry/tui/profile-tree.ts +419 -0
  75. package/src/entry/tui/providers/app.tsx +162 -0
  76. package/src/entry/tui/providers/boundary.tsx +57 -0
  77. package/src/entry/tui/providers/data.tsx +133 -0
  78. package/src/entry/tui/providers/keymap.tsx +166 -0
  79. package/src/entry/tui/providers/root.tsx +45 -0
  80. package/src/entry/tui/providers/theme.tsx +17 -0
  81. package/src/entry/tui/providers/toast.tsx +77 -0
  82. package/src/entry/tui/run.tsx +98 -0
  83. package/src/entry/tui/state.ts +336 -0
  84. package/src/entry/tui/stories.ts +75 -0
  85. package/src/entry/tui/text.ts +118 -0
  86. package/src/entry/tui/theme.ts +139 -0
  87. package/src/entry/tui/views/app.tsx +208 -0
  88. package/src/entry/tui/views/dialogs.tsx +41 -0
  89. package/src/entry/tui/views/profile-actions.ts +321 -0
  90. package/src/entry/tui/views/profiles.tsx +440 -0
  91. package/src/entry/tui/views/runs.tsx +281 -0
  92. package/src/entry/tui/views/save-dialog.tsx +332 -0
  93. package/src/entry/tui/views/status.tsx +206 -0
  94. package/src/entry/tui/widgets/chrome.tsx +114 -0
  95. package/src/entry/tui/widgets/dialog-confirm.tsx +58 -0
  96. package/src/entry/tui/widgets/dialog-prompt.tsx +52 -0
  97. package/src/entry/tui/widgets/dialog-select.tsx +212 -0
  98. package/src/entry/tui/widgets/dialog.tsx +68 -0
  99. package/src/entry/tui/widgets/line.tsx +92 -0
  100. package/src/entry/tui/widgets/list.tsx +196 -0
  101. package/src/entry/tui/widgets/spinner.tsx +49 -0
  102. package/src/entry/tui/widgets/toast.tsx +62 -0
  103. package/src/infra/assets.ts +4 -0
  104. package/src/infra/dispatch-dir.ts +278 -0
  105. package/src/infra/env.ts +43 -0
  106. package/src/infra/filelock.ts +159 -0
  107. package/src/infra/git.ts +91 -0
  108. package/src/infra/heavy-lock.ts +36 -0
  109. package/src/infra/jev-client.ts +162 -0
  110. package/src/infra/launch.ts +25 -0
  111. package/src/infra/log.ts +84 -0
  112. package/src/infra/paths.ts +38 -0
  113. package/src/infra/proc.ts +110 -0
  114. package/src/infra/store.ts +229 -0
  115. package/src/infra/supervisor.ts +257 -0
  116. package/src/infra/version.ts +6 -0
  117. package/src/services/admission.ts +277 -0
  118. package/src/services/agent-links.ts +139 -0
  119. package/src/services/backends.ts +44 -0
  120. package/src/services/budget.ts +47 -0
  121. package/src/services/capture.ts +233 -0
  122. package/src/services/catalog-service.ts +382 -0
  123. package/src/services/dispatch-service.ts +479 -0
  124. package/src/services/dispatches.ts +145 -0
  125. package/src/services/doctor-backends.ts +185 -0
  126. package/src/services/doctor-checks.ts +103 -0
  127. package/src/services/doctor.ts +358 -0
  128. package/src/services/finalize.ts +359 -0
  129. package/src/services/jev-service.ts +183 -0
  130. package/src/services/lane-service.ts +248 -0
  131. package/src/services/ports.ts +132 -0
  132. package/src/services/preflight.ts +207 -0
  133. package/src/services/profile-service.ts +292 -0
  134. package/src/services/profile-store.ts +141 -0
  135. package/src/services/reconcile.ts +101 -0
  136. package/src/services/routing-service.ts +190 -0
  137. package/src/services/run-debug.ts +79 -0
  138. package/src/services/run-service.ts +161 -0
  139. package/src/services/run-store.ts +316 -0
  140. package/src/services/setup.ts +87 -0
  141. package/src/services/state.ts +104 -0
  142. package/src/services/summary.ts +227 -0
  143. package/catalog/catalog.json +0 -112
  144. package/catalog/models-dev.json +0 -56453
  145. package/src/core/codex-events.ts +0 -49
  146. package/src/core/codex.ts +0 -166
  147. package/src/core/harness.ts +0 -89
  148. package/src/core/lanes.ts +0 -67
  149. package/src/core/lock.ts +0 -111
  150. package/src/core/opencode.ts +0 -207
  151. package/src/core/reconcile.ts +0 -26
  152. package/src/core/reply.ts +0 -54
  153. package/src/core/runstore.ts +0 -163
  154. package/src/core/spawn.ts +0 -89
  155. package/src/core/status.ts +0 -193
  156. package/src/files.ts +0 -19
  157. package/src/mcp/dispatch.ts +0 -154
  158. package/src/mcp/lane-tools.ts +0 -200
  159. package/src/mcp/out.ts +0 -6
  160. package/src/mcp/preflight.ts +0 -84
  161. package/src/mcp/run-tools.ts +0 -121
  162. package/src/mcp/runs.ts +0 -118
  163. package/src/mcp/server.ts +0 -26
  164. package/src/mcp/setup-tools.ts +0 -175
  165. package/src/paths.ts +0 -20
  166. package/src/profile/agents.ts +0 -130
  167. package/src/profile/profile.ts +0 -228
  168. package/src/routing/catalog.ts +0 -227
  169. package/src/routing/commands.ts +0 -22
  170. package/src/routing/jev.ts +0 -185
  171. package/src/routing/route.ts +0 -155
  172. package/src/routing/select.ts +0 -90
  173. package/src/tui/backends.ts +0 -57
  174. package/src/tui/commands.ts +0 -53
  175. package/src/tui/dashboard.tsx +0 -245
  176. package/src/tui/editor.tsx +0 -214
  177. package/src/tui/init.tsx +0 -208
  178. package/src/tui/matrix-model.ts +0 -279
  179. package/src/tui/matrix.tsx +0 -512
  180. package/src/tui/profiles.ts +0 -38
  181. package/src/tui/theme.ts +0 -177
  182. package/src/tui/ui.tsx +0 -267
  183. package/src/tui/watch-model.ts +0 -83
  184. package/src/tui/watch.tsx +0 -236
  185. package/src/types.ts +0 -184
  186. package/src/version.ts +0 -7
  187. /package/src/{mcp/commands.ts → entry/mcp/command.ts} +0 -0
@@ -23,32 +23,47 @@ 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.0.0`. If it differs, stop and tell the user to restart Claude Code so the plugin and its server match.
27
+
26
28
  **You never edit product files.** Every line of code, tests or docs comes from a role, including a one-line fix.
27
29
 
28
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.
29
31
 
30
- **Each role runs in its vendor's own harness with the user's customizations — never isolate it.** Codex and opencode 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.
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.
31
33
 
32
34
  ## Tools
33
35
 
34
36
  The catherd MCP tools ship with this plugin. They appear as `mcp__plugin_catherd_catherd__<name>`. If they are deferred, load them all with one ToolSearch call at the start, together with `PushNotification`.
35
37
 
36
- | Tool | Use |
37
- | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
38
- | `run_start(repo, title, a_lines)` | Once per project. Returns `run`, the id every other call takes, and `dir`, the run folder `R` |
39
- | `route(run, lane_file?, role?)` | A lane's rung, decided by Jev from its lane file; or, without a lane file, a role's rung from the profile. Returns `rung`, `ladder`, `backend`, and `agent` for a Claude rung |
40
- | `preflight(run)` | Runs each lane's fast check once, on the base tree, behind the machine-wide lock. Call it once every lane file exists, before dispatching any of them |
41
- | `dispatch(run, role, name, brief, rung, thread?, lane?, next?)` | Runs one Codex or opencode role and returns its `record` and `hints` |
42
- | `climb(run, lane, reason, evidence?)` | The lane's next rung, or `top: true` |
43
- | `ask(run, question, state)` | Jev's `finding` or `same-defect` answer |
44
- | `land(run, milestone, what, commit, evidence, next, learned?)` | A landed milestone's ledger row, and `state.md`. `learned` appends to this repo's `knowledge.md` |
45
- | `read_knowledge(repo)` | What past runs of this repo learned. The dossier brief reads it |
46
- | `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 |
47
- | `result(run, name)` | A role's capped reply and its record |
48
- | `status(run?)` | One screen: the `state.md` tail, live roles with their age, totals, Jev fallbacks, the harness cost, the run's budget spend |
49
- | `set_next(run, next)` | The next step, when you pause or the plan changes |
50
- | `runs_summary(filter)` | Time, tokens, refusals and climbs per role and rung, and the harness cost, for the report |
51
- | `profile_get()` | The active profile: enabled roles, and the moments to push |
38
+ | Tool | Use |
39
+ | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
40
+ | `status(run?)` | First, and whenever the user asks where it stands: `version`, then per run the `state.md` tail, live roles, totals, Claude subagents, budget, milestones |
41
+ | `run_start(repo, title, a_lines)` | Once per project. Returns `run`, the id every other call takes, and `dir`, the run folder `R` |
42
+ | `route(run, lane_file?, role?)` | A lane's rung (from Jev, else the lane file's `Kind:`/`Difficulty:`, else the profile), or a role's rung. Returns `rung`, `ladder`, `backend`, `agent` |
43
+ | `preflight(run, confirmed?)` | Each lane's fast check once, before any lane runs. Outcomes below |
44
+ | `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`; `wait` collects its record |
45
+ | `wait(run, names?, all?)` | Blocks until a role finishes (every one with `all: true`); returns `records` (each role's `record` and `hints`), `started`, and `running`: what is still to collect |
46
+ | `cancel(run, name)` | Stops a live role and returns its record, `cancelled`, and `hints` |
47
+ | `record_agent_run(run, name, role, rung, total_tokens, duration_ms?, cost_usd?, status?, lane?)` | After every Claude subagent: what its Agent result reported. The budget counts it; `lane` counts its time toward that lane's kind |
48
+ | `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 |
49
+ | `ask(run, question, state)` | Jev's `finding` or `same-defect` answer |
50
+ | `land(run, milestone, what, commit, evidence, next, learned?)` | A landed milestone's ledger row, with the minutes it took, and `state.md`. `learned` appends to this repo's `knowledge.md` |
51
+ | `read_knowledge(repo)` | What past runs of this repo learned. The dossier brief reads it |
52
+ | `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 |
53
+ | `result(run, name)` | A role's latest reply, capped, and its record |
54
+ | `set_next(run, next)` | The next step, when you pause or the plan changes. Returns `state` and, when git fails, `hints` |
55
+ | `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 |
56
+ | `profile_get(repo?)` | The profile this repo runs on: each role's access, rungs and enforcement, failover, budget, timeouts, and the moments to push |
57
+
58
+ **A tool returns `hints` when it has any:** one line each, on what to do next. Read them before you move on.
59
+
60
+ **Every error is `{ code, message, fix }`.** Read the code, act on the fix, and never retry the same call blindly:
61
+
62
+ - `E_ADMIT_OVERLAP`: the lane shares an owned path with a running lane. Dispatch it when that one returns.
63
+ - `E_ADMIT_DUPLICATE`: that role name is already running. Wait for it, or `cancel` it.
64
+ - `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; run a `claude:` rung as its agent; skip a role that is off.
65
+ - `E_RUN_BUDGET`: the run's budget is spent (a soft cap: roles already running finish). Pause, report and push.
66
+ - `E_BACKEND_MISSING`, `E_BACKEND_NOT_LOGGED_IN`, `E_BACKEND_TOO_OLD`: tell the user the `fix`, word for word, then pause.
52
67
 
53
68
  ## Roles
54
69
 
@@ -63,27 +78,29 @@ The catherd MCP tools ship with this plugin. They appear as `mcp__plugin_catherd
63
78
  | writer | `dispatch(run, "writer", "writer-<m>", brief, rung)` | README, docs, changelog, MR body |
64
79
  | researcher | `dispatch(run, "researcher", "researcher-<n>", brief, rung)` | The dossier, or one factual question about the code |
65
80
 
66
- - **Every rung comes from `route`,** never from you. The table shows the usual backend; the profile decides. When `route` returns `backend: "claude"`, run that role with `Agent(subagent_type: <agent>)`, with the brief and the run id as its prompt; otherwise `dispatch` it. `dispatch` refuses a Claude rung and names the agent.
81
+ - **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`. The table shows the usual backend; the profile decides. When `route` returns `backend: "claude"`, run that role with `Agent(subagent_type: <agent>)`, with the brief and the run id as its prompt; otherwise `dispatch` it. `dispatch` refuses a Claude rung and names the agent.
82
+ - **After every Claude subagent returns,** 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.
67
83
  - **A role the profile disables** (`profile_get`; every role but the worker can be off) is skipped, and the report says so.
68
- - **Sandbox:** catherd sets it per role. Worker, artist and writer write in the repo only; reviewer, researcher and architect read; verifier and UI reviewer get full access for Docker, a browser, gate logs and screenshots.
84
+ - **Access:** catherd sets it per role. Worker, artist and writer write in the repo only; reviewer, researcher and architect read; verifier and UI reviewer get full access for Docker, a browser, gate logs and screenshots.
85
+ - **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.
69
86
  - **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.
70
87
  - **Claude agents:**
71
88
  - catherd generates them from the profile, and Claude Code registers them at session start.
72
89
  - Pass no `model` to those Agent calls. Never let `Plan`, `general-purpose` or `Explore` stand in.
73
90
  - `Agent type … not found` means the profile changed after this session started: tell the user to open a new session.
74
91
  - If the model needs a newer Claude Code, stop and tell the user to upgrade Claude Code. Never pass a `model` to get past it.
75
- - **The record:** `R/roles/<name>.out` is the latest reply; earlier rounds are kept as `<name>.r<k>.*`. `R/runs.jsonl` holds one record per run (role, rung, status, thread, seconds, tokens). It is the token and time record: never keep your own.
92
+ - **The record:** each dispatch has its own folder, `R/roles/<name>/<dispatchId>/` (brief, reply, events, stderr); `result(run, name)` reads the latest. `R/runs.jsonl` holds one record per dispatch (role, rung, status, thread, seconds, tokens, the owned files it changed and any it should not have). `R/agents.jsonl` holds the Claude subagent runs you reported. Together they are the token and time record: never keep your own.
76
93
 
77
94
  ## Effort: the ladder, decided by Jev
78
95
 
79
96
  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:
80
97
 
81
- | Track | Lanes | Rungs, low to high |
82
- | ----- | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
83
- | A | `copy` or `build`: an existing pattern, a clear fast check | `gpt-6-luna#high` → `gpt-6-sol#medium` → `gpt-6-sol#high` → `gpt-6-sol#xhigh` |
84
- | B | `logic` or `hard`, or `terminal` work: state, concurrency, ops, unclear cause | `gpt-6-sol#medium` → `gpt-6-sol#high` → `gpt-6-sol#xhigh` |
98
+ | Track | Lanes | Rungs, low to high |
99
+ | ----- | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
100
+ | A | `copy` or `build`: an existing pattern, a clear fast check | `codex:gpt-6-luna#high` → `codex:gpt-6-sol#medium` → `codex:gpt-6-sol#high` → `codex:gpt-6-sol#xhigh` |
101
+ | 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` |
85
102
 
86
- **Jev picks the start.** Jev (TypeSafe) is a decision model: it answers a fixed question with calibrated confidence in about three seconds, for a fraction of a cent. On low confidence, or with no Jev, the tools return the profile's default (`source: "default"`), so the run never waits on it. Every call is logged to `R/jev.jsonl`.
103
+ **Jev picks the start, when the user has it.** 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. When its probabilities do not settle the track, or with no Jev key, `route` uses the lane file's `Kind:` and `Difficulty:` lines (`source: "lane"`), 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 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.
87
104
 
88
105
  | When | Call | On the answer |
89
106
  | --------------------------------------------- | ----------------------------------------------------------------------------- | ------------------------------------------- |
@@ -91,16 +108,18 @@ A lane starts on the lowest rung that can do it, and climbs one rung when it sho
91
108
  | 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 |
92
109
  | A finding back after its fix round | `ask(run, "same-defect", { before: "<old line>", after: "<new line>" })` | `yes` → climb one rung |
93
110
 
94
- **Climb one rung** with `climb(run, lane, reason, evidence)`, 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:
111
+ **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:
95
112
 
96
113
  - your fast check fails twice on that lane (`check-failed-twice`);
97
114
  - the lane gets a BLOCKER (`blocker`), or Jev calls a returning finding the same defect (`same-defect`);
98
- - the reply says `STATUS: refused` or `blocked`, or the run exits 0 while the lane's owned files are unchanged: that is a refusal, whatever the reply says (`refused`, `blocked`, `unchanged`). `dispatch` flags each of these in its `hints` as `climb: <reason>`.
115
+ - the reply says `STATUS: refused` or `blocked`, or the run exits 0 while the lane's owned files are unchanged: that is a refusal, whatever the reply says (`refused`, `blocked`, `unchanged`). `wait` flags each of these in the record's `hints` as `climb: <reason>`.
116
+
117
+ A usage limit is not a climb. When the profile names a stand-in for that rung, `wait` has already started the role on it, on a fresh thread: the limited record's first hint says `limit: … failed over to <rung>`, the stand-in is in `started` and its name stays in `running`, and a later `wait` returns its record. With no stand-in, the hint is `limit: …` and the run is paused.
99
118
 
100
119
  A failure on the top rung (`top: true`) goes to the architect when Jev calls it design, else to the report as open.
101
120
 
102
121
  - Jev decides which model does the work. It never decides that the work is done: only a check, the reviewer or the verifier does.
103
- - `R/routes.jsonl` records each lane's rung and every climb with its reason.
122
+ - `R/routes.jsonl` records each lane's rung and every climb with its reason; `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.
104
123
  - Never put a secret or a key into an `ask` state. Keep the state short and in English.
105
124
 
106
125
  ## Threads
@@ -115,7 +134,7 @@ A resumed thread replays its whole history on every tool call. In the first real
115
134
 
116
135
  Your context is re-read on every turn, and it is the run's most expensive token. In the first real run you were 68% of the Claude spend: 269 turns at about 220k tokens each.
117
136
 
118
- - **One message per transition.** Independent tool calls (a `land`, the next lanes' dispatches, a `route`) go in the same message.
137
+ - **One message per transition.** Independent tool calls (a `land`, the next lanes' dispatches, a `route`) go in the same message. They run one after another, and each `dispatch` returns in about a second; the `wait` comes after them.
119
138
  - **Read little, and read it narrowly.**
120
139
  - From the architect, read its short reply. The plan lives in `plan.md` and the lane files; read a section with `read_run_file` only when a decision needs it.
121
140
  - From a role, read its record and its capped reply.
@@ -125,7 +144,14 @@ Your context is re-read on every turn, and it is the run's most expensive token.
125
144
 
126
145
  ## Waiting
127
146
 
128
- Call `dispatch` from your main thread, never from a subagent: a subagent's MCP call never backgrounds, so it would hold that subagent for the whole run. Launch every independent role in the same message. Each dispatch backgrounds by itself after two minutes, and its result arrives as a notification that wakes you. Then end your turn with one status line. Never `sleep` and never poll.
147
+ Call `dispatch` and `wait` from your main thread only, never from a subagent: a subagent's MCP call never backgrounds, so a `wait` there would hold that subagent for the whole run.
148
+
149
+ - **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.
150
+ - **Then call `wait(run)`.** It returns once one of them finishes, and its result wakes you. After two minutes Claude Code backgrounds it: end your turn with one status line, and its notification wakes you.
151
+ - **Act on each record it returns,** dispatch what follows, and call `wait(run)` again while its `running` is not empty. `running` names every role whose record no `wait` has returned yet, still running or already finished; each record comes back once.
152
+ - **A single role is `dispatch`, then `wait`.**
153
+
154
+ Never `sleep` and never poll.
129
155
 
130
156
  **When the user asks where it stands,** call `status(run)` once and answer from it.
131
157
 
@@ -145,43 +171,56 @@ Nothing else pushes: a phone that buzzes for progress teaches the user to ignore
145
171
  - **`lanes/Mx.Ly.md`:** one file per lane, the only plan a worker reads. Its second line, `Owns: <paths>`, is what `dispatch` uses to refuse a lane that shares a file with a running one, and to spot a refusal.
146
172
  - **`ledger.md`:** one row per landed piece, appended by `land`. Never rewritten.
147
173
  - **`state.md`:** rewritten by the server at every dispatch, climb and landing, so a fresh session resumes from it alone: HEAD, the dirty files and their owners, each running role with its brief, thread and rung, the last check, and the next step on the last line.
148
- - **`runs.jsonl`, `jev.jsonl`, `routes.jsonl`, `harness.jsonl`:** the record.
149
- - **`roles/`** (briefs and replies) and **`shots/`** (screenshots).
150
- - **`<data>/<repo-slug>/knowledge.md`** (one level up, per repo, not per run): what past runs learned. `land`'s `learned` appends to it; `read_knowledge` reads it.
174
+ - **`runs.jsonl`, `agents.jsonl`, `jev.jsonl`, `routes.jsonl`, `outcomes.jsonl`, `harness.jsonl`:** the record.
175
+ - **`roles/<name>/<dispatchId>/`** (each dispatch's brief, reply, events and stderr) and **`shots/`** (screenshots).
176
+ - **The repo's `knowledge.md`** (beside the runs, per repo, not per run): what past runs learned. `land`'s `learned` appends to it; `read_knowledge` reads it.
177
+
178
+ **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, `wait` has already written the pause.
151
179
 
152
- **Pause** (on the user's word, or a usage limit): dispatch nothing new, 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, `dispatch` has already written the pause.
180
+ **Cancel** a role with `cancel(run, name)` when the user asks, or when a role is plainly stuck on work you no longer need. It returns the role's record, `cancelled`, and no later `wait` returns it again; a `wait` already in flight may return it too, and `cancel` then says so in its `hints`.
153
181
 
154
- **Resume:** `status()` names the run, and `status(run)` shows it. Check HEAD and the dirty files against its `state.md`. Continue each role on its own thread with `dispatch(…, thread, brief: "<where it stopped>")`.
182
+ **Resume:** `status()` names the run, and `status(run)` shows it. Check HEAD and the dirty files against its `state.md`. Before dispatching anything, call `wait(run)`, again while its `running` is not empty, to collect the roles the last session left running or unread (`dispatch` refuses a name that is still running). Continue each role on its own thread with `dispatch(…, thread, brief: "<where it stopped>")`.
155
183
 
156
184
  ## The sequence
157
185
 
158
186
  **Once per project:**
159
187
 
160
188
  1. **A-lines.** Write the request as numbered, observable lines `A1…An`. Ask the user every open product question now; after this point, run without them. Then `run_start(repo, title, a_lines)`.
189
+ - **Plan in hand:** when the request names a plan the user already has, keep it as an A-line, `plan: <path>[, <path>…]`. The run then skips the dossier, and the architect translates that plan instead of designing one (step 3).
161
190
  2. **Dossier.** One researcher, `researcher-dossier`, maps the code the A-lines touch. Its brief asks for, with `file:line` everywhere:
162
191
  - what past runs of this repo learned (`read_knowledge(repo)`), so it maps only what changed since then;
163
192
  - the files and folders involved, one line each on what they hold;
164
193
  - the existing patterns the work should copy, by path;
165
194
  - the build, test and run commands, with how long the full suite takes;
195
+ - the lint and type-check commands, and how to scope each to one package;
166
196
  - the symbols the change will call or alter, with their signatures;
167
197
  - the repo's rules (`CLAUDE.md`, `AGENTS.md`, conventions) that bind this work;
168
198
  - risks: shared files, generated code, slow or flaky tests.
169
199
 
170
- It may run to 200 lines; the 15-line cap does not apply to it. Its reply is `R/roles/researcher-dossier.out`. A Claude researcher writes it to `R/dossier.md` with `write_run_file` instead.
200
+ 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.
171
201
 
172
- 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** (targeted tests, seconds to a minute or two); each milestone names its **full check** (the whole suite).
202
+ 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 the lane's owned packages (seconds to a minute or two). Each milestone names its **full check** (the whole suite).
173
203
  - 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.
174
- - **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 three header lines the architect writes (`# Mx.Ly — …`, `Owns: …`, `Fast check: …`).
175
- 4. **Route and preflight.** `route(run, "lanes/Mx.Ly.md")` for every lane, all in one message. Then, once every lane file exists, `preflight(run)` once, before dispatching any lane: a lane whose fast check cannot even start blocks the run with its output until fixed.
204
+ - 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`.
205
+ - **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.
206
+ - **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.
207
+ 4. **Route and preflight.** `route(run, "lanes/Mx.Ly.md")` for every lane, one call per lane; each may wait up to 25 s on Jev. Then, once every lane file exists, `preflight(run)` once, before dispatching any lane. Each lane comes back as one of:
208
+ - `pass`: the check already passes on the base tree;
209
+ - `fails-as-expected`: it runs and fails, because the lane has not been done yet;
210
+ - `skipped`: it checks a file the lane creates;
211
+ - `cannot-start`: it could not even run (a missing command, exit 126 or 127, a 120 s timeout, or no `Fast check:` line). Only this blocks: fix that lane's check, or ask the architect to, and run `preflight` again.
212
+
213
+ 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)`.
176
214
 
177
215
  **Per milestone:**
178
216
 
179
- 5. **Lanes.** Launch every lane of the milestone in one message, each at its rung: 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.
217
+ 5. **Lanes.** Dispatch every lane of the milestone, one after another, each at its rung, then `wait(run)`: 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.
180
218
  - When a worker returns, 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.
219
+ - 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.
181
220
  6. **writer,** when the milestone changes docs. It starts once the workers are done.
182
221
  7. **reviewer,** once, over the whole milestone diff on a frozen tree.
183
222
  - **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.
184
- 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. All lanes go at once. Then resume the same reviewer thread, and it re-checks only the BLOCKER and BUG lines.
223
+ 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 `wait`. Then resume the same reviewer thread, and it re-checks only the BLOCKER and BUG lines.
185
224
  - Before routing a finding that questions the plan, `ask(run, "finding", …)`. `design` goes to the architect (`SendMessage` to the same agent), and its delta rewrites the lane files.
186
225
  - 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.
187
226
  9. **verifier,** with the milestone's A-lines and the **full check**, on a frozen tree. 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.
@@ -206,8 +245,8 @@ Then report to the user, in their language, and push if `notify` has `finish`:
206
245
  - the verdict per milestone;
207
246
  - the commits;
208
247
  - one line per role run, with its rung;
209
- - the time and token sums per backend and per rung, and the climbs with their reasons (`runs_summary({ run })`), the Claude subagent runs, and the Jev decisions with how many fell back (`status(run)`);
210
- - the harness line from `runs_summary`: what the user's Codex and opencode customizations cost per run, so they can judge the isolation toggle (`/catherd-setup` offers it);
248
+ - the time and token sums per backend and per rung, and the climbs with their reasons (`runs_summary({ run })`), the Claude subagent runs you reported (say they are reported, not measured), and the Jev decisions with how many fell back (`status(run)`);
249
+ - the harness line from `runs_summary`: what the user's claude-code and opencode customizations cost per run, so they can judge the isolation toggle (`/catherd-setup` offers it). Codex reports no per-request input, so it has no harness figure: say so instead of offering one;
211
250
  - the run's budget spend (`status(run)`), if the profile set one;
212
251
  - what was left open, and why.
213
252
 
@@ -215,15 +254,15 @@ Roles never commit. You commit, following the repo's rules.
215
254
 
216
255
  ## Brief for a role
217
256
 
218
- The brief is the `brief` text you pass to `dispatch` (catherd writes it to `R/roles/<name>.md`, or `<name>.fix.md` for a fix), or the prompt of a Claude role's Agent call, with the run id. It has these parts, in order:
257
+ 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:
219
258
 
220
259
  1. The role and the goal, in one line.
221
260
  2. The A-lines this run must meet.
222
261
  3. The files it owns, and the files it must not touch. If the repo has a `CLAUDE.md`, add "Read CLAUDE.md first": Codex loads only `AGENTS.md`.
223
262
  4. For a worker:
224
263
  - "Read `<R>/lanes/Mx.Ly.md`": decisions, signatures, data shapes;
225
- - its fast check, to run until it passes;
226
- - "Run the full suite only if this brief says so", and "Wrap any full build or full test suite in `bunx catherd-cli@0.2.1 lock -- <command>`": other lanes share the machine.
264
+ - its fast check (targeted tests, lint and type check), to run until it passes;
265
+ - "Run the full suite only if this brief says so", and "Wrap any full build or full test suite in `bunx catherd-cli@1.0.0 lock -- <command>`": other lanes share the machine.
227
266
  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:
228
267
  - unmet A-lines and edge cases;
229
268
  - code or abstractions nobody needs;
@@ -255,16 +294,18 @@ The brief is the `brief` text you pass to `dispatch` (catherd writes it to `R/ro
255
294
 
256
295
  ## Reading results
257
296
 
258
- - Read the record `dispatch` returns, its `hints`, and the reply (`result(run, name)`), nothing else. Read `roles/<name>.jsonl` or `.err` with `read_run_file` only when `status` is `failed`. Never read a diff or a log yourself: that is the reviewer's and verifier's job, and your context is the run's most expensive token.
297
+ - Read each record `wait` returns, its `hints`, and the reply (`result(run, name)`), nothing else. Read the stderr the `failed: read <path>` hint names, with `read_run_file`, only when `status` is `failed`. Never read a diff or a log yourself: that is the reviewer's and verifier's job, and your context is the run's most expensive token.
259
298
  - Exit 0 means the model finished, not that it is right. The STATUS line is the role's claim; `changedOwned` and your fast check are the facts.
260
299
  - `failed` comes only from a real turn failure or an exit with no reply; a reconnect mid-run does not count. Read the reply before you retry.
261
- - `cli-too-old`: tell the user the upgrade command in the record's `error`. `limit`: a usage limit is the user's to fix: pause, report and push that turn.
300
+ - `cli-too-old`: tell the user the upgrade command its `cli-too-old:` hint names. `limit` with no stand-in: a usage limit is the user's to fix: pause, report and push that turn. `timeout`: the role went quiet for the profile's idle minutes, or ran past its wall minutes; resume its thread once with where it stopped, then climb.
301
+ - `thread-heavy`: that thread is spent; its next piece starts fresh.
262
302
 
263
303
  ## Red flags
264
304
 
265
305
  | You notice | Do instead |
266
306
  | ----------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
267
- | A lane waits on another lane that shares none of its files | Launch it now, in the same message |
307
+ | A lane waits on another lane that shares none of its files | Dispatch it now, beside the others, then `wait` |
308
+ | A role dispatched, then waited on, before the next independent one is dispatched | Dispatch every independent role first, then one `wait(run)` |
268
309
  | 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 |
269
310
  | A reviewer or verifier runs after each lane | Once per milestone, over the whole milestone |
270
311
  | A third review round | One fix round, one climb for a returning defect, one re-check. The rest goes to the report |
@@ -275,9 +316,14 @@ The brief is the `brief` text you pass to `dispatch` (catherd writes it to `R/ro
275
316
  | A new bug, cleanup, re-run or climb sent with `thread` | A fresh thread. Resume only for that piece's own findings at the same rung |
276
317
  | One transition spread over several turns (a land, routes, dispatches) | One message, with every independent call in it |
277
318
  | A plan, log or test file read whole into your context | `read_run_file` for the one section, or `grep -n` then those lines. Or name the path in a brief |
278
- | You copy the architect's plan into `plan.md` yourself | The architect writes `plan.md` and the lane files |
319
+ | You copy the architect's plan into `plan.md` yourself | The architect writes `plan.md` and the lane files. With a plan in hand, it copies the user's plan into them |
320
+ | 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 |
279
321
  | A dossier or an architect for a polish or fix run | Write the lane files from the A-lines yourself |
280
322
  | A lane file without an `Owns:` line | Add it: `dispatch` refuses the lane without one |
323
+ | A Claude subagent returned and you moved on | `record_agent_run` with its `total_tokens` and `duration_ms` first |
324
+ | A rung written as `model#effort` | `backend:model#effort`, exactly as `route` returned it |
325
+ | `dispatch` retried after an `E_*` error without doing what its `fix` says | Do the `fix`, or pause and tell the user |
326
+ | A `cannot-start` preflight lane dispatched anyway | Fix its fast check first, and run `preflight` again |
281
327
  | You pick a rung by feel | `route`. Its default covers the case where Jev is unsure or down |
282
328
  | Jev's answer taken as proof a lane is done | Jev picks who works. Checks, the reviewer and the verifier decide done |
283
329
  | A reply from the lowest Track A rung trusted without your fast check | Run it. Cheap rungs hide broken tools |
@@ -293,7 +339,7 @@ The brief is the `brief` text you pass to `dispatch` (catherd writes it to `R/ro
293
339
  | A push for progress that is not a landed milestone, the finish or a block | No push. `status(run)` answers when the user asks |
294
340
  | 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 |
295
341
  | `Agent(subagent_type: "Plan", model: "opus")` for the architect | The `agent` that `route(run, role: "architect")` returned, with no model |
296
- | `dispatch` called from a subagent | The main thread. A subagent's MCP call never backgrounds |
342
+ | `dispatch` or `wait` called from a subagent | The main thread. A subagent's MCP call never backgrounds |
297
343
  | `codex exec` or `opencode run` called by hand | Always `dispatch`: it records the run, keeps `state.md` true and guards the lanes |
298
344
  | 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 |
299
345
  | The architect's plan contains function bodies | Ask for decisions and signatures. The worker writes the code |
@@ -1,13 +1,13 @@
1
1
  ---
2
2
  name: catherd-setup
3
- description: Use when the user wants to tune catherd — which models and efforts each role may use, cost versus speed, which subscriptions to lean on, harness isolation — /catherd-setup, "set up catherd", "tune my catherd profile". Not for running a task; that is the catherd skill.
3
+ description: Use when the user wants to tune catherd — which models and efforts each role may use, how much each role may touch, cost versus speed, which subscriptions to lean on, failover, budget, harness isolation — /catherd-setup, "set up catherd", "tune my catherd profile". Not for running a task; that is the catherd skill.
4
4
  ---
5
5
 
6
6
  # catherd setup
7
7
 
8
- You tune the user's catherd profile in conversation. A profile says, per role, which models and efforts it may run on; whether routing favours cost or speed; whether each vendor harness runs with the user's customizations or isolated; how many heavy commands run at once; and when to push.
8
+ You tune the user's catherd profile in conversation. A profile says, per role, which rungs it may run on (`backend:model#effort`, in ladder order) and what it may touch (its access mode); whether routing favours cost or speed; how each backend is billed; whether Jev picks the rung; which rung stands in when a backend hits its usage limit; the run budget and timeouts; whether each vendor harness runs with the user's customizations or isolated; how many heavy commands run at once; and when to push.
9
9
 
10
- **You never edit a file by hand.** `profile_set` is the only writer, so this conversation and the TUI (`bunx catherd-cli`) cannot drift apart.
10
+ **You never edit a file by hand.** `profile_set` is the only writer, so this conversation, the `catherd profile` commands and the TUI cannot drift apart.
11
11
 
12
12
  ## Rules
13
13
 
@@ -21,45 +21,55 @@ You tune the user's catherd profile in conversation. A profile says, per role, w
21
21
  Ask these, one at a time, each with its recommended answer:
22
22
 
23
23
  1. **Their order of speed, cost and quality.** Recommend cost first, the default `objective`: catherd climbs a rung when a cheap one cannot do the work, so the lanes that need speed get it anyway, and the reviewer and the verifier hold quality either way.
24
- 2. **The subscriptions they hold:** a ChatGPT plan (Codex), a Claude plan, OpenRouter or other opencode credit. Recommend leaning on subscriptions before metered spend, and on Claude last among the workers, since it spends the same quota as this conversation.
24
+ 2. **The subscriptions they hold:** a ChatGPT plan (Codex), a Claude plan, OpenCode Go, Zen credit or API keys. They set `billing` per key (`codex`, `claude`, `claude-code`, `opencode-go`, `opencode`): `chatgpt-plan`, `claude-plan`, `subscription` or `metered`. Recommend leaning on subscriptions before metered spend, and on Claude last among the workers, since it spends the same quota as this conversation.
25
25
  3. **The kind of work they orchestrate:** front-end screens, back-end services, terminal and ops work, docs. Recommend from their own runs when `runs_summary` has any.
26
26
 
27
27
  ## 2. Read the facts before proposing
28
28
 
29
29
  In one message, call:
30
30
 
31
- - `catalog_query({ role: "<role>" })` for each role you will discuss: what their installed backends can run, with capabilities and scores. A model whose `installed` is false is not offered;
32
- - `runs_summary({})`: how each rung has done on their own runs (runs, refusals, climbs, time) and the harness cost line;
33
- - `profile_get()`: where they stand now.
31
+ - `catalog_query({ role: "<role>" })` for each role you will discuss: the models that can fill it, their scored rungs, any "treat like", each rung's cost under their billing, and whether their backend's last listing offers it (`listed: false` means their account does not);
32
+ - `runs_summary({})`: how each rung has done on their own runs (runs, refusals, climbs, time) and the harness cost line, for claude-code and opencode only: Codex reports no per-request input, so it has no harness figure;
33
+ - `profile_get({ repo })`: where they stand now, with each role's `access` and `enforcement`. Pass the user's repo: without a name, the profile tools act on the profile this repo runs on (the one bound to it, else the active one), which `here` names; `active` is the global active profile.
34
34
 
35
35
  ## 3. Propose one decision at a time
36
36
 
37
37
  Go through these in order, and skip any the user does not care about:
38
38
 
39
- 1. `objective`;
40
- 2. the worker's ladder: its enabled models and efforts, and its `defaultRung`;
41
- 3. the reviewer and the UI reviewer;
42
- 4. the architect and the verifier, which spend Claude quota;
43
- 5. the writer, the researcher and the artist;
44
- 6. harness isolation, per harness (below);
45
- 7. `lock.heavy` and `notify`.
39
+ 1. `objective`, and `jev.use` (`auto` asks Jev when a key exists; `off` routes on each lane's `Kind:` and `Difficulty:` lines);
40
+ 2. `billing`, from step 1;
41
+ 3. the worker's `rungs` and its `defaultRung`;
42
+ 4. the reviewer and the UI reviewer;
43
+ 5. the architect and the verifier, which spend Claude quota. A `claude:` rung runs as a native subagent in this session; a `claude-code:` rung runs headless through `dispatch`. Recommend native for these two;
44
+ 6. the writer, the researcher and the artist;
45
+ 7. each role's `access` (below);
46
+ 8. `failover` (below);
47
+ 9. `budget`, `timeouts` and `preflight.confirm`;
48
+ 10. harness isolation, per harness (below);
49
+ 11. `lock.heavy` and `notify`.
46
50
 
47
51
  Each proposal has three parts: the change, a worked example from their facts, and the tradeoff in their terms. For instance: "Luna high on build lanes: about 5 min slower than Sol medium, no Claude quota, climbs on 1 in 5 of your runs so far."
48
52
 
49
- - Offer only the models `catalog_query` lists as capable for the role.
50
- - An unscored model can be enabled only once it has a "treat like <scored model#effort>". That lives in the catalog override, which the TUI edits: send the user to `bunx catherd-cli` for it, then come back.
53
+ - Offer only the rungs `catalog_query` lists as capable for the role, written `backend:model#effort`.
54
+ - An unscored model can be enabled only once it has a "treat like <scored rung>". There is no tool for that: give the user the command to run in a terminal, `catherd catalog treat-like <rung> <scored rung>`, then come back and propose again.
51
55
 
52
- **Harness isolation.** For each harness they use (`codex`, `opencode`), offer `harness.<name>.isolated` with its harness line from `runs_summary` and this tradeoff: "native keeps your hooks, skills and AGENTS.md; isolated saves ~N tokens per run, but the role loses them." Recommend native. When they have no isolated runs yet, the number is the median first-turn input of their native runs: say that isolation would save some part of it, not all of it. When there are no runs at all, say there is no number yet, and recommend native until there is.
56
+ **Access.** Each role runs `read-only`, `workspace-write` or `full`. The defaults: architect, reviewer and researcher `read-only`; worker, writer and artist `workspace-write`; verifier and UI reviewer `full`. `profile_get` says per role whether its backend holds it to that mode (`enforced`: Codex's sandbox) or only asks (`advisory`: claude-code, opencode and native subagents, where the model can still reach past it). Recommend the defaults; when the user wants a role tighter or looser, say what it can no longer do (a read-only reviewer on claude-code or opencode has no shell, so it cannot run `git diff`) and that `profile_validate` will warn about it.
57
+
58
+ **Failover.** `failover` maps a rung to its stand-in when that rung's backend hits a usage limit. A stand-in must be scored and on another quota (Go and Zen bill apart; native `claude` and `claude-code` share the Claude plan). The default profile fails each Codex rung over to OpenCode Go, marked inferred: Go's GPT-6 Luna for Luna, and Kimi K3, treated like Sol medium, for Sol. Say so, and offer to change it when they have no Go subscription. `null` removes an entry.
59
+
60
+ **Budget and timeouts.** `budget` (`minutes`, `tokens`, `usd`) is a soft cap: from 80 % routing starts at the cheapest rung that clears the bar, and at 100 % no new role starts. `timeouts.idleMin` (15) stops a role that has gone quiet, `timeouts.wallMin` (90) one that runs too long. `preflight.confirm: true` makes `preflight` show its commands for the user to approve first.
61
+
62
+ **Harness isolation.** For each harness they use (`codex`, `claude-code`, `opencode`), offer `harness.<name>.isolated` with its harness line from `runs_summary` (Codex has none: it reports no per-request input, so say there is no figure for it instead of offering one) and this tradeoff: "native keeps your hooks, skills and AGENTS.md; isolated saves ~N tokens per run, but the role loses them." Recommend native. When they have no isolated runs yet, the number is the median first-turn input of their native runs: say that isolation would save some part of it, not all of it. When there are no runs at all, say there is no number yet, and recommend native until there is.
53
63
 
54
64
  ## 4. Write it
55
65
 
56
- - Call `profile_set({ patch })` with only what changed; pass `name` only to edit a profile other than the active one. It validates before it writes:
57
- - `saved: false` comes with `errors`. Explain each in their terms, fix the patch, and propose again.
58
- - `saved: true` comes with the `diff`. Read it back to the user, one line per change.
59
- - Then call `profile_validate()`. It checks that every enabled role has a capable model, that the worker's ladder is non-empty for every kind, and that no unscored model is enabled without its "treat like".
66
+ - Call `profile_set({ repo, patch })` with only what changed; pass `name` only to edit a profile other than the one this repo runs on (a name that does not exist yet starts a new profile from the default one). Lists (`rungs`, `notify`) replace, maps (`billing`, `failover`, `budget`) merge, and `null` removes a key. A key it does not know is refused with `E_INPUT_INVALID`. It validates before it writes:
67
+ - `saved: false` comes with `errors`, each with a `path`, a `message` and often a `fix`. Explain each in their terms, fix the patch, and propose again.
68
+ - `saved: true` comes with the `diff` (`path`, `before`, `after`) and any `warnings`. Read the diff back to the user, one line per change, and each warning with it.
69
+ - Then call `profile_validate({ repo })`. `errors` block a save: the worker disabled, an enabled role with no usable rung, an unscored rung without a "treat like", a failover stand-in unscored or on the same quota, a backend catherd cannot run. `warnings` do not: an access mode other than the role's default, a model the backend's listing lacks, a stand-in that never runs.
60
70
 
61
71
  ## 5. Say what applies when
62
72
 
63
- - Codex and opencode changes, isolation included, apply at the next dispatch, even in a run already under way.
64
- - A Claude agent listed in `new_session_needed_for` applies from the next Claude Code session: Claude Code registers agent files when a session starts. Other Claude changes apply now.
65
- - Editing a profile that is not the active one writes its agent files but links none; they apply once it becomes active.
73
+ - Codex, claude-code and opencode changes, access and isolation included, apply at the next dispatch, even in a run already under way.
74
+ - An agent listed in `newSessionNeededFor` applies from the next Claude Code session: Claude Code reads agent files when a session starts. Other Claude changes apply now.
75
+ - Editing a profile that is not the active one writes its agent files but links none; they apply once it becomes active (`catherd profile use <name>`), or in a repo it is bound to (`catherd profile use <name> --repo`).
@@ -0,0 +1,8 @@
1
+ import { claudeCodeAdapter } from "./claude-code/index.ts";
2
+ import { codexAdapter } from "./codex/index.ts";
3
+ import { opencodeAdapter } from "./opencode/index.ts";
4
+ import { registerAdapter } from "./registry.ts";
5
+
6
+ registerAdapter(codexAdapter);
7
+ registerAdapter(claudeCodeAdapter);
8
+ registerAdapter(opencodeAdapter);