total-recall-brain 3.44.0 → 3.45.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 (212) hide show
  1. package/bin/total-recall.mjs +6 -4
  2. package/docs/ARCHITECTURE.md +10 -7
  3. package/docs/reference/cli-reference.md +13 -8
  4. package/frontend/dist/assets/index-DACh1dhx.js +5613 -0
  5. package/frontend/dist/index.html +1 -1
  6. package/frontend/src/App.tsx +2 -2
  7. package/frontend/src/pages/PluginsPage.tsx +1 -1
  8. package/package.json +1 -1
  9. package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +7 -7
  10. package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +1 -1
  11. package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +1 -1
  12. package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +20 -1
  13. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-store.mjs +12 -1
  14. package/scaffold/.agent/skills/total-recall/plugins/automations/BRICK.md +7 -0
  15. package/scaffold/.agent/skills/total-recall/plugins/automations/LICENSE +21 -0
  16. package/scaffold/.agent/skills/total-recall/plugins/automations/README.md +66 -0
  17. package/scaffold/.agent/skills/total-recall/plugins/automations/adapters.mjs +231 -0
  18. package/scaffold/.agent/skills/total-recall/plugins/automations/capability/index.mjs +16 -0
  19. package/scaffold/.agent/skills/total-recall/plugins/automations/cli.mjs +121 -0
  20. package/{src/core/plugin-cron.mjs → scaffold/.agent/skills/total-recall/plugins/automations/cron.mjs} +1 -3
  21. package/scaffold/.agent/skills/total-recall/plugins/automations/inventory.mjs +88 -0
  22. package/scaffold/.agent/skills/total-recall/plugins/automations/package-lock.json +7661 -0
  23. package/scaffold/.agent/skills/total-recall/plugins/automations/package.json +35 -0
  24. package/scaffold/.agent/skills/total-recall/plugins/automations/plugin.json +167 -0
  25. package/scaffold/.agent/skills/total-recall/plugins/automations/scheduler.mjs +246 -0
  26. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/DESIGN.md +42 -0
  27. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/automations.js +2 -0
  28. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/card.js +273 -0
  29. package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/run-core.mjs +1 -1
  30. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/check.mjs +57 -5
  31. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report-data.mjs +40 -14
  32. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report.mjs +15 -40
  33. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +7 -7
  34. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +1 -1
  35. package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +1 -1
  36. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +7 -7
  37. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +1 -1
  38. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +1 -1
  39. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +7 -7
  40. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +1 -1
  41. package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +1 -1
  42. package/scaffold/.agent/skills/total-recall/plugins/git/README.md +112 -0
  43. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/cli.mjs +135 -23
  44. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package-lock.json +7 -7
  45. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package.json +2 -1
  46. package/scaffold/.agent/skills/total-recall/plugins/git/plugin.json +161 -0
  47. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/context.mjs +94 -0
  48. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/phase3-spec-support.mjs +50 -0
  49. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/runtime.mjs +40 -0
  50. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/spec-support.mjs +51 -0
  51. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/_support.mjs +195 -0
  52. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/auth.mjs +201 -0
  53. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/bisect.mjs +40 -0
  54. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/cli-meta.mjs +159 -0
  55. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit-memory.mjs +30 -0
  56. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit.mjs +46 -0
  57. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/fleet.mjs +156 -0
  58. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/force-push.mjs +58 -0
  59. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/group.mjs +119 -0
  60. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/handoff.mjs +59 -0
  61. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/message.mjs +22 -0
  62. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/resolve.mjs +60 -0
  63. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/rules.mjs +81 -0
  64. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/secret-firewall.mjs +66 -0
  65. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/undo.mjs +132 -0
  66. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/why.mjs +16 -0
  67. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/ui/card.js +2 -2
  68. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +7 -7
  69. package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +1 -1
  70. package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +1 -1
  71. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +25 -9
  72. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/BRICK.md +1 -1
  73. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +18 -6
  74. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +124 -24
  75. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +7 -7
  76. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +2 -2
  77. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +147 -14
  78. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +17 -3
  79. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/adoption.mjs +2 -2
  80. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +3 -3
  81. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/cascade.mjs +92 -18
  82. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/control.mjs +145 -0
  83. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +42 -21
  84. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch-request.mjs +4 -2
  85. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +33 -9
  86. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +55 -11
  87. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +26 -4
  88. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/hermes.mjs +29 -1
  89. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/index.mjs +1 -1
  90. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/included-usage.mjs +1 -1
  91. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/jobs.mjs +84 -0
  92. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +3 -12
  93. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/maintenance.mjs +17 -0
  94. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/mcp-server.mjs +228 -0
  95. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/process-manager.mjs +25 -2
  96. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reasoning.mjs +1 -1
  97. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/remote-operation.mjs +5 -1
  98. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/review.mjs +34 -3
  99. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reviews.mjs +25 -0
  100. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +1 -1
  101. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/claude.mjs +319 -0
  102. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/codex.mjs +387 -0
  103. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/result.mjs +67 -0
  104. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/resume.mjs +373 -0
  105. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/run.mjs +223 -0
  106. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session-jobs.mjs +72 -0
  107. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/setup.mjs +6 -3
  108. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/termination.mjs +16 -12
  109. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/threads.mjs +32 -1
  110. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/usage-queue.mjs +1 -1
  111. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/verify-ssss.mjs +3 -3
  112. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +285 -40
  113. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worktree.mjs +76 -9
  114. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/DESIGN.md +5 -5
  115. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +3 -3
  116. package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +47 -18
  117. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +7 -7
  118. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +1 -1
  119. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +1 -1
  120. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh-diagnostics.mjs +44 -0
  121. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +120 -113
  122. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +7 -7
  123. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +1 -1
  124. package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +1 -1
  125. package/scaffold/.agent/skills/total-recall/references/operations.md +1 -1
  126. package/src/cli/agent.mjs +1 -1
  127. package/src/cli/command.mjs +41 -2
  128. package/src/cli/dream.mjs +93 -69
  129. package/src/cli/harness.mjs +4 -4
  130. package/src/cli/help.mjs +1 -1
  131. package/src/cli/init.mjs +0 -25
  132. package/src/cli/lessons.mjs +51 -0
  133. package/src/cli/memory-plugin.mjs +1 -1
  134. package/src/cli/plugin/copies.mjs +64 -14
  135. package/src/cli/plugin/create.mjs +6 -7
  136. package/src/cli/plugin/index.mjs +11 -7
  137. package/src/cli/plugin/install.mjs +10 -15
  138. package/src/cli/plugin/remove.mjs +1 -1
  139. package/src/cli/plugin/search.mjs +2 -2
  140. package/src/cli/preflight.mjs +19 -6
  141. package/src/cli/proposals.mjs +1 -1
  142. package/src/cli/recall.mjs +66 -23
  143. package/src/cli/remember.mjs +3 -14
  144. package/src/cli/server.mjs +18 -0
  145. package/src/cli/startup.mjs +1 -0
  146. package/src/cli/task.mjs +9 -2
  147. package/src/cli/update.mjs +6 -2
  148. package/src/core/access-ledger.mjs +78 -0
  149. package/src/core/agent-manager.mjs +2 -2
  150. package/src/core/brain-locate.mjs +15 -0
  151. package/src/core/brain-updates.mjs +31 -11
  152. package/src/core/claude-hooks.mjs +3 -1
  153. package/src/core/cleanup-task.mjs +15 -0
  154. package/src/core/cli-commands.mjs +2 -0
  155. package/src/core/cli-maintenance.mjs +4 -1
  156. package/src/core/cli-semantic-index.mjs +23 -3
  157. package/src/core/context-compiler.mjs +20 -0
  158. package/src/core/daemon-loop.mjs +39 -62
  159. package/src/core/default-plugins.mjs +1 -1
  160. package/src/core/dream.mjs +33 -105
  161. package/src/core/ensure-dependencies.mjs +148 -0
  162. package/src/core/fast-recall.mjs +2 -2
  163. package/src/core/field-evaluation.mjs +23 -0
  164. package/src/core/gate-mode.mjs +1 -1
  165. package/src/core/harness-hooks.mjs +14 -2
  166. package/src/core/lesson-hooks.mjs +74 -0
  167. package/src/core/lesson-memory.mjs +24 -0
  168. package/src/core/lesson-sessions.mjs +52 -0
  169. package/src/core/lessons.mjs +96 -0
  170. package/src/core/meta-harness.mjs +2 -2
  171. package/src/core/optimizer.mjs +66 -165
  172. package/src/core/plugin-contracts/card.mjs +3 -0
  173. package/src/core/plugin-loader.mjs +12 -3
  174. package/src/core/plugin-runtime.mjs +40 -1
  175. package/src/core/plugin-store.mjs +35 -23
  176. package/src/core/post-mortem.mjs +52 -36
  177. package/src/core/preflight-gate.mjs +23 -2
  178. package/src/core/proposal-applier.mjs +31 -2
  179. package/src/core/repo-sync.mjs +4 -6
  180. package/src/core/required-plugins.mjs +26 -4
  181. package/src/core/rule-length.mjs +23 -0
  182. package/src/core/runtime.mjs +1 -1
  183. package/src/core/scheduler.mjs +35 -43
  184. package/src/core/schema.mjs +1 -2
  185. package/src/core/search.mjs +4 -3
  186. package/src/core/server-service.mjs +148 -0
  187. package/src/core/session-watcher.mjs +19 -1
  188. package/src/core/skill-gap.mjs +16 -0
  189. package/src/core/ssss-kernel-bridge.mjs +32 -4
  190. package/src/core/startup-health.mjs +6 -1
  191. package/src/core/surface.mjs +65 -54
  192. package/src/core/task-executors.mjs +54 -106
  193. package/src/core/validated-write.mjs +1 -0
  194. package/src/core/vector-field.mjs +134 -192
  195. package/src/server/invoke-core-child.mjs +24 -0
  196. package/src/server/invoke-core-operations.mjs +102 -0
  197. package/src/server/route-manifest.json +0 -56
  198. package/src/server/routes/brains.mjs +0 -18
  199. package/src/server/routes/context.mjs +0 -41
  200. package/src/server/routes/invoke.mjs +26 -1
  201. package/src/server/routes/ssss.mjs +0 -169
  202. package/src/server/routes/system.mjs +0 -15
  203. package/src/server/routes/tasks.mjs +0 -26
  204. package/src/server/routes/vault.mjs +2 -23
  205. package/frontend/dist/assets/index-DD4qONvH.js +0 -5607
  206. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/README.md +0 -53
  207. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/plugin.json +0 -129
  208. package/src/core/append-log.mjs +0 -131
  209. package/src/core/plugin-tasks.mjs +0 -173
  210. package/templates/onboarding-interview.md +0 -82
  211. /package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/LICENSE +0 -0
  212. /package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/ui/DESIGN.md +0 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "meta-harness",
3
- "name": "Meta Harness",
4
- "version": "0.3.9",
3
+ "name": "Metaharness",
4
+ "version": "0.3.13",
5
5
  "description": "Discover, configure, document, track, and run coding agent harnesses through Total Recall across mesh nodes.",
6
6
  "gate": {
7
7
  "tasks": ["Run work in another agent harness", "Choose which harness takes a task", "Check harness versions, sign-in and quota", "Read a harness's official documentation"],
@@ -124,9 +124,119 @@
124
124
  "description": "Run a task from stdin after automatic Total Recall bootstrap",
125
125
  "effect": "write",
126
126
  "output": "json",
127
- "args": [],
127
+ "args": [
128
+ { "name": "background", "flag": "--background", "type": "boolean", "label": "Return the job ID and log" },
129
+ { "name": "goal", "flag": "--goal", "type": "string", "label": "Goal checked at completion" },
130
+ { "name": "maxTurns", "flag": "--max-turns", "type": "number", "label": "Job turn limit" },
131
+ { "name": "maxBudgetUsd", "flag": "--max-budget-usd", "type": "number", "label": "Job cost limit in USD" },
132
+ { "name": "maxTokens", "flag": "--max-tokens", "type": "number", "label": "Job token limit" },
133
+ { "name": "resultSchema", "flag": "--result-schema", "type": "string", "label": "JSON Schema file" },
134
+ { "name": "approvals", "flag": "--approvals", "type": "string", "label": "auto, operator, or rules" },
135
+ { "name": "questionTimeoutMs", "flag": "--question-timeout-ms", "type": "number", "label": "Question timeout in milliseconds" }
136
+ ],
137
+ "timeout_ms": 600000
138
+ },
139
+ {
140
+ "name": "fork",
141
+ "description": "Start a new job from a saved session and branch",
142
+ "effect": "write",
143
+ "output": "json",
144
+ "args": [
145
+ { "name": "job", "type": "string", "positional": true, "required": true, "label": "Job ID prefix" },
146
+ { "name": "at", "flag": "--at", "type": "string", "label": "Last turn to copy when supported" },
147
+ { "name": "background", "flag": "--background", "type": "boolean", "label": "Return the job ID and log" }
148
+ ],
128
149
  "timeout_ms": 600000
129
150
  },
151
+ {
152
+ "name": "resume",
153
+ "description": "Continue a finished job session on its saved branch",
154
+ "effect": "write",
155
+ "output": "json",
156
+ "args": [
157
+ { "name": "job", "type": "string", "positional": true, "required": true, "label": "Job ID prefix" },
158
+ { "name": "text", "type": "string", "positional": true, "required": true, "label": "Continuation message" },
159
+ { "name": "background", "flag": "--background", "type": "boolean", "label": "Return the job ID and log" }
160
+ ],
161
+ "timeout_ms": 600000
162
+ },
163
+ {
164
+ "name": "cancel",
165
+ "description": "Stop a running job and wait up to 60 seconds for its cancelled result",
166
+ "effect": "write",
167
+ "output": "json",
168
+ "args": [
169
+ { "name": "job", "type": "string", "label": "Job ID prefix or log path", "positional": true, "required": true }
170
+ ],
171
+ "timeout_ms": 65000
172
+ },
173
+ {
174
+ "name": "reply",
175
+ "description": "Send a reply to a running job or answer its oldest waiting question",
176
+ "effect": "write",
177
+ "output": "json",
178
+ "args": [
179
+ { "name": "job", "type": "string", "label": "Job ID prefix or log path", "positional": true, "required": true },
180
+ { "name": "text", "type": "string", "label": "Reply text", "positional": true, "required": true },
181
+ { "name": "localDelivery", "flag": "--local-delivery", "type": "boolean", "label": "Deliver on this node" }
182
+ ],
183
+ "timeout_ms": 65000
184
+ },
185
+ {
186
+ "name": "approve",
187
+ "description": "Allow or deny a waiting job approval",
188
+ "effect": "write",
189
+ "output": "json",
190
+ "args": [
191
+ { "name": "job", "type": "string", "label": "Job ID prefix or log path", "positional": true, "required": true },
192
+ { "name": "requestId", "type": "string", "label": "Approval request ID", "positional": true, "required": true },
193
+ { "name": "value", "type": "string", "label": "allow or deny", "positional": true, "required": true },
194
+ { "name": "localDelivery", "flag": "--local-delivery", "type": "boolean", "label": "Deliver on this node" }
195
+ ],
196
+ "timeout_ms": 65000
197
+ },
198
+ {
199
+ "name": "interrupt",
200
+ "description": "Stop the current job turn and keep its session resumable",
201
+ "effect": "write",
202
+ "output": "json",
203
+ "args": [
204
+ { "name": "job", "type": "string", "label": "Job ID prefix or log path", "positional": true, "required": true },
205
+ { "name": "localDelivery", "flag": "--local-delivery", "type": "boolean", "label": "Deliver on this node" }
206
+ ],
207
+ "timeout_ms": 65000
208
+ },
209
+ {
210
+ "name": "goal",
211
+ "description": "Set the goal of a running job",
212
+ "effect": "write",
213
+ "output": "json",
214
+ "args": [
215
+ { "name": "job", "type": "string", "label": "Job ID prefix or log path", "positional": true, "required": true },
216
+ { "name": "text", "type": "string", "label": "Goal text", "positional": true, "required": true },
217
+ { "name": "localDelivery", "flag": "--local-delivery", "type": "boolean", "label": "Deliver on this node" }
218
+ ],
219
+ "timeout_ms": 65000
220
+ },
221
+ {
222
+ "name": "mcp",
223
+ "description": "Serve the Metaharness ask and approve tools over MCP stdio",
224
+ "effect": "write",
225
+ "args": [
226
+ { "name": "approvals", "flag": "--approvals", "type": "string", "label": "Approval policy: auto, operator, or rules" },
227
+ { "name": "questionTimeoutMs", "flag": "--question-timeout-ms", "type": "number", "label": "Question timeout in milliseconds" }
228
+ ]
229
+ },
230
+ {
231
+ "name": "watch",
232
+ "description": "Show job status, recent events, waiting questions and approvals",
233
+ "effect": "read",
234
+ "output": "json",
235
+ "args": [
236
+ { "name": "job", "type": "string", "label": "Job id prefix or event log path", "positional": true },
237
+ { "name": "tail", "flag": "--tail", "type": "string", "label": "Events to show" }
238
+ ]
239
+ },
130
240
  {
131
241
  "name": "included-status",
132
242
  "description": "Read included usage policy and optionally check Codex allowance without a turn",
@@ -383,6 +493,13 @@
383
493
  "output": "json",
384
494
  "timeout_ms": 600000,
385
495
  "args": [
496
+ { "name": "goal", "flag": "--goal", "type": "string", "label": "Goal checked at completion" },
497
+ { "name": "maxTurns", "flag": "--max-turns", "type": "number", "label": "Job turn limit" },
498
+ { "name": "maxBudgetUsd", "flag": "--max-budget-usd", "type": "number", "label": "Job cost limit in USD" },
499
+ { "name": "maxTokens", "flag": "--max-tokens", "type": "number", "label": "Job token limit" },
500
+ { "name": "resultSchema", "flag": "--result-schema", "type": "string", "label": "JSON Schema file" },
501
+ { "name": "approvals", "flag": "--approvals", "type": "string", "label": "auto, operator, or rules" },
502
+ { "name": "questionTimeoutMs", "flag": "--question-timeout-ms", "type": "number", "label": "Question timeout in milliseconds" },
386
503
  {
387
504
  "name": "task",
388
505
  "type": "string",
@@ -427,6 +544,13 @@
427
544
  "effect": "write",
428
545
  "output": "json",
429
546
  "args": [
547
+ { "name": "goal", "flag": "--goal", "type": "string", "label": "Goal checked at completion" },
548
+ { "name": "maxTurns", "flag": "--max-turns", "type": "number", "label": "Job turn limit" },
549
+ { "name": "maxBudgetUsd", "flag": "--max-budget-usd", "type": "number", "label": "Job cost limit in USD" },
550
+ { "name": "maxTokens", "flag": "--max-tokens", "type": "number", "label": "Job token limit" },
551
+ { "name": "resultSchema", "flag": "--result-schema", "type": "string", "label": "JSON Schema file" },
552
+ { "name": "approvals", "flag": "--approvals", "type": "string", "label": "auto, operator, or rules" },
553
+ { "name": "questionTimeoutMs", "flag": "--question-timeout-ms", "type": "number", "label": "Question timeout in milliseconds" },
430
554
  {
431
555
  "name": "task",
432
556
  "type": "string",
@@ -474,16 +598,10 @@
474
598
  },
475
599
  {
476
600
  "name": "learn",
477
- "description": "Summarize run receipts into scorecards and quota walls; --emit writes proposals to memory",
601
+ "description": "Summarize run receipts into scorecards, quota walls and proposals",
478
602
  "effect": "write",
479
603
  "output": "json",
480
604
  "args": [
481
- {
482
- "name": "emit",
483
- "flag": "--emit",
484
- "type": "boolean",
485
- "label": "Write proposals through the memory owner"
486
- },
487
605
  { "name": "void", "flag": "--void", "type": "string", "label": "Comma-separated receipt ids to void" },
488
606
  { "name": "reason", "flag": "--reason", "type": "string", "label": "Why the receipts are void" }
489
607
  ]
@@ -584,7 +702,7 @@
584
702
  {
585
703
  "name": "action",
586
704
  "type": "string",
587
- "label": "list | show | create | update | close | delete",
705
+ "label": "list | show | create | update | close | delete | cleanup-fixtures",
588
706
  "required": true,
589
707
  "positional": true
590
708
  },
@@ -598,7 +716,8 @@
598
716
  { "name": "summary", "flag": "--summary", "type": "string", "label": "Thread summary" },
599
717
  { "name": "status", "flag": "--status", "type": "string", "label": "Lifecycle status or list filter" },
600
718
  { "name": "platform", "flag": "--platform", "type": "string", "label": "Harness platform or list filter" },
601
- { "name": "branch", "flag": "--branch", "type": "string", "label": "Deliverable branch" }
719
+ { "name": "branch", "flag": "--branch", "type": "string", "label": "Deliverable branch" },
720
+ { "name": "dryRun", "flag": "--dry-run", "type": "boolean", "label": "Preview fixture cleanup" }
602
721
  ]
603
722
  },
604
723
  {
@@ -623,6 +742,13 @@
623
742
  { "name": "allowUnknownAuth", "type": "boolean", "flag": "--allow-unknown-auth", "label": "Explicitly allow an unknown login state" }
624
743
  ]
625
744
  },
745
+ {
746
+ "name": "reviews",
747
+ "description": "Read durable review results, including disposition and selected diff",
748
+ "effect": "read",
749
+ "output": "json",
750
+ "args": []
751
+ },
626
752
  {
627
753
  "name": "review",
628
754
  "description": "Execute automated adversarial cross-model code review on Git diffs, PR branches, or staged changes",
@@ -682,7 +808,7 @@
682
808
  "setup_command": "setup"
683
809
  },
684
810
  "branding": {
685
- "name": "Meta Harness",
811
+ "name": "Metaharness",
686
812
  "color": "#3b82f6",
687
813
  "accent": "#38bdf8",
688
814
  "background": "#0c1427",
@@ -695,7 +821,7 @@
695
821
  "kind": "card",
696
822
  "tag": "meta-harness-card",
697
823
  "module": "ui/card.js",
698
- "description": "Meta Harness multi-agent coordination matrix"
824
+ "description": "Metaharness multi-agent coordination matrix"
699
825
  }
700
826
  ]
701
827
  },
@@ -741,6 +867,13 @@
741
867
  "command": "maintenance --models",
742
868
  "placement": "selected-node",
743
869
  "timeout_ms": 300000
870
+ },
871
+ {
872
+ "intent": "Report quota walls and measured usage to the operator",
873
+ "schedule": "*/15 * * * *",
874
+ "command": "alerts check",
875
+ "placement": "selected-node",
876
+ "timeout_ms": 60000
744
877
  }
745
878
  ]
746
879
  }
@@ -4,7 +4,7 @@ description: Discover, route, coordinate, review and run coding agent harnesses
4
4
  repo_scoped: false
5
5
  ---
6
6
 
7
- # Meta Harness
7
+ # Metaharness
8
8
 
9
9
  Coordinates coding agent harnesses across mesh nodes. `cli-agents` is an alias of `meta-harness`. Run `total-recall meta-harness --help` for flags.
10
10
 
@@ -12,7 +12,7 @@ Coordinates coding agent harnesses across mesh nodes. `cli-agents` is an alias o
12
12
  1. **Believe the status words.** `ready` was proved by a login probe. `unverified` has no probe, so only a completed run proves it. `needs_auth`, `blocked` and `exhausted` cannot take work; the reason is in `quotaNote` and in `excluded`.
13
13
  2. **No figure without a source.** `benchmarks` shows only fetched figures with their source and time. A dash means nobody measured it. Adapter quality and cost scores are configured priors, not measurements.
14
14
  3. **Writing happens on a branch.** `route --execute`, `coordinate --execute` and race work in their own worktree on a `meta-harness/...` branch. Nothing merges into your branch; review the branch and merge it yourself.
15
- 4. **Long runs go to the background.** The host stops plugin commands after 10 minutes, so add `--background` to any multi-stage `--execute`. It prints a log path that receives the final JSON, and a pipeline also records a thread. Stopping a run stops its agents and removes its worktrees, unfinished branches and open thread. A remote job (`--node` with an absolute `--cwd`) has a one-hour budget; `--timeout-ms` changes it.
15
+ 4. **Long runs go to the background.** The host stops plugin commands after 10 minutes, so add `--background` to any multi-stage `--execute`. Dispatch also accepts `--background`. The command prints a job ID and log path. The first log line and `jobs/<id>.pid` record its PID. Use `cancel <job>` to stop it. SIGTERM and SIGINT stop its agents and prevent further attempts. Changed work stays on its branch as a WIP commit; the result reports that branch. Worktrees are removed and progress becomes `cancelled`. The PID file is removed when the job ends. A pipeline also closes its thread. A remote job (`--node` with an absolute `--cwd`) has a one-hour budget; `--timeout-ms` changes it.
16
16
  5. **Cost.** dsh, hermes and other metered harnesses bill per token, and `route` warns when it picks one. `--jev` also makes a metered call.
17
17
 
18
18
  ## Commands
@@ -24,9 +24,23 @@ Coordinates coding agent harnesses across mesh nodes. `cli-agents` is an alias o
24
24
  - `decompose "<task>"`: the stages and which ready harness takes each.
25
25
  - `review [<git-range>] [--staged] [--author <id>] [--reviewer <id>] [--thread <id>]`: review by another model family. `NO_DIFF` means there was nothing to review.
26
26
  - `dispatch --stdin` with JSON `{agent, cwd, prompt, permissions, node, allowUnknownAuth}`: one bootstrapped run with a receipt.
27
- - `threads list|show|create|close|delete`, `learn [--emit]`, `alerts check|test [--dry-run]`, `adapter list|add|remove`.
27
+ - `cancel <job>`: stop a running local job by ID prefix or log path. Wait up to 60 seconds for its cancelled result. Finished and unknown jobs return an error. Use `watch <job>` to read progress.
28
+ - `reply <job> "<text>"`: steer the running turn or answer its oldest question. `approve <job> <request> allow|deny` answers an approval. Native requests and MCP requests use the same approval policy.
29
+ - `interrupt <job>`: stop the current turn and keep its session for `resume`. `goal <job> "<text>"` replaces the running job's goal.
30
+ - `fork <job> [--at <turn>] [--background]`: start a new job from the session history on a new branch. Selected turns require native support. `resume <job> "<text>" [--background]` continues a finished session on its saved branch. Both commands keep the parent's history.
31
+ - `route`, `coordinate` and `dispatch` accept `--goal`, `--max-turns`, `--max-budget-usd`, `--max-tokens`, `--result-schema <file>`, `--approvals auto|operator|rules` and `--question-timeout-ms`. Dispatch accepts the same fields in camelCase in its stdin JSON (`approvalPolicy` selects approvals). The default goal is the task. Completion requires a separate goal check with concrete evidence. File changes alone do not prove completion. A limit fails the job without escalation. Structured results get one correction turn if validation fails.
32
+ - `mcp [--approvals auto|operator|rules] [--question-timeout-ms <ms>]`: serve the job's `ask` and `approve` tools over stdio. Metaharness configures this server for Claude and Codex. `watch <job>` includes capabilities, usage, plans, diffs, hooks, model reroutes and waiting requests.
33
+ - A routed change gets a review from another model family after its gate and before its push. A blocking finding or an unreadable review stops the push. The job result contains the review.
34
+ - A token or USD cap requires measured usage. A harness that cannot report the required usage fails with `failureKind: limit`. A zero USD cap starts no agent turn. Goal checks and JSON corrections count toward job limits. JSON Schema assertions outside the portable validator's supported set fail validation.
35
+ - `threads list|show|create|close|delete`, `learn`, `alerts check|test [--dry-run]`, `adapter list|add|remove`.
36
+ - `reviews`: read the durable review ledger, including selected diff, commit, reviewer, verdict and disposition. Thread deletion does not remove reviews.
37
+ - `threads cleanup-fixtures [--dry-run]`: one-time cleanup of exact test fixture titles. The atomic SSSS record lists removed and retained IDs. Real end-to-end job threads stay in place.
28
38
  - `adoption [--node <id> | --all]`: inspect native gate activity and real managed run receipts separately from installation and login.
29
39
  - `docs-status`, `docs-refresh`, `docs-search --query <q>`, `docs-read`, `maintenance`, `setup`, `runtime <agent> <maintenance-command>`. Use dispatch for agent tasks and the service owner for gateway changes.
30
40
 
41
+ `setup --node <recorded-node>` places documentation maintenance, model maintenance
42
+ and quota alerts through the existing daemon. Maintenance attempts all stale
43
+ documentation sources and reports setup, gate and documentation failures separately.
44
+
31
45
  ## When something fails
32
46
  Read `failureKind` on the result. `quota` carries `resetsAt`, and routing avoids that harness until then. `auth` and `not-ready` mean the operator must sign in or install something. These do not count against a harness's pass rate; `agent` and `gate` failures do.
@@ -90,9 +90,9 @@ export async function inspectFleetAdoption({ node, inventory = listTargets, read
90
90
  argv: ['total-recall', 'meta-harness', 'adoption', '--json'],
91
91
  });
92
92
  try { report = JSON.parse(result.output); } catch { /* The installed CLI did not supply an adoption report. */ }
93
- if (report?.error === 'Unknown operation: adoption') return { ...row, status: 'update-required', error: 'The installed Meta Harness does not support adoption reports.' };
93
+ if (report?.error === 'Unknown operation: adoption') return { ...row, status: 'update-required', error: 'The installed Metaharness does not support adoption reports.' };
94
94
  if (!report || !Array.isArray(report.agents) || typeof report.pluginVersion !== 'string' || typeof report.ok !== 'boolean' || (report.ok && !result.ok)) {
95
- return { ...row, status: 'adoption-unavailable', error: 'The installed Meta Harness did not return an adoption report. Verify its version and CLI availability.' };
95
+ return { ...row, status: 'adoption-unavailable', error: 'The installed Metaharness did not return an adoption report. Verify its version and CLI availability.' };
96
96
  }
97
97
  }
98
98
  return { ...row, status: report.ok ? 'inspected' : 'partial', report };
@@ -4,7 +4,7 @@ import { readReceipts, activeQuotaWalls } from './learner.mjs';
4
4
 
5
5
  const SEVERITY = { info: 'info', warning: 'high', high: 'high', critical: 'critical' };
6
6
 
7
- export async function notifyAlert({ title = 'Meta Harness', message = '', severity = 'info', eventId, dryRun = false } = {}) {
7
+ export async function notifyAlert({ title = 'Metaharness', message = '', severity = 'info', eventId, dryRun = false } = {}) {
8
8
  const args = ['alerts', 'send', '--title', title, '--message', message, '--severity', SEVERITY[severity] || 'info', '--source', 'meta-harness'];
9
9
  if (eventId) args.push('--event-id', eventId);
10
10
  if (dryRun) args.push('--dry-run');
@@ -20,13 +20,13 @@ export async function notifyAlert({ title = 'Meta Harness', message = '', severi
20
20
  export async function checkAlerts({ quotas = {}, threshold = 85, dryRun = false } = {}) {
21
21
  const walls = activeQuotaWalls(await readReceipts());
22
22
  const candidates = Object.entries(walls).map(([harness, wall]) => ({
23
- title: `Meta Harness: ${harness} is out of quota`,
23
+ title: `Metaharness: ${harness} is out of quota`,
24
24
  message: `${wall.message || 'Quota wall observed.'} Routing avoids ${harness} until ${wall.until}.`,
25
25
  severity: 'warning', eventId: `meta-harness-wall-${harness}-${wall.until}`,
26
26
  }));
27
27
  for (const [id, quota] of Object.entries(quotas)) {
28
28
  if (typeof quota?.usagePercent === 'number' && quota.usagePercent > threshold && quota.billingModel !== 'local') candidates.push({
29
- title: `Meta Harness: ${id} is at ${quota.usagePercent}%`, message: `Measured usage on ${id} is above ${threshold}%.`,
29
+ title: `Metaharness: ${id} is at ${quota.usagePercent}%`, message: `Measured usage on ${id} is above ${threshold}%.`,
30
30
  severity: 'warning', eventId: `meta-harness-usage-${id}-${new Date().toISOString().slice(0, 10)}`,
31
31
  });
32
32
  }
@@ -9,18 +9,46 @@ import { recordRunReceipt } from './learner.mjs';
9
9
  import { resolvePool, localExecutionNode } from './pool.mjs';
10
10
  import { routeTask } from './router.mjs';
11
11
  import { runQualityGate } from './gate.mjs';
12
- import { isGitRepo, createWorktree, removeWorktree, commitAll, linkDependencies, deleteBranch } from './worktree.mjs';
12
+ import { isGitRepo, isDirty, pushBranch, createWorktree, removeWorktree, commitAll, linkDependencies, deleteBranch, provideDependencies } from './worktree.mjs';
13
13
  import { forwardRemoteOperation } from './remote-operation.mjs';
14
14
  import { queueIncludedJob } from './usage-queue.mjs';
15
+ import { configDir } from './paths.mjs';
16
+ import path from 'node:path';
17
+ import { cancellationResult } from './worker.mjs';
18
+ import { executeCodeReview, reviewBlocksPush } from './review.mjs';
19
+ import { sessionOptions } from './session/run.mjs';
20
+ import fs from 'node:fs';
21
+ import { jobPaths } from './control.mjs';
22
+
23
+ // A routed attempt gets time in proportion to the work the route measured (A-101); --timeout-ms overrides.
24
+ export const ROUTED_TIMEOUT_MS = { local: 15 * 60_000, budget: 30 * 60_000, frontier: 90 * 60_000 };
25
+
26
+ /**
27
+ * What Metaharness itself recorded about this job, for the reviewer (A-122): questions the agent
28
+ * asked, the operator's replies, the goal check and the gate. The reviewer sees only the diff
29
+ * otherwise and cannot verify claims about how the job ran.
30
+ */
31
+ export function reviewEvidence(jobId, attempt = {}) {
32
+ const evidence = { goalCheck: attempt.goalCheck ?? null, gate: attempt.gate ? { status: attempt.gate.status, passed: attempt.gate.passed } : null, questions: [], replies: [] };
33
+ if (!jobId) return evidence;
34
+ let paths;
35
+ try { paths = jobPaths(jobId); } catch { return evidence; }
36
+ const lines = file => { try { return fs.readFileSync(file, 'utf8').split('\n').filter(Boolean).map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean); } catch { return []; } };
37
+ for (const e of lines(paths.events)) if (e.kind === 'question') evidence.questions.push({ requestId: e.requestId, questions: e.questions || e.text, source: e.source, t: e.t });
38
+ for (const c of lines(paths.inbox)) if (c.type === 'reply' || c.command === 'reply') evidence.replies.push({ requestId: c.requestId || null, text: c.text, t: c.t });
39
+ return evidence;
40
+ }
15
41
 
16
42
  export async function executeRoutedTask(task, options = {}) {
43
+ if (cancellationResult()) return cancellationResult();
17
44
  const remote = await forwardRemoteOperation('route', task, { ...options, execute: true });
18
45
  if (remote) return remote;
19
46
  if (options.node) localExecutionNode(options);
20
47
  const cwd = options.cwd || process.cwd();
48
+ const controls = sessionOptions({ ...options, cwd });
21
49
  if (!isGitRepo(cwd)) throw new Error('Routed execution writes on its own branch, so it must run inside a git repository');
22
50
  const route = options.route || await routeTask(task, options);
23
- const replay = { kind: 'route', gate: options.gate === true, gateOptions: options.gateOptions };
51
+ const replay = { kind: 'route', gate: options.gate === true, gateOptions: options.gateOptions, controls };
24
52
  if (!route.selected && route.deferred) {
25
53
  const blocked = { ok: false, failureKind: 'quota', error: 'The included usage allowance is exhausted.', resetsAt: route.deferred.resetsAt };
26
54
  const job = await queueIncludedJob({ agent: route.deferred.harness, cwd, prompt: task }, blocked,
@@ -35,34 +63,50 @@ export async function executeRoutedTask(task, options = {}) {
35
63
  const attempts = [];
36
64
  const alerts = [];
37
65
  const tried = new Set();
38
- let harness = route.selected, diagnostic = null, previous = null;
66
+ let harness = route.selected, diagnostic = null, previous = null, carried = null;
67
+ const timeoutMs = options.timeoutMs || ROUTED_TIMEOUT_MS[route.tier] || ROUTED_TIMEOUT_MS.budget;
39
68
 
40
69
  for (let index = 1; index <= maxAttempts && harness; index += 1) {
70
+ if (cancellationResult()) return { route, attempts, alerts, ...cancellationResult() };
41
71
  tried.add(harness);
42
72
  const branch = `meta-harness/${id}-${index}-${harness}`;
43
- const directory = createWorktree(cwd, { branch });
44
- const attempt = { harness, branch: null, commit: null, gate: null, escalatedFrom: previous };
73
+ // An escalation continues from the previous attempt's unfinished work instead of starting over (A-102).
74
+ const directory = createWorktree(cwd, { branch, ...(carried ? { ref: carried } : {}) });
75
+ // A-107: each attempt streams its agent's events here live; the path is announced first.
76
+ const progressFile = options.progressDir === false ? undefined
77
+ : path.join(options.progressDir || path.join(configDir(), 'jobs'), `${id}-${index}-${harness}.events.jsonl`);
78
+ const attempt = { harness, branch: null, commit: null, gate: null, escalatedFrom: previous, ...(progressFile ? { progressFile } : {}) };
45
79
  attempts.push(attempt);
80
+ if (progressFile) console.error(JSON.stringify({ progress: progressFile, attempt: index, harness }));
81
+ // A-109: the agent gets the repository's dependencies in its own checkout, so its tests run.
46
82
  let stopped;
47
83
  try {
84
+ attempt.dependencies = (options.provideDependencies || provideDependencies)(cwd, directory);
48
85
  const prompt = `Work only in this checkout. Do not commit; the harness commits your changes.\nTask:\n${task}${diagnostic ? `\n\nA previous attempt by ${previous} failed. Diagnostic:\n${diagnostic}` : ''}`;
49
- const result = await dispatchWithReceipt({ agent: harness, node, cwd: directory, contextCwd: cwd, contextTask: task, prompt, permissions: { read: true, write: true }, allowUnknownAuth: options.allowUnknownAuth === true }, { dispatchFn: options.dispatchFn, role: 'Routed', complexityScore: route.complexity?.score, escalatedFrom: previous, replay, retryId: options.retryId });
86
+ const result = await dispatchWithReceipt({ agent: harness, node, branch, cwd: directory, contextCwd: cwd, contextTask: task, prompt, permissions: { read: true, write: true }, timeoutMs,
87
+ ...controls, goal: controls.goal || task,
88
+ ...(progressFile ? { progressFile } : {}), allowUnknownAuth: options.allowUnknownAuth === true }, { dispatchFn: options.dispatchFn, role: 'Routed', complexityScore: route.complexity?.score, escalatedFrom: previous, replay, retryId: options.retryId });
50
89
  attempt.ok = result.ok === true;
90
+ if (result.cancelled) stopped = result;
51
91
  attempt.durationMs = result.durationMs;
52
- attempt.output = result.response || null;
92
+ attempt.timeoutMs = timeoutMs;
93
+ attempt.output = result.response || result.partialOutput || null;
94
+ if (result.sessionId) { attempt.sessionId = result.sessionId; attempt.branch = branch; }
95
+ attempt.goalCheck = result.goalCheck;
96
+ attempt.structured = result.structured;
97
+ attempt.usage = result.usage;
98
+ if (!attempt.ok && result.stderr) attempt.stderr = result.stderr;
53
99
  if (result.ok) {
54
100
  attempt.commit = commitAll(directory, `meta-harness: ${task.slice(0, 60)}\n\nAgent: ${harness}`);
55
101
  if (attempt.commit) attempt.branch = branch;
56
- else {
57
- attempt.ok = false; attempt.error = 'The agent reported success but changed no files.';
58
- await recordRunReceipt({ harness, role: 'Implementer', task, success: false, failureKind: 'no-change', error: attempt.error });
59
- }
102
+ // Keep the session branch even when the goal required no repository edit.
103
+ else if (result.sessionId) attempt.branch = branch;
60
104
  } else {
61
105
  attempt.error = result.error;
62
106
  attempt.failureKind = result.failureKind;
63
- if (result.queued || result.queueError || (result.failureKind === 'quota' && options.retryId) || result.failureKind === 'billing-policy') stopped = result;
107
+ if (result.queued || result.queueError || (result.failureKind === 'quota' && options.retryId) || ['billing-policy', 'limit', 'interrupted'].includes(result.failureKind)) stopped = result;
64
108
  }
65
- if (attempt.ok && options.gate) {
109
+ if (attempt.ok && options.gate && !cancellationResult()) {
66
110
  linkDependencies(cwd, directory);
67
111
  attempt.gate = await (options.gateFn || runQualityGate)(directory, options.gateOptions);
68
112
  // A gate that could not run says nothing about this agent's work, so it does not escalate.
@@ -74,16 +118,46 @@ export async function executeRoutedTask(task, options = {}) {
74
118
  await recordRunReceipt({ harness, role: 'Gate', task, success: attempt.gate.passed, failureKind: attempt.gate.passed ? null : 'gate', error: attempt.gate.passed ? null : attempt.gate.summary, gate: attempt.gate.status });
75
119
  attempt.ok = attempt.gate.passed;
76
120
  }
121
+ if (attempt.ok && attempt.commit && !cancellationResult()) {
122
+ attempt.review = await (options.reviewFn || executeCodeReview)({ cwd, author: harness, task,
123
+ evidence: reviewEvidence(options.jobId || process.env.TR_METAHARNESS_JOB, attempt),
124
+ target: `HEAD...${attempt.commit}`, dispatchFn: options.dispatchFn, reviewer: options.reviewer,
125
+ allowUnknownAuth: options.allowUnknownAuth === true, availableHarnesses: options.availableHarnesses });
126
+ if (reviewBlocksPush(attempt.review)) {
127
+ attempt.ok = false; attempt.failureKind = 'review';
128
+ attempt.error = attempt.review.error || JSON.stringify(attempt.review.review?.findings || []) || 'Review blocked the push.';
129
+ }
130
+ }
77
131
  } catch (error) {
78
132
  attempt.ok = false;
79
133
  attempt.error = String(error.message);
80
134
  } finally {
81
- removeWorktree(cwd, directory);
82
- // Only the branch this run hands back survives; the attempt record keeps what happened.
83
- if (!attempt.ok) { deleteBranch(cwd, branch); attempt.branch = null; }
135
+ if (cancellationResult()) {
136
+ Object.assign(attempt, cancellationResult());
137
+ stopped = cancellationResult();
138
+ }
139
+ // A failed attempt's edits are kept as an unfinished commit on its branch for review and for the
140
+ // next attempt to continue from; only an attempt that changed nothing loses its branch (A-102).
141
+ if (!attempt.ok && !attempt.commit) {
142
+ let wip = null;
143
+ try { wip = commitAll(directory, `meta-harness: unfinished attempt by ${harness}\n\n${String(attempt.error || '').slice(0, 300)}`); }
144
+ catch (error) { attempt.wipError = String(error.message); }
145
+ if (wip) { attempt.wip = wip; attempt.branch = branch; carried = wip; }
146
+ } else if (!attempt.ok && attempt.commit) carried = attempt.commit;
147
+ // Edits that could not be committed are never deleted (A-114): the checkout and branch stay for recovery.
148
+ if (attempt.wipError && isDirty(directory)) { attempt.keptWorktree = directory; attempt.branch = branch; }
149
+ else {
150
+ removeWorktree(cwd, directory);
151
+ if (!attempt.ok && !attempt.wip && !attempt.commit && !attempt.sessionId) { deleteBranch(cwd, branch); attempt.branch = null; }
152
+ }
84
153
  }
85
154
 
86
- if (attempt.ok) return { ok: true, route, gateApplied: Boolean(options.gate), branch: attempt.branch, commit: attempt.commit, harness, attempts, alerts };
155
+ if (stopped?.cancelled) return { ...stopped, route, attempts, alerts, branch: attempt.branch, commit: attempt.commit, wip: attempt.wip, harness };
156
+ if (attempt.ok) {
157
+ const push = options.push === false ? { pushed: false, reason: 'push disabled for this run' } : pushBranch(cwd, attempt.branch);
158
+ return { ok: true, route, goalCheck: attempt.goalCheck, structured: attempt.structured, review: attempt.review,
159
+ gateApplied: Boolean(options.gate), branch: attempt.branch, commit: attempt.commit, push, harness, attempts, alerts };
160
+ }
87
161
  if (stopped) return { ...stopped, route, attempts, alerts };
88
162
 
89
163
  const remaining = pool.filter(item => !tried.has(item));
@@ -91,7 +165,7 @@ export async function executeRoutedTask(task, options = {}) {
91
165
  diagnostic = attempt.gate?.summary || attempt.error || 'The previous attempt failed.';
92
166
  previous = harness;
93
167
  harness = next?.recommendedHarness && !tried.has(next.recommendedHarness) ? next.recommendedHarness : null;
94
- if (harness) alerts.push(await notifyAlert({ title: 'Meta Harness: escalating', message: `${previous} failed on "${task.slice(0, 60)}"; escalating to ${harness}.`, severity: 'warning' }));
168
+ if (harness) alerts.push(await notifyAlert({ title: 'Metaharness: escalating', message: `${previous} failed on "${task.slice(0, 60)}"; escalating to ${harness}.`, severity: 'warning' }));
95
169
  }
96
170
  return { ok: false, route, gateApplied: Boolean(options.gate), attempts, alerts, error: 'No attempt passed.' };
97
171
  }