catherd-cli 1.4.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 +93 -30
- 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 +1 -1
- package/plugin/bin/catherd-mcp +1 -1
- package/plugin/skills/catherd/SKILL.md +110 -45
- package/plugin/skills/catherd-setup/SKILL.md +2 -2
- 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/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 +5 -0
- 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/lane.ts +99 -3
- package/src/domain/notice.ts +9 -2
- package/src/domain/profile-rules.ts +112 -16
- package/src/domain/profile.ts +24 -0
- package/src/domain/record.ts +16 -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 +14 -6
- package/src/entry/cli-kit.ts +17 -1
- package/src/entry/deps.ts +2 -0
- package/src/entry/doctor-command.ts +20 -2
- package/src/entry/host-arg.ts +28 -0
- package/src/entry/init-command.ts +2 -1
- package/src/entry/knowledge-command.ts +78 -3
- package/src/entry/lock-command.ts +104 -8
- package/src/entry/mcp/dispatch-tools.ts +3 -3
- package/src/entry/mcp/handshake.ts +6 -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 +20 -4
- package/src/entry/mcp/server.ts +98 -7
- package/src/entry/mcp/setup-tools.ts +4 -3
- package/src/entry/mcp/workspace-tools.ts +142 -0
- package/src/entry/pause-command.ts +67 -0
- package/src/entry/profile-command.ts +23 -4
- package/src/entry/role-cli-command.ts +115 -0
- package/src/entry/runs-command.ts +78 -8
- package/src/entry/supervise-command.ts +30 -3
- package/src/entry/tui/effects.ts +4 -0
- package/src/entry/tui/state.ts +37 -10
- package/src/entry/tui/views/profiles.tsx +1 -1
- package/src/entry/tui/views/runs.tsx +66 -20
- package/src/entry/tui/views/save-dialog.tsx +6 -2
- 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/delivery.ts +3 -0
- package/src/infra/dispatch-dir.ts +2 -0
- package/src/infra/env.ts +11 -1
- package/src/infra/git.ts +6 -0
- package/src/infra/heavy-lock.ts +93 -4
- 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/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 +107 -16
- package/src/services/admission.ts +110 -23
- 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 +204 -16
- package/src/services/dispatches.ts +30 -2
- package/src/services/doctor-access.ts +8 -3
- package/src/services/doctor-docker.ts +221 -0
- package/src/services/doctor-role-mcp.ts +65 -0
- package/src/services/doctor.ts +68 -6
- package/src/services/end-push.ts +157 -0
- package/src/services/finalize.ts +12 -2
- 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 +257 -52
- package/src/services/milestones.ts +137 -26
- package/src/services/notifier.ts +56 -3
- package/src/services/orchestrator-wait.ts +82 -0
- package/src/services/pause.ts +161 -0
- package/src/services/peek.ts +79 -17
- package/src/services/ports.ts +31 -2
- package/src/services/preflight.ts +153 -19
- package/src/services/profile-service.ts +1 -0
- package/src/services/profile-store.ts +10 -3
- 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 +144 -36
- package/src/services/run-pin.ts +125 -0
- package/src/services/run-service.ts +151 -19
- package/src/services/run-store.ts +109 -15
- package/src/services/runs-page.ts +42 -7
- package/src/services/scratch.ts +60 -0
- package/src/services/sessions.ts +3 -1
- 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 +49 -9
- package/src/services/treat-likes.ts +19 -3
- 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,7 +23,7 @@ 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
|
|
|
@@ -35,35 +35,43 @@ The catherd server keeps `state.md` true: it rewrites it on every dispatch, clim
|
|
|
35
35
|
|
|
36
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
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
|
+
|
|
38
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.
|
|
39
41
|
|
|
40
|
-
| Tool | Use
|
|
41
|
-
| ----------------------------------------------------------------------------------------------------- |
|
|
42
|
-
| `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
|
|
43
|
-
| `run_start(repo, title, a_lines)`
|
|
44
|
-
| `
|
|
45
|
-
| `
|
|
46
|
-
| `
|
|
47
|
-
| `
|
|
48
|
-
| `
|
|
49
|
-
| `
|
|
50
|
-
| `
|
|
51
|
-
| `
|
|
52
|
-
| `
|
|
53
|
-
| `
|
|
54
|
-
| `
|
|
55
|
-
| `
|
|
56
|
-
| `
|
|
57
|
-
| `
|
|
58
|
-
| `
|
|
59
|
-
| `
|
|
60
|
-
| `
|
|
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 |
|
|
61
66
|
|
|
62
67
|
**A tool returns `hints` when it has any:** one line each, on what to do next. Read them before you move on.
|
|
63
68
|
|
|
64
69
|
**Every error is `{ code, message, fix }`.** Read the code, act on the fix, and never retry the same call blindly:
|
|
65
70
|
|
|
66
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.
|
|
67
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.
|
|
68
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.
|
|
69
77
|
- `E_RUN_BUDGET`: the run's budget is spent (a soft cap: roles already running finish). Pause, report and push.
|
|
@@ -71,6 +79,45 @@ Pass the actual project `repo` explicitly to profile, setup and catalog tools th
|
|
|
71
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.
|
|
72
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.
|
|
73
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.
|
|
74
121
|
|
|
75
122
|
## Roles
|
|
76
123
|
|
|
@@ -104,18 +151,20 @@ Pass the actual project `repo` explicitly to profile, setup and catalog tools th
|
|
|
104
151
|
|
|
105
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:
|
|
106
153
|
|
|
107
|
-
| Track | Lanes | Rungs, low to high
|
|
108
|
-
| ----- | ----------------------------------------------------------------------------- |
|
|
109
|
-
| A | `copy` or `build`: an existing pattern, a clear fast check | `codex:gpt-6-luna#high` → `codex:gpt-6-sol#
|
|
110
|
-
| 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` |
|
|
158
|
+
|
|
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.
|
|
111
160
|
|
|
112
|
-
**
|
|
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`.
|
|
113
162
|
|
|
114
|
-
| When
|
|
115
|
-
|
|
|
116
|
-
|
|
|
117
|
-
| A review finding that may be the plan's fault
|
|
118
|
-
| A finding back after its fix round
|
|
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 |
|
|
119
168
|
|
|
120
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:
|
|
121
170
|
|
|
@@ -130,14 +179,14 @@ A climb is for capability only. When the evidence says the lane cannot be done a
|
|
|
130
179
|
A failure on the top rung (`top: true`) goes to the architect when Jev calls it design, else to the report as open.
|
|
131
180
|
|
|
132
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.
|
|
133
|
-
- `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.
|
|
134
183
|
- Never put a secret or a key into an `ask` state. Keep the state short and in English.
|
|
135
184
|
|
|
136
185
|
## Threads
|
|
137
186
|
|
|
138
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.
|
|
139
188
|
|
|
140
|
-
- **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`).
|
|
141
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.
|
|
142
191
|
- A `thread-heavy` hint means that thread is spent. Its next piece starts fresh.
|
|
143
192
|
|
|
@@ -157,6 +206,8 @@ Your context is re-read on every turn, and it is the run's most expensive token.
|
|
|
157
206
|
|
|
158
207
|
Call `dispatch` from your main thread only, never from a subagent: catherd messages the session that dispatched.
|
|
159
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
|
+
|
|
160
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.
|
|
161
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.
|
|
162
213
|
- **A single role is `dispatch`, then end your turn.**
|
|
@@ -175,6 +226,12 @@ Call `dispatch` from your main thread only, never from a subagent: catherd messa
|
|
|
175
226
|
|
|
176
227
|
Nothing else pushes: a phone that buzzes for progress teaches the user to ignore it.
|
|
177
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
|
+
|
|
178
235
|
## The run folder
|
|
179
236
|
|
|
180
237
|
`run_start` creates `R` under catherd's data directory, outside the repo. It outlives the session. `R` holds:
|
|
@@ -186,7 +243,7 @@ Nothing else pushes: a phone that buzzes for progress teaches the user to ignore
|
|
|
186
243
|
- **`runs.jsonl`, `agents.jsonl`, `jev.jsonl`, `routes.jsonl`, `outcomes.jsonl`, `harness.jsonl`, `verifier.jsonl`, `questions.jsonl`:** the record.
|
|
187
244
|
- **`digests/<milestone>.md`:** each landed milestone's digest, written by `land`.
|
|
188
245
|
- **`roles/<name>/<dispatchId>/`** (each dispatch's brief, reply, events and stderr) and **`shots/`** (screenshots).
|
|
189
|
-
- **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.
|
|
190
247
|
|
|
191
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.
|
|
192
249
|
|
|
@@ -214,16 +271,19 @@ Nothing else pushes: a phone that buzzes for progress teaches the user to ignore
|
|
|
214
271
|
|
|
215
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.
|
|
216
273
|
|
|
217
|
-
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).
|
|
218
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.
|
|
219
|
-
- 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`.
|
|
220
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.
|
|
221
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.
|
|
222
|
-
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:
|
|
223
280
|
- `pass`: the check already passes on the base tree;
|
|
224
281
|
- `fails-as-expected`: it runs and fails, because the lane has not been done yet;
|
|
225
|
-
- `skipped`: it checks a file the lane creates;
|
|
226
|
-
- `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.
|
|
227
287
|
|
|
228
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)`.
|
|
229
289
|
|
|
@@ -231,20 +291,24 @@ Nothing else pushes: a phone that buzzes for progress teaches the user to ignore
|
|
|
231
291
|
|
|
232
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.
|
|
233
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.
|
|
234
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.
|
|
235
|
-
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.
|
|
236
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.
|
|
237
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.
|
|
238
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.
|
|
239
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.
|
|
240
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.
|
|
241
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.
|
|
242
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.
|
|
243
|
-
- 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`. A process verifier on any backend counts through its dispatch record only when its reply opens `VERDICT: PASS`; collect it with `result` before landing.
|
|
244
|
-
-
|
|
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.
|
|
245
309
|
- A second FAIL on the same line goes to the architect.
|
|
246
310
|
- A third one: pause, report and push.
|
|
247
|
-
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.
|
|
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.
|
|
248
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.
|
|
249
313
|
- Commit only while no role is writing. A pre-commit hook may stash unstaged files, and a role's edits vanish under it.
|
|
250
314
|
- The next milestone's lanes can start in the same turn as the `land`.
|
|
@@ -280,7 +344,7 @@ The brief is the `brief` text you pass to `dispatch` (catherd writes it to the d
|
|
|
280
344
|
4. For a worker:
|
|
281
345
|
- "Read `<R>/lanes/Mx.Ly.md`": decisions, signatures, data shapes;
|
|
282
346
|
- its fast check (targeted tests, lint and type check), to run until it passes;
|
|
283
|
-
- "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.
|
|
284
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:
|
|
285
349
|
- unmet A-lines and edge cases;
|
|
286
350
|
- code or abstractions nobody needs;
|
|
@@ -309,6 +373,7 @@ The brief is the `brief` text you pass to `dispatch` (catherd writes it to the d
|
|
|
309
373
|
The tool tops out around 1536×1024.
|
|
310
374
|
|
|
311
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.
|
|
312
377
|
|
|
313
378
|
## Reading results
|
|
314
379
|
|
|
@@ -71,7 +71,7 @@ Each proposal has three parts: the change, a worked example from their facts, an
|
|
|
71
71
|
|
|
72
72
|
**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 a Codex rung over only to a stand-in that clears the same bars at least as well: Go's GPT-6 Luna for Luna high, and Kimi K3 (its scores borrowed from Sol medium) for Sol medium. Sol high and xhigh have none, so a limit there pauses the lane rather than dropping it a tier. Say so, and offer to change it when they have no Go subscription. `null` removes an entry.
|
|
73
73
|
|
|
74
|
-
**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.
|
|
74
|
+
**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; `roles.<role>.timeouts.idleMin` and `wallMin` override them for one role (a verifier with a long gate), and `null` removes the override. `preflight.confirm: true` makes `preflight` show its commands for the user to approve first.
|
|
75
75
|
|
|
76
76
|
**Notifications and readiness.** `notify` controls milestone, finish and blocked moments through whatever notification facility the host actually exposes; Claude Code may have `PushNotification`. Do not invent a Codex tool or a scheduling promise. Completion uses Claude's peer inbox or Codex's existing native queue (`--remote unix://`); a Codex busy session receives ordered next input after its active turn, not urgent next-tool-round delivery. Queue acceptance proves neither processing nor `result` collection; unloaded/interrupted sessions may retain input. `catherd doctor --host codex` checks capability without sending; only explicit `--test-push` sends a labeled smoke input. Use the orchestration skill's `peek`/`result` recovery and current-owner, exact-event `runs retry-push` decision for ambiguity.
|
|
77
77
|
|
|
@@ -86,6 +86,6 @@ Each proposal has three parts: the change, a worked example from their facts, an
|
|
|
86
86
|
|
|
87
87
|
## 5. Say what applies when
|
|
88
88
|
|
|
89
|
-
- Codex, claude-code, opencode, Cursor, grok and Antigravity
|
|
89
|
+
- Codex, claude-code, opencode, Cursor, grok and Antigravity rung and ladder changes apply at the next dispatch, even in a run already under way. Access and isolation are pinned per run when it starts: a run under way keeps them (its `state.md` and `status` name the change) until its orchestrator re-pins it with `run_pin` (`catherd runs pin <run>`). Isolating Cursor needs `CURSOR_API_KEY` in the environment catherd runs in, isolating grok `XAI_API_KEY`, and isolating Antigravity `GEMINI_API_KEY`; `profile_validate` refuses each without. A read-only role (reviewer, architect, researcher) can run on Antigravity only isolated: agy has no read-only mode.
|
|
90
90
|
- For native Claude roles, 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. `record_agent_run` accounts only for native Claude Agent results on Claude Code; process records are automatic.
|
|
91
91
|
- ProfileService manages Claude agent files/links only when native Claude roles are used. Codex-only setup and profile operations do not write under `~/.claude`; Claude CLI/login is needed only by an enabled selected role or reachable failover that uses it. Editing an inactive Claude-dependent profile writes its managed files but links none; they apply once active or repo-bound. Keep role-disabled, access, isolation, budget and failover choices intact.
|
|
@@ -102,11 +102,20 @@ function plan(r: RunRequest): SpawnPlan {
|
|
|
102
102
|
};
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
/** `agy models` under `env`: the user's HOME (its Google login) by default, an isolated one with the key. */
|
|
106
|
+
async function listAs(env: Record<string, string> = {}): Promise<DiscoveredModel[]> {
|
|
107
|
+
const r = await runCli("agy", ["models"], { ...agyShell, env: { ...ENV, ...env } });
|
|
107
108
|
return r?.ok ? parseAgyModels(r.out) : [];
|
|
108
109
|
}
|
|
109
110
|
|
|
111
|
+
const listModels = (): Promise<DiscoveredModel[]> => listAs();
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The discovery cache an isolated run is judged by: the listing under catherd's isolated HOME, where
|
|
115
|
+
* GEMINI_API_KEY signs agy in, apart from the native login's (1.3 follow-ups: the two may serve differently).
|
|
116
|
+
*/
|
|
117
|
+
export const ISOLATED_LISTING = "antigravity-isolated";
|
|
118
|
+
|
|
110
119
|
/**
|
|
111
120
|
* Spec 1.3 §6.3, §6.4, §9 Q2: read-only runs only isolated; an isolated run needs GEMINI_API_KEY and gets its
|
|
112
121
|
* home and settings; a native one needs agy's own login; the rung's model and effort must be ones agy lists.
|
|
@@ -137,7 +146,13 @@ async function prepare(req: {
|
|
|
137
146
|
{ fix: LOGIN_FIX },
|
|
138
147
|
);
|
|
139
148
|
const { model, effort } = req.rung;
|
|
140
|
-
|
|
149
|
+
// an isolated run signs in with the key, so its model is checked against the key's own listing
|
|
150
|
+
const models = req.isolated
|
|
151
|
+
? await discovered(ISOLATED_LISTING, () => listAs(agyHomeEnv(req.access, req.network !== false)), {
|
|
152
|
+
maxAgeMs: DAY_MS,
|
|
153
|
+
need: model,
|
|
154
|
+
})
|
|
155
|
+
: await discovered("antigravity", listModels, { maxAgeMs: DAY_MS, need: model });
|
|
141
156
|
if (models.length === 0) return; // agy listed nothing: let the run itself say what is wrong
|
|
142
157
|
const m = models.find((x) => x.id === model);
|
|
143
158
|
if (!m)
|
package/src/adapters/backend.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { ADAPTER_IDS, Rung } from "../domain/ids.ts";
|
|
|
3
3
|
import type { BillingMode } from "../domain/cost.ts";
|
|
4
4
|
import type { Access, ExitInfo, RunStatus, Tokens } from "../domain/record.ts";
|
|
5
5
|
import type { CliResult } from "./cli.ts";
|
|
6
|
+
import type { RoleMcpContext } from "../domain/role-tools.ts";
|
|
6
7
|
type AdapterId = (typeof ADAPTER_IDS)[number];
|
|
7
8
|
|
|
8
9
|
export interface Probe {
|
|
@@ -43,6 +44,10 @@ export interface DiscoveredModel {
|
|
|
43
44
|
export interface RunRequest {
|
|
44
45
|
rung: Rung;
|
|
45
46
|
access: Access;
|
|
47
|
+
/** Native catherd roles receive a dedicated, run-bound control-plane server. */
|
|
48
|
+
roleMcp?: RoleMcpContext;
|
|
49
|
+
/** spec 1.5 plan 21: the role's TMPDIR (`<run>/scratch/<name>`), a write root besides the repo */
|
|
50
|
+
scratch?: string;
|
|
46
51
|
/** spec §5: a workspace-write role's network and loopback grants; false only when the profile says `network: false` */
|
|
47
52
|
network?: boolean;
|
|
48
53
|
thread: string | null;
|
|
@@ -82,6 +87,11 @@ export interface EventDelta {
|
|
|
82
87
|
limit?: boolean;
|
|
83
88
|
tooOld?: boolean;
|
|
84
89
|
retrying?: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* plan 23: a model step started (opencode's step_start). Two in a row with nothing between are a provider retry,
|
|
92
|
+
* which the supervisor never counts as progress
|
|
93
|
+
*/
|
|
94
|
+
step?: boolean;
|
|
85
95
|
/** the stream's terminal event: the supervisor may kill a CLI that lingers after it */
|
|
86
96
|
final?: boolean;
|
|
87
97
|
/** a tool call starting (`open`) or ending: while one is open the run is busy, however quiet */
|
|
@@ -147,14 +157,20 @@ export interface BackendAdapter {
|
|
|
147
157
|
interrupt?(thread: string, cwd: string): Promise<void>;
|
|
148
158
|
/** `sinceMs`: when this run started, so what an earlier run on the thread left behind does not count */
|
|
149
159
|
isBusy?(thread: string, cwd: string, sinceMs?: number): Promise<boolean>;
|
|
160
|
+
/**
|
|
161
|
+
* Plan 23: the provider retry the session is waiting out, by its attempt number, when its newest message since
|
|
162
|
+
* `sinceMs` is retrying (a 503, an overloaded backend); null when it is not
|
|
163
|
+
*/
|
|
164
|
+
providerRetry?(thread: string, cwd: string, sinceMs: number): Promise<number | null>;
|
|
150
165
|
/** Spec §4.5: this backend's own stand-in for a rung on a usage limit, when the profile names none. */
|
|
151
166
|
failoverFor?(rung: Rung, repo?: string): Rung | null;
|
|
152
167
|
/**
|
|
153
168
|
* Spec §5 and §12: a shell that runs a command the way this backend's workspace-write worker runs one,
|
|
154
169
|
* with the grants the worker gets, for doctor's access probes; a string says why it cannot be tested here.
|
|
155
|
-
* Absent: the CLI has no way to run a shell in its sandbox without a model turn (spec 1.3 §3.4).
|
|
170
|
+
* Absent: the CLI has no way to run a shell in its sandbox without a model turn (spec 1.3 §3.4). `repos`: the
|
|
171
|
+
* repositories workers run in (doctor's own and every bound one), for a backend whose sandbox is set per repo.
|
|
156
172
|
*/
|
|
157
|
-
accessShell?(o: { network: boolean }): Promise<AccessShell | string>;
|
|
173
|
+
accessShell?(o: { network: boolean; repos?: string[] }): Promise<AccessShell | string>;
|
|
158
174
|
/** Spec §10.3: why this backend's isolation is weak; doctor warns when a profile uses it. */
|
|
159
175
|
isolationNote?: string;
|
|
160
176
|
/**
|