dual-brain 0.2.30 → 0.3.1

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 (312) hide show
  1. package/.dual-brain/docs/claude-code-extension-points.md +32 -0
  2. package/.dual-brain/docs/data-tools-capabilities.md +181 -0
  3. package/.dual-brain/docs/ecosystem-tools.md +91 -0
  4. package/.dual-brain/docs/panel-handoff.md +124 -0
  5. package/.dual-brain/docs/ruflo-analysis.md +48 -0
  6. package/bin/dual-brain.mjs +56 -56
  7. package/dist/mcp-server/index.d.ts +27 -0
  8. package/dist/mcp-server/index.js +359 -0
  9. package/dist/mcp-server/index.js.map +1 -0
  10. package/dist/src/agent-protocol.d.ts +163 -0
  11. package/dist/src/agent-protocol.js +368 -0
  12. package/dist/src/agent-protocol.js.map +1 -0
  13. package/dist/src/agents/registry.d.ts +52 -0
  14. package/dist/src/agents/registry.js +393 -0
  15. package/dist/src/agents/registry.js.map +1 -0
  16. package/dist/src/awareness.d.ts +93 -0
  17. package/dist/src/awareness.js +406 -0
  18. package/dist/src/awareness.js.map +1 -0
  19. package/dist/src/brief.d.ts +48 -0
  20. package/dist/src/brief.js +179 -0
  21. package/dist/src/brief.js.map +1 -0
  22. package/dist/src/calibration.d.ts +32 -0
  23. package/dist/src/calibration.js +133 -0
  24. package/dist/src/calibration.js.map +1 -0
  25. package/dist/src/checkpoint.d.ts +33 -0
  26. package/dist/src/checkpoint.js +99 -0
  27. package/dist/src/checkpoint.js.map +1 -0
  28. package/dist/src/ci-triage.d.ts +33 -0
  29. package/dist/src/ci-triage.js +193 -0
  30. package/dist/src/ci-triage.js.map +1 -0
  31. package/dist/src/cognitive-loop.d.ts +56 -0
  32. package/dist/src/cognitive-loop.js +495 -0
  33. package/dist/src/cognitive-loop.js.map +1 -0
  34. package/dist/src/collaboration.d.ts +147 -0
  35. package/dist/src/collaboration.js +438 -0
  36. package/dist/src/collaboration.js.map +1 -0
  37. package/dist/src/context-intel.d.ts +47 -0
  38. package/dist/src/context-intel.js +156 -0
  39. package/dist/src/context-intel.js.map +1 -0
  40. package/dist/src/context.d.ts +53 -0
  41. package/dist/src/context.js +332 -0
  42. package/dist/src/context.js.map +1 -0
  43. package/dist/src/continuity.d.ts +89 -0
  44. package/dist/src/continuity.js +230 -0
  45. package/dist/src/continuity.js.map +1 -0
  46. package/dist/src/cost-tracker.d.ts +47 -0
  47. package/dist/src/cost-tracker.js +170 -0
  48. package/dist/src/cost-tracker.js.map +1 -0
  49. package/dist/src/debrief.d.ts +53 -0
  50. package/dist/src/debrief.js +222 -0
  51. package/dist/src/debrief.js.map +1 -0
  52. package/dist/src/decide.d.ts +96 -0
  53. package/dist/src/decide.js +744 -0
  54. package/dist/src/decide.js.map +1 -0
  55. package/dist/src/decompose.d.ts +39 -0
  56. package/dist/src/decompose.js +218 -0
  57. package/dist/src/decompose.js.map +1 -0
  58. package/dist/src/detect.d.ts +91 -0
  59. package/dist/src/detect.js +544 -0
  60. package/dist/src/detect.js.map +1 -0
  61. package/dist/src/dispatch.d.ts +154 -0
  62. package/dist/src/dispatch.js +1306 -0
  63. package/dist/src/dispatch.js.map +1 -0
  64. package/dist/src/doctor.d.ts +421 -0
  65. package/dist/src/doctor.js +1689 -0
  66. package/dist/src/doctor.js.map +1 -0
  67. package/dist/src/engine.d.ts +70 -0
  68. package/dist/src/engine.js +155 -0
  69. package/dist/src/engine.js.map +1 -0
  70. package/dist/src/envelope.d.ts +36 -0
  71. package/dist/src/envelope.js +80 -0
  72. package/dist/src/envelope.js.map +1 -0
  73. package/dist/src/failure-memory.d.ts +55 -0
  74. package/dist/src/failure-memory.js +175 -0
  75. package/dist/src/failure-memory.js.map +1 -0
  76. package/dist/src/fx.d.ts +87 -0
  77. package/dist/src/fx.js +272 -0
  78. package/dist/src/fx.js.map +1 -0
  79. package/dist/src/governance.d.ts +93 -0
  80. package/dist/src/governance.js +261 -0
  81. package/dist/src/governance.js.map +1 -0
  82. package/dist/src/handoff.d.ts +11 -0
  83. package/dist/src/handoff.js +90 -0
  84. package/dist/src/handoff.js.map +1 -0
  85. package/dist/src/head-protocol.d.ts +76 -0
  86. package/dist/src/head-protocol.js +109 -0
  87. package/dist/src/head-protocol.js.map +1 -0
  88. package/dist/src/head.d.ts +222 -0
  89. package/dist/src/head.js +765 -0
  90. package/dist/src/head.js.map +1 -0
  91. package/dist/src/health.d.ts +132 -0
  92. package/dist/src/health.js +435 -0
  93. package/dist/src/health.js.map +1 -0
  94. package/dist/src/inbox.d.ts +70 -0
  95. package/dist/src/inbox.js +218 -0
  96. package/dist/src/inbox.js.map +1 -0
  97. package/dist/src/index.d.ts +33 -0
  98. package/dist/src/index.js +38 -0
  99. package/dist/src/index.js.map +1 -0
  100. package/dist/src/install-hooks.d.ts +13 -0
  101. package/dist/src/install-hooks.js +88 -0
  102. package/dist/src/install-hooks.js.map +1 -0
  103. package/dist/src/integrity.d.ts +59 -0
  104. package/dist/src/integrity.js +206 -0
  105. package/dist/src/integrity.js.map +1 -0
  106. package/dist/src/intelligence.d.ts +104 -0
  107. package/dist/src/intelligence.js +391 -0
  108. package/dist/src/intelligence.js.map +1 -0
  109. package/dist/src/ledger.d.ts +54 -0
  110. package/dist/src/ledger.js +179 -0
  111. package/dist/src/ledger.js.map +1 -0
  112. package/dist/src/living-docs.d.ts +14 -0
  113. package/dist/src/living-docs.js +197 -0
  114. package/dist/src/living-docs.js.map +1 -0
  115. package/dist/src/memory-tiers.d.ts +37 -0
  116. package/dist/src/memory-tiers.js +160 -0
  117. package/dist/src/memory-tiers.js.map +1 -0
  118. package/dist/src/model-profiles.d.ts +65 -0
  119. package/dist/src/model-profiles.js +568 -0
  120. package/dist/src/model-profiles.js.map +1 -0
  121. package/dist/src/models.d.ts +58 -0
  122. package/dist/src/models.js +327 -0
  123. package/dist/src/models.js.map +1 -0
  124. package/dist/src/narrative.d.ts +54 -0
  125. package/dist/src/narrative.js +163 -0
  126. package/dist/src/narrative.js.map +1 -0
  127. package/dist/src/nextstep.d.ts +16 -0
  128. package/dist/src/nextstep.js +103 -0
  129. package/dist/src/nextstep.js.map +1 -0
  130. package/dist/src/observer.d.ts +18 -0
  131. package/dist/src/observer.js +251 -0
  132. package/dist/src/observer.js.map +1 -0
  133. package/dist/src/outcome.d.ts +110 -0
  134. package/dist/src/outcome.js +377 -0
  135. package/dist/src/outcome.js.map +1 -0
  136. package/dist/src/pipeline.d.ts +167 -0
  137. package/dist/src/pipeline.js +1503 -0
  138. package/dist/src/pipeline.js.map +1 -0
  139. package/dist/src/playbook.d.ts +59 -0
  140. package/dist/src/playbook.js +238 -0
  141. package/dist/src/playbook.js.map +1 -0
  142. package/dist/src/pr-agent.d.ts +97 -0
  143. package/dist/src/pr-agent.js +195 -0
  144. package/dist/src/pr-agent.js.map +1 -0
  145. package/dist/src/predictive.d.ts +57 -0
  146. package/dist/src/predictive.js +230 -0
  147. package/dist/src/predictive.js.map +1 -0
  148. package/dist/src/profile.d.ts +294 -0
  149. package/dist/src/profile.js +1347 -0
  150. package/dist/src/profile.js.map +1 -0
  151. package/dist/src/prompt-audit.d.ts +22 -0
  152. package/dist/src/prompt-audit.js +194 -0
  153. package/dist/src/prompt-audit.js.map +1 -0
  154. package/dist/src/prompt-intel.d.ts +12 -0
  155. package/dist/src/prompt-intel.js +321 -0
  156. package/dist/src/prompt-intel.js.map +1 -0
  157. package/dist/src/provider-context.d.ts +121 -0
  158. package/dist/src/provider-context.js +222 -0
  159. package/dist/src/provider-context.js.map +1 -0
  160. package/dist/src/provider-manager.d.ts +92 -0
  161. package/dist/src/provider-manager.js +428 -0
  162. package/dist/src/provider-manager.js.map +1 -0
  163. package/dist/src/receipt.d.ts +87 -0
  164. package/dist/src/receipt.js +326 -0
  165. package/dist/src/receipt.js.map +1 -0
  166. package/dist/src/recommendations.d.ts +13 -0
  167. package/dist/src/recommendations.js +291 -0
  168. package/dist/src/recommendations.js.map +1 -0
  169. package/dist/src/redact.d.ts +15 -0
  170. package/dist/src/redact.js +129 -0
  171. package/dist/src/redact.js.map +1 -0
  172. package/dist/src/replit.d.ts +397 -0
  173. package/dist/src/replit.js +1160 -0
  174. package/dist/src/replit.js.map +1 -0
  175. package/dist/src/repo.d.ts +149 -0
  176. package/dist/src/repo.js +416 -0
  177. package/dist/src/repo.js.map +1 -0
  178. package/dist/src/revert.d.ts +30 -0
  179. package/dist/src/revert.js +166 -0
  180. package/dist/src/revert.js.map +1 -0
  181. package/dist/src/room.d.ts +102 -0
  182. package/dist/src/room.js +212 -0
  183. package/dist/src/room.js.map +1 -0
  184. package/dist/src/routing-advisor.d.ts +57 -0
  185. package/dist/src/routing-advisor.js +221 -0
  186. package/dist/src/routing-advisor.js.map +1 -0
  187. package/dist/src/self-correct.d.ts +40 -0
  188. package/dist/src/self-correct.js +137 -0
  189. package/dist/src/self-correct.js.map +1 -0
  190. package/dist/src/session-lock.d.ts +35 -0
  191. package/dist/src/session-lock.js +134 -0
  192. package/dist/src/session-lock.js.map +1 -0
  193. package/dist/src/session.d.ts +267 -0
  194. package/dist/src/session.js +1660 -0
  195. package/dist/src/session.js.map +1 -0
  196. package/dist/src/settings-tui.d.ts +5 -0
  197. package/dist/src/settings-tui.js +422 -0
  198. package/dist/src/settings-tui.js.map +1 -0
  199. package/dist/src/setup-flow.d.ts +63 -0
  200. package/dist/src/setup-flow.js +233 -0
  201. package/dist/src/setup-flow.js.map +1 -0
  202. package/dist/src/signal.d.ts +19 -0
  203. package/dist/src/signal.js +122 -0
  204. package/dist/src/signal.js.map +1 -0
  205. package/dist/src/simmer.d.ts +85 -0
  206. package/dist/src/simmer.js +224 -0
  207. package/dist/src/simmer.js.map +1 -0
  208. package/dist/src/state-export.d.ts +129 -0
  209. package/dist/src/state-export.js +233 -0
  210. package/dist/src/state-export.js.map +1 -0
  211. package/dist/src/strategy.d.ts +54 -0
  212. package/dist/src/strategy.js +95 -0
  213. package/dist/src/strategy.js.map +1 -0
  214. package/dist/src/subscription.d.ts +40 -0
  215. package/dist/src/subscription.js +189 -0
  216. package/dist/src/subscription.js.map +1 -0
  217. package/dist/src/templates.d.ts +208 -0
  218. package/dist/src/templates.js +238 -0
  219. package/dist/src/templates.js.map +1 -0
  220. package/dist/src/test.d.ts +9 -0
  221. package/dist/src/test.js +1173 -0
  222. package/dist/src/test.js.map +1 -0
  223. package/dist/src/think-engine.d.ts +67 -0
  224. package/dist/src/think-engine.js +412 -0
  225. package/dist/src/think-engine.js.map +1 -0
  226. package/dist/src/tui.d.ts +71 -0
  227. package/dist/src/tui.js +242 -0
  228. package/dist/src/tui.js.map +1 -0
  229. package/dist/src/types.d.ts +177 -0
  230. package/dist/src/types.js +6 -0
  231. package/dist/src/types.js.map +1 -0
  232. package/dist/src/update-check.d.ts +7 -0
  233. package/dist/src/update-check.js +36 -0
  234. package/dist/src/update-check.js.map +1 -0
  235. package/dist/src/wave-planner.d.ts +30 -0
  236. package/dist/src/wave-planner.js +281 -0
  237. package/dist/src/wave-planner.js.map +1 -0
  238. package/hooks/head-guard.sh +41 -0
  239. package/hooks/precompact.mjs +3 -3
  240. package/hooks/session-end.mjs +3 -3
  241. package/hooks/task-classifier.mjs +328 -0
  242. package/hooks/vibe-router.mjs +387 -0
  243. package/install.mjs +2 -2
  244. package/package.json +29 -153
  245. package/src/agents/registry.mjs +0 -405
  246. package/src/awareness.mjs +0 -425
  247. package/src/brief.mjs +0 -266
  248. package/src/calibration.mjs +0 -148
  249. package/src/checkpoint.mjs +0 -109
  250. package/src/ci-triage.mjs +0 -191
  251. package/src/cognitive-loop.mjs +0 -562
  252. package/src/collaboration.mjs +0 -545
  253. package/src/context-intel.mjs +0 -158
  254. package/src/context.mjs +0 -389
  255. package/src/continuity.mjs +0 -298
  256. package/src/cost-tracker.mjs +0 -184
  257. package/src/debrief.mjs +0 -228
  258. package/src/decide.mjs +0 -1099
  259. package/src/decompose.mjs +0 -331
  260. package/src/detect.mjs +0 -702
  261. package/src/dispatch.mjs +0 -1447
  262. package/src/doctor.mjs +0 -1607
  263. package/src/envelope.mjs +0 -139
  264. package/src/failure-memory.mjs +0 -178
  265. package/src/fx.mjs +0 -276
  266. package/src/governance.mjs +0 -279
  267. package/src/handoff.mjs +0 -87
  268. package/src/head-protocol.mjs +0 -128
  269. package/src/head.mjs +0 -952
  270. package/src/health.mjs +0 -528
  271. package/src/inbox.mjs +0 -195
  272. package/src/index.mjs +0 -44
  273. package/src/install-hooks.mjs +0 -100
  274. package/src/integrity.mjs +0 -245
  275. package/src/intelligence.mjs +0 -447
  276. package/src/ledger.mjs +0 -196
  277. package/src/living-docs.mjs +0 -210
  278. package/src/memory-tiers.mjs +0 -193
  279. package/src/models.mjs +0 -363
  280. package/src/narrative.mjs +0 -169
  281. package/src/nextstep.mjs +0 -100
  282. package/src/observer.mjs +0 -241
  283. package/src/outcome.mjs +0 -400
  284. package/src/pipeline.mjs +0 -1711
  285. package/src/playbook.mjs +0 -257
  286. package/src/pr-agent.mjs +0 -214
  287. package/src/predictive.mjs +0 -250
  288. package/src/profile.mjs +0 -1411
  289. package/src/prompt-audit.mjs +0 -231
  290. package/src/prompt-intel.mjs +0 -325
  291. package/src/provider-context.mjs +0 -257
  292. package/src/receipt.mjs +0 -344
  293. package/src/recommendations.mjs +0 -296
  294. package/src/redact.mjs +0 -192
  295. package/src/replit.mjs +0 -1210
  296. package/src/repo.mjs +0 -445
  297. package/src/revert.mjs +0 -149
  298. package/src/routing-advisor.mjs +0 -204
  299. package/src/self-correct.mjs +0 -147
  300. package/src/session-lock.mjs +0 -160
  301. package/src/session.mjs +0 -1655
  302. package/src/settings-tui.mjs +0 -373
  303. package/src/setup-flow.mjs +0 -223
  304. package/src/signal.mjs +0 -115
  305. package/src/simmer.mjs +0 -241
  306. package/src/strategy.mjs +0 -235
  307. package/src/subscription.mjs +0 -212
  308. package/src/templates.mjs +0 -260
  309. package/src/think-engine.mjs +0 -428
  310. package/src/tui.mjs +0 -276
  311. package/src/update-check.mjs +0 -35
  312. package/src/wave-planner.mjs +0 -294
@@ -1,562 +0,0 @@
1
- // Cognitive Loop — the integration layer that makes HEAD a continuous process.
2
- // Replaces single-shot deliberation with: perceive → plan → predict → dispatch → debrief → replan → ...
3
-
4
- import { processTurn, loadState, perceive, assessUncertainty, deriveObligations, notice, deliberate, assessDepth, recordDispatchOutcome } from './head.mjs';
5
- import { planWaves, shouldReplan, replan, estimateWaveCost } from './wave-planner.mjs';
6
- import { parseDebrief, generateDebriefInstruction, integrateDebrief, summarizeWaveOutcome } from './debrief.mjs';
7
- import { predictFailureModes, generatePreventions, scoreDispatchReadiness, evolvePatterns, loadSessionPatterns } from './predictive.mjs';
8
- import { writeDeliberation } from './head-protocol.mjs';
9
- import { check as checkInbox, generateInboxBrief, purge as purgeInbox } from './inbox.mjs';
10
- import * as memoryTiers from './memory-tiers.mjs';
11
- import * as narrative from './narrative.mjs';
12
- import * as simmer from './simmer.mjs';
13
- import { build as buildEnvelope } from './envelope.mjs';
14
- import { acquire as acquireSession, release as releaseSession, isOwner as isSessionOwner } from './session-lock.mjs';
15
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
16
- import { join } from 'node:path';
17
-
18
- // ── Pattern cache ──────────────────────────────────────────────────────────
19
- let _patternsCache = null;
20
- let _patternsCacheTs = 0;
21
- function getCachedPatterns() {
22
- if (!_patternsCache || Date.now() - _patternsCacheTs > 5000) {
23
- _patternsCache = loadSessionPatterns();
24
- _patternsCacheTs = Date.now();
25
- }
26
- return _patternsCache;
27
- }
28
-
29
- const LOOP_STATE_DIR = join(process.cwd(), '.dualbrain');
30
- const LOOP_STATE_FILE = join(LOOP_STATE_DIR, 'cognitive-loop.json');
31
-
32
- // ── Loop state persistence ──────────────────────────────────────────────────
33
-
34
- function loadLoopState() {
35
- try {
36
- if (existsSync(LOOP_STATE_FILE)) {
37
- return JSON.parse(readFileSync(LOOP_STATE_FILE, 'utf8'));
38
- }
39
- } catch {}
40
- return freshLoopState();
41
- }
42
-
43
- function freshLoopState() {
44
- return {
45
- sessionId: Date.now().toString(36),
46
- activePlan: null,
47
- completedWaves: [],
48
- debriefs: [],
49
- situationHistory: [],
50
- replans: 0,
51
- totalDispatches: 0,
52
- totalTokensEstimated: 0,
53
- };
54
- }
55
-
56
- function saveLoopState(state) {
57
- mkdirSync(LOOP_STATE_DIR, { recursive: true });
58
- writeFileSync(LOOP_STATE_FILE, JSON.stringify(state, null, 2));
59
- }
60
-
61
- // ── The cognitive loop ──────────────────────────────────────────────────────
62
-
63
- /**
64
- * Entry point: process a user message through the full cognitive loop.
65
- * Returns a LoopResult that tells the caller exactly what to do next.
66
- *
67
- * @param {string} userMessage
68
- * @param {object} context - { files, recentFiles, priorFailures, uncommittedFiles, etc }
69
- * @returns {LoopResult}
70
- */
71
- export function enter(userMessage, context = {}) {
72
- // Session lock: one HEAD at a time
73
- const lock = acquireSession();
74
- if (!lock.acquired) {
75
- return {
76
- phase: 'readonly',
77
- action: { type: 'respond', mode: 'readonly' },
78
- rationale: `Another session (${lock.existingSession}) is active. This session is read-only to prevent split-brain.`,
79
- shouldAskUser: false,
80
- surfaceNoticings: [],
81
- plan: null,
82
- nextDispatch: null,
83
- };
84
- }
85
-
86
- const headState = loadState();
87
- const loopState = loadLoopState();
88
-
89
- // Check if we just auto-updated — surface it naturally
90
- _checkUpdateNotice(context);
91
-
92
- // Immersion: load memory tiers so HEAD is "in the song"
93
- const memory = memoryTiers.assemble({
94
- userMessage,
95
- files: context.files || [],
96
- intent: context._detectedIntent || null,
97
- });
98
- if (memory.combined) {
99
- context._immersionContext = memory.combined;
100
- }
101
-
102
- // Check inbox for HEAD before processing
103
- const inboxBrief = generateInboxBrief('head');
104
- if (inboxBrief) {
105
- context._inboxBrief = inboxBrief;
106
- }
107
-
108
- // Simmer: check for crystallized ideas to surface
109
- const crystallized = simmer.harvest();
110
- if (crystallized.length > 0) {
111
- context._crystallizedIdeas = crystallized.map(i => i.idea);
112
- }
113
-
114
- // Phase 1: Full cognitive pipeline
115
- const turn = processTurn(headState, userMessage, context);
116
-
117
- // Save situation for history (includes mode for turn-over-turn tracking)
118
- const mode = turn.situation?.mode || { primary: 'work', confidence: 0.5 };
119
- loopState.situationHistory.push({
120
- ts: Date.now(),
121
- depth: turn.depth,
122
- action: turn.action.type,
123
- confidence: turn.result.confidence.score,
124
- mode: mode.primary,
125
- });
126
-
127
- // Surface update notice as a noticing
128
- if (context._updateNotice) {
129
- turn.result.surfaceNoticings = turn.result.surfaceNoticings || [];
130
- turn.result.surfaceNoticings.unshift(context._updateNotice);
131
- }
132
-
133
- // Narrative evolution: update HEAD's running understanding
134
- _evolveNarrative(turn, userMessage, context);
135
-
136
- // Simmer: capture any ideas from user message that aren't direct tasks
137
- _captureSimmerSignals(userMessage, turn);
138
-
139
- // If HEAD says don't dispatch, or it's a "proceed" with no active plan, respond
140
- if ((!turn.shouldDispatch && !turn.shouldThink) || (turn.action.type === 'proceed' && !loopState.activePlan)) {
141
- saveLoopState(loopState);
142
- return {
143
- phase: 'respond',
144
- action: turn.action,
145
- rationale: turn.rationale,
146
- shouldAskUser: turn.shouldAskUser,
147
- surfaceNoticings: turn.result.surfaceNoticings,
148
- plan: null,
149
- nextDispatch: null,
150
- mode,
151
- };
152
- }
153
-
154
- // Persist deliberation artifact for deliberation-gate hook validation
155
- try {
156
- writeDeliberation(userMessage, { situation: turn.situation, result: turn.result });
157
- } catch (err) {
158
- // Non-fatal: don't break the loop if deliberation persistence fails
159
- process.stderr.write(`[cognitive-loop] writeDeliberation failed: ${err?.message?.slice(0, 120)}\n`);
160
- }
161
-
162
- // Phase 2: Plan waves
163
- const plan = planWaves(turn, {
164
- files: context.files || [],
165
- priorDebriefs: loopState.debriefs,
166
- diagnosticPatterns: getCachedPatterns(),
167
- });
168
-
169
- loopState.activePlan = plan;
170
- saveLoopState(loopState);
171
-
172
- // Phase 3: Prepare first wave for dispatch
173
- const firstWave = plan.waves[0];
174
- if (!firstWave) {
175
- return {
176
- phase: 'respond',
177
- action: turn.action,
178
- rationale: 'Wave planner produced empty plan — falling back to direct response',
179
- shouldAskUser: true,
180
- surfaceNoticings: turn.result.surfaceNoticings,
181
- plan,
182
- nextDispatch: null,
183
- };
184
- }
185
-
186
- const prepared = prepareWave(firstWave, turn, context, loopState);
187
-
188
- // Don't dispatch unready work — return as blocked
189
- if (prepared.blockers.length > 0 && !prepared.allReady) {
190
- saveLoopState(loopState);
191
- return {
192
- phase: 'blocked',
193
- action: turn.action,
194
- rationale: turn.rationale,
195
- shouldAskUser: true,
196
- surfaceNoticings: turn.result.surfaceNoticings,
197
- plan,
198
- nextDispatch: prepared,
199
- suggestion: prepared.blockers[0],
200
- mode,
201
- };
202
- }
203
-
204
- return {
205
- phase: 'dispatch',
206
- action: turn.action,
207
- rationale: turn.rationale,
208
- shouldAskUser: turn.shouldAskUser,
209
- surfaceNoticings: turn.result.surfaceNoticings,
210
- plan,
211
- nextDispatch: prepared,
212
- estimatedCost: plan.estimatedCost,
213
- mode,
214
- };
215
- }
216
-
217
- /**
218
- * Called after a wave completes. Processes debriefs and determines next action.
219
- *
220
- * @param {string[]} rawResults - raw output from each agent in the completed wave
221
- * @param {string} completedWaveId - which wave just finished
222
- * @param {object} context
223
- * @returns {LoopResult}
224
- */
225
- export function advance(rawResults, completedWaveId, context = {}) {
226
- const loopState = loadLoopState();
227
- const plan = loopState.activePlan;
228
-
229
- if (!plan) {
230
- return { phase: 'done', action: { type: 'respond', mode: 'direct' }, rationale: 'No active plan', plan: null, nextDispatch: null };
231
- }
232
-
233
- // Parse debriefs from raw results
234
- const debriefs = rawResults.map(r => parseDebrief(r));
235
-
236
- // Track dispatch outcomes in HEAD state for self-awareness
237
- const headState = loadState();
238
- for (const d of debriefs) {
239
- recordDispatchOutcome(headState, { type: d.artifacts?.tier || 'execute', objective: completedWaveId, status: d.status, durationMs: 0 });
240
- }
241
-
242
- const waveSummary = summarizeWaveOutcome(debriefs);
243
- // Bridge field names between debrief (scopeDelta) and wave-planner (scopeChange)
244
- waveSummary.scopeChange = waveSummary.scopeDelta;
245
- waveSummary.confidence = waveSummary.aggregateConfidence;
246
- waveSummary.blockers = waveSummary.allBlockers;
247
-
248
- // Record
249
- loopState.debriefs.push(...debriefs);
250
- loopState.completedWaves.push(completedWaveId);
251
-
252
- // Evolve prediction accuracy
253
- const predictions = loopState._lastPredictions || [];
254
- for (const d of debriefs) {
255
- evolvePatterns(d, predictions);
256
- }
257
-
258
- // Post-wave verbal reflection (Reflexion pattern)
259
- _postWaveReflection(waveSummary, loopState);
260
-
261
- // Check if we need to replan
262
- const needsReplan = shouldReplan(plan, waveSummary);
263
-
264
- let activePlan = plan;
265
- if (needsReplan) {
266
- // Integrate each debrief into situation progressively
267
- let updatedSituation = loopState.situationHistory[loopState.situationHistory.length - 1] || {};
268
- for (const d of debriefs) {
269
- updatedSituation = integrateDebrief(updatedSituation, d);
270
- }
271
-
272
- activePlan = replan(plan, waveSummary, { situation: updatedSituation, result: { depth: plan._depth || 'full' } });
273
- loopState.activePlan = activePlan;
274
- loopState.replans++;
275
- }
276
-
277
- // Find next wave to dispatch
278
- const nextWave = activePlan.waves.find(w => !loopState.completedWaves.includes(w.id));
279
-
280
- if (!nextWave) {
281
- // All waves done — synthesize
282
- const finalSummary = summarizeWaveOutcome(loopState.debriefs);
283
-
284
- // Clean expired inbox messages now that all work is complete
285
- try { purgeInbox(); } catch { /* non-fatal */ }
286
-
287
- saveLoopState(loopState);
288
- return {
289
- phase: 'done',
290
- action: { type: 'synthesize', mode: 'complete' },
291
- rationale: `All ${loopState.completedWaves.length} waves complete. ${loopState.replans} replan(s).`,
292
- plan: activePlan,
293
- nextDispatch: null,
294
- waveSummary: finalSummary,
295
- replanned: needsReplan,
296
- };
297
- }
298
-
299
- // Check gate condition
300
- if (nextWave.gateCondition) {
301
- const gateMet = evaluateGate(nextWave.gateCondition, waveSummary, loopState);
302
- if (!gateMet) {
303
- saveLoopState(loopState);
304
- return {
305
- phase: 'gated',
306
- action: { type: 'pause', mode: 'gate-unmet' },
307
- rationale: `Gate condition not met: ${nextWave.gateCondition}`,
308
- plan: activePlan,
309
- nextDispatch: null,
310
- waveSummary,
311
- gateCondition: nextWave.gateCondition,
312
- };
313
- }
314
- }
315
-
316
- // Prepare next wave
317
- const lastDeliberation = { situation: context, result: { depth: activePlan._depth || 'full' } };
318
- const prepared = prepareWave(nextWave, lastDeliberation, { ...context, priorDebriefs: loopState.debriefs }, loopState);
319
-
320
- saveLoopState(loopState);
321
-
322
- return {
323
- phase: 'dispatch',
324
- action: { type: 'dispatch', mode: nextWave.phase },
325
- rationale: `Wave ${loopState.completedWaves.length + 1}/${activePlan.waves.length}: ${nextWave.phase}`,
326
- plan: activePlan,
327
- nextDispatch: prepared,
328
- waveSummary,
329
- replanned: needsReplan,
330
- };
331
- }
332
-
333
- /**
334
- * Prepare a wave for dispatch: run predictions, generate preventions, check readiness.
335
- */
336
- function prepareWave(wave, deliberation, context, loopState) {
337
- const agents = wave.agents.map(agentSpec => {
338
- // Predict failure modes for this agent
339
- const predictions = predictFailureModes(agentSpec, {
340
- priorDebriefs: context.priorDebriefs || [],
341
- diagnosticPatterns: getCachedPatterns(),
342
- files: context.files || [],
343
- });
344
-
345
- // Generate prevention instructions
346
- const preventions = generatePreventions(predictions);
347
-
348
- // Generate debrief instruction
349
- const debriefInstruction = generateDebriefInstruction(agentSpec.tier, {
350
- objective: agentSpec.objective,
351
- scope: agentSpec.scope,
352
- });
353
-
354
- // Score readiness
355
- const readiness = scoreDispatchReadiness(agentSpec, { waves: [wave] }, predictions);
356
-
357
- // Check inbox for messages relevant to this worker tier
358
- const workerInbox = generateInboxBrief(`worker:${agentSpec.tier}`);
359
-
360
- return {
361
- ...agentSpec,
362
- preventions,
363
- debriefInstruction,
364
- readiness,
365
- predictions,
366
- prompt: buildAgentPrompt(agentSpec, preventions, debriefInstruction, workerInbox),
367
- };
368
- });
369
-
370
- // Store predictions for later evolution
371
- loopState._lastPredictions = agents.flatMap(a => a.predictions);
372
-
373
- return {
374
- waveId: wave.id,
375
- phase: wave.phase,
376
- parallel: wave.parallel,
377
- agents,
378
- allReady: agents.every(a => a.readiness.ready),
379
- blockers: agents.flatMap(a => a.readiness.blockers),
380
- warnings: agents.flatMap(a => a.readiness.warnings),
381
- };
382
- }
383
-
384
- /**
385
- * Build the full prompt for an agent using dispatch envelopes.
386
- * Workers get understanding (prose preamble), not just instructions.
387
- */
388
- function buildAgentPrompt(agentSpec, preventions, debriefInstruction, inboxBrief) {
389
- const envelope = buildEnvelope(agentSpec, {
390
- preventions,
391
- debriefInstruction,
392
- inboxBrief,
393
- });
394
- return envelope.full;
395
- }
396
-
397
- /**
398
- * Evaluate a wave gate condition against the current state.
399
- */
400
- function evaluateGate(condition, waveSummary, loopState) {
401
- const lower = condition.toLowerCase();
402
-
403
- if (lower.includes('confidence') && lower.includes('above')) {
404
- const threshold = parseFloat(condition.match(/[\d.]+/)?.[0] || '0.5');
405
- return (waveSummary.aggregateConfidence || 0) >= threshold;
406
- }
407
-
408
- if (lower.includes('no blocker')) {
409
- return (waveSummary.allBlockers || []).length === 0;
410
- }
411
-
412
- if (lower.includes('scope confirmed')) {
413
- return waveSummary.scopeDelta === 'same' || waveSummary.scopeDelta === 'smaller';
414
- }
415
-
416
- // Default: gate passes
417
- return true;
418
- }
419
-
420
- // ── Immersion helpers ──────────────────────────────────────────────────────
421
-
422
- /**
423
- * Evolve HEAD's running narrative after processing a turn.
424
- * Captures: what happened, what was decided, what the user cared about.
425
- */
426
- function _evolveNarrative(turn, userMessage, context) {
427
- const parts = [];
428
-
429
- // What the user said (compressed)
430
- const userSnippet = userMessage.length > 120 ? userMessage.slice(0, 120) + '...' : userMessage;
431
- parts.push(`User: "${userSnippet}"`);
432
-
433
- // What HEAD decided
434
- const action = turn.action;
435
- if (action.type === 'dispatch') {
436
- parts.push(`Decision: dispatch ${action.mode || 'work'} (confidence: ${turn.result.confidence.score})`);
437
- } else if (action.type === 'respond') {
438
- parts.push(`Decision: respond directly (${turn.depth} depth)`);
439
- } else if (action.type === 'clarify') {
440
- parts.push(`Decision: need clarification`);
441
- }
442
-
443
- // Noticings worth remembering
444
- if (turn.result.surfaceNoticings?.length) {
445
- parts.push(`Noticed: ${turn.result.surfaceNoticings.join('; ')}`);
446
- }
447
-
448
- // Crystallized simmer items surfaced this turn
449
- if (context._crystallizedIdeas?.length) {
450
- parts.push(`Crystallized ideas surfaced: ${context._crystallizedIdeas.join('; ')}`);
451
- }
452
-
453
- narrative.evolve(parts.join('. '));
454
- }
455
-
456
- /**
457
- * Capture signals from user message that might be simmer-worthy.
458
- * Looks for: analogies, "what if" ideas, vague suggestions, meta-observations.
459
- */
460
- function _captureSimmerSignals(userMessage, turn) {
461
- const lower = userMessage.toLowerCase();
462
-
463
- // Skip short/command messages
464
- if (userMessage.length < 30) return;
465
-
466
- // Detect idea-like patterns
467
- const ideaPatterns = [
468
- /what if (.{10,80})/i,
469
- /maybe (?:we |it |this )(.{10,80})/i,
470
- /i feel like (.{10,80})/i,
471
- /(?:like|similar to) (.{10,80}?)(?: - | — |\.|\?|$)/i,
472
- /consider (.{10,80})/i,
473
- ];
474
-
475
- for (const pattern of ideaPatterns) {
476
- const match = userMessage.match(pattern);
477
- if (match) {
478
- simmer.add(match[0].slice(0, 120), { origin: 'user-message' });
479
- return; // One simmer per message max
480
- }
481
- }
482
-
483
- // If the message is exploratory (questions about approach) and depth is "full" or "deep",
484
- // the whole message might be worth simmering
485
- if (turn.depth === 'deep' && /\?/.test(userMessage) && userMessage.length > 60) {
486
- const isExploration = /how|should|could|what about|think/i.test(lower);
487
- if (isExploration) {
488
- simmer.add(userMessage.slice(0, 150), { origin: 'exploratory-question', initialHeat: 1.5 });
489
- }
490
- }
491
- }
492
-
493
- /**
494
- * Post-wave narrative reflection — called after advance() processes debriefs.
495
- * This is the verbal self-reflection piece (Reflexion pattern).
496
- */
497
- function _postWaveReflection(waveSummary, loopState) {
498
- const parts = [];
499
-
500
- parts.push(`Wave ${loopState.completedWaves.length} complete.`);
501
-
502
- if (waveSummary.aggregateConfidence != null) {
503
- parts.push(`Confidence: ${waveSummary.aggregateConfidence.toFixed(2)}`);
504
- }
505
-
506
- if (waveSummary.allBlockers?.length) {
507
- parts.push(`Blockers emerged: ${waveSummary.allBlockers.join('; ')}`);
508
- }
509
-
510
- if (waveSummary.scopeDelta && waveSummary.scopeDelta !== 'same') {
511
- parts.push(`Scope shifted: ${waveSummary.scopeDelta}`);
512
- }
513
-
514
- if (loopState.replans > 0) {
515
- parts.push(`Replanned ${loopState.replans} time(s) — adapting to reality.`);
516
- }
517
-
518
- narrative.evolve(parts.join(' '));
519
- }
520
-
521
- /**
522
- * Check for update notice and surface it to HEAD's awareness.
523
- */
524
- function _checkUpdateNotice(context) {
525
- try {
526
- const noticeFile = join(LOOP_STATE_DIR, '.update-notice');
527
- if (!existsSync(noticeFile)) return;
528
- const notice = JSON.parse(readFileSync(noticeFile, 'utf8'));
529
- if (Date.now() - notice.ts < 5 * 60 * 1000) {
530
- context._updateNotice = `Updated dual-brain ${notice.from} → ${notice.to}`;
531
- }
532
- // Clear it after reading (one-shot)
533
- try { writeFileSync(noticeFile, ''); } catch {}
534
- } catch {}
535
- }
536
-
537
- // ── Query functions ─────────────────────────────────────────────────────────
538
-
539
- export function getActivePlan() {
540
- return loadLoopState().activePlan;
541
- }
542
-
543
- export function getLoopStatus() {
544
- const state = loadLoopState();
545
- return {
546
- hasActivePlan: !!state.activePlan,
547
- completedWaves: state.completedWaves.length,
548
- totalWaves: state.activePlan?.waves?.length || 0,
549
- replans: state.replans,
550
- totalDispatches: state.totalDispatches,
551
- debriefCount: state.debriefs.length,
552
- };
553
- }
554
-
555
- export function resetLoop() {
556
- saveLoopState(freshLoopState());
557
- releaseSession();
558
- }
559
-
560
- export function shutdown() {
561
- releaseSession();
562
- }