total-recall-brain 3.41.4 → 3.41.6

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 (147) hide show
  1. package/bin/total-recall.mjs +10 -6
  2. package/docs/reference/cli-reference.md +18 -0
  3. package/frontend/dist/assets/index-4tGQfTa2.js +5605 -0
  4. package/frontend/dist/assets/index-DtwsDyFM.css +1 -0
  5. package/frontend/dist/index.html +2 -2
  6. package/frontend/package.json +11 -0
  7. package/frontend/src/App.tsx +47 -122
  8. package/frontend/src/components/plugins/plugin-frame.ts +1 -0
  9. package/frontend/src/index.css +152 -86
  10. package/frontend/src/pages/HelpPage.tsx +13 -10
  11. package/frontend/src/pages/MemoryPage.tsx +28 -30
  12. package/frontend/src/pages/NetworkPage.tsx +3 -3
  13. package/frontend/src/pages/PluginsPage.tsx +6 -5
  14. package/frontend/src/pages/RulesPage.tsx +16 -12
  15. package/package.json +4 -3
  16. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +3114 -36
  17. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +4 -1
  18. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +3114 -36
  19. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +4 -1
  20. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +3121 -33
  21. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +4 -1
  22. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +3122 -34
  23. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package.json +4 -1
  24. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +3127 -49
  25. package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +5 -2
  26. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh.mjs +1 -1
  27. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +136 -7
  28. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +135 -26
  29. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +3121 -33
  30. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +5 -2
  31. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +228 -9
  32. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +24 -30
  33. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +29 -16
  34. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/cascade.mjs +73 -0
  35. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +182 -365
  36. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +33 -0
  37. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +40 -14
  38. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +4 -4
  39. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/agy.mjs +187 -0
  40. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/claude.mjs +275 -0
  41. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/cline.mjs +132 -0
  42. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/codex.mjs +187 -0
  43. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/cursor.mjs +158 -0
  44. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/devin.mjs +157 -0
  45. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/dsh.mjs +183 -0
  46. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/grok.mjs +132 -0
  47. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/hermes.mjs +173 -0
  48. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/index.mjs +87 -0
  49. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/openclaw.mjs +173 -0
  50. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/pi.mjs +129 -0
  51. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate.mjs +12 -0
  52. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/json-reply.mjs +22 -0
  53. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +72 -70
  54. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/model-catalog.mjs +195 -0
  55. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/paths.mjs +11 -0
  56. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/pool.mjs +36 -0
  57. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/review.mjs +169 -0
  58. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +136 -346
  59. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/runtime.mjs +1 -1
  60. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/ssss-store.mjs +20 -0
  61. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/telemetry.mjs +43 -0
  62. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/threads.mjs +156 -0
  63. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +117 -35
  64. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worktree.mjs +51 -0
  65. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +398 -57
  66. package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +304 -46
  67. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +367 -41
  68. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +5 -2
  69. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +33 -0
  70. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/config.mjs +2 -2
  71. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/generate.mjs +159 -0
  72. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/graph.mjs +197 -0
  73. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/lint.mjs +153 -0
  74. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/mesh-vaults.mjs +261 -0
  75. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +151 -4
  76. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/search.mjs +114 -0
  77. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/token.mjs +96 -0
  78. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +899 -662
  79. package/scaffold/.agent/skills/total-recall/plugins/openwiki/vendor/openwiki.mjs +8987 -0
  80. package/scaffold/.agent/skills/total-recall/plugins/openwiki/vendor/skills/mermaid-diagrams/SKILL.md +44 -0
  81. package/scaffold/.agent/skills/total-recall/plugins/openwiki/vendor/skills/write-connector/SKILL.md +56 -0
  82. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +3097 -81
  83. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +4 -1
  84. package/src/cli/agent.mjs +12 -1
  85. package/src/cli/compact.mjs +108 -0
  86. package/src/cli/connect.mjs +56 -1
  87. package/src/cli/context.mjs +55 -23
  88. package/src/cli/doctor.mjs +5 -2
  89. package/src/cli/forget.mjs +2 -0
  90. package/src/cli/harness.mjs +62 -50
  91. package/src/cli/memory-entry.mjs +2 -2
  92. package/src/cli/preflight.mjs +152 -16
  93. package/src/cli/release.mjs +386 -0
  94. package/src/cli/remember.mjs +19 -1
  95. package/src/cli/rules.mjs +54 -12
  96. package/src/cli/startup.mjs +1 -1
  97. package/src/cli/status.mjs +12 -2
  98. package/src/core/claude-hooks.mjs +41 -3
  99. package/src/core/cli-commands.mjs +2 -0
  100. package/src/core/cli-semantic-index.mjs +3 -2
  101. package/src/core/conflict-detector.mjs +23 -12
  102. package/src/core/context-compactor.mjs +145 -0
  103. package/src/core/context-policy.mjs +20 -0
  104. package/src/core/duplicate-guard.mjs +350 -0
  105. package/src/core/meta-harness.mjs +31 -11
  106. package/src/core/preflight-gate.mjs +2 -2
  107. package/src/core/preflight-receipt.mjs +29 -1
  108. package/src/core/scaffold-plugins.mjs +7 -0
  109. package/src/core/stdio-flush.mjs +23 -0
  110. package/src/core/surface.mjs +33 -10
  111. package/src/server/invoke-durability.mjs +39 -0
  112. package/src/server/invoke-write-child.mjs +25 -0
  113. package/src/server/routes/invoke.mjs +7 -0
  114. package/src/server/routes/plugins.mjs +2 -2
  115. package/frontend/dist/assets/index-BIQmqDKP.css +0 -1
  116. package/frontend/dist/assets/index-CRzjQLu7.js +0 -5631
  117. package/frontend/src/components/DaemonLogsTab.tsx +0 -54
  118. package/frontend/src/components/ResearchAgendaTab.tsx +0 -712
  119. package/frontend/src/components/TaskQueueTab.tsx +0 -193
  120. package/frontend/src/components/VoiceInput.tsx +0 -112
  121. package/frontend/src/pages/ChatPage.tsx +0 -1059
  122. package/frontend/src/pages/CollabPage.tsx +0 -470
  123. package/frontend/src/pages/DesignDocsPage.tsx +0 -621
  124. package/frontend/src/pages/FilesPage.tsx +0 -393
  125. package/frontend/src/pages/HealthPage.tsx +0 -359
  126. package/frontend/src/pages/InboxPage.tsx +0 -83
  127. package/frontend/src/pages/InstructionsPage.tsx +0 -583
  128. package/frontend/src/pages/IntegrationsPage.tsx +0 -761
  129. package/frontend/src/pages/MeshPage.css +0 -58
  130. package/frontend/src/pages/MeshPage.tsx +0 -1219
  131. package/frontend/src/pages/OkfPage.tsx +0 -535
  132. package/frontend/src/pages/SandboxPage.tsx +0 -113
  133. package/frontend/src/pages/SkillsPage.tsx +0 -1994
  134. package/frontend/src/pages/TasksPage.tsx +0 -427
  135. package/frontend/src/pages/UsagePage.tsx +0 -702
  136. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/README.md +0 -65
  137. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/cli.mjs +0 -76
  138. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/package.json +0 -19
  139. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/plugin.json +0 -266
  140. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/adapters/dsh.mjs +0 -446
  141. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/documentation.mjs +0 -152
  142. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/executor.mjs +0 -71
  143. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/runtime.mjs +0 -36
  144. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/setup.mjs +0 -35
  145. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/worker.mjs +0 -175
  146. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/DESIGN.md +0 -32
  147. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/card.js +0 -575
@@ -1,398 +1,215 @@
1
1
  /**
2
- * Meta Harness Multi-Agent Coordinator
3
- * Orchestrates role-specialized multi-agent execution pipelines:
4
- * - Pipeline: Architect -> Implementer -> Verifier (cross-model family, worktree-isolated)
5
- * - Council: Multi-model consensus voting
6
- * - Race: Speculative concurrent execution
7
- *
8
- * Works with any user's installed harness pool:
9
- * - Dynamically discovers installed harnesses and assigns distinct model families
10
- * for Architect, Implementer, and Verifier so confirmation bias is avoided.
11
- * - Supports plan generation (default safe inspection) and live execution (`execute: true`)
12
- * via `dispatchAgent` with real git worktree isolation for the Verifier stage.
2
+ * Multi-agent coordination.
3
+ * Pipeline: Explorer -> Architect -> Implementer -> Verifier. Writing happens only on a dedicated
4
+ * branch in its own worktree, and the Verifier inspects that exact commit from a different model
5
+ * family. Nothing merges into the operator's branch; the branch is the deliverable.
13
6
  */
14
-
15
- import fs from 'node:fs';
16
- import path from 'node:path';
17
- import os from 'node:os';
18
- import { spawnSync } from 'node:child_process';
19
- import { ADAPTER_CATALOG, classifyTaskComplexity } from '../capability/index.mjs';
20
- import { inspectAgents, dispatchAgent, getActiveAdapters } from './worker.mjs';
7
+ import { ADAPTER_CATALOG, classifyTaskComplexity, synthesizeOneWayValve } from '../capability/index.mjs';
21
8
  import { notifyAlert } from './alerts.mjs';
22
- import { recordRunReceipt } from './learner.mjs';
9
+ import { dispatchWithReceipt } from './dispatch.mjs';
10
+ import { resolvePool } from './pool.mjs';
11
+ import { createThread, appendThreadTurn, updateThread } from './threads.mjs';
12
+ import { executeCodeReview } from './review.mjs';
13
+ import { isGitRepo, isDirty, headCommit, createWorktree, removeWorktree, commitAll, diffBetween, linkDependencies, deleteBranch } from './worktree.mjs';
14
+ import { lastJsonWith } from './json-reply.mjs';
15
+ import { runQualityGate } from './gate.mjs';
23
16
 
24
- /**
25
- * Resolve installed or available harnesses with their model family metadata
26
- */
27
- async function resolvePool(options = {}) {
28
- const active = getActiveAdapters();
29
- let installedIds = [];
30
- if (Array.isArray(options.availableHarnesses) && options.availableHarnesses.length > 0) {
31
- installedIds = options.availableHarnesses;
32
- } else {
33
- try {
34
- const inspected = await inspectAgents({ env: process.env });
35
- installedIds = (inspected.agents || []).filter(a => a.installed).map(a => a.id);
36
- } catch {}
37
- }
38
-
39
- const candidateIds = installedIds.length > 0 ? installedIds : active.map(a => a.id);
40
- return candidateIds.map(id => {
41
- const cap = ADAPTER_CATALOG[id] || {};
42
- const workerSpec = active.find(a => a.id === id) || {};
43
- return {
44
- id,
45
- name: cap.name || workerSpec.name || id,
46
- family: cap.family || 'custom',
47
- tier: cap.tier || 'budget',
48
- qualityScore: cap.qualityScore ?? 7.5,
49
- costScore: cap.costScore ?? 4
50
- };
51
- });
52
- }
53
-
54
- /**
55
- * Select three role-specialized harnesses preferring distinct model families
56
- */
57
17
  export function assignPipelineRoles(pool = [], options = {}) {
58
18
  const sorted = [...pool].sort((a, b) => b.qualityScore - a.qualityScore);
59
- const findById = (id) => sorted.find(h => h.id === id) || (ADAPTER_CATALOG[id] ? { id, ...ADAPTER_CATALOG[id] } : { id, name: id, family: 'custom' });
60
-
61
- const architect = options.architect
62
- ? findById(options.architect)
63
- : (sorted[0] || findById('claude'));
64
-
65
- const implementer = options.implementer
66
- ? findById(options.implementer)
67
- : (sorted.find(h => h.id !== architect.id) || sorted[0] || findById('agy'));
68
-
69
- // Verifier must use a distinct model family from Implementer whenever more than 1 family is available
70
- const verifier = options.verifier
71
- ? findById(options.verifier)
72
- : (sorted.find(h => h.family !== implementer.family && h.id !== implementer.id)
73
- || sorted.find(h => h.id !== implementer.id)
74
- || implementer);
75
-
76
- return {
77
- architect,
78
- implementer,
79
- verifier,
80
- heterogeneousFamilies: verifier.family !== implementer.family || pool.length <= 1
81
- };
19
+ const findById = id => sorted.find(h => h.id === id) || (ADAPTER_CATALOG[id] ? { ...ADAPTER_CATALOG[id], id } : { id, name: id, family: 'custom' });
20
+ // The session's own harness leads only when it can take work; TR_ACTIVE_HARNESS is advisory.
21
+ const active = process.env.TR_ACTIVE_HARNESS;
22
+ const orchestrator = options.orchestrator ? findById(options.orchestrator) : sorted.find(h => h.id === active) || sorted[0] || null;
23
+ if (!orchestrator) return { orchestrator: null, explorer: null, architect: null, implementer: null, verifier: null, heterogeneousFamilies: false };
24
+
25
+ const byCost = [...pool].sort((a, b) => a.costScore - b.costScore);
26
+ const explorer = options.explorer ? findById(options.explorer) : byCost.find(h => h.id !== orchestrator.id) || orchestrator;
27
+ const architect = options.architect ? findById(options.architect) : orchestrator;
28
+ const implementer = options.implementer ? findById(options.implementer) : sorted.find(h => h.id !== explorer.id && h.id !== architect.id) || sorted.find(h => h.id !== architect.id) || architect;
29
+ const verifier = options.verifier ? findById(options.verifier) : sorted.find(h => h.family !== implementer.family) || null;
30
+ return { orchestrator, explorer, architect, implementer, verifier, heterogeneousFamilies: Boolean(verifier) && verifier.family !== implementer.family };
82
31
  }
83
32
 
84
- /**
85
- * Coordinate a task across specialized roles and model families
86
- */
87
- export async function coordinateTask(taskDescription, options = {}) {
88
- const mode = options.mode || 'pipeline';
89
- const task = String(taskDescription || '').trim();
90
- if (!task) throw new Error('Task description is required for coordination');
91
-
92
- const pool = await resolvePool(options);
93
-
94
- if (mode === 'pipeline') {
95
- return await runSpecializedPipeline(task, pool, options);
96
- } else if (mode === 'council') {
97
- return await runCouncilCoordination(task, pool, options);
98
- } else if (mode === 'race') {
99
- return await runRaceCoordination(task, pool, options);
100
- } else {
101
- throw new Error(`Unsupported coordination mode: ${mode}`);
102
- }
33
+ /** The agent must state a verdict. Silence or an unreadable answer is not a pass. */
34
+ export function parseVerification(response) {
35
+ const parsed = lastJsonWith(response, 'verdict');
36
+ const verdict = String(parsed?.verdict).toUpperCase();
37
+ if (['PASS', 'FAIL'].includes(verdict)) return { parsed: true, verdict, findings: Array.isArray(parsed.findings) ? parsed.findings : [] };
38
+ return { parsed: false, verdict: 'UNREADABLE', findings: [] };
103
39
  }
104
-
105
- /**
106
- * Create a temporary detached git worktree if inside a git repository
107
- */
108
- function createVerifierWorktree(cwd) {
109
- try {
110
- const check = spawnSync('git', ['rev-parse', '--is-inside-work-tree'], { cwd, encoding: 'utf8', timeout: 3000 });
111
- if (check.status !== 0) return null;
112
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'meta-harness-verify-'));
113
- const add = spawnSync('git', ['worktree', 'add', '--detach', tmpDir], { cwd, encoding: 'utf8', timeout: 10000 });
114
- if (add.status !== 0) {
115
- fs.rmSync(tmpDir, { recursive: true, force: true });
116
- return null;
117
- }
118
- return tmpDir;
119
- } catch {
120
- return null;
121
- }
40
+ export function parseVote(response) {
41
+ const parsed = lastJsonWith(response, 'vote');
42
+ const vote = String(parsed?.vote).toUpperCase();
43
+ return ['APPROVE', 'REJECT'].includes(vote) ? { vote, reason: parsed.reason || null } : { vote: 'ABSTAIN', reason: null };
122
44
  }
123
45
 
124
- function removeVerifierWorktree(cwd, worktreeDir) {
125
- if (!worktreeDir) return;
126
- try {
127
- spawnSync('git', ['worktree', 'remove', '--force', worktreeDir], { cwd, encoding: 'utf8', timeout: 5000 });
128
- } catch {}
129
- try {
130
- fs.rmSync(worktreeDir, { recursive: true, force: true });
131
- } catch {}
46
+ const stageOf = (role, harness, permissions, action, extra = {}) => ({ role, harness: harness?.id || null, harnessName: harness?.name || null, modelFamily: harness?.family || null, permissions, action, status: 'planned', output: null, ...extra });
47
+ const READ = { read: true, write: false };
48
+ const WRITE = { read: true, write: true };
49
+
50
+ function buildStages(roles, withSubagents) {
51
+ const stages = [];
52
+ if (withSubagents) stages.push(stageOf('Explorer', roles.explorer, READ, 'Find the relevant files, symbols and interfaces.', { contextDigest: null }));
53
+ stages.push(stageOf('Architect', roles.architect, READ, 'Produce the blueprint, file list and verification steps.'));
54
+ stages.push(stageOf('Implementer', roles.implementer, WRITE, 'Write the code and tests on a dedicated branch in its own worktree.'));
55
+ stages.push(stageOf('Verifier', roles.verifier, READ, 'Audit the implementation commit from a different model family.', { worktreeIsolated: true, passed: null }));
56
+ return stages;
132
57
  }
133
58
 
134
- /**
135
- * Architect -> Implementer -> Verifier Pipeline
136
- * Enforces heterogeneous model families to eliminate confirmation bias.
137
- */
138
- async function runSpecializedPipeline(task, pool, options = {}) {
139
- const startTime = Date.now();
59
+ async function runPipeline(task, poolInfo, options) {
60
+ const started = Date.now();
61
+ const pool = poolInfo.ready;
140
62
  const roles = assignPipelineRoles(pool, options);
141
- const executeLive = options.execute === true;
142
63
  const cwd = options.cwd || process.cwd();
143
- const dispatchFn = options.dispatchFn || dispatchAgent;
64
+ const execute = options.execute === true;
65
+ const withSubagents = options.subagents !== false;
66
+ const stages = roles.orchestrator ? buildStages(roles, withSubagents) : [];
67
+ const base = { mode: 'pipeline', task, orchestrator: roles.orchestrator?.id || null, executedLive: execute, excluded: poolInfo.excluded, heterogeneousFamiliesVerified: roles.heterogeneousFamilies, stages };
68
+
69
+ if (!execute) {
70
+ const blockers = [];
71
+ if (!roles.orchestrator) blockers.push('No harness can take work right now.');
72
+ else if (!roles.heterogeneousFamilies) blockers.push('Only one model family is available, so no independent Verifier exists.');
73
+ return { ...base, ok: blockers.length === 0, threadId: null, blockers };
74
+ }
75
+ if (!roles.orchestrator) throw new Error(`No harness can take work right now: ${poolInfo.excluded.map(e => `${e.harness} (${e.reason})`).join(', ') || 'none installed'}`);
76
+ if (!roles.heterogeneousFamilies && options.allowSameFamily !== true) throw new Error('Refusing to run: only one model family is available, so no independent Verifier exists. Pass --allow-same-family to accept self-review.');
77
+ if (!isGitRepo(cwd)) throw new Error('A pipeline writes on its own branch, so it must run inside a git repository');
78
+
79
+ const verifier = roles.verifier || roles.implementer;
144
80
  const complexity = classifyTaskComplexity(task);
81
+ const thread = createThread({ title: task.slice(0, 80), task, platform: roles.orchestrator.id, model: roles.orchestrator.name, status: 'active' });
82
+ const baseCommit = headCommit(cwd);
83
+ const branch = options.branch || `meta-harness/${thread.id}`;
84
+ const warnings = isDirty(cwd) ? ['The working tree has uncommitted changes; the branch starts from the last commit and does not include them.'] : [];
85
+ const run = async (stage, harness, prompt, { workdir = cwd, permissions, role = stage.role } = {}) => {
86
+ stage.status = 'running';
87
+ const result = await dispatchWithReceipt({ agent: harness.id, cwd: workdir, contextCwd: cwd, contextTask: task, prompt, permissions: permissions || stage.permissions, allowUnknownAuth: true }, { dispatchFn: options.dispatchFn, role, complexityScore: complexity.score });
88
+ stage.status = result.ok ? 'completed' : 'failed';
89
+ stage.output = result.response || result.error || null;
90
+ stage.failureKind = result.ok ? undefined : result.failureKind;
91
+ appendThreadTurn(thread.id, { role: role.toLowerCase(), agent: harness.id, content: stage.output || '' });
92
+ return result;
93
+ };
145
94
 
146
- const stages = [
147
- {
148
- role: 'Architect',
149
- harness: roles.architect.id,
150
- harnessName: roles.architect.name,
151
- modelFamily: roles.architect.family,
152
- permissions: { read: true, write: false },
153
- status: executeLive ? 'pending' : 'planned',
154
- action: 'Analyze requirements, design modular contracts, and formulate verification plan.',
155
- output: null
156
- },
157
- {
158
- role: 'Implementer',
159
- harness: roles.implementer.id,
160
- harnessName: roles.implementer.name,
161
- modelFamily: roles.implementer.family,
162
- permissions: { read: true, write: true },
163
- status: executeLive ? 'pending' : 'planned',
164
- action: 'Author implementation files and unit tests following Architectural blueprint.',
165
- output: null
166
- },
167
- {
168
- role: 'Verifier',
169
- harness: roles.verifier.id,
170
- harnessName: roles.verifier.name,
171
- modelFamily: roles.verifier.family,
172
- permissions: { read: true, write: false },
173
- worktreeIsolated: true,
174
- status: executeLive ? 'pending' : 'planned',
175
- action: 'Execute deterministic test gates, audit diffs, and verify invariant compliance in isolated worktree.',
176
- passed: executeLive ? null : true,
177
- output: null
95
+ let ok = true, commit = null, implementerDir = null, gate = null;
96
+ const stage = role => stages.find(item => item.role === role);
97
+ try {
98
+ let digest = '';
99
+ if (withSubagents) {
100
+ const explorer = await run(stage('Explorer'), roles.explorer, `[ROLE: EXPLORER]\nDo not change any file. Find the files, symbols and interfaces relevant to this task:\n${task}`);
101
+ if (!explorer.ok) { ok = false; return finish(); }
102
+ digest = synthesizeOneWayValve(explorer.response, { maxChars: 2000 });
103
+ stage('Explorer').contextDigest = digest;
178
104
  }
179
- ];
180
-
181
- let allSucceeded = true;
182
-
183
- if (executeLive) {
184
- // Stage 1: Architect (Read-Only Plan)
185
- const archStart = Date.now();
186
- const archRes = await dispatchFn({
187
- agent: roles.architect.id,
188
- cwd,
189
- prompt: `[ROLE: ARCHITECT]\nProduce a concise architectural blueprint, file list, and verification steps for:\n${task}`,
190
- permissions: { read: true, write: false },
191
- allowUnknownAuth: options.allowUnknownAuth === true
192
- });
193
- stages[0].status = archRes.ok ? 'completed' : 'failed';
194
- stages[0].output = archRes.response || archRes.error || null;
195
- recordRunReceipt({
196
- harness: roles.architect.id,
197
- task: `[Architect] ${task}`,
198
- complexityScore: complexity.score,
199
- success: Boolean(archRes.ok),
200
- durationMs: Date.now() - archStart
201
- });
105
+ const architect = await run(stage('Architect'), roles.architect, `[ROLE: ARCHITECT]\nDo not change any file. Task:\n${task}\n\nExplorer digest:\n${digest || 'None.'}\n\nProduce a concise blueprint, the files to change, and how to verify the result.`);
106
+ if (!architect.ok) { ok = false; return finish(); }
107
+
108
+ implementerDir = createWorktree(cwd, { branch });
109
+ const implementer = await run(stage('Implementer'), roles.implementer, `[ROLE: IMPLEMENTER]\nWork only in this checkout. Implement the task and its tests. Do not commit; the harness commits your changes.\nTask:\n${task}\n\nBlueprint:\n${architect.response}`, { workdir: implementerDir });
110
+ if (!implementer.ok) { ok = false; return finish(); }
111
+ commit = commitAll(implementerDir, `meta-harness: ${task.slice(0, 60)}\n\nImplementer: ${roles.implementer.id}`);
112
+ if (!commit) {
113
+ Object.assign(stage('Implementer'), { status: 'failed', failureKind: 'agent', output: 'The Implementer reported success but changed no files.' });
114
+ ok = false; return finish();
115
+ }
116
+ stage('Implementer').commit = commit;
117
+ updateThread(thread.id, { branch, worktreePath: null });
202
118
 
203
- if (!archRes.ok) {
204
- allSucceeded = false;
205
- } else {
206
- // Stage 2: Implementer
207
- const implStart = Date.now();
208
- const implRes = await dispatchFn({
209
- agent: roles.implementer.id,
210
- cwd,
211
- prompt: `[ROLE: IMPLEMENTER]\nFollow this Architect blueprint:\n${archRes.response}\n\nImplement the requested task:\n${task}`,
212
- permissions: { read: true, write: true },
213
- allowUnknownAuth: options.allowUnknownAuth === true
214
- });
215
- stages[1].status = implRes.ok ? 'completed' : 'failed';
216
- stages[1].output = implRes.response || implRes.error || null;
217
- recordRunReceipt({
218
- harness: roles.implementer.id,
219
- task: `[Implementer] ${task}`,
220
- complexityScore: complexity.score,
221
- success: Boolean(implRes.ok),
222
- durationMs: Date.now() - implStart
223
- });
119
+ if (options.gate) {
120
+ linkDependencies(cwd, implementerDir);
121
+ gate = runQualityGate(implementerDir, options.gateOptions);
122
+ if (!gate.passed) { ok = false; stage('Verifier').status = 'skipped'; stage('Verifier').output = `Quality gate did not pass: ${gate.summary}`; return finish(); }
123
+ }
224
124
 
225
- if (!implRes.ok) {
226
- allSucceeded = false;
227
- } else {
228
- // Stage 3: Verifier (Isolated git worktree when available)
229
- const verifyStart = Date.now();
230
- const worktreeDir = createVerifierWorktree(cwd);
231
- const verifyCwd = worktreeDir || cwd;
232
- try {
233
- const verRes = await dispatchFn({
234
- agent: roles.verifier.id,
235
- cwd: verifyCwd,
236
- prompt: `[ROLE: INDEPENDENT VERIFIER]\nAudit the implementation and run verification checks for:\n${task}\n\nArchitect Blueprint:\n${archRes.response}\n\nImplementer Summary:\n${implRes.response}`,
237
- permissions: { read: true, write: false },
238
- allowUnknownAuth: options.allowUnknownAuth === true
239
- });
240
- stages[2].status = verRes.ok ? 'completed' : 'failed';
241
- stages[2].passed = Boolean(verRes.ok);
242
- stages[2].output = verRes.response || verRes.error || null;
243
- stages[2].worktreePath = worktreeDir || null;
244
- recordRunReceipt({
245
- harness: roles.verifier.id,
246
- task: `[Verifier] ${task}`,
247
- complexityScore: complexity.score,
248
- success: Boolean(verRes.ok),
249
- durationMs: Date.now() - verifyStart
250
- });
251
- if (!verRes.ok) allSucceeded = false;
252
- } finally {
253
- removeVerifierWorktree(cwd, worktreeDir);
254
- }
255
- }
125
+ const diff = diffBetween(cwd, baseCommit, commit);
126
+ const verifyDir = createWorktree(cwd, { ref: commit, prefix: 'meta-harness-verify-' });
127
+ try {
128
+ const verification = await run(stage('Verifier'), verifier, `[ROLE: INDEPENDENT VERIFIER]\nYou did not write this code. Do not change any file. Run the checks you can and audit commit ${commit} for this task:\n${task}\n\nBlueprint:\n${architect.response}\n\nDiff${diff.length > 30000 ? ' (first 30000 characters; read the rest from this checkout)' : ''}:\n${diff.slice(0, 30000)}\n\nEnd your reply with exactly one JSON object: {"verdict":"PASS"|"FAIL","findings":["..."]}`, { workdir: verifyDir });
129
+ const parsed = verification.ok ? parseVerification(verification.response) : { parsed: false, verdict: 'NOT_RUN', findings: [] };
130
+ Object.assign(stage('Verifier'), { commit, verdict: parsed.verdict, findings: parsed.findings, passed: parsed.verdict === 'PASS' });
131
+ if (parsed.verdict !== 'PASS') ok = false;
132
+ } finally { removeWorktree(cwd, verifyDir); }
133
+
134
+ if (options.autoReview) {
135
+ const review = await executeCodeReview({ cwd, author: roles.implementer.id, threadId: thread.id, task, target: `${baseCommit}...${commit}`, dispatchFn: options.dispatchFn, reviewer: options.reviewer });
136
+ stages.push({ role: 'Reviewer', harness: review.review?.reviewer || null, status: review.ok ? 'completed' : 'failed', verdict: review.review?.verdict || null, riskScore: review.review?.riskScore ?? null, output: review.review?.summary || review.error || null });
137
+ if (!review.ok || review.review?.verdict !== 'APPROVE') ok = false;
256
138
  }
257
- } else {
258
- stages[0].output = `Planned Architect stage (${roles.architect.name}) for: ${task.slice(0, 80)}`;
259
- stages[1].output = `Planned Implementer stage (${roles.implementer.name}) following Architect blueprint.`;
260
- stages[2].output = `Planned cross-family Verifier stage (${roles.verifier.name}, family: ${roles.verifier.family}) in isolated git worktree.`;
139
+ return finish();
140
+ } catch (error) {
141
+ // An infrastructure failure (git, a refused commit) must still close the thread and report.
142
+ ok = false;
143
+ const current = stages.find(item => ['running', 'planned'].includes(item.status));
144
+ if (current) Object.assign(current, { status: 'failed', failureKind: 'process', output: String(error.message) });
145
+ return finish();
146
+ } finally {
147
+ removeWorktree(cwd, implementerDir);
148
+ if (implementerDir && !commit) deleteBranch(cwd, branch);
261
149
  }
262
150
 
263
- const durationMs = Date.now() - startTime;
264
- const result = {
265
- ok: allSucceeded,
266
- mode: 'pipeline',
267
- executedLive: executeLive,
268
- task,
269
- stages,
270
- durationMs,
271
- heterogeneousFamiliesVerified: roles.heterogeneousFamilies
272
- };
273
-
274
- if (executeLive) {
275
- await notifyAlert({
276
- title: allSucceeded ? 'Meta Harness: Pipeline Completed' : 'Meta Harness: Pipeline Failed',
277
- message: `Multi-agent pipeline ${allSucceeded ? 'succeeded' : 'failed'} for task: ${task.slice(0, 60)}`,
278
- severity: allSucceeded ? 'info' : 'warning'
279
- });
151
+ function finish() {
152
+ return (async () => {
153
+ updateThread(thread.id, { status: ok ? 'completed' : 'failed', ...(commit ? { branch } : {}) });
154
+ const alert = await notifyAlert({ title: ok ? 'Meta Harness: pipeline passed' : 'Meta Harness: pipeline failed', message: `${task.slice(0, 80)}${commit ? ` (branch ${branch})` : ''}`, severity: ok ? 'info' : 'warning' });
155
+ return { ...base, ok, threadId: thread.id, branch: commit ? branch : null, commit, baseCommit, gate, warnings, durationMs: Date.now() - started, alert };
156
+ })();
280
157
  }
281
-
282
- return result;
283
158
  }
284
159
 
285
- /**
286
- * Council Consensus (Multi-model deliberation across available harnesses)
287
- */
288
- async function runCouncilCoordination(task, pool, options = {}) {
289
- const councilHarnesses = options.harnesses
290
- || pool.slice(0, 3).map(h => h.id);
291
- const executeLive = options.execute === true;
160
+ async function runCouncil(task, poolInfo, options) {
161
+ // One seat per model family, strongest first, so the seats can actually disagree.
162
+ const families = new Set();
163
+ const seats = options.harnesses || [...poolInfo.ready].sort((a, b) => b.qualityScore - a.qualityScore).filter(h => !families.has(h.family) && families.add(h.family)).slice(0, 3).map(h => h.id);
164
+ if (options.execute !== true) return { ok: seats.length > 0, mode: 'council', task, executedLive: false, consensus: 'planned', excluded: poolInfo.excluded, votes: seats.map(harness => ({ harness, status: 'planned' })) };
165
+ if (!seats.length) throw new Error('No harness can take work right now');
292
166
  const cwd = options.cwd || process.cwd();
293
- const dispatchFn = options.dispatchFn || dispatchAgent;
294
-
295
- if (executeLive) {
296
- const dispatches = await Promise.all(councilHarnesses.map(async (h) => {
297
- try {
298
- const res = await dispatchFn({
299
- agent: h,
300
- cwd,
301
- prompt: `[COUNCIL DELIBERATION]\nProvide your independent engineering assessment and recommendation for:\n${task}`,
302
- permissions: { read: true, write: false },
303
- allowUnknownAuth: options.allowUnknownAuth === true
304
- });
305
- return {
306
- harness: h,
307
- ok: Boolean(res.ok),
308
- recommendation: res.ok ? 'completed' : 'failed',
309
- response: res.response || res.error || null
310
- };
311
- } catch (err) {
312
- return {
313
- harness: h,
314
- ok: false,
315
- recommendation: 'error',
316
- response: err.message
317
- };
318
- }
319
- }));
320
-
321
- const passedCount = dispatches.filter(d => d.ok).length;
322
- return {
323
- ok: passedCount > 0,
324
- mode: 'council',
325
- executedLive: true,
326
- task,
327
- council: councilHarnesses,
328
- consensus: passedCount === councilHarnesses.length ? 'unanimous' : (passedCount > 0 ? 'majority' : 'failed'),
329
- votes: dispatches
330
- };
331
- }
332
-
333
- const votes = councilHarnesses.map(h => ({
334
- harness: h,
335
- status: 'planned',
336
- recommendation: 'ready',
337
- reasoning: `Scheduled independent read-only deliberation on ${h}.`
167
+ const votes = await Promise.all(seats.map(async harness => {
168
+ const result = await dispatchWithReceipt({ agent: harness, cwd, contextTask: task, prompt: `[ROLE: COUNCIL MEMBER]\nDo not change any file. Assess this proposal, with its trade-offs and risks:\n${task}\n\nEnd your reply with exactly one JSON object: {"vote":"APPROVE"|"REJECT","reason":"..."}`, permissions: READ, allowUnknownAuth: true }, { dispatchFn: options.dispatchFn, role: 'Council' });
169
+ return { harness, ok: result.ok === true, ...(result.ok ? parseVote(result.response) : { vote: 'ABSTAIN', reason: null, error: result.error }), feedback: result.response || null };
338
170
  }));
339
-
340
- return {
341
- ok: true,
342
- mode: 'council',
343
- executedLive: false,
344
- task,
345
- council: councilHarnesses,
346
- consensus: 'planned',
347
- votes
348
- };
171
+ const count = vote => votes.filter(item => item.vote === vote).length;
172
+ const majority = votes.length / 2;
173
+ const consensus = count('APPROVE') > majority ? 'approved' : count('REJECT') > majority ? 'rejected' : 'no-consensus';
174
+ return { ok: votes.some(item => item.ok), mode: 'council', task, executedLive: true, consensus, excluded: poolInfo.excluded, votes };
349
175
  }
350
176
 
351
- /**
352
- * Speculative Concurrent Race
353
- */
354
- async function runRaceCoordination(task, pool, options = {}) {
355
- const contenders = options.harnesses
356
- || pool.slice(0, 2).map(h => h.id);
357
- const executeLive = options.execute === true;
177
+ async function runRace(task, poolInfo, options) {
178
+ const seats = options.candidates || [...poolInfo.ready].sort((a, b) => b.qualityScore - a.qualityScore).slice(0, 2).map(h => h.id);
179
+ if (options.execute !== true) return { ok: seats.length > 1, mode: 'race', task, executedLive: false, excluded: poolInfo.excluded, candidates: seats.map(harness => ({ harness, status: 'planned' })) };
180
+ if (seats.length < 2) throw new Error('A race needs at least two harnesses that can take work');
358
181
  const cwd = options.cwd || process.cwd();
359
- const dispatchFn = options.dispatchFn || dispatchAgent;
360
-
361
- if (executeLive) {
362
- const results = await Promise.all(contenders.map(async (h) => {
363
- const start = Date.now();
364
- try {
365
- const res = await dispatchFn({
366
- agent: h,
367
- cwd,
368
- prompt: task,
369
- permissions: { read: true, write: false },
370
- allowUnknownAuth: options.allowUnknownAuth === true
371
- });
372
- return { harness: h, ok: Boolean(res.ok), durationMs: Date.now() - start, response: res.response || null };
373
- } catch (err) {
374
- return { harness: h, ok: false, durationMs: Date.now() - start, error: err.message };
375
- }
376
- }));
377
- const winners = results.filter(r => r.ok).sort((a, b) => a.durationMs - b.durationMs);
378
- return {
379
- ok: winners.length > 0,
380
- mode: 'race',
381
- executedLive: true,
382
- task,
383
- contenders,
384
- winner: winners[0]?.harness || null,
385
- results
386
- };
387
- }
182
+ if (!isGitRepo(cwd)) throw new Error('A race writes on separate branches, so it must run inside a git repository');
183
+ const id = `race-${Date.now().toString(36)}`;
184
+ const entries = await Promise.all(seats.map(async harness => {
185
+ const branch = `meta-harness/${id}-${harness}`;
186
+ const directory = createWorktree(cwd, { branch });
187
+ let keep = false;
188
+ try {
189
+ const result = await dispatchWithReceipt({ agent: harness, cwd: directory, contextCwd: cwd, contextTask: task, prompt: `[ROLE: IMPLEMENTER]\nWork only in this checkout. Implement the task and its tests. Do not commit.\nTask:\n${task}`, permissions: WRITE, allowUnknownAuth: true }, { dispatchFn: options.dispatchFn, role: 'Race' });
190
+ const commit = result.ok ? commitAll(directory, `meta-harness race: ${task.slice(0, 60)}\n\nAgent: ${harness}`) : null;
191
+ if (commit && options.gate) linkDependencies(cwd, directory);
192
+ const gate = commit && options.gate ? runQualityGate(directory, options.gateOptions) : null;
193
+ keep = Boolean(commit) && (!gate || gate.passed);
194
+ return { harness, branch: keep ? branch : null, commit: keep ? commit : null, ok: keep, gate, durationMs: result.durationMs, error: result.ok ? (commit ? (keep ? undefined : 'The quality gate failed') : 'No files changed') : result.error };
195
+ } catch (error) {
196
+ return { harness, branch: null, commit: null, ok: false, gate: null, durationMs: null, error: String(error.message) };
197
+ } finally {
198
+ removeWorktree(cwd, directory);
199
+ if (!keep) deleteBranch(cwd, branch);
200
+ }
201
+ }));
202
+ const finishers = entries.filter(entry => entry.ok).sort((a, b) => a.durationMs - b.durationMs);
203
+ return { ok: finishers.length > 0, mode: 'race', task, executedLive: true, gateApplied: Boolean(options.gate), winner: finishers[0]?.harness || null, excluded: poolInfo.excluded, candidates: entries };
204
+ }
388
205
 
389
- return {
390
- ok: true,
391
- mode: 'race',
392
- executedLive: false,
393
- task,
394
- contenders,
395
- winner: contenders[0] || null,
396
- reason: 'Planned concurrent race across selected contenders.'
397
- };
206
+ export async function coordinateTask(taskDescription, options = {}) {
207
+ const task = String(taskDescription || '').trim();
208
+ if (!task) throw new Error('Task description is required for coordination');
209
+ const mode = options.mode || 'pipeline';
210
+ if (!['pipeline', 'orchestrate', 'council', 'race'].includes(mode)) throw new Error(`Unsupported coordination mode: ${mode}`);
211
+ const poolInfo = await resolvePool(options);
212
+ if (mode === 'council') return runCouncil(task, poolInfo, options);
213
+ if (mode === 'race') return runRace(task, poolInfo, options);
214
+ return runPipeline(task, poolInfo, options);
398
215
  }
@@ -0,0 +1,33 @@
1
+ /** One path for every agent run: contain it if needed, dispatch it, classify the outcome, write its receipt. */
2
+ import { executeOnNode } from './executor.mjs';
3
+ import { classifyFailure, READ_ONLY_ADAPTERS } from './worker.mjs';
4
+ import { recordRunReceipt } from './learner.mjs';
5
+ import { isGitRepo, createWorktree, removeWorktree } from './worktree.mjs';
6
+
7
+ export async function dispatchWithReceipt(request, { dispatchFn, node = request.node || 'local', role, complexityScore, escalatedFrom } = {}) {
8
+ const started = Date.now();
9
+ // A harness that cannot be told not to write does read-only work in a checkout that is thrown away.
10
+ const contain = node === 'local' && request.permissions?.write !== true && !READ_ONLY_ADAPTERS.includes(request.agent) && request.isolated !== true && isGitRepo(request.cwd);
11
+ let scratch = null, result;
12
+ try {
13
+ let effective = request;
14
+ if (contain) {
15
+ scratch = createWorktree(request.cwd, { prefix: 'meta-harness-readonly-' });
16
+ effective = { ...request, cwd: scratch, contextCwd: request.contextCwd || request.cwd, isolated: true };
17
+ }
18
+ result = dispatchFn ? await dispatchFn(effective) : await executeOnNode(node, { ...effective, operation: 'dispatch' });
19
+ if (contain) result = { ...result, readOnly: 'disposable-checkout' };
20
+ } catch (error) {
21
+ const message = String(error?.message || error);
22
+ result = { ok: false, error: message, agent: request.agent, ...(error?.code === 'NOT_READY' ? { failureKind: 'not-ready' } : classifyFailure(message)) };
23
+ } finally {
24
+ if (scratch) removeWorktree(request.cwd, scratch);
25
+ }
26
+ const durationMs = Date.now() - started;
27
+ await recordRunReceipt({
28
+ harness: request.agent, role, node, task: request.contextTask || request.prompt, complexityScore,
29
+ success: result.ok === true, durationMs, failureKind: result.ok ? null : result.failureKind || 'agent',
30
+ error: result.ok ? null : result.error, resetsAt: result.resetsAt, escalatedFrom,
31
+ });
32
+ return { ...result, durationMs };
33
+ }