@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.
- package/README.md +160 -135
- package/configs/autoloop-reviewer-prompt.md +1 -1
- package/configs/engines/README.md +31 -11
- package/dist/bin/acp-server.js +7 -12
- package/dist/bin/acp-server.js.map +1 -1
- package/dist/bin/cli.js +71 -40
- package/dist/bin/cli.js.map +1 -1
- package/dist/bin/mcp-server.js +2 -54
- package/dist/bin/mcp-server.js.map +1 -1
- package/dist/src/acp-server.d.ts +17 -9
- package/dist/src/acp-server.js +149 -30
- package/dist/src/acp-server.js.map +1 -1
- package/dist/src/autoloop/agent-tools.d.ts +2 -0
- package/dist/src/autoloop/agent-tools.js +7 -0
- package/dist/src/autoloop/agent-tools.js.map +1 -1
- package/dist/src/autoloop/dispatcher.d.ts +6 -0
- package/dist/src/autoloop/dispatcher.js +23 -1
- package/dist/src/autoloop/dispatcher.js.map +1 -1
- package/dist/src/autoloop/planner-tools.js +2 -0
- package/dist/src/autoloop/planner-tools.js.map +1 -1
- package/dist/src/council.d.ts +8 -0
- package/dist/src/council.js +45 -12
- package/dist/src/council.js.map +1 -1
- package/dist/src/dashboard/index.html +28 -14
- package/dist/src/embedded-server.js +27 -8
- package/dist/src/embedded-server.js.map +1 -1
- package/dist/src/engine-spawn.js +3 -1
- package/dist/src/engine-spawn.js.map +1 -1
- package/dist/src/fanout.d.ts +13 -3
- package/dist/src/fanout.js +10 -0
- package/dist/src/fanout.js.map +1 -1
- package/dist/src/inbox-manager.d.ts +9 -0
- package/dist/src/inbox-manager.js +8 -2
- package/dist/src/inbox-manager.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +117 -51
- package/dist/src/index.js.map +1 -1
- package/dist/src/kernel/conditions.d.ts +1 -1
- package/dist/src/kernel/conditions.js +1 -1
- package/dist/src/kernel/engine.d.ts +28 -8
- package/dist/src/kernel/engine.js +93 -19
- package/dist/src/kernel/engine.js.map +1 -1
- package/dist/src/kernel/nodes/agent.js +2 -0
- package/dist/src/kernel/nodes/agent.js.map +1 -1
- package/dist/src/kernel/nodes/council.js +9 -4
- package/dist/src/kernel/nodes/council.js.map +1 -1
- package/dist/src/kernel/nodes/fanout.js +5 -0
- package/dist/src/kernel/nodes/fanout.js.map +1 -1
- package/dist/src/kernel/nodes/goal.d.ts +8 -1
- package/dist/src/kernel/nodes/goal.js +40 -19
- package/dist/src/kernel/nodes/goal.js.map +1 -1
- package/dist/src/kernel/nodes/subflow.js +9 -1
- package/dist/src/kernel/nodes/subflow.js.map +1 -1
- package/dist/src/kernel/nodes/ultraapp.d.ts +1 -0
- package/dist/src/kernel/nodes/ultraapp.js +1 -0
- package/dist/src/kernel/nodes/ultraapp.js.map +1 -1
- package/dist/src/kernel/nodes/verifier.js +5 -1
- package/dist/src/kernel/nodes/verifier.js.map +1 -1
- package/dist/src/kernel/projections.d.ts +1 -0
- package/dist/src/kernel/projections.js +7 -1
- package/dist/src/kernel/projections.js.map +1 -1
- package/dist/src/kernel/templates/index.d.ts +6 -3
- package/dist/src/kernel/templates/index.js +8 -3
- package/dist/src/kernel/templates/index.js.map +1 -1
- package/dist/src/kernel/types.d.ts +30 -10
- package/dist/src/kernel/types.js.map +1 -1
- package/dist/src/logger.d.ts +6 -0
- package/dist/src/logger.js +14 -0
- package/dist/src/logger.js.map +1 -1
- package/dist/src/mcp-annotations.d.ts +13 -0
- package/dist/src/mcp-annotations.js +100 -0
- package/dist/src/mcp-annotations.js.map +1 -0
- package/dist/src/models.d.ts +7 -0
- package/dist/src/models.js +9 -0
- package/dist/src/models.js.map +1 -1
- package/dist/src/openai-compat.d.ts +11 -5
- package/dist/src/openai-compat.js +55 -54
- package/dist/src/openai-compat.js.map +1 -1
- package/dist/src/persistent-acp-session.d.ts +7 -0
- package/dist/src/persistent-acp-session.js +26 -2
- package/dist/src/persistent-acp-session.js.map +1 -1
- package/dist/src/persistent-codex-app-session.d.ts +2 -0
- package/dist/src/persistent-codex-app-session.js +18 -9
- package/dist/src/persistent-codex-app-session.js.map +1 -1
- package/dist/src/persistent-codex-session.d.ts +14 -3
- package/dist/src/persistent-codex-session.js +22 -19
- package/dist/src/persistent-codex-session.js.map +1 -1
- package/dist/src/persistent-custom-session.d.ts +9 -0
- package/dist/src/persistent-custom-session.js +41 -21
- package/dist/src/persistent-custom-session.js.map +1 -1
- package/dist/src/persistent-grok-session.js +1 -1
- package/dist/src/persistent-grok-session.js.map +1 -1
- package/dist/src/run-ledger.d.ts +3 -3
- package/dist/src/session-manager.d.ts +34 -1
- package/dist/src/session-manager.js +160 -30
- package/dist/src/session-manager.js.map +1 -1
- package/dist/src/types.d.ts +34 -11
- package/dist/src/types.js.map +1 -1
- package/dist/src/ultraapp/build.d.ts +5 -11
- package/dist/src/ultraapp/build.js +5 -11
- package/dist/src/ultraapp/build.js.map +1 -1
- package/dist/src/ultraapp/council-adapter.d.ts +5 -2
- package/dist/src/ultraapp/council-adapter.js +6 -6
- package/dist/src/ultraapp/council-adapter.js.map +1 -1
- package/dist/src/ultraapp/files.d.ts +7 -0
- package/dist/src/ultraapp/files.js +7 -1
- package/dist/src/ultraapp/files.js.map +1 -1
- package/dist/src/ultraapp/manager.d.ts +23 -3
- package/dist/src/ultraapp/manager.js +170 -72
- package/dist/src/ultraapp/manager.js.map +1 -1
- package/dist/src/ultraapp/narrator.js +1 -1
- package/dist/src/ultraapp/narrator.js.map +1 -1
- package/dist/src/ultraapp/versions.d.ts +27 -5
- package/dist/src/ultraapp/versions.js +58 -13
- package/dist/src/ultraapp/versions.js.map +1 -1
- package/openclaw.plugin.json +4 -13
- package/package.json +13 -6
- package/skills/SKILL.md +52 -24
- package/skills/references/acp.md +140 -61
- package/skills/references/autoloop.md +141 -35
- package/skills/references/claude-cli-tracking.md +8 -6
- package/skills/references/cli.md +122 -93
- package/skills/references/council.md +44 -22
- package/skills/references/dashboard.md +37 -21
- package/skills/references/getting-started.md +49 -15
- package/skills/references/inbox.md +13 -8
- package/skills/references/mcp.md +25 -17
- package/skills/references/multi-engine.md +161 -145
- package/skills/references/observability.md +90 -58
- package/skills/references/openai-compat.md +172 -82
- package/skills/references/sessions.md +101 -65
- package/skills/references/tools.md +209 -158
- package/skills/references/ultra.md +7 -7
- package/skills/references/ultraapp.md +101 -56
- package/skills/references/verification.md +112 -56
- package/skills/references/workflow.md +227 -93
- 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
|
|
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
|
|
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/
|
|
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
|
|
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/ #
|
|
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
|
-
|
|
136
|
-
|
|
|
137
|
-
|
|
|
138
|
-
| `
|
|
139
|
-
| `
|
|
140
|
-
| `
|
|
141
|
-
| `POST /ultraapp/<id>/
|
|
142
|
-
| `POST /ultraapp/<id>/
|
|
143
|
-
| `
|
|
144
|
-
| `
|
|
145
|
-
| `POST /ultraapp/<id>/build
|
|
146
|
-
| `
|
|
147
|
-
| `
|
|
148
|
-
| `POST /ultraapp/<id>/
|
|
149
|
-
| `POST /ultraapp/<id>/
|
|
150
|
-
| `POST /ultraapp/<id>/
|
|
151
|
-
| `POST /ultraapp/<id>/
|
|
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
|
-
|
|
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).
|
|
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 |
|
|
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
|
|
179
|
-
`ultraapp_promote_version` (MCP)
|
|
180
|
-
|
|
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 (
|
|
190
|
-
├── image-batch-resize.jsonl (batch upload +
|
|
191
|
-
├── vlog-cut.jsonl (ffmpeg
|
|
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 (
|
|
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
|
|
205
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
266
|
-
|
|
267
|
-
|
|
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
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
25
|
-
| `http` |
|
|
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 |
|
|
28
|
-
| `file` | Checks a path
|
|
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
|
-
|
|
52
|
-
and
|
|
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 `
|
|
62
|
-
`PATH`. A host with no browser fails the check
|
|
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
|
|
71
|
-
|
|
72
|
-
|
|
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
|
|
80
|
-
|
|
81
|
-
|
|
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,
|
|
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
|
|
118
|
+
shot-<label>.png screenshots, when a screenshot check ran
|
|
89
119
|
```
|
|
90
120
|
|
|
91
|
-
|
|
92
|
-
`
|
|
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.*`
|
|
123
|
-
`spec/spec_helper.rb`, `spec/rails_helper.rb`,
|
|
124
|
-
`*Test
|
|
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
|
|
127
|
-
`.mocharc*`, `pytest.ini`, `conftest.py`, `tox.ini`,
|
|
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 —
|
|
130
|
-
any script a contract command runs by
|
|
131
|
-
in turn through `npm run`, `yarn`,
|
|
132
|
-
|
|
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
|
|
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
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
|
154
|
-
|
|
|
155
|
-
| **UltraApp**
|
|
156
|
-
|
|
|
157
|
-
| **
|
|
158
|
-
| **Fanout
|
|
159
|
-
| **
|
|
160
|
-
|
|
161
|
-
|
|
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
|
|
193
|
-
|
|
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.
|