@enderfga/claw-orchestrator 7.13.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 (143) hide show
  1. package/README.md +163 -137
  2. package/configs/autoloop-reviewer-prompt.md +1 -1
  3. package/configs/engines/README.md +45 -5
  4. package/configs/engines/dsh.json +17 -0
  5. package/configs/engines/kimi-code.json +17 -0
  6. package/dist/bin/acp-server.js +7 -12
  7. package/dist/bin/acp-server.js.map +1 -1
  8. package/dist/bin/cli.js +127 -67
  9. package/dist/bin/cli.js.map +1 -1
  10. package/dist/bin/mcp-server.js +2 -54
  11. package/dist/bin/mcp-server.js.map +1 -1
  12. package/dist/src/acp-server.d.ts +17 -9
  13. package/dist/src/acp-server.js +151 -30
  14. package/dist/src/acp-server.js.map +1 -1
  15. package/dist/src/autoloop/agent-tools.d.ts +2 -0
  16. package/dist/src/autoloop/agent-tools.js +7 -0
  17. package/dist/src/autoloop/agent-tools.js.map +1 -1
  18. package/dist/src/autoloop/dispatcher.d.ts +6 -0
  19. package/dist/src/autoloop/dispatcher.js +23 -1
  20. package/dist/src/autoloop/dispatcher.js.map +1 -1
  21. package/dist/src/autoloop/planner-tools.js +2 -0
  22. package/dist/src/autoloop/planner-tools.js.map +1 -1
  23. package/dist/src/council.d.ts +8 -0
  24. package/dist/src/council.js +45 -12
  25. package/dist/src/council.js.map +1 -1
  26. package/dist/src/dashboard/index.html +28 -14
  27. package/dist/src/embedded-server.js +34 -10
  28. package/dist/src/embedded-server.js.map +1 -1
  29. package/dist/src/engine-presets.d.ts +7 -3
  30. package/dist/src/engine-presets.js +39 -6
  31. package/dist/src/engine-presets.js.map +1 -1
  32. package/dist/src/engine-spawn.js +3 -1
  33. package/dist/src/engine-spawn.js.map +1 -1
  34. package/dist/src/fanout.d.ts +13 -3
  35. package/dist/src/fanout.js +10 -0
  36. package/dist/src/fanout.js.map +1 -1
  37. package/dist/src/inbox-manager.d.ts +9 -0
  38. package/dist/src/inbox-manager.js +8 -2
  39. package/dist/src/inbox-manager.js.map +1 -1
  40. package/dist/src/index.d.ts +2 -0
  41. package/dist/src/index.js +136 -52
  42. package/dist/src/index.js.map +1 -1
  43. package/dist/src/kernel/conditions.d.ts +1 -1
  44. package/dist/src/kernel/conditions.js +1 -1
  45. package/dist/src/kernel/engine.d.ts +28 -8
  46. package/dist/src/kernel/engine.js +93 -19
  47. package/dist/src/kernel/engine.js.map +1 -1
  48. package/dist/src/kernel/nodes/agent.js +2 -0
  49. package/dist/src/kernel/nodes/agent.js.map +1 -1
  50. package/dist/src/kernel/nodes/council.js +9 -4
  51. package/dist/src/kernel/nodes/council.js.map +1 -1
  52. package/dist/src/kernel/nodes/fanout.js +5 -0
  53. package/dist/src/kernel/nodes/fanout.js.map +1 -1
  54. package/dist/src/kernel/nodes/goal.d.ts +8 -1
  55. package/dist/src/kernel/nodes/goal.js +40 -19
  56. package/dist/src/kernel/nodes/goal.js.map +1 -1
  57. package/dist/src/kernel/nodes/subflow.js +9 -1
  58. package/dist/src/kernel/nodes/subflow.js.map +1 -1
  59. package/dist/src/kernel/nodes/ultraapp.d.ts +1 -0
  60. package/dist/src/kernel/nodes/ultraapp.js +1 -0
  61. package/dist/src/kernel/nodes/ultraapp.js.map +1 -1
  62. package/dist/src/kernel/nodes/verifier.js +5 -1
  63. package/dist/src/kernel/nodes/verifier.js.map +1 -1
  64. package/dist/src/kernel/projections.d.ts +1 -0
  65. package/dist/src/kernel/projections.js +7 -1
  66. package/dist/src/kernel/projections.js.map +1 -1
  67. package/dist/src/kernel/templates/index.d.ts +6 -3
  68. package/dist/src/kernel/templates/index.js +8 -3
  69. package/dist/src/kernel/templates/index.js.map +1 -1
  70. package/dist/src/kernel/types.d.ts +30 -10
  71. package/dist/src/kernel/types.js.map +1 -1
  72. package/dist/src/logger.d.ts +6 -0
  73. package/dist/src/logger.js +14 -0
  74. package/dist/src/logger.js.map +1 -1
  75. package/dist/src/mcp-annotations.d.ts +13 -0
  76. package/dist/src/mcp-annotations.js +100 -0
  77. package/dist/src/mcp-annotations.js.map +1 -0
  78. package/dist/src/models.d.ts +7 -0
  79. package/dist/src/models.js +9 -0
  80. package/dist/src/models.js.map +1 -1
  81. package/dist/src/openai-compat.d.ts +11 -5
  82. package/dist/src/openai-compat.js +55 -54
  83. package/dist/src/openai-compat.js.map +1 -1
  84. package/dist/src/persistent-acp-session.d.ts +83 -0
  85. package/dist/src/persistent-acp-session.js +433 -0
  86. package/dist/src/persistent-acp-session.js.map +1 -0
  87. package/dist/src/persistent-codex-app-session.d.ts +2 -0
  88. package/dist/src/persistent-codex-app-session.js +18 -9
  89. package/dist/src/persistent-codex-app-session.js.map +1 -1
  90. package/dist/src/persistent-codex-session.d.ts +14 -3
  91. package/dist/src/persistent-codex-session.js +22 -19
  92. package/dist/src/persistent-codex-session.js.map +1 -1
  93. package/dist/src/persistent-custom-session.d.ts +9 -0
  94. package/dist/src/persistent-custom-session.js +41 -21
  95. package/dist/src/persistent-custom-session.js.map +1 -1
  96. package/dist/src/persistent-grok-session.js +1 -1
  97. package/dist/src/persistent-grok-session.js.map +1 -1
  98. package/dist/src/run-ledger.d.ts +3 -3
  99. package/dist/src/session-manager.d.ts +34 -1
  100. package/dist/src/session-manager.js +169 -30
  101. package/dist/src/session-manager.js.map +1 -1
  102. package/dist/src/types.d.ts +63 -12
  103. package/dist/src/types.js +2 -0
  104. package/dist/src/types.js.map +1 -1
  105. package/dist/src/ultraapp/build.d.ts +5 -11
  106. package/dist/src/ultraapp/build.js +5 -11
  107. package/dist/src/ultraapp/build.js.map +1 -1
  108. package/dist/src/ultraapp/council-adapter.d.ts +5 -2
  109. package/dist/src/ultraapp/council-adapter.js +6 -6
  110. package/dist/src/ultraapp/council-adapter.js.map +1 -1
  111. package/dist/src/ultraapp/files.d.ts +7 -0
  112. package/dist/src/ultraapp/files.js +7 -1
  113. package/dist/src/ultraapp/files.js.map +1 -1
  114. package/dist/src/ultraapp/manager.d.ts +23 -3
  115. package/dist/src/ultraapp/manager.js +170 -72
  116. package/dist/src/ultraapp/manager.js.map +1 -1
  117. package/dist/src/ultraapp/narrator.js +1 -1
  118. package/dist/src/ultraapp/narrator.js.map +1 -1
  119. package/dist/src/ultraapp/versions.d.ts +27 -5
  120. package/dist/src/ultraapp/versions.js +58 -13
  121. package/dist/src/ultraapp/versions.js.map +1 -1
  122. package/openclaw.plugin.json +4 -13
  123. package/package.json +13 -6
  124. package/skills/SKILL.md +52 -24
  125. package/skills/references/acp.md +140 -57
  126. package/skills/references/autoloop.md +141 -35
  127. package/skills/references/claude-cli-tracking.md +9 -6
  128. package/skills/references/cli.md +129 -91
  129. package/skills/references/council.md +44 -22
  130. package/skills/references/dashboard.md +37 -21
  131. package/skills/references/getting-started.md +49 -15
  132. package/skills/references/inbox.md +13 -8
  133. package/skills/references/mcp.md +25 -17
  134. package/skills/references/multi-engine.md +180 -144
  135. package/skills/references/observability.md +90 -58
  136. package/skills/references/openai-compat.md +172 -82
  137. package/skills/references/sessions.md +101 -65
  138. package/skills/references/tools.md +209 -157
  139. package/skills/references/ultra.md +7 -7
  140. package/skills/references/ultraapp.md +101 -56
  141. package/skills/references/verification.md +112 -56
  142. package/skills/references/workflow.md +227 -93
  143. package/assets/banner.jpg +0 -0
@@ -1,25 +1,30 @@
1
1
  # Agent Client Protocol (ACP)
2
2
 
3
- Claw Orchestrator can run **as an ACP agent**: a coding agent that any ACP client
4
- drives over JSON-RPC stdio. Zed, JetBrains IDEs, Neovim, Emacs and the VS Code ACP
5
- extension are clients; so is `dsh`, whose `@deepseek-ai/dsh-subagent-acp` provider
6
- spawns an arbitrary command as a subagent.
7
-
8
- The model selector spans several engines, and session modes select the
9
- orchestration shape: a single agent, a council, a planning pass, or a review.
3
+ Claw Orchestrator speaks ACP in both directions:
4
+
5
+ - **As an ACP agent** (`clawo acp`): a coding agent that any ACP client drives over
6
+ JSON-RPC stdio. Zed, JetBrains IDEs, Neovim, Emacs and the VS Code ACP extension
7
+ are clients; so is `dsh`, whose `@deepseek-ai/dsh-subagent-acp` provider spawns an
8
+ arbitrary command as a subagent. The model selector spans several engines, and
9
+ session modes select the orchestration shape: a single agent, a council, a
10
+ planning pass, or a review. Most of this page covers this direction.
11
+ - **As an ACP client** (the `acp` engine): the orchestrator drives an agent that
12
+ serves ACP, such as `kimi acp` or `dsh --profile acp`, as one of its own sessions.
13
+ See [The `acp` engine](#the-acp-engine-driving-other-acp-agents) below.
10
14
 
11
15
  ```bash
12
16
  clawo acp # or the dedicated binary:
13
17
  clawo-acp
14
18
  # without a global install:
15
- npx -y @enderfga/claw-orchestrator acp
19
+ npx -y -p @enderfga/claw-orchestrator clawo-acp
16
20
  ```
17
21
 
18
22
  Requires Node ≥ 22 and at least one engine CLI (`claude`, `codex`, `agy`,
19
23
  `grok`) installed and logged in.
20
24
 
21
25
  Both read the protocol from stdin and write it to stdout. **Stdout carries nothing
22
- but protocol frames** — all logging goes to stderr.
26
+ but protocol frames** — all logging goes to stderr. The agent exits when the client
27
+ closes its stdin, stopping the engine processes it started.
23
28
 
24
29
  ## Protocol version
25
30
 
@@ -31,13 +36,25 @@ Implemented: `initialize`, `authenticate`, `session/new`, `session/resume`,
31
36
  `session/list`, `session/prompt`, `session/set_mode`, `session/set_config_option`,
32
37
  `session/cancel`.
33
38
 
39
+ `session/new` requires an absolute `cwd`.
40
+
34
41
  ## Configuration
35
42
 
36
- | Env var | Default | Meaning |
37
- | ---------------------- | ------------------- | ----------------------------------------------------------------------------------------------- |
38
- | `CLAWO_ACP_MODEL` | `claude-sonnet-4-6` | Model (and therefore engine) for a new session |
39
- | `CLAWO_ACP_PERMISSION` | `acceptEdits` | `plan` \| `acceptEdits` \| `bypassPermissions`; an unrecognised value is ignored with a warning |
40
- | `OPENCLAW_LOG_LEVEL` | `info` | Set `warn` to quieten the stderr channel |
43
+ | Env var | Default | Meaning |
44
+ | ----------------------------- | ------------------- | ----------------------------------------------------------------------------------------------- |
45
+ | `CLAWO_ACP_MODEL` | `claude-sonnet-4-6` | Model (and therefore engine) for a new session |
46
+ | `CLAWO_ACP_PERMISSION` | `acceptEdits` | `plan` \| `acceptEdits` \| `bypassPermissions`; an unrecognised value is ignored with a warning |
47
+ | `OPENCLAW_SERVE_MAX_SESSIONS` | `32` | Maximum number of engine sessions running at once |
48
+ | `OPENCLAW_SERVE_TTL_MINUTES` | `60` | Idle minutes before an engine session is stopped |
49
+ | `OPENCLAW_LOG_LEVEL` | `info` | Set `warn` to quieten the stderr channel |
50
+
51
+ ## Prompt content
52
+
53
+ Only `text` and `resource_link` blocks are read from a prompt. `text` blocks are
54
+ concatenated; a `resource_link` becomes a textual `[resource_link name=… uri=…]`
55
+ reference that the engine may open with its own tools; other blocks are dropped
56
+ (`initialize` advertises no image, audio or embedded-context support). A prompt
57
+ that yields no text this way is refused with `invalid params`.
41
58
 
42
59
  ## Session modes
43
60
 
@@ -52,12 +69,15 @@ slash command, advertised through `available_commands_update` when the session o
52
69
 
53
70
  That is the only way to reach a mode in a client that does not draw the picker.
54
71
 
55
- | Mode | What a turn does |
56
- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
57
- | `single` | One engine answers. Text streams as `agent_message_chunk`; tool use becomes `tool_call`. |
58
- | `council` | Several engines debate in isolated git worktrees. Each agent becomes a `tool_call` the client can collapse, each round emits a `plan`, and the synthesis arrives as text. |
59
- | `ultraplan` | Long-horizon planning pass. Result arrives as a `plan` update and as text. |
60
- | `ultrareview` | Parallel reviewers sweep the working tree; findings arrive the same way. |
72
+ | Mode | What a turn does |
73
+ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
74
+ | `single` | One engine answers. Text streams as `agent_message_chunk`; tool use becomes `tool_call`. The turn ends with a `usage_update` (context used and size, session cost) when the model's context window is known. |
75
+ | `council` | Several engines debate in isolated git worktrees. Each agent becomes a `tool_call` the client can collapse, each round emits a `plan`, and the synthesis arrives as text. |
76
+ | `ultraplan` | Long-horizon planning pass. Result arrives as a `plan` update and as text. |
77
+ | `ultrareview` | Parallel reviewers sweep the working tree; findings arrive the same way. |
78
+
79
+ A `council`, `ultraplan` or `ultrareview` turn gives up after 30 minutes; a council
80
+ is aborted at that point.
61
81
 
62
82
  ### Council
63
83
 
@@ -72,28 +92,37 @@ Treat council as a deliberate action, not a default.
72
92
  Agent deltas are buffered per agent and delivered on that agent's `tool_call_update`
73
93
  rather than streamed into the text channel. Several agents speak at once, and a
74
94
  consumer that only reads text — `dsh`'s ACP subagent reads nothing else — would
75
- otherwise receive them interleaved into one unreadable blob.
76
-
77
- Council requires a git repository at the session `cwd`; it creates one worktree and
78
- branch per agent. Its guardrails (non-git directory, too-short task, and others)
79
- surface as an `invalid params` error naming the reason.
95
+ otherwise receive them interleaved into one unreadable blob. An agent that fails a
96
+ round has its `tool_call_update` marked `failed` with the error; the council carries
97
+ on with the others.
98
+
99
+ Council works in git at the session `cwd`: a directory that is not a repository, or
100
+ has no commits yet, is initialised and given an initial commit of what it holds,
101
+ and each agent gets its own worktree under `.worktrees/` and a `council/<agent>`
102
+ branch. A run that fails before any agent answers — a too-short task, a `cwd`
103
+ inside the orchestrator's own install directory, a worktree that cannot be
104
+ created — ends the prompt with an `invalid params` error naming the reason. A run
105
+ that fails later reports the reason as text and ends with stop reason `refusal`.
80
106
 
81
107
  **Consensus parks the run rather than finishing it.** The turn ends at the gate, and
82
- the decision becomes a slash command, advertised through `available_commands_update`:
108
+ the decision becomes a slash command, advertised through `available_commands_update`.
109
+ Neither command merges anything: the agents merge their own work into the main branch
110
+ during the rounds.
83
111
 
84
- | Command | Effect |
85
- | ---------------------------- | ----------------------------------------------------------- |
86
- | `/council_accept` | Merge the winning agent's worktree. |
87
- | `/council_reject <feedback>` | Discard the result; the text is passed through as feedback. |
112
+ | Command | Effect |
113
+ | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
114
+ | `/council_accept` | Accept the work the agents have merged on the main branch, and clean up: the worktrees, the `council/*` branches, `plan.md` and `reviews/` are removed. |
115
+ | `/council_reject [<feedback>]` | Reject the work: `plan.md` is rewritten with the feedback and committed. Worktrees and branches are kept so the council can retry. Feedback is optional. |
88
116
 
89
- Any other prompt while a council is parked is refused, so a second run cannot start
90
- over the same worktrees.
117
+ Any other prompt while a council is parked is answered with a reminder of these two
118
+ commands and runs nothing, so a second run cannot start over the same worktrees.
119
+ Either command with no council parked is refused with `invalid params`.
91
120
 
92
121
  ### Ultraplan and ultrareview
93
122
 
94
123
  Neither emits events — they are started and then polled — so progress is reported as
95
- `agent_thought_chunk` heartbeats. Ultraplan has **no abort path at all**, so
96
- cancelling one abandons the poll rather than stopping the work.
124
+ `agent_thought_chunk` heartbeats. The agent does not stop either run on a cancel:
125
+ cancelling one abandons the poll, and the run carries on.
97
126
 
98
127
  ## Cross-engine model selector
99
128
 
@@ -106,20 +135,22 @@ another engine starts a new one, since one engine cannot read another's.
106
135
 
107
136
  Other engines are absent for different reasons:
108
137
 
109
- - **`gemini`** — the Gemini CLI is sunset and superseded by Antigravity. It still
110
- works for callers that name it directly; it is not offered in a new picker.
111
- - **`cursor`** — a legacy engine, superseded by Grok Build in this lineup. Same
112
- treatment: callers that name it still work.
113
- - **`opencode`** — its models are open-ended `provider/model` strings passed
114
- straight through, so there is nothing in the registry to enumerate. An opencode
115
- session is reachable by naming the model, just not by picking it from this list.
138
+ - **`gemini`** and **`cursor`** — legacy engines (the Gemini CLI is sunset and
139
+ superseded by Antigravity; Cursor is superseded by Grok Build in this lineup).
140
+ Their models are filtered out of the list; a model name that routes to one, set
141
+ through `CLAWO_ACP_MODEL`, still works.
142
+ - **`opencode`** — its models are open-ended `provider/model` strings with nothing
143
+ in the registry to enumerate, and no model name routes to it (a `provider/model`
144
+ string resolves to the Claude Code engine), so the ACP agent does not start
145
+ opencode sessions.
146
+ - **`acp`** — an ACP agent picks its own model.
116
147
 
117
148
  ## Generated images
118
149
 
119
150
  When the engine generates an image in a turn (Codex does, when asked for one), the agent names the
120
151
  file's path in a text chunk and then sends the image itself as an `image` content block, so a client
121
- that renders images shows it and one that does not still gets the path. Files over 8 MB are named
122
- only.
152
+ that renders images shows it and one that does not still gets the path. Files over 8 MB, and files
153
+ that are not PNG, JPEG, WebP or GIF, are named only.
123
154
 
124
155
  ## Resuming sessions
125
156
 
@@ -131,7 +162,9 @@ Sessions outlive the agent process. `initialize` advertises
131
162
  - **`session/resume`** reattaches a session by id after the editor or the agent
132
163
  restarts, on the engine and model it was using, and the next prompt continues
133
164
  its conversation. The `cwd` must be the one the session was created in — the
134
- engines keep conversations per directory.
165
+ engines keep conversations per directory. A session resumed into a new agent
166
+ process starts from the default permission setting and mode again
167
+ (`CLAWO_ACP_PERMISSION`, `single`).
135
168
 
136
169
  `session/load` is not offered (`loadSession: false`): it requires replaying the
137
170
  earlier conversation to the client, and no engine returns its history in a form
@@ -139,6 +172,11 @@ that can be replayed. A resumed session therefore opens with an empty view in th
139
172
  client while the engine still remembers it. Sessions are kept for 7 days after
140
173
  their last use.
141
174
 
175
+ Within a running agent, the session layer stops an engine session that has sat
176
+ idle for `OPENCLAW_SERVE_TTL_MINUTES` (default 60) and keeps its resume handle. The
177
+ next prompt or `session/resume` on that ACP session starts it again on the same
178
+ engine, model and permission, and the conversation continues.
179
+
142
180
  ## Permission
143
181
 
144
182
  ACP can ask the user mid-turn through `session/request_permission`. This agent does
@@ -147,20 +185,36 @@ permission is resolved once into engine CLI flags at session start, and
147
185
  `permissionPromptTool` routes to an MCP tool the caller hosts rather than back
148
186
  through the manager. Offering a prompt that cannot be honoured would be worse than
149
187
  saying so, so the choice is a session config option instead — which also suits
150
- `dsh-subagent-acp`, whose default is to auto-reject permission requests.
188
+ `dsh-subagent-acp`, whose default is to auto-reject permission requests. The option
189
+ accepts `plan`, `acceptEdits` and `bypassPermissions`; any other value is refused
190
+ with `invalid params` and the session is left as it was. Changing it restarts the
191
+ underlying session on the same engine, which continues the conversation.
151
192
 
152
193
  ## Cancellation
153
194
 
154
- `session/cancel` settles the in-flight prompt as `cancelled` immediately, then tears
155
- the underlying session down and recreates it. The recreated session resumes the
156
- engine's conversation from the saved resume handle. Codex, codex-app, agy, cursor and
157
- opencode report theirs during a turn, so a cancel in the very first turn of a session
158
- can still start a fresh conversation. A cancel with **nothing in flight does
159
- nothing**.
195
+ What `session/cancel` does depends on the mode:
196
+
197
+ - **`single`** — the in-flight prompt settles as `cancelled` immediately. The
198
+ underlying session is torn down and recreated, and the recreated session
199
+ resumes the engine's conversation from the saved resume handle. The handle
200
+ exists only once the engine has reported it, so a cancel in the very first turn
201
+ of a session can leave the next prompt on a fresh conversation.
202
+ - **`council`** — the council run is aborted, and the turn ends with the
203
+ council's status message and stop reason `end_turn` or `refusal`, not
204
+ `cancelled`.
205
+ - **`ultraplan`** / **`ultrareview`** — the poll, which checks every 3 seconds,
206
+ stops at its next check and the prompt settles as `cancelled`; the run carries
207
+ on.
208
+
209
+ A cancel with **nothing in flight does nothing**.
210
+
211
+ In council mode a cancel aborts the run, which removes its worktrees, and the prompt
212
+ ends with `cancelled`. In ultraplan and ultrareview mode it ends the prompt with
213
+ `cancelled` and abandons the poll.
160
214
 
161
215
  The session layer has **no mid-turn cancel** — `stopSession()` is the only lever and
162
- it destroys the session rather than pausing the turn. So the ACP turn returns
163
- promptly while the engine subprocess may take a moment longer to die, and any
216
+ it destroys the session rather than pausing the turn. So in `single` mode the ACP
217
+ turn returns promptly while the engine subprocess may take a moment longer to die, and any
164
218
  partial work in that turn is lost.
165
219
 
166
220
  ## Use from `dsh`
@@ -174,20 +228,49 @@ needed:
174
228
  config:
175
229
  providerName: clawo
176
230
  command: npx
177
- args: ['-y', '@enderfga/claw-orchestrator', 'acp']
231
+ args: ['-y', '-p', '@enderfga/claw-orchestrator', 'clawo-acp']
178
232
  permission: reject
179
233
  ```
180
234
 
235
+ With a global install, `command: clawo-acp` and `args: []` does the same.
236
+
181
237
  That provider collects **only `agent_message_chunk` text** into its result — it
182
238
  ignores tool calls and other updates — and runs a fresh subprocess per run with no
183
239
  parent context. The adapter therefore keeps the text stream self-sufficient: an
184
240
  engine that never streams still gets its whole answer emitted as one final chunk.
185
241
 
242
+ ## The `acp` engine: driving other ACP agents
243
+
244
+ In the other direction, `engine: 'acp'` starts an ACP agent as a session and drives
245
+ it as a client. One wrapper covers every such agent, with no per-agent parsing:
246
+
247
+ ```bash
248
+ clawo engines # lists the bundled presets
249
+ clawo session-start kimi -e acp --acp-agent kimi-code -d ./project
250
+ clawo session-send kimi "Fix the failing test in tests/test_io.py"
251
+ ```
252
+
253
+ - `acpAgent` names the agent: a bundled preset id (`kimi-code`, `dsh`), or, from a
254
+ local caller only, an inline config (`name`, `command`, and optionally
255
+ `commandEnv`, `args`, `env`, `contextWindow`, `pricing`).
256
+ - One agent process per session; each send is one `session/prompt` on one ACP
257
+ session. The client advertises no `fs` or `terminal` capability, so the agent
258
+ reads, writes and runs commands with its own tools.
259
+ - `session/request_permission` is answered at once from the session's permission
260
+ mode: `bypassPermissions`, `acceptEdits`, `auto` and `dontAsk` allow, any other
261
+ mode (such as `plan`) rejects.
262
+ - A resumed session reattaches the agent's own session id with `session/resume`, or
263
+ `session/load` when that is all the agent offers.
264
+
265
+ The full reference — turn outcomes, cost, each preset's environment — is in
266
+ [multi-engine.md](./multi-engine.md#acp-agents-engine-acp).
267
+
186
268
  ## Development notes
187
269
 
188
- Unit tests cover the translation helpers and the config-option shapes
189
- (`src/__tests__/acp-server.test.ts`). They cannot catch a corrupted frame
190
- stream, so also drive the real binary:
270
+ Unit tests cover the translation helpers, the config-option shapes, and session
271
+ and council behaviour over an in-process client connection
272
+ (`src/__tests__/acp-server.test.ts`, `src/__tests__/acp-session-resume.test.ts`).
273
+ They cannot catch a corrupted frame stream, so also drive the real binary:
191
274
 
192
275
  ```bash
193
276
  npm run build
@@ -28,12 +28,14 @@ This page is the operator reference.
28
28
  | **Coder** | claude / sonnet | workspace | code changes, eval execution |
29
29
  | **Reviewer** | claude / sonnet | `<workspace>/tasks/<run_id>/reviewer_sandbox/` | distrust audit; advance / hold / rollback |
30
30
 
31
- Each role can use any built-in engine, or a `custom` engine config supplied by a
32
- local caller (custom engines name an executable, so the HTTP API does not accept
33
- them — see [tools.md](./tools.md)). If a non-Claude role omits `model`, that CLI
34
- uses its own default model rather than receiving the Claude `opus` / `sonnet`
35
- defaults. Role instructions are included in-band for engines that do not expose a
36
- native system-prompt flag.
31
+ Each role can use any built-in engine except `acp`, or a `custom` engine config
32
+ supplied by a local caller (custom engines name an executable, so the HTTP API
33
+ does not accept them — see [tools.md](./tools.md)). `acp` is refused for every
34
+ role: a role is configured by engine name alone and has no field for the ACP
35
+ agent to start. If a non-Claude role omits `model`, that CLI uses its own default
36
+ model rather than receiving the Claude `opus` / `sonnet` defaults. Claude receives
37
+ each role's instructions as its system prompt; every other engine receives them
38
+ in-band, at the top of each message to that role.
37
39
 
38
40
  Each role also accepts an optional fixed reasoning effort at run start:
39
41
  `planner_effort`, `coder_effort`, and `reviewer_effort`. Accepted values are
@@ -46,14 +48,15 @@ their existing behavior.
46
48
  Engines without native multi-turn conversation (one-shot custom engines) spawn a
47
49
  fresh process per send with nothing to resume, so the dispatcher replays that
48
50
  role's transcript in-band as a `<conversation_history>` block, oldest turns dropped
49
- past a character budget. Claude, Codex, Antigravity, Grok and OpenCode each
50
- resume their own conversation by id and get no replay — see
51
+ past a character budget. Claude, Codex (`codex` and `codex-app`), Antigravity,
52
+ Grok and OpenCode each resume their own conversation and get no replay — see
51
53
  `engineHasNativeConversation` in `types.ts`, which is the single source of truth for
52
54
  this and is checked with a two-turn recall test per engine.
53
55
 
54
56
  The Planner runs read-only so strategy cannot turn into source edits, and that is
55
57
  enforced by the engine rather than requested politely: Claude uses plan mode,
56
- Antigravity uses its plan mode, and OpenCode gets a generated
58
+ Antigravity uses its plan mode, Codex and Grok run in their own read-only
59
+ sandbox, and OpenCode gets a generated
57
60
  `clawo-readonly` agent that denies `edit`/`bash`/`external_directory` (its built-in
58
61
  `plan` agent is a user-overridable preset that denies neither, so a "read-only"
59
62
  session could otherwise still author files through a shell heredoc). A custom
@@ -90,7 +93,9 @@ failing with "Session not found".
90
93
  ## UX flow
91
94
 
92
95
  ```
93
- 1. autoloop_start { run_id, workspace } → Planner session ready (refuses if the workspace Git tree already has tracked, staged, untracked, or submodule changes — including when repository status configuration hides them, `status.showUntrackedFiles=no`, or the porcelain listing is too large to buffer; unexpected `git status` failures also refuse; untracked (`??`) or index-clean worktree modifications (` M`) under `tasks/<id>/` with a regular `tasks/<id>/goal.json` are ignored so a second start can proceed after a committed ledger is appended, including when workspace is a nested repo subdirectory after stripping a verified `show-prefix`; dirt outside the workspace still refuses; ignored paths and non-repo workspaces still start). On a Git workspace, after that dirty check passes, start creates `tasks/<runId>/.gitignore` with `*` as the first ledger file (accepting an existing exact `*`, `*\n`, or `*\r\n` without overwrite), proves the ledger is ignored via `git check-ignore`, and refuses with one fixed generic error when the ledger is tracked, the ignore file is partial/deceptive, or `tasks`/`tasks/<runId>` is not a real in-workspace directory (e.g. a committed `tasks` symlink). Failed proofs and later start failures retain the run-local dirs/`.gitignore` (never deleted). Does not write `info/exclude` or any repository-wide ignore. The same fresh-start boundary runs on the first boot of a workflow `autoloop` node (`visits === 1`); `autoloopResume` / `workflowResume` and later visits skip it. Non-Git starts are unchanged.
96
+ 1. autoloop_start { run_id, workspace } → Planner session ready
97
+ (a Git workspace must be clean,
98
+ see below)
94
99
  2. autoloop_chat { run_id, "<your goal>" } → Planner reads workspace,
95
100
  drafts plan.md + goal.json,
96
101
  asks "ready to spawn?"
@@ -105,6 +110,38 @@ failing with "Session not found".
105
110
  Stopping at a target or at `max_iters` from `goal.json` is the Planner's
106
111
  decision: the runtime does not evaluate `goal.json`.
107
112
 
113
+ ### Starting on a Git workspace
114
+
115
+ On a Git workspace, `autoloop_start` refuses with _Autoloop workspace has
116
+ uncommitted changes; commit or stash them before starting_ when the worktree has
117
+ any tracked, staged, untracked or submodule change. Repository configuration
118
+ cannot hide a change from this check (`status.showUntrackedFiles=no` included),
119
+ and a status listing too large to buffer, or any unexpected `git status` failure,
120
+ also refuses. The one exception is an earlier run's ledger: untracked (`??`) or
121
+ index-clean modified (` M`) files under `tasks/<id>/` are ignored when
122
+ `tasks/<id>/goal.json` is a regular file, so a second run can start after a
123
+ committed ledger was appended to. This also holds when the workspace is a
124
+ subdirectory of the repository; changes outside the workspace still refuse.
125
+ Ignored paths and non-Git workspaces start normally.
126
+
127
+ Once that check passes, start keeps the run's ledger out of Git before writing
128
+ anything else to it:
129
+
130
+ - It creates `tasks/<runId>/.gitignore` containing `*` as the ledger's first
131
+ file. An existing file whose content is exactly `*`, `*\n` or `*\r\n` is
132
+ accepted without being overwritten.
133
+ - It proves with `git check-ignore` that the ledger is ignored.
134
+ - It refuses with the fixed error _Autoloop could not isolate the run ledger
135
+ from Git_ when the ledger is tracked, the ignore file holds anything else, or
136
+ `tasks` / `tasks/<runId>` is not a real directory inside the workspace (for
137
+ example a committed `tasks` symlink).
138
+
139
+ Nothing is deleted on failure: a failed proof or a later start failure leaves the
140
+ run's directories and `.gitignore` in place. Start never writes
141
+ `.git/info/exclude` or any repository-wide ignore. The same boundary runs on the
142
+ first boot of a workflow `autoloop` node (`visits === 1`); `autoloopResume` /
143
+ `workflowResume` and later visits skip it.
144
+
108
145
  ## Timeout hierarchy and recoverable sends
109
146
 
110
147
  Autoloop has three independent start-time controls. Their bounds are inclusive,
@@ -261,7 +298,8 @@ for the `CustomEngineConfig` shape.
261
298
  | on_decision_needed | decision / both |
262
299
 
263
300
  The runtime fires five of these itself: `on_stall_30min` (30 minutes without
264
- activity), `on_metric_regression_2`, `on_reviewer_reject_2` (two in a row),
301
+ activity), `on_metric_regression_2` (two `rollback` verdicts in a row),
302
+ `on_reviewer_reject_2` (two `hold` or `rollback` verdicts in a row),
265
303
  `on_phase_error`, and `on_target_hit` (only when an acceptance contract passes,
266
304
  see [Acceptance contracts](#acceptance-contracts)). `on_start`,
267
305
  `on_iter_done_ok` and `on_decision_needed` are policy entries for the Planner's
@@ -302,7 +340,8 @@ CLIs expose no such command. The threshold is still meaningful there because
302
340
  `contextPercent` tracks real occupancy, but crossing it cannot free space:
303
341
  the session emits a single warning on its log channel the first time compaction
304
342
  is requested, then the thread keeps growing until the CLI refuses the request.
305
- Treat that warning as the signal to start a fresh session.
343
+ Treat that warning as the signal to start a fresh session. `codex-app` does
344
+ compact, through the app-server's own thread compaction.
306
345
 
307
346
  ## Phase-error circuit
308
347
 
@@ -344,6 +383,8 @@ decisions:
344
383
  | `update_push_policy` | Planner mutates the policy |
345
384
  | `policy_silence_blocked` | Planner tried to silence a critical channel |
346
385
  | `phase_error` | Surfaced from dispatcher to runner |
386
+ | `send_timeout` | A role's send hit `send_timeout_ms`; run paused |
387
+ | `timeout_migration` | A resume raised `send_timeout_ms` (see above) |
347
388
  | `terminate` | Run ends (planner reason or `phase_error_circuit`) |
348
389
 
349
390
  JSONL, one entry per line, ts-prefixed.
@@ -353,19 +394,18 @@ JSONL, one entry per line, ts-prefixed.
353
394
  ```
354
395
  <workspace>/tasks/<run_id>/
355
396
  ├── .gitignore # run-local `*`; first file on Git start; keeps the ledger out of status/add
356
- ├── plan.md # Planner-authored (workspace-root copies are what iters commit)
357
- ├── goal.json # Planner-authored (workspace-root copies are what iters commit)
358
397
  ├── push_log.jsonl # every notify_user attempt + channel used
359
398
  ├── decisions.jsonl # runner / dispatcher audit trail (see above)
360
- ├── chat.jsonl # Planner-pane conversation, replayed by /chat_history
399
+ ├── chat.jsonl # chat for all three panes (user, Planner, Coder, Reviewer), replayed by /chat_history
400
+ ├── coder_log.jsonl # one row per Coder `coder_log` call: { ts, iter, message }
361
401
  ├── evidence/iter-<n>/ # acceptance-contract bundle, when a contract is configured
362
402
  ├── reviewer_sandbox/ # Reviewer cwd; restaged per iter
363
- │ ├── plan.md # copy
364
- │ ├── goal.json # copy
403
+ │ ├── plan.md # copy of the workspace-root file
404
+ │ ├── goal.json # copy of the workspace-root file
365
405
  │ ├── iter-N/ # this iter's directive + diff + eval
366
406
  │ ├── prior_verdict.json
367
407
  │ ├── reviewer_memory.md # persistent (frozen-injected at session start)
368
- │ └── reviewer_log.jsonl # persistent (Reviewer's append-only audit log)
408
+ │ └── reviewer_log.jsonl # persistent; one row per Reviewer `reviewer_log` call: { ts, iter, message }
369
409
  └── iter/<n>/
370
410
  ├── directive.json # Planner → Coder (schema_version: 1)
371
411
  ├── eval_output.json # what Coder reported (schema_version: 1)
@@ -374,9 +414,10 @@ JSONL, one entry per line, ts-prefixed.
374
414
  └── coder_summary.txt
375
415
  ```
376
416
 
377
- On a Git workspace, `autoloop_start` writes `.gitignore` before other ledger
378
- files and fails closed unless `git check-ignore` confirms the ledger is
379
- ignored. The run-local ignore is self-contained: it does not rely on
417
+ `plan.md` and `goal.json` live at the workspace root, where `write_plan` /
418
+ `write_goal` put them; the Reviewer gets copies in its sandbox. On a Git
419
+ workspace the ledger is kept out of Git by its own `.gitignore` (see
420
+ [Starting on a Git workspace](#starting-on-a-git-workspace)); it does not rely on
380
421
  `.git/info/exclude` or a root `.gitignore` entry for `tasks/`.
381
422
 
382
423
  The orchestrator git-commits each iter automatically. Coder must NOT call
@@ -393,18 +434,83 @@ Every JSON artifact in the ledger carries a `schema_version` field (currently
393
434
 
394
435
  ## Backend HTTP / SSE
395
436
 
396
- | Endpoint | Returns |
397
- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
398
- | `GET /autoloop/list` | `{ ok, runs: AutoloopState[] }` |
399
- | `POST /autoloop/new` | `{ ok, run_id, planner_session }` — body `{ workspace, run_id?, planner_engine?, planner_model?, coder_engine?, coder_model?, reviewer_engine?, reviewer_model?, send_timeout_ms?, activity_lease_ms?, autoloop_hard_timeout_ms? }`. Timeout fields use the defaults and inclusive bounds documented above; malformed or out-of-range values return 400 before a run starts. |
400
- | `GET /autoloop/<id>/state` | `{ ok, state: AutoloopState, live }` — `live` is `true` only when the run is running in this process. For a run that is not live here, `state` is the last state recorded in the run store, so historical runs open with their real iteration count. 404 when there is no such run. |
401
- | `GET /autoloop/<id>/push_log` | `{ ok, entries: PushLogEntry[] }` — served from the ledger via `autoloopStatus`, so historical runs work the same as live ones. |
402
- | `GET /autoloop/<id>/chat_history` | `{ ok, entries: ChatEntry[] }` — replays `<ledger>/chat.jsonl`. The dashboard fetches this when opening a run so the Planner-pane conversation survives a page refresh / cross-process / re-opening a terminated run. Returns `[]` when the file doesn't exist. |
403
- | `GET /autoloop/<id>/events` | SSE: `snapshot` / `message` / `state` / `push` / `iter_done` / `planner_reply` / `planner_error` / `coder_reply` / `reviewer_reply` / `terminated`. For runs that are NOT in this process's memory (terminated, or live in another process), the endpoint emits a single-shot `snapshot` + `terminated` then closes — the dashboard's existing handlers render history without hanging. A run still in memory that has already reached `terminated` or `crashed` gets the same single-shot pair instead of an open stream that would never receive another event. Every such stream sets `retry: 864000000`, so an `EventSource` does not keep reconnecting to a stream that can only end again. |
404
- | `POST /autoloop/<id>/chat` | **202** `{ ok, queued: true }` — body `{ text }`. Fire-and-forget: the Planner's reply streams back via the `/events` SSE channel as a `planner_reply` event (or `planner_error` on failure); the HTTP response intentionally does NOT wait for it, because first-contact replies routinely exceed reverse-proxy idle limits (e.g. Cloudflare Tunnel cuts at ~100s → 524). 400 on empty text. 404 when the run is not in this process's memory: if the store still holds it, the error says so and names `POST /autoloop/<id>/resume`; an unknown or malformed id is plain `not found`. The MCP `autoloop_chat` tool path keeps the synchronous await-and-return-reply semantics (it runs in-process). |
405
- | `GET /autoloop/<id>/resume-requirements` | `{ ok, runId, rolesNeedingCustomEngine }` — the roles whose engine was `custom`, so a caller knows which secret references a resume needs. Role names only; nothing sensitive. 404 when there is no such run. |
406
- | `POST /autoloop/<id>/resume` | `{ ok, state }` — for a live ordinary or activity-lease pause (`pending_dispatch` null), an empty timeout body continues that run: the response returns once status is `planning` or `running` again, the lease re-arms, the hard deadline stays put, and parked Planner input is then delivered once, in order. A live `awaiting_resume` pause is unchanged unless the body increases the send timeout. Otherwise restore the role engine/model choices from the run's spec and re-create dispatcher + runner. For recoverable send timeouts, body fields `send_timeout_ms` and `pending_dispatch_id` apply the increase-only migration described above; `allow_decrease`, lease overrides, and hard-cap overrides are rejected. A custom-engine config is never persisted and is never accepted over HTTP, so a role using `custom` is re-supplied by **reference**: `plannerCustomEngineRef` / `coderCustomEngineRef` / `reviewerCustomEngineRef` name an environment variable `CLAWO_CUSTOM_ENGINE_<NAME>` on the orchestrator host, which the server reads and resolves. The name is not sensitive, the value never crosses the wire, and an unknown name is an error rather than a silent start without credentials. Existing engine-specific conversation resume behavior is reused where supported; `chat.jsonl` remains the visual history fallback. 404 when there is no such run. |
407
- | `POST /autoloop/<id>/delete` | `{ ok }` — stops the loop if still live, deletes the run record from the run store, and purges the role sessions' persisted resume ids so the run cannot be resumed. The ledger directory under `<workspace>/tasks/<run_id>/` is kept on disk. 404 when there is no such run. |
437
+ | Endpoint | Returns |
438
+ | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
439
+ | `GET /autoloop/list` | `{ ok, runs: AutoloopState[] }` |
440
+ | `POST /autoloop/new` | `{ ok, run_id, planner_session }` — see [Starting a run](#starting-a-run). 409 when the workspace refuses the start (see [Starting on a Git workspace](#starting-on-a-git-workspace)). |
441
+ | `GET /autoloop/<id>/state` | `{ ok, state: AutoloopState, live }` — `live` is `true` only when the run is running in this process. For a run that is not live here, `state` is the last state recorded in the run store, so historical runs open with their real iteration count. 404 when there is no such run. |
442
+ | `GET /autoloop/<id>/push_log` | `{ ok, entries: PushLogEntry[] }` — served from the ledger via `autoloopStatus`, so historical runs work the same as live ones. |
443
+ | `GET /autoloop/<id>/chat_history` | `{ ok, entries: ChatEntry[] }` — replays `<ledger>/chat.jsonl`, so the dashboard's panes survive a page refresh, another process, or re-opening a terminated run. `[]` when the file doesn't exist. |
444
+ | `GET /autoloop/<id>/events` | SSE — see [Event stream](#event-stream). |
445
+ | `POST /autoloop/<id>/chat` | **202** `{ ok, queued: true }` — see [Chat over HTTP](#chat-over-http). |
446
+ | `GET /autoloop/<id>/resume-requirements` | `{ ok, runId, rolesNeedingCustomEngine }` — the roles whose engine was `custom`, so a caller knows which secret references a resume needs. Role names only; nothing sensitive. 404 when there is no such run. |
447
+ | `POST /autoloop/<id>/resume` | `{ ok, state }` — see [Resume over HTTP](#resume-over-http). 404 when there is no such run. |
448
+ | `POST /autoloop/<id>/delete` | `{ ok }` — stops the loop if still live, deletes the run record from the run store, and purges the role sessions' persisted resume ids so the run cannot be resumed. The ledger directory under `<workspace>/tasks/<run_id>/` is kept on disk. 404 when there is no such run. |
449
+
450
+ ### Starting a run
451
+
452
+ `POST /autoloop/new` takes `{ workspace, run_id?, planner_engine?, planner_model?,
453
+ planner_effort?, coder_engine?, coder_model?, coder_effort?, reviewer_engine?,
454
+ reviewer_model?, reviewer_effort?, send_timeout_ms?, activity_lease_ms?,
455
+ autoloop_hard_timeout_ms? }`. A `run_id` that is missing or does not match
456
+ `^[a-zA-Z0-9_-]{1,64}$` is replaced by a generated `auto-<timestamp>-<hex>`.
457
+ Timeout fields use the defaults and inclusive bounds documented above; malformed
458
+ or out-of-range values return 400 before a run starts. Custom-engine configs are
459
+ not accepted over HTTP, so a `custom` role can only be started by a local caller.
460
+
461
+ ### Event stream
462
+
463
+ `GET /autoloop/<id>/events` emits `snapshot` / `message` / `state` / `push` /
464
+ `iter_done` / `planner_reply` / `planner_error` / `coder_reply` /
465
+ `reviewer_reply` / `compact` / `terminated`.
466
+
467
+ For a run that is NOT in this process's memory (terminated, or live in another
468
+ process), the endpoint emits a single-shot `snapshot` + `terminated` then closes —
469
+ the dashboard's existing handlers render history without hanging. A run still in
470
+ memory that has already reached `terminated` or `crashed` gets the same
471
+ single-shot pair instead of an open stream that would never receive another
472
+ event. Every such stream sets `retry: 864000000`, so an `EventSource` does not
473
+ keep reconnecting to a stream that can only end again.
474
+
475
+ ### Chat over HTTP
476
+
477
+ `POST /autoloop/<id>/chat` takes `{ text }` and is fire-and-forget: the
478
+ Planner's reply streams back on `/events` as a `planner_reply` event (or
479
+ `planner_error` on failure). The HTTP response intentionally does not wait for
480
+ it, because first-contact replies routinely exceed reverse-proxy idle limits
481
+ (e.g. Cloudflare Tunnel cuts at ~100s → 524). The MCP `autoloop_chat` tool keeps
482
+ the synchronous await-and-return-reply semantics (it runs in-process).
483
+
484
+ 400 on empty text. 404 when the run is not in this process's memory: if the
485
+ store still holds it, the error says so and names `POST /autoloop/<id>/resume`;
486
+ an unknown or malformed id is plain `not found`.
487
+
488
+ ### Resume over HTTP
489
+
490
+ `POST /autoloop/<id>/resume` returns `{ ok, state }`.
491
+
492
+ - **A live ordinary or activity-lease pause** (`pending_dispatch` null): an empty
493
+ timeout body continues that run. The response returns once status is
494
+ `planning` or `running` again, the lease re-arms, the hard deadline stays put,
495
+ and parked Planner input is then delivered once, in order.
496
+ - **A live `awaiting_resume` pause** is unchanged unless the body increases the
497
+ send timeout.
498
+ - **Otherwise** the role engine/model choices are restored from the run's spec
499
+ and the dispatcher and runner are re-created. Existing engine-specific
500
+ conversation resume is reused where supported; `chat.jsonl` remains the visual
501
+ history fallback.
502
+
503
+ For recoverable send timeouts, body fields `send_timeout_ms` and
504
+ `pending_dispatch_id` apply the increase-only migration described above;
505
+ `allow_decrease`, lease overrides, and hard-cap overrides are rejected.
506
+
507
+ A custom-engine config is never persisted and never accepted over HTTP, so a role
508
+ using `custom` is re-supplied by **reference**: `plannerCustomEngineRef` /
509
+ `coderCustomEngineRef` / `reviewerCustomEngineRef` name an environment variable
510
+ `CLAWO_CUSTOM_ENGINE_<NAME>` on the orchestrator host, whose value is the JSON
511
+ custom-engine config; the server reads and resolves it. The name is not
512
+ sensitive, the value never crosses the wire, and an unknown name is an error
513
+ rather than a silent start without credentials.
408
514
 
409
515
  The dashboard's 3-pane autoloop view (`/dashboard`) consumes these endpoints:
410
516
 
@@ -519,8 +625,8 @@ retries it.
519
625
  - **One-way push.** Replies to a push are not routed back to the Planner;
520
626
  answer with `autoloop_chat`.
521
627
  - **No fork / population mode.** Single linear iter trajectory per run.
522
- - **Cross-run knowledge isolated.** Each run's `reviewer_memory.md` and
523
- `coder_notes.md` live in that run's ledger; there is no shared store.
628
+ - **Cross-run knowledge isolated.** Each run's `reviewer_memory.md` lives in
629
+ that run's ledger; there is no shared store.
524
630
  - **No cost cap.** `maxBudgetUsd` is not exposed for autoloop. Wall-clock time
525
631
  is bounded by `autoloop_hard_timeout_ms` (default 24 h); the iteration count
526
632
  is bounded only by the Planner honouring `max_iters` in `goal.json`.