@enderfga/claw-orchestrator 7.14.0 → 7.15.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 (137) hide show
  1. package/README.md +160 -135
  2. package/configs/autoloop-reviewer-prompt.md +1 -1
  3. package/configs/engines/README.md +31 -11
  4. package/dist/bin/acp-server.js +7 -12
  5. package/dist/bin/acp-server.js.map +1 -1
  6. package/dist/bin/cli.js +71 -40
  7. package/dist/bin/cli.js.map +1 -1
  8. package/dist/bin/mcp-server.js +2 -54
  9. package/dist/bin/mcp-server.js.map +1 -1
  10. package/dist/src/acp-server.d.ts +17 -9
  11. package/dist/src/acp-server.js +149 -30
  12. package/dist/src/acp-server.js.map +1 -1
  13. package/dist/src/autoloop/agent-tools.d.ts +2 -0
  14. package/dist/src/autoloop/agent-tools.js +7 -0
  15. package/dist/src/autoloop/agent-tools.js.map +1 -1
  16. package/dist/src/autoloop/dispatcher.d.ts +6 -0
  17. package/dist/src/autoloop/dispatcher.js +23 -1
  18. package/dist/src/autoloop/dispatcher.js.map +1 -1
  19. package/dist/src/autoloop/planner-tools.js +2 -0
  20. package/dist/src/autoloop/planner-tools.js.map +1 -1
  21. package/dist/src/council.d.ts +8 -0
  22. package/dist/src/council.js +45 -12
  23. package/dist/src/council.js.map +1 -1
  24. package/dist/src/dashboard/index.html +28 -14
  25. package/dist/src/embedded-server.js +27 -8
  26. package/dist/src/embedded-server.js.map +1 -1
  27. package/dist/src/engine-spawn.js +3 -1
  28. package/dist/src/engine-spawn.js.map +1 -1
  29. package/dist/src/fanout.d.ts +13 -3
  30. package/dist/src/fanout.js +10 -0
  31. package/dist/src/fanout.js.map +1 -1
  32. package/dist/src/inbox-manager.d.ts +9 -0
  33. package/dist/src/inbox-manager.js +8 -2
  34. package/dist/src/inbox-manager.js.map +1 -1
  35. package/dist/src/index.d.ts +2 -0
  36. package/dist/src/index.js +117 -51
  37. package/dist/src/index.js.map +1 -1
  38. package/dist/src/kernel/conditions.d.ts +1 -1
  39. package/dist/src/kernel/conditions.js +1 -1
  40. package/dist/src/kernel/engine.d.ts +28 -8
  41. package/dist/src/kernel/engine.js +93 -19
  42. package/dist/src/kernel/engine.js.map +1 -1
  43. package/dist/src/kernel/nodes/agent.js +2 -0
  44. package/dist/src/kernel/nodes/agent.js.map +1 -1
  45. package/dist/src/kernel/nodes/council.js +9 -4
  46. package/dist/src/kernel/nodes/council.js.map +1 -1
  47. package/dist/src/kernel/nodes/fanout.js +5 -0
  48. package/dist/src/kernel/nodes/fanout.js.map +1 -1
  49. package/dist/src/kernel/nodes/goal.d.ts +8 -1
  50. package/dist/src/kernel/nodes/goal.js +40 -19
  51. package/dist/src/kernel/nodes/goal.js.map +1 -1
  52. package/dist/src/kernel/nodes/subflow.js +9 -1
  53. package/dist/src/kernel/nodes/subflow.js.map +1 -1
  54. package/dist/src/kernel/nodes/ultraapp.d.ts +1 -0
  55. package/dist/src/kernel/nodes/ultraapp.js +1 -0
  56. package/dist/src/kernel/nodes/ultraapp.js.map +1 -1
  57. package/dist/src/kernel/nodes/verifier.js +5 -1
  58. package/dist/src/kernel/nodes/verifier.js.map +1 -1
  59. package/dist/src/kernel/projections.d.ts +1 -0
  60. package/dist/src/kernel/projections.js +7 -1
  61. package/dist/src/kernel/projections.js.map +1 -1
  62. package/dist/src/kernel/templates/index.d.ts +6 -3
  63. package/dist/src/kernel/templates/index.js +8 -3
  64. package/dist/src/kernel/templates/index.js.map +1 -1
  65. package/dist/src/kernel/types.d.ts +30 -10
  66. package/dist/src/kernel/types.js.map +1 -1
  67. package/dist/src/logger.d.ts +6 -0
  68. package/dist/src/logger.js +14 -0
  69. package/dist/src/logger.js.map +1 -1
  70. package/dist/src/mcp-annotations.d.ts +13 -0
  71. package/dist/src/mcp-annotations.js +100 -0
  72. package/dist/src/mcp-annotations.js.map +1 -0
  73. package/dist/src/models.d.ts +7 -0
  74. package/dist/src/models.js +9 -0
  75. package/dist/src/models.js.map +1 -1
  76. package/dist/src/openai-compat.d.ts +11 -5
  77. package/dist/src/openai-compat.js +55 -54
  78. package/dist/src/openai-compat.js.map +1 -1
  79. package/dist/src/persistent-acp-session.d.ts +7 -0
  80. package/dist/src/persistent-acp-session.js +26 -2
  81. package/dist/src/persistent-acp-session.js.map +1 -1
  82. package/dist/src/persistent-codex-app-session.d.ts +2 -0
  83. package/dist/src/persistent-codex-app-session.js +18 -9
  84. package/dist/src/persistent-codex-app-session.js.map +1 -1
  85. package/dist/src/persistent-codex-session.d.ts +14 -3
  86. package/dist/src/persistent-codex-session.js +22 -19
  87. package/dist/src/persistent-codex-session.js.map +1 -1
  88. package/dist/src/persistent-custom-session.d.ts +9 -0
  89. package/dist/src/persistent-custom-session.js +41 -21
  90. package/dist/src/persistent-custom-session.js.map +1 -1
  91. package/dist/src/persistent-grok-session.js +1 -1
  92. package/dist/src/persistent-grok-session.js.map +1 -1
  93. package/dist/src/run-ledger.d.ts +3 -3
  94. package/dist/src/session-manager.d.ts +34 -1
  95. package/dist/src/session-manager.js +160 -30
  96. package/dist/src/session-manager.js.map +1 -1
  97. package/dist/src/types.d.ts +34 -11
  98. package/dist/src/types.js.map +1 -1
  99. package/dist/src/ultraapp/build.d.ts +5 -11
  100. package/dist/src/ultraapp/build.js +5 -11
  101. package/dist/src/ultraapp/build.js.map +1 -1
  102. package/dist/src/ultraapp/council-adapter.d.ts +5 -2
  103. package/dist/src/ultraapp/council-adapter.js +6 -6
  104. package/dist/src/ultraapp/council-adapter.js.map +1 -1
  105. package/dist/src/ultraapp/files.d.ts +7 -0
  106. package/dist/src/ultraapp/files.js +7 -1
  107. package/dist/src/ultraapp/files.js.map +1 -1
  108. package/dist/src/ultraapp/manager.d.ts +23 -3
  109. package/dist/src/ultraapp/manager.js +170 -72
  110. package/dist/src/ultraapp/manager.js.map +1 -1
  111. package/dist/src/ultraapp/narrator.js +1 -1
  112. package/dist/src/ultraapp/narrator.js.map +1 -1
  113. package/dist/src/ultraapp/versions.d.ts +27 -5
  114. package/dist/src/ultraapp/versions.js +58 -13
  115. package/dist/src/ultraapp/versions.js.map +1 -1
  116. package/openclaw.plugin.json +4 -13
  117. package/package.json +13 -6
  118. package/skills/SKILL.md +52 -24
  119. package/skills/references/acp.md +140 -61
  120. package/skills/references/autoloop.md +141 -35
  121. package/skills/references/claude-cli-tracking.md +8 -6
  122. package/skills/references/cli.md +122 -93
  123. package/skills/references/council.md +44 -22
  124. package/skills/references/dashboard.md +37 -21
  125. package/skills/references/getting-started.md +49 -15
  126. package/skills/references/inbox.md +13 -8
  127. package/skills/references/mcp.md +25 -17
  128. package/skills/references/multi-engine.md +161 -145
  129. package/skills/references/observability.md +90 -58
  130. package/skills/references/openai-compat.md +172 -82
  131. package/skills/references/sessions.md +101 -65
  132. package/skills/references/tools.md +209 -158
  133. package/skills/references/ultra.md +7 -7
  134. package/skills/references/ultraapp.md +101 -56
  135. package/skills/references/verification.md +112 -56
  136. package/skills/references/workflow.md +227 -93
  137. package/assets/banner.jpg +0 -0
@@ -116,12 +116,12 @@ if (status?.status === 'completed') {
116
116
 
117
117
  ### Configuration
118
118
 
119
- | Parameter | Default | Range | Description |
120
- | -------------------- | ------------------------- | ----- | ----------------------------------------------------------------------------------------------------- |
121
- | `agentCount` | 5 | 1-20 | Number of reviewer agents |
122
- | `maxDurationMinutes` | 10 | 5-25 | Per-agent timeout |
123
- | `model` | session default | — | Model for all reviewers |
124
- | `focus` | bugs + security + quality | — | Review focus description |
125
- | `engines` | `['claude']` | — | Engines assigned to reviewers round-robin. Not `custom`, which has no config to start a reviewer from |
119
+ | Parameter | Default | Range | Description |
120
+ | -------------------- | ------------------------- | ----- | ---------------------------------------------------------------------------------------------------- |
121
+ | `agentCount` | 5 | 1-20 | Number of reviewer agents |
122
+ | `maxDurationMinutes` | 10 | 5-25 | Per-agent timeout |
123
+ | `model` | session default | — | Model for all reviewers |
124
+ | `focus` | bugs + security + quality | — | Review focus description |
125
+ | `engines` | `['claude']` | — | Engines assigned to reviewers round-robin. Not `custom` or `acp`: neither can be held read-only here |
126
126
 
127
127
  Reviewers run once each (up to 20 turns); there is no cross-review round. Results are stored as a durable run and remain queryable after a restart.
@@ -37,20 +37,21 @@ interview ─► queued ─► building ─► build-complete ─► deploying
37
37
  | `interview` | AppSpec being filled by Q&A. Chat input goes to the interview agent (Claude Opus). |
38
38
  | `queued` | Build accepted, waiting for a slot in the FIFO build queue. |
39
39
  | `building` | Council writing code, fix-on-failure driving install/build/test. |
40
- | `build-complete` | Codebase ready, awaiting `deploy` step. |
40
+ | `build-complete` | Codebase ready. The resting state when no router is wired (see Runtime modes). |
41
41
  | `deploying` | Container/process being started, router map being updated. |
42
42
  | `done` | App live at `/forge/<slug>/`. Chat input now goes to the done-mode classifier. |
43
43
  | `failed` | Council didn't reach consensus, or fix-on-failure couldn't get the build green. |
44
+ | `cancelled` | The build was cancelled (`build/cancel` or `ultraapp_build_cancel`). |
44
45
 
45
46
  ### The build is a workflow run
46
47
 
47
48
  Everything from `building` onward is a run on the durable kernel — the same one
48
49
  `council_start`, `fanout_start` and `autoloop_start` use. The workflow has three
49
- nodes:
50
+ nodes (two when no router is wired — there is then nowhere to deploy):
50
51
 
51
52
  | Node | Kind | Does |
52
53
  | -------- | ----------------- | ------------------------------------------------------------ |
53
- | `synth` | `ultraapp_synth` | The 3-agent council; snapshots consensus into `versions/v1/` |
54
+ | `synth` | `ultraapp_synth` | The 3-agent council; snapshots consensus into `versions/vN/` |
54
55
  | `build` | `verifier` | The build contract, with its fix-on-red loop and evidence |
55
56
  | `deploy` | `ultraapp_deploy` | Starts the app, registers the route, runs the §7g gate |
56
57
 
@@ -104,57 +105,84 @@ clawo serve --ultraapp-runtime docker # opt-in
104
105
  Both modes allocate a backend port in `[19100, 19999]`. The reverse-
105
106
  proxy router runs at port `19000` (auto-fallback up to `19099` if
106
107
  taken) and maps `/forge/<slug>/*` to the right backend. Slug→port map
107
- persists to `~/.claw-orchestrator/_router.json`. Host-mode pid metadata
108
+ persists to `~/.claw-orchestrator/ultraapps/_router.json`. Host-mode pid metadata
108
109
  persists to `~/.claw-orchestrator/host-procs.json`.
109
110
 
111
+ The router is started by `clawo serve`. The OpenClaw plugin does not start one,
112
+ and `clawo serve` runs without one when the router cannot start (for example,
113
+ no free port in `19000`–`19099`): builds then stop at `build-complete` and are
114
+ not deployed.
115
+
110
116
  ## File layout (per run)
111
117
 
112
118
  ```text
113
119
  ~/.claw-orchestrator/ultraapps/<runId>/
114
120
  ├── spec.json # current AppSpec (latest)
115
- ├── spec.history.jsonl # every accepted update_spec patch
121
+ ├── spec.history.jsonl # the full spec after every write (interview update_spec or manual edit)
116
122
  ├── chat.jsonl # all chat turns (interview + done-mode)
117
- ├── state.json # { runId, mode, createdAt, updatedAt }
123
+ ├── state.json # { runId, mode, createdAt, updatedAt, failure? }
118
124
  ├── examples/ # uploaded sample files
119
125
  ├── data/ # passed to host-mode app as DATA_DIR
126
+ ├── evidence/deploy-01/ # deploy-stage evidence (see Acceptance contract)
120
127
  ├── council-project/ # fresh git repo the council collaborates in
121
128
  │ ├── .worktrees/{agent-A,agent-B,agent-C}/
122
129
  │ └── (council code, merged to main on consensus)
123
130
  └── versions/
124
131
  ├── v1/
125
- │ ├── codebase/ # snapshot of council main HEAD
126
- │ └── artifact.json # { worktreePath, builtAt, deploy: { url, port, … } }
127
- └── v2/ # patcher / spec-delta produces v2, v3, …
132
+ │ ├── codebase/ # snapshot of council main HEAD, its own git repo
133
+ │ └── artifact.json # { worktreePath, builtAt, deploy: { url, port, … }, deployedAt }
134
+ └── v2/ # each build and each successful patch takes the next vN
128
135
  ```
129
136
 
137
+ Every version keeps its own `codebase/`, so any of them can be promoted. The live
138
+ version is the one most recently deployed or promoted; `start`, `stop` and the
139
+ patcher act on it.
140
+
141
+ Run ids have the form `ua-<timestamp>-<hex>`. The build queue
142
+ (`build-queue.json`) and the router map (`_router.json`) sit beside the run
143
+ directories, in `~/.claw-orchestrator/ultraapps/`. A run is read back from its
144
+ directory on first use, so every route works on a run started before the
145
+ orchestrator restarted; its interview session is restarted by name on the run's
146
+ next turn, which resumes the persisted conversation.
147
+
130
148
  ## HTTP routes (drive headlessly)
131
149
 
132
150
  All routes are served by the embedded server (default `:18796`), under
133
- `Authorization: Bearer <token>` from `~/.openclaw/server-token`.
134
-
135
- | Method + path | Purpose |
136
- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
137
- | `GET /ultraapp/list` | All runs with mode + createdAt. |
138
- | `POST /ultraapp/new` | No body. Returns `{ runId }`. |
139
- | `GET /ultraapp/<id>` | Full snapshot: spec + chat + state. |
140
- | `POST /ultraapp/<id>/answer` | Body: `{ value, freeform? }`. Submit interview answer. |
141
- | `POST /ultraapp/<id>/spec-edit` | Body: `{ patch: [...] }` (RFC 6902 ops). Edit the spec mid-interview. |
142
- | `POST /ultraapp/<id>/files` | JSON body `{ absolutePath }` (a file on the server host) or `{ filename, dataB64 }`. Copies into `examples/`. |
143
- | `GET /ultraapp/<id>/events` | SSE stream of build/chat events (mode pill, narrator, council activity). |
144
- | `POST /ultraapp/<id>/build` | Validate spec strictly + enqueue. |
145
- | `POST /ultraapp/<id>/build/cancel` | Abort the active build. |
146
- | `GET /ultraapp/<id>/artifacts` | List `versions/vN/`. |
147
- | `POST /ultraapp/<id>/start` | Start the deployed container/process for the active version. |
148
- | `POST /ultraapp/<id>/stop` | Stop without deleting. |
149
- | `POST /ultraapp/<id>/delete` | Stop + remove all per-run state. |
150
- | `POST /ultraapp/<id>/feedback` | Body: `{ text }`. Done-mode classifier routes cosmetic / spec-delta / structural. |
151
- | `POST /ultraapp/<id>/promote-version` | Body: `{ version: "vN" }`. Atomically swap deployed version. |
151
+ `Authorization: Bearer <token>` from `~/.openclaw/server-token`. Request bodies
152
+ are JSON (`Content-Type: application/json`) and capped at 5 MB.
153
+
154
+ | Method + path | Purpose |
155
+ | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
156
+ | `GET /ultraapp/list` | All runs (runId, title, mode, createdAt, updatedAt), newest first. |
157
+ | `POST /ultraapp/new` | No body. Returns `{ runId }`. |
158
+ | `GET /ultraapp/<id>` | Full snapshot: spec + chat + state. |
159
+ | `POST /ultraapp/<id>/answer` | Body: `{ value, freeform? }`. Submit interview answer. |
160
+ | `POST /ultraapp/<id>/spec-edit` | Body: `{ patch: [...] }` (RFC 6902 ops). Edit the spec mid-interview. |
161
+ | `POST /ultraapp/<id>/files` | Add an example file: body `{ filename, dataB64 }` or `{ absolutePath }` (below). |
162
+ | `GET /ultraapp/<id>/events` | SSE stream of `ultraapp` events: questions, spec updates, chat, completeness, interview completion, build events, the app URL, errors. |
163
+ | `POST /ultraapp/<id>/build` | Validate spec strictly + enqueue. |
164
+ | `POST /ultraapp/<id>/build/cancel` | Cancel a queued or in-flight build. |
165
+ | `GET /ultraapp/<id>/artifacts` | List `versions/vN/`. |
166
+ | `POST /ultraapp/<id>/start` | Start the deployed container/process of the live version and re-register its route. |
167
+ | `POST /ultraapp/<id>/stop` | Stop without deleting; the route is deregistered until `start`. |
168
+ | `POST /ultraapp/<id>/delete` | Stop every deployed version, remove the images, and delete all per-run state. |
169
+ | `POST /ultraapp/<id>/feedback` | Body: `{ text }`. Done-mode classifier routes cosmetic / spec-delta / structural. |
170
+ | `POST /ultraapp/<id>/promote-version` | Body: `{ version: "vN" }`. Make that version live, deploying it first if it never was. |
171
+
172
+ `files` with `{ filename, dataB64 }` copies an upload into `examples/`; an upload
173
+ may be up to 100 MiB, and this route accepts a body sized for its base64 text
174
+ (every other route caps bodies at 5 MiB). With `{ absolutePath }` it references a file
175
+ on the server host in place; the path must be under `$HOME` or `/tmp`, with no
176
+ dotfile segment and no symlink. `feedback` sent before the run reaches `done`
177
+ goes to the interview as a free-form answer.
152
178
 
153
179
  ## MCP tools (14)
154
180
 
155
- Same surface as HTTP, callable from any Model Context Protocol host
181
+ The same operations as HTTP, minus the SSE stream and the artifacts listing,
182
+ plus a cheap `ultraapp_status`. Callable from any Model Context Protocol host
156
183
  (Claude Desktop, Hermes Agent, Cursor, Cline, Continue, Zed,
157
- Windsurf, Goose). Param schemas in [`tools.md`](./tools.md#ultraapp-14).
184
+ Windsurf, Goose). `ultraapp_add_file` takes an `absolutePath` only; browser
185
+ uploads go through the dashboard. Param schemas in [`tools.md`](./tools.md#ultraapp-14).
158
186
 
159
187
  ```text
160
188
  ultraapp_list ultraapp_get ultraapp_status
@@ -169,15 +197,17 @@ ultraapp_start_container ultraapp_stop_container ultraapp_delete
169
197
  After the run reaches `done`, chat input goes to a per-run Haiku
170
198
  classifier. Three classes:
171
199
 
172
- | Class | Routes to | Behaviour |
173
- | ------------ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
174
- | `cosmetic` | Patcher | A Claude Opus session generates a unified diff against the deployed worktree → `applyUnifiedDiff` → validate via fix-on-failure → on success snapshot to `versions/vN+1/`, on any failure restore the snapshot atomically and post the reason to chat. |
175
- | `spec-delta` | Focused interview | Flips mode back to `interview` with a bootstrap message that names the field(s) being changed. Completion auto-triggers a fresh `startBuild`. |
176
- | `structural` | Suggestion only | Posts a narrator note: "this sounds like a different app — click + New". |
200
+ | Class | Routes to | Behaviour |
201
+ | ------------ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
202
+ | `cosmetic` | Patcher | Copies the live version's codebase to the next `versions/vN/`, then a Claude Opus session generates a unified diff against the copy → `applyUnifiedDiff` → validate via fix-on-failure. On success the copy is recorded as `vN` (not deployed; the live app is untouched until you promote it); on any failure the copy is discarded and the reason posted to chat. |
203
+ | `spec-delta` | Focused interview | Flips mode back to `interview` with a bootstrap message that names the field(s) being changed. Completion auto-triggers a fresh `startBuild`, which builds the next `vN`, deploys it in place of the live version and stops the one it replaced. |
204
+ | `structural` | Suggestion only | Posts a narrator note: "this sounds like a different app — click + New". |
177
205
 
178
- To swap which version is live, use `promote-version` (HTTP) or
179
- `ultraapp_promote_version` (MCP) — the router map and host-procs map
180
- update atomically.
206
+ To change which version is live, use `promote-version` (HTTP),
207
+ `ultraapp_promote_version` (MCP) or **Promote** in the dashboard's Versions panel.
208
+ A version deployed before is restarted on its recorded port and the live one
209
+ stopped; if it does not start, the previous version is put back. A version never
210
+ deployed is built and deployed first.
181
211
 
182
212
  ## Reference traces + replay
183
213
 
@@ -186,11 +216,11 @@ output against drift:
186
216
 
187
217
  ```text
188
218
  src/__tests__/fixtures/ultraapp-traces/
189
- ├── text-summariser.jsonl (synthetic, simple text in/out)
190
- ├── image-batch-resize.jsonl (batch upload + Pillow + zip)
191
- ├── vlog-cut.jsonl (ffmpeg + whisper + branching DAG)
219
+ ├── text-summariser.jsonl (hand-written; text in, one LLM step, text out)
220
+ ├── image-batch-resize.jsonl (batch image upload, resize + watermark, zip out)
221
+ ├── vlog-cut.jsonl (ffmpeg silence detection, cut + concat)
192
222
  ├── llm-agent-pipeline.jsonl (BYOK, multi-step LLM)
193
- ├── branching-dag.jsonl (parallel paths converging)
223
+ ├── branching-dag.jsonl (whisper transcript + ffmpeg keyframes, converging)
194
224
  ├── _format.md (trace JSONL schema)
195
225
  └── expected/<name>.appspec.json (frozen target)
196
226
  ```
@@ -201,8 +231,10 @@ npx tsx scripts/test-ultraapp-integration.ts --trace=all
201
231
  ```
202
232
 
203
233
  The `spec-extraction-quality.test.ts` test replays each trace through
204
- the interview engine and asserts the resulting `AppSpec` matches the
205
- frozen snapshot — any future engine or skill drift fails this test.
234
+ the interview engine, with the model's replies taken from the trace, and
235
+ asserts the resulting `AppSpec` matches the frozen snapshot — a change in
236
+ how replies are parsed or applied to the spec fails it. Because the replies
237
+ are recorded, it does not exercise the live model or `SKILL.md`.
206
238
 
207
239
  ## Operator quick start
208
240
 
@@ -214,9 +246,9 @@ open "http://127.0.0.1:18796/dashboard?token=$(cat ~/.openclaw/server-token)"
214
246
  # 2. Forge tab → + New → walk through the interview
215
247
 
216
248
  # 3. After [Start Build] (or POST /ultraapp/<id>/build), watch:
217
- # mode pill: queued → building → done
249
+ # mode pill: queued → building → build-complete → done
218
250
  # narrator: short conversational chat updates
219
- # Versions panel: v1, v2, … with Promote per row
251
+ # Versions panel: v1, v2, …; the live one links to the app, the others have Promote
220
252
 
221
253
  # 4. Live URL appears in the share card. The deployed app is reachable at:
222
254
  curl http://127.0.0.1:19000/forge/<slug>/health
@@ -226,24 +258,29 @@ curl http://127.0.0.1:19000/forge/<slug>/health
226
258
 
227
259
  UltraApp is the one mode whose acceptance contract is on by default.
228
260
 
229
- **Build stage**, in the council's worktree:
261
+ **Build stage**, in the consensus snapshot (`versions/v1/codebase/`):
230
262
 
231
263
  ```
232
264
  npm install → npm run build → npm test → [docker build] → npm run smoke
233
265
  ```
234
266
 
235
- A codebase without a working `scripts.smoke` fails its build (§4).
267
+ `docker build` runs only in `docker` runtime mode. On a red check a repair
268
+ session is started and the whole list re-runs, up to 5 times. A codebase
269
+ without a working `scripts.smoke` fails its build (§4).
236
270
 
237
271
  **Deploy stage**, against the live URL: both §7g viewports (1440×900 and
238
272
  375×812) are captured by the orchestrator with headless Chrome and stored as run
239
273
  evidence.
240
274
 
241
- Evidence lands under the run directory:
275
+ Evidence lands in two places. The build stage is an ordinary `verifier` node,
276
+ so its bundle is in the workflow run's directory; the deploy stage writes its own
277
+ into the app's run directory:
242
278
 
243
279
  ```
280
+ ~/.claw-orchestrator/wf/ultraapp-<runId>/evidence/
281
+ build-v01-01/ bundle.json, checks/*.log, diff.patch
244
282
  ~/.claw-orchestrator/ultraapps/<runId>/evidence/
245
- build-01/ bundle.json, checks/*.log, diff.patch
246
- deploy-01/ bundle.json, shot-desktop.png, shot-mobile.png
283
+ deploy-01/ bundle.json, shot-desktop.png, shot-mobile.png
247
284
  ```
248
285
 
249
286
  ### What the visual gate does and does not do
@@ -259,15 +296,23 @@ the usual macOS app paths, then `PATH`.
259
296
 
260
297
  ## Durable build queue
261
298
 
262
- The build queue is persisted to `<store>/build-queue.json` and restored on
263
- startup.
299
+ The build queue is persisted to `~/.claw-orchestrator/ultraapps/build-queue.json`
300
+ and restored on startup. Builds run one at a time, first in, first out. Only one
301
+ process owns the queue: restoring it takes a lease, and a process that cannot
302
+ take the lease starts with an empty queue.
264
303
 
265
- A build that was in flight when the process died is **re-queued, not resumed**,
266
- and goes to the front: each build starts from a fresh council worktree, so
267
- re-running is safe and continuing a half-built tree is not.
304
+ A build that was in flight when the process died goes back to the **front** of
305
+ the queue. When it runs again, its workflow run resumes from the last stage it
306
+ finished (see [The build is a workflow run](#the-build-is-a-workflow-run)), so a
307
+ finished council is not paid for twice. A build started again after it completed,
308
+ failed or was cancelled is a fresh run under the same id.
268
309
 
269
310
  ## Known limitations
270
311
 
312
+ - The interview, spec edits, file additions, builds, done-mode feedback and the
313
+ `events` stream work only on a run created by the running process. After a
314
+ restart they answer `unknown runId`; listing, reading, start / stop / delete
315
+ and promote still work.
271
316
  - The done-mode patcher loop occasionally hangs between
272
317
  feedback classification and the start of the patcher's Claude Opus session;
273
318
  cosmetic changes can be applied manually until the underlying race
@@ -1,31 +1,52 @@
1
1
  # Verification — acceptance contracts and evidence
2
2
 
3
3
  An **acceptance contract** is a list of checks the runtime executes itself and
4
- whose results it reads. A run that declares one cannot reach `completed` unless
5
- every required check passes. Completion signals that come from an agent or an
6
- engine (consensus votes, a Coder's reported metric, an engine's `ok`) are
7
- recorded, but they are never treated as verification.
4
+ whose results it reads. A run that declares one is judged by its last
5
+ verification: it ends `failed` when a required check of that verification fails,
6
+ and `completed` / `verified` only when every required check passes.
7
+ Completion signals that come from an agent or an engine (consensus votes, a
8
+ Coder's reported metric, an engine's `ok`) are recorded, but they are never
9
+ treated as verification.
8
10
 
9
11
  ## The one rule about where contracts come from
10
12
 
11
13
  **A contract comes from the caller or from a mode default. Never from agent
12
14
  output.** If an agent could declare its own checks, we would be back to
13
15
  self-grading with more steps. Nothing in the kernel reads a contract out of a
14
- node's result, and `normalizeContract()` drops anything it does not recognise, so
15
- a contract that arrived through a tool call carries no fields the executor did
16
- not model.
16
+ node's result.
17
+
18
+ The `contract` argument of `workflow_start`, `POST /workflow/new` and `verify_run`
19
+ goes through `normalizeContract()`, which drops anything it does not recognise, so
20
+ it carries no fields the executor did not model; one with no recognised check is
21
+ refused before the run starts. That argument takes each check bare,
22
+ `{ "type": "command", … }`, or wrapped, `{ "id"?, "spec": { … }, "required"? }`.
23
+ A contract written inside a spec — `spec.contract`, or a verifier's inline
24
+ `contract` — is used as written, so its checks must be in the wrapped form.
25
+
26
+ A workflow start normalises every contract the run carries the same way: the
27
+ `contract` start option, the spec's own `contract`, and each verifier node's
28
+ inline `contract`. A check may be written bare (`{ "type": "command", "cmd": … }`)
29
+ or wrapped (`{ "spec": { … }, "required": … }`) in any of them. A contract in
30
+ which no check is recognised is refused at start rather than dropped.
17
31
 
18
32
  Concretely: there is no shell string anywhere. A `command` check is argv.
19
33
 
20
34
  ## Check types
21
35
 
22
- | Type | What it does | Passes when |
23
- | ------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
24
- | `command` | Runs argv in a directory | Exit code equals `expectExit` (default 0) |
25
- | `http` | Polls a URL until the deadline | Status equals `expectStatus` (default 200) |
26
- | `screenshot` | Captures the page at each viewport with headless Chrome and stores the PNGs | Every viewport produced a non-empty image |
27
- | `diff_policy` | Compares the change set against the recorded baseline | File count, forbidden paths, and required paths all satisfied |
28
- | `file` | Checks a path | Exists (or is absent when `exists: false`) and matches `matches` if given |
36
+ | Type | Fields | What it does | Passes when |
37
+ | ------------- | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
38
+ | `command` | `cmd`, `args`, `cwd`, `expectExit`, `timeoutMs` | Runs argv in a directory (`cwd` is relative to where the checks run) | Exit code equals `expectExit` (default 0) |
39
+ | `http` | `url`, `expectStatus`, `timeoutMs`, `intervalMs` | GETs a URL every `intervalMs` (default 1 s) until the deadline | Status equals `expectStatus` (default 200) |
40
+ | `screenshot` | `url`, `viewports` (`[{ width, height, label? }]`), `browserBin`, `timeoutMs` | Captures the page at each viewport with headless Chrome and stores the PNGs | Every viewport produced a non-empty image |
41
+ | `diff_policy` | `maxFiles`, `forbidPaths`, `requirePaths` | Compares the change set against the recorded baseline | At most `maxFiles` files changed, none under a `forbidPaths` entry, and at least one under a `requirePaths` entry |
42
+ | `file` | `path`, `exists`, `matches` | Checks a path, relative to where the checks run | Exists (or is absent when `exists: false`) and, if `matches` is given, its contents match that regex |
43
+
44
+ Each check may also carry an `id` (default `<type>-<n>`, used in the evidence) and
45
+ `required`. The contract itself is `{ id?, checks, fixOnFailureRounds?,
46
+ protectTests? }`. `forbidPaths` and `requirePaths` are path prefixes matched by
47
+ whole segments (`src` covers `src/a.ts`, not `srcfoo/a.ts`), not globs.
48
+
49
+ As the `contract` argument of `workflow_start` or `verify_run`:
29
50
 
30
51
  ```jsonc
31
52
  {
@@ -48,8 +69,11 @@ blocking.
48
69
 
49
70
  ### Timeouts
50
71
 
51
- Every check has one; the default is 10 minutes. A check that overruns is killed (its whole process group, with SIGKILL)
52
- and recorded as failed with `timedOut: true`.
72
+ `timeoutMs` defaults to 10 minutes for a `command` check, 30 seconds for an `http`
73
+ check, and 60 seconds per viewport for a `screenshot`. A command or browser that
74
+ overruns is killed (its whole process group, with SIGKILL); an overrunning command,
75
+ and an `http` check that never saw the expected status, are recorded as failed with
76
+ `timedOut: true`. `diff_policy` and `file` take no `timeoutMs`.
53
77
 
54
78
  ### What `screenshot` does and does not claim
55
79
 
@@ -58,38 +82,48 @@ visual regression testing. The runtime performs the capture, so the screenshots
58
82
  are files on disk rather than an agent's claim. Judging the rendering is still a
59
83
  human's or an agent's job.
60
84
 
61
- Chrome is resolved from `CLAWO_CHROME_BIN`, then the usual macOS app paths, then
62
- `PATH`. A host with no browser fails the check immediately rather than paying the
63
- timeout to find out.
85
+ Chrome is resolved from the check's `browserBin`, then `CLAWO_CHROME_BIN`, then
86
+ the usual macOS app paths, then `PATH`. A host with no browser fails the check
87
+ immediately rather than paying the timeout to find out.
64
88
 
65
89
  ### `diff_policy` and the baseline
66
90
 
67
91
  `diff_policy` needs to know what the run changed, which needs a baseline. The
68
92
  kernel records `git rev-parse HEAD` when a run starts and diffs against that.
93
+ `verify_run` takes the baseline as `baseSha`; without one, `diff_policy` sees
94
+ untracked files only.
69
95
 
70
- The change set is **tracked changes ∪ untracked files**. That union matters: a
71
- bare `git diff` lists tracked modifications only, so files an agent _created_ are
72
- invisible to it.
96
+ The change set is **tracked changes ∪ untracked files**: `git diff <base>` (so
97
+ commits made during the run count too) plus every untracked file that
98
+ `.gitignore` does not exclude. That union matters: a bare `git diff` lists tracked
99
+ modifications only, so files an agent _created_ are invisible to it.
73
100
 
74
101
  `requirePaths: ["."]` means "the run must have changed something".
75
102
 
76
103
  ## Evidence bundles
77
104
 
78
105
  Every verifier attempt writes a directory under the run, named
79
- `<node>-v<visit>-<attempt>`. Both counters are in the id because the attempt
80
- counter is per-visit and restarts at 1 each time a loop comes back round — so a
81
- repair loop keeps every pass rather than overwriting the last one:
106
+ `<node>-v<visit>-<attempt>` with both counters zero-padded (`verify-v02-01`,
107
+ `__verify-v01-01`). Both counters are in the id because the attempt counter is
108
+ per-visit and restarts at 1 each time a loop comes back round — so a repair loop
109
+ keeps every pass rather than overwriting the last one. Fix-on-red rounds inside one
110
+ attempt share its bundle, which records how many were used:
82
111
 
83
112
  ```
84
113
  ~/.claw-orchestrator/wf/<runId>/evidence/<evidenceId>/
85
- bundle.json verdict, per-check results, changed files, base/head SHA
114
+ bundle.json verdict, per-check results, fix rounds, changed files,
115
+ base/head SHA, tree digest
86
116
  checks/<id>.log output tail for each failed check
87
117
  diff.patch the patch the run produced, new files included
88
- shot-*.png screenshots, when a screenshot check ran
118
+ shot-<label>.png screenshots, when a screenshot check ran
89
119
  ```
90
120
 
91
- Read one with `clawo verify <runId>`, `GET /workflow/<id>/evidence`, or
92
- `workflow_status` (which returns the id).
121
+ A failed command's tail is the last 200 lines of its stderr, or of its stdout when
122
+ stderr is empty; a failed `diff_policy` logs the change set it judged.
123
+
124
+ Read one with `clawo verify <runId>` (`--evidence <id>` for an earlier bundle) or
125
+ `GET /workflow/<id>/evidence` (`?evidenceId=`); without an id both return the
126
+ bundle behind the run's current verdict, whose id `workflow_status` reports.
93
127
 
94
128
  Bundle writes are best-effort. The verdict is already decided by the check
95
129
  results, so a bundle that fails to land loses the record, never the answer.
@@ -100,6 +134,11 @@ results, so a bundle that fails to land loses the record, never the answer.
100
134
  **re-runs the whole check list**. The fixer's own claim to have fixed it is
101
135
  ignored; only the re-run decides. Set it to 0 (the default) to disable.
102
136
 
137
+ Each round's fixer is a session in the checks' directory, with
138
+ `bypassPermissions`, given the first failing required check and its output tail,
139
+ framed as data. Fix-on-red runs only inside a workflow's verifier node:
140
+ `verify_run` runs the checks once and ignores the field.
141
+
103
142
  ## Protected tests
104
143
 
105
144
  A `command` check runs in the tree the agent just worked in, so an agent could make
@@ -119,30 +158,33 @@ whether it passed or not and runs again before every fixer round.
119
158
  symlink by its target), so the index, git attributes and filters, line-ending
120
159
  conversion and path quoting have no say.
121
160
  - **Counted as tests:** files under `__tests__/`, `__snapshots__/`, `test/`, `tests/`;
122
- `*.test.*` and `*.spec.*` scripts; `test_*.py`, `*_test.py`, `*_test.go`, `*_spec.rb`,
123
- `spec/spec_helper.rb`, `spec/rails_helper.rb`, `spec/support/`, `.rspec`;
124
- `*Test.java`/`.kt`/`.cs`.
161
+ JavaScript and TypeScript `*.test.*` and `*.spec.*` files; `test_*.py`, `*_test.py`,
162
+ `*_test.go`, `*_spec.rb`, `spec/spec_helper.rb`, `spec/rails_helper.rb`,
163
+ `spec/support/`; `*Test` and `*Tests` files in `.java`, `.kt` and `.cs`.
125
164
  - **Counted as test configuration:** vitest/jest/playwright/cypress config (including
126
- `.json`), `karma.conf.*`, `vitest.setup.*`/`jest.setup.*`/`setupTests.*`,
127
- `.mocharc*`, `pytest.ini`, `conftest.py`, `tox.ini`, `phpunit.xml`.
165
+ `.json`), `vitest.workspace.*`, `karma.conf.*`, `vitest.setup.*`/`jest.setup.*`/
166
+ `setupTests.*`, `.mocharc*`, `.rspec`, `pytest.ini`, `conftest.py`, `tox.ini`,
167
+ `phpunit.xml` and `phpunit.xml.dist`.
128
168
  - **In a `package.json`:** the `jest`, `mocha`, `ava`, `vitest`, `c8` and `nyc` keys,
129
- and every script the checks can reach — the `test`, `pretest` and `posttest` scripts,
130
- any script a contract command runs by name (`npm run verify`), and whatever those run
131
- in turn through `npm run`, `yarn`, `pnpm`, `run-s`/`run-p`, including their `pre`/`post`
132
- hooks. Other scripts, dependencies and key order may change.
169
+ and every script the checks can reach — `test`, `pretest`, `posttest` and any
170
+ `test:*` script (with its `pre`/`post` forms), any script a contract command runs by
171
+ name (`npm run verify`), and whatever those run in turn through `npm run`, `yarn`,
172
+ `pnpm`, `bun`, `npm-run-all` or `run-s`/`run-p`, including their `pre`/`post` hooks.
173
+ Other scripts, dependencies and key order may change.
133
174
  - **Allowed:** adding test files, packages, and test configuration that governs no test
134
175
  the run started with (a new package with its own `jest.config.ts`). New configuration
135
- in a directory that holds such a test — a `conftest.py` beside existing tests — is
176
+ in a directory at or above such a test — a `conftest.py` beside existing tests — is
136
177
  refuted, since it changes what they do.
137
178
  - **Not considered:** installed dependencies (`node_modules/`, `vendor/`,
138
179
  `site-packages/`, `.venv/`), which ship their own tests and configs.
139
180
  - **Opting out:** set `"protectTests": false` when changing existing tests is the task.
140
181
  - **Where it applies:** kernel runs in a git repository with a `command` check that
141
182
  protects tests, including a subflow's verifier, which uses its parent run's record.
142
- The record is taken only for such runs. A run created before this check existed
143
- reports it as not checked (not required), and so does a verifier running outside the
144
- recorded repository. `verify_run`, UltraApp's build gate and Autoloop's gate have no
145
- record of the tree before the work and are not covered.
183
+ The record is taken only for such runs and for runs with a `subflow` node. A run in
184
+ a repository that has no record (created before this check existed, or one git could
185
+ not list) reports it as not checked (not required), and so does a verifier running
186
+ outside the recorded repository. `verify_run`, UltraApp's build gate and Autoloop's
187
+ gate have no record of the tree before the work and are not covered.
146
188
  - **What it does not catch:** tests inside source files (Rust `#[cfg(test)]`), test
147
189
  settings inside general config (`vite.config.*`, `pyproject.toml`, `setup.cfg`),
148
190
  files hidden by `.gitignore`, and source code that special-cases the test
@@ -150,16 +192,15 @@ whether it passed or not and runs again before every fixer round.
150
192
 
151
193
  ## Per-mode defaults
152
194
 
153
- | Mode | Contract | Notes |
154
- | ------------------------ | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
155
- | **UltraApp** | **On by default** | Build: `npm install` → `npm run build` → `npm test` → (`docker build`) → **`npm run smoke`**. Deploy: **screenshots at 1440×900 and 375×812** against the live URL. |
156
- | **Council** | Caller-declared | Consensus votes are recorded on the run as advisory and do not decide completion. |
157
- | **Autoloop** | Not yet exposed | Library-level only (the dispatcher's `contract` option); `autoloop_start` and the HTTP API do not accept one. See [`autoloop.md`](./autoloop.md#acceptance-contracts). |
158
- | **Fanout / Ultrareview** | Caller-declared | Per-agent `ok` is the engine's terminal verdict for its turn. |
159
- | **Plain sessions** | None | Use `verify_run` to check work after the fact. |
160
-
161
- UltraApp's build gate includes `npm run smoke`, and its deploy gate captures
162
- screenshots at 1440×900 and 375×812 against the live URL.
195
+ | Mode | Contract | Notes |
196
+ | --------------------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
197
+ | **UltraApp** | **On by default** | Build: `npm install` → `npm run build` → `npm test` → (`docker build`) → **`npm run smoke`**, with up to 5 fix-on-red rounds. Deploy: **screenshots at 1440×900 and 375×812** against the live URL. |
198
+ | **`solve` / `goal`** | Caller-declared | `workflow_start`'s `contract`, or `--check` / `--contract` on `clawo solve` and `clawo goal`. Without one the `verify` node passes through and the run ends `unverified`. |
199
+ | **Council** | Caller-declared | Through `workflow_start` (`template: "council"` or a spec); `council_start` takes none. Consensus votes are recorded on the run as advisory and do not decide completion. |
200
+ | **Fanout** | Caller-declared | Through `workflow_start` (`template: "fanout"` or a spec) or `clawo fanout --check`; `fanout_start` takes none. Per-agent `ok` is the engine's terminal verdict for its turn. |
201
+ | **Ultrareview / Ultraplan** | None | `ultrareview_start` and `ultraplan_start` take no contract. |
202
+ | **Autoloop** | Not yet exposed | Library-level only (the dispatcher's `contract` option); `autoloop_start` and the HTTP API do not accept one. See [`autoloop.md`](./autoloop.md#acceptance-contracts). |
203
+ | **Plain sessions** | None | Use `verify_run` to check work after the fact. |
163
204
 
164
205
  The visual gate is **advisory by default** so that a host without Chrome does not
165
206
  lose a working app to a missing browser. Set
@@ -175,6 +216,14 @@ verify_run({ cwd: "/repo", contract: { checks: [{ type: "command", cmd: "npm", a
175
216
  Use it for a plain `session_send` that edited a repo, or for anything from an
176
217
  older version. The contract is yours; nothing is read from agent output.
177
218
 
219
+ `verify_run` is a tool (plugin and MCP); it has no HTTP route or CLI command. It
220
+ runs the checks once — no fix-on-red, no protected-tests check — and returns the
221
+ bundle. Pass `baseSha` so `diff_policy` sees tracked changes, and `label` to name
222
+ the stored evidence: the bundle is written to
223
+ `~/.claw-orchestrator/wf/<label>/evidence/verify-01/`, and `clawo verify <label>`
224
+ reads it back. A later call with the same label writes over it. It writes no ledger
225
+ row and starts no workflow run.
226
+
178
227
  ## Three outcomes, not two
179
228
 
180
229
  A run ends `verified`, `refuted`, or `unverified`.
@@ -183,14 +232,21 @@ A run ends `verified`, `refuted`, or `unverified`.
183
232
  is not a failure and it is not a pass. `clawo runs` prints it as `—` and
184
233
  `clawo workflow list` as `unchecked`, and the `clawo runs` summary line says so in
185
234
  words, because collapsing it into either bucket would let an unchecked run read
186
- as a checked one. A verdict that expired (see below) also reads `unverified`.
235
+ as a checked one. A verdict that expired (see below) also reads `unverified`, and
236
+ so does a run that declared a contract and ended without running it — that run
237
+ ends `failed`.
187
238
 
188
239
  ## What the guarantee is, precisely
189
240
 
190
241
  - The checks are run by the runtime, and their exit codes are read by the
191
242
  runtime. No part of the verdict is an agent's report about itself.
192
- - A run carrying a contract cannot reach `completed` unless every required check
193
- passed.
243
+ - A run whose verdict is `refuted` ends `failed`, never `completed`, and a run
244
+ ends `completed` / `verified` only when every required check of its last
245
+ verification passed. A run-level contract appends the terminal verifier
246
+ `__verify`, which every completed run passes through; a spec that places its own
247
+ `"contract": "run"` verifier has to route every path through it — a run that a
248
+ router steered past it ends `failed` (outcome `unverified`), see
249
+ [`workflow.md`](./workflow.md#completion).
194
250
  - If anything that can touch the workspace runs after the checks and the tree's
195
251
  **content** changes, the verdict expires: the outcome drops to `unverified`
196
252
  with the reason recorded. Not `refuted` — no check failed.