@cleocode/core 2026.3.58 → 2026.3.60

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 (153) hide show
  1. package/dist/agents/agent-registry.d.ts +206 -0
  2. package/dist/agents/agent-registry.d.ts.map +1 -0
  3. package/dist/agents/agent-registry.js +288 -0
  4. package/dist/agents/agent-registry.js.map +1 -0
  5. package/dist/agents/agent-schema.js +5 -0
  6. package/dist/agents/agent-schema.js.map +1 -1
  7. package/dist/agents/execution-learning.js +474 -0
  8. package/dist/agents/execution-learning.js.map +1 -0
  9. package/dist/agents/health-monitor.d.ts +161 -0
  10. package/dist/agents/health-monitor.d.ts.map +1 -0
  11. package/dist/agents/health-monitor.js +217 -0
  12. package/dist/agents/health-monitor.js.map +1 -0
  13. package/dist/agents/index.d.ts +3 -1
  14. package/dist/agents/index.d.ts.map +1 -1
  15. package/dist/agents/index.js +9 -1
  16. package/dist/agents/index.js.map +1 -1
  17. package/dist/agents/retry.d.ts +57 -4
  18. package/dist/agents/retry.d.ts.map +1 -1
  19. package/dist/agents/retry.js +57 -4
  20. package/dist/agents/retry.js.map +1 -1
  21. package/dist/backfill/index.d.ts +27 -0
  22. package/dist/backfill/index.d.ts.map +1 -1
  23. package/dist/backfill/index.js +229 -0
  24. package/dist/backfill/index.js.map +1 -0
  25. package/dist/bootstrap.d.ts +2 -1
  26. package/dist/bootstrap.d.ts.map +1 -1
  27. package/dist/bootstrap.js +135 -28
  28. package/dist/bootstrap.js.map +1 -1
  29. package/dist/cleo.d.ts +40 -0
  30. package/dist/cleo.d.ts.map +1 -1
  31. package/dist/config.js +83 -0
  32. package/dist/config.js.map +1 -1
  33. package/dist/index.d.ts +1 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +1036 -536
  36. package/dist/index.js.map +4 -4
  37. package/dist/intelligence/adaptive-validation.js +497 -0
  38. package/dist/intelligence/adaptive-validation.js.map +1 -0
  39. package/dist/intelligence/impact.d.ts +34 -1
  40. package/dist/intelligence/impact.d.ts.map +1 -1
  41. package/dist/intelligence/impact.js +176 -0
  42. package/dist/intelligence/impact.js.map +1 -1
  43. package/dist/intelligence/index.d.ts +2 -2
  44. package/dist/intelligence/index.d.ts.map +1 -1
  45. package/dist/intelligence/index.js +6 -1
  46. package/dist/intelligence/index.js.map +1 -1
  47. package/dist/intelligence/types.d.ts +60 -0
  48. package/dist/intelligence/types.d.ts.map +1 -1
  49. package/dist/internal.d.ts +5 -4
  50. package/dist/internal.d.ts.map +1 -1
  51. package/dist/internal.js +11 -2
  52. package/dist/internal.js.map +1 -1
  53. package/dist/lib/index.d.ts +10 -0
  54. package/dist/lib/index.d.ts.map +1 -0
  55. package/dist/lib/index.js +10 -0
  56. package/dist/lib/index.js.map +1 -0
  57. package/dist/lib/retry.d.ts +128 -0
  58. package/dist/lib/retry.d.ts.map +1 -0
  59. package/dist/lib/retry.js +152 -0
  60. package/dist/lib/retry.js.map +1 -0
  61. package/dist/nexus/sharing/index.d.ts +48 -2
  62. package/dist/nexus/sharing/index.d.ts.map +1 -1
  63. package/dist/nexus/sharing/index.js +110 -1
  64. package/dist/nexus/sharing/index.js.map +1 -1
  65. package/dist/scaffold.d.ts.map +1 -1
  66. package/dist/scaffold.js +22 -2
  67. package/dist/scaffold.js.map +1 -1
  68. package/dist/sessions/session-enforcement.js +4 -0
  69. package/dist/sessions/session-enforcement.js.map +1 -1
  70. package/dist/stats/index.js +2 -0
  71. package/dist/stats/index.js.map +1 -1
  72. package/dist/stats/workflow-telemetry.d.ts +15 -0
  73. package/dist/stats/workflow-telemetry.d.ts.map +1 -1
  74. package/dist/stats/workflow-telemetry.js +400 -0
  75. package/dist/stats/workflow-telemetry.js.map +1 -0
  76. package/dist/store/brain-schema.js +4 -1
  77. package/dist/store/brain-schema.js.map +1 -1
  78. package/dist/store/converters.js +2 -0
  79. package/dist/store/converters.js.map +1 -1
  80. package/dist/store/cross-db-cleanup.d.ts +35 -0
  81. package/dist/store/cross-db-cleanup.d.ts.map +1 -1
  82. package/dist/store/cross-db-cleanup.js +169 -0
  83. package/dist/store/cross-db-cleanup.js.map +1 -0
  84. package/dist/store/db-helpers.js +2 -0
  85. package/dist/store/db-helpers.js.map +1 -1
  86. package/dist/store/migration-sqlite.js +5 -0
  87. package/dist/store/migration-sqlite.js.map +1 -1
  88. package/dist/store/sqlite-data-accessor.js +20 -28
  89. package/dist/store/sqlite-data-accessor.js.map +1 -1
  90. package/dist/store/sqlite.js +13 -2
  91. package/dist/store/sqlite.js.map +1 -1
  92. package/dist/store/task-store.js +4 -0
  93. package/dist/store/task-store.js.map +1 -1
  94. package/dist/store/tasks-schema.js +50 -20
  95. package/dist/store/tasks-schema.js.map +1 -1
  96. package/dist/tasks/add.js +87 -3
  97. package/dist/tasks/add.js.map +1 -1
  98. package/dist/tasks/complete.d.ts.map +1 -1
  99. package/dist/tasks/complete.js +15 -4
  100. package/dist/tasks/complete.js.map +1 -1
  101. package/dist/tasks/enforcement.d.ts.map +1 -1
  102. package/dist/tasks/enforcement.js +8 -1
  103. package/dist/tasks/enforcement.js.map +1 -1
  104. package/dist/tasks/epic-enforcement.d.ts +61 -0
  105. package/dist/tasks/epic-enforcement.d.ts.map +1 -1
  106. package/dist/tasks/epic-enforcement.js +294 -0
  107. package/dist/tasks/epic-enforcement.js.map +1 -0
  108. package/dist/tasks/index.js +1 -1
  109. package/dist/tasks/index.js.map +1 -1
  110. package/dist/tasks/pipeline-stage.d.ts +70 -1
  111. package/dist/tasks/pipeline-stage.d.ts.map +1 -1
  112. package/dist/tasks/pipeline-stage.js +248 -0
  113. package/dist/tasks/pipeline-stage.js.map +1 -0
  114. package/dist/tasks/update.js +28 -0
  115. package/dist/tasks/update.js.map +1 -1
  116. package/package.json +5 -5
  117. package/schemas/config.schema.json +37 -1547
  118. package/src/__tests__/sharing.test.ts +24 -0
  119. package/src/agents/__tests__/agent-registry.test.ts +351 -0
  120. package/src/agents/__tests__/health-monitor.test.ts +332 -0
  121. package/src/agents/agent-registry.ts +394 -0
  122. package/src/agents/health-monitor.ts +279 -0
  123. package/src/agents/index.ts +24 -1
  124. package/src/agents/retry.ts +57 -4
  125. package/src/backfill/index.ts +27 -0
  126. package/src/bootstrap.ts +171 -30
  127. package/src/cleo.ts +103 -2
  128. package/src/config.ts +3 -3
  129. package/src/index.ts +1 -0
  130. package/src/intelligence/__tests__/impact.test.ts +165 -1
  131. package/src/intelligence/impact.ts +203 -0
  132. package/src/intelligence/index.ts +3 -0
  133. package/src/intelligence/types.ts +76 -0
  134. package/src/internal.ts +20 -0
  135. package/src/lib/__tests__/retry.test.ts +321 -0
  136. package/src/lib/index.ts +16 -0
  137. package/src/lib/retry.ts +224 -0
  138. package/src/nexus/sharing/index.ts +142 -2
  139. package/src/scaffold.ts +24 -2
  140. package/src/stats/workflow-telemetry.ts +15 -0
  141. package/src/store/__tests__/session-store.test.ts +43 -7
  142. package/src/store/__tests__/task-store.test.ts +1 -1
  143. package/src/store/__tests__/test-db-helper.ts +7 -3
  144. package/src/store/cross-db-cleanup.ts +35 -0
  145. package/src/tasks/__tests__/epic-enforcement.test.ts +9 -4
  146. package/src/tasks/__tests__/minimal-test.test.ts +2 -2
  147. package/src/tasks/__tests__/update.test.ts +25 -25
  148. package/src/tasks/complete.ts +11 -6
  149. package/src/tasks/enforcement.ts +6 -3
  150. package/src/tasks/epic-enforcement.ts +61 -0
  151. package/src/tasks/pipeline-stage.ts +70 -1
  152. package/templates/config.template.json +5 -116
  153. package/templates/global-config.template.json +2 -44
@@ -0,0 +1,474 @@
1
+ /**
2
+ * Agent Execution Learning — Agent dimension BRAIN integration.
3
+ *
4
+ * Tracks which agent types succeed or fail on which task types, logs agent
5
+ * decisions to brain_decisions with structured context, provides queries to
6
+ * retrieve agent performance history, and implements basic self-healing by
7
+ * recording failure patterns as brain_observations and surfacing healing
8
+ * suggestions when the same failure pattern recurs.
9
+ *
10
+ * This module bridges the agent registry (tasks.db) with the cognitive
11
+ * memory layer (brain.db) without introducing circular dependencies:
12
+ * - Tasks.db agent tables: agent_instances, agent_error_log (agent-schema.ts)
13
+ * - Brain.db tables: brain_decisions, brain_patterns, brain_observations
14
+ *
15
+ * All brain.db writes are best-effort — failures are silently swallowed so
16
+ * agent lifecycle events never fail due to a brain.db write error.
17
+ *
18
+ * @module agents/execution-learning
19
+ * @task T034
20
+ * @epic T029
21
+ */
22
+ import { randomBytes } from 'node:crypto';
23
+ import { getBrainAccessor } from '../store/brain-accessor.js';
24
+ // ============================================================================
25
+ // ID generation helpers
26
+ // ============================================================================
27
+ /** Generate a unique brain_decisions ID with `AGT-` prefix. */
28
+ function generateDecisionId() {
29
+ return `AGT-${randomBytes(5).toString('hex')}`;
30
+ }
31
+ /** Generate a unique brain_patterns ID with `P-agt-` prefix. */
32
+ function generatePatternId() {
33
+ return `P-agt-${randomBytes(4).toString('hex')}`;
34
+ }
35
+ /** Generate a unique brain_observations ID with `O-agt-` prefix. */
36
+ function generateObservationId() {
37
+ return `O-agt-${randomBytes(4).toString('hex')}`;
38
+ }
39
+ /** Normalised ISO timestamp in SQLite-friendly format (space separator). */
40
+ function nowSql() {
41
+ return new Date().toISOString().replace('T', ' ').slice(0, 19);
42
+ }
43
+ // ============================================================================
44
+ // Execution event logging
45
+ // ============================================================================
46
+ /**
47
+ * Record an agent execution event to brain_decisions.
48
+ *
49
+ * Each event becomes a `tactical` decision entry describing which agent type
50
+ * handled which task type and whether it succeeded. This gives the BRAIN
51
+ * system a queryable history of agent-task execution for pattern extraction.
52
+ *
53
+ * The call is best-effort — if brain.db is unavailable the error is swallowed
54
+ * and null is returned so agent lifecycle code is never disrupted.
55
+ *
56
+ * @param event - Execution event to record
57
+ * @param cwd - Working directory (resolves brain.db path)
58
+ * @returns The stored decision row, or null on failure
59
+ */
60
+ export async function recordAgentExecution(event, cwd) {
61
+ try {
62
+ const brain = await getBrainAccessor(cwd);
63
+ return await _recordAgentExecutionWithAccessor(event, brain);
64
+ }
65
+ catch {
66
+ // Best-effort — never propagate brain.db write failures to callers
67
+ return null;
68
+ }
69
+ }
70
+ /**
71
+ * Internal implementation that accepts a pre-constructed accessor.
72
+ * Separated for testability without touching the real file system.
73
+ *
74
+ * @internal
75
+ */
76
+ export async function _recordAgentExecutionWithAccessor(event, brain) {
77
+ const id = generateDecisionId();
78
+ const outcomeMap = {
79
+ success: 'success',
80
+ failure: 'failure',
81
+ partial: 'mixed',
82
+ };
83
+ const decisionText = `Agent type "${event.agentType}" ${event.outcome === 'success' ? 'successfully completed' : `failed (${event.errorType ?? 'unknown'}) on`} task ${event.taskId} (type: ${event.taskType})`;
84
+ const rationale = [
85
+ `Agent: ${event.agentId}`,
86
+ `Task type: ${event.taskType}`,
87
+ `Outcome: ${event.outcome}`,
88
+ event.taskLabels?.length ? `Labels: ${event.taskLabels.join(', ')}` : null,
89
+ event.errorMessage ? `Error: ${event.errorMessage}` : null,
90
+ event.durationMs != null ? `Duration: ${event.durationMs}ms` : null,
91
+ ]
92
+ .filter(Boolean)
93
+ .join(' | ');
94
+ const alternativesJson = JSON.stringify({
95
+ agentId: event.agentId,
96
+ agentType: event.agentType,
97
+ taskType: event.taskType,
98
+ taskLabels: event.taskLabels ?? [],
99
+ errorType: event.errorType ?? null,
100
+ durationMs: event.durationMs ?? null,
101
+ sessionId: event.sessionId ?? null,
102
+ });
103
+ const row = await brain.addDecision({
104
+ id,
105
+ type: 'tactical',
106
+ decision: decisionText,
107
+ rationale,
108
+ confidence: event.outcome === 'success' ? 'high' : event.outcome === 'partial' ? 'medium' : 'low',
109
+ outcome: outcomeMap[event.outcome],
110
+ alternativesJson,
111
+ contextTaskId: event.taskId,
112
+ contextEpicId: null,
113
+ contextPhase: null,
114
+ createdAt: nowSql(),
115
+ updatedAt: null,
116
+ });
117
+ return row;
118
+ }
119
+ // ============================================================================
120
+ // Performance queries
121
+ // ============================================================================
122
+ /**
123
+ * Retrieve agent execution performance history from brain_decisions.
124
+ *
125
+ * Queries all `tactical` decisions recorded by `recordAgentExecution` and
126
+ * aggregates them into per-(agentType, taskType) performance summaries.
127
+ *
128
+ * @param filters - Optional filters to narrow results
129
+ * @param cwd - Working directory
130
+ * @returns Array of performance summaries sorted by agentType then taskType
131
+ */
132
+ export async function getAgentPerformanceHistory(filters = {}, cwd) {
133
+ try {
134
+ const brain = await getBrainAccessor(cwd);
135
+ return await _getAgentPerformanceHistoryWithAccessor(filters, brain);
136
+ }
137
+ catch {
138
+ return [];
139
+ }
140
+ }
141
+ /**
142
+ * Internal implementation with injected accessor for testability.
143
+ *
144
+ * @internal
145
+ */
146
+ export async function _getAgentPerformanceHistoryWithAccessor(filters, brain) {
147
+ const decisions = await brain.findDecisions({
148
+ type: 'tactical',
149
+ limit: filters.limit ?? 500,
150
+ });
151
+ // Only process rows that were written by recordAgentExecution
152
+ const agentDecisions = decisions.filter((d) => d.id.startsWith('AGT-'));
153
+ // Aggregate per (agentType, taskType)
154
+ const buckets = new Map();
155
+ for (const d of agentDecisions) {
156
+ let meta = {};
157
+ try {
158
+ meta = d.alternativesJson ? JSON.parse(d.alternativesJson) : {};
159
+ }
160
+ catch {
161
+ continue;
162
+ }
163
+ const agentType = meta.agentType;
164
+ const taskType = meta.taskType;
165
+ if (!agentType || !taskType)
166
+ continue;
167
+ // Apply optional filters
168
+ if (filters.agentType && agentType !== filters.agentType)
169
+ continue;
170
+ if (filters.taskType && taskType !== filters.taskType)
171
+ continue;
172
+ const key = `${agentType}::${taskType}`;
173
+ const existing = buckets.get(key) ?? {
174
+ agentType,
175
+ taskType,
176
+ successes: 0,
177
+ failures: 0,
178
+ total: 0,
179
+ lastOutcome: null,
180
+ lastSeenAt: null,
181
+ };
182
+ existing.total += 1;
183
+ const outcome = d.outcome;
184
+ if (outcome === 'success') {
185
+ existing.successes += 1;
186
+ existing.lastOutcome = 'success';
187
+ }
188
+ else if (outcome === 'failure') {
189
+ existing.failures += 1;
190
+ existing.lastOutcome = 'failure';
191
+ }
192
+ else if (outcome === 'mixed') {
193
+ existing.lastOutcome = 'partial';
194
+ }
195
+ // Track most-recent timestamp
196
+ if (!existing.lastSeenAt || (d.createdAt && d.createdAt > existing.lastSeenAt)) {
197
+ existing.lastSeenAt = d.createdAt;
198
+ }
199
+ buckets.set(key, existing);
200
+ }
201
+ return Array.from(buckets.values())
202
+ .map((b) => ({
203
+ agentType: b.agentType,
204
+ taskType: b.taskType,
205
+ totalAttempts: b.total,
206
+ successCount: b.successes,
207
+ failureCount: b.failures,
208
+ successRate: b.total > 0 ? Math.round((b.successes / b.total) * 1000) / 1000 : 0,
209
+ lastOutcome: b.lastOutcome,
210
+ lastSeenAt: b.lastSeenAt,
211
+ }))
212
+ .sort((a, b) => a.agentType.localeCompare(b.agentType) || a.taskType.localeCompare(b.taskType));
213
+ }
214
+ // ============================================================================
215
+ // Failure pattern recording
216
+ // ============================================================================
217
+ /**
218
+ * Record a task failure pattern to brain_patterns.
219
+ *
220
+ * When a task fails, the (agentType, taskType, errorType) combination is
221
+ * stored as a `failure` pattern in brain.db. On subsequent failures matching
222
+ * the same combination, the frequency counter is incremented and the success
223
+ * rate updated. This data is what powers `getSelfHealingSuggestions`.
224
+ *
225
+ * The call is best-effort and never throws.
226
+ *
227
+ * @param event - A failure execution event (outcome must be 'failure')
228
+ * @param cwd - Working directory
229
+ * @returns The upserted pattern row, or null on error
230
+ */
231
+ export async function recordFailurePattern(event, cwd) {
232
+ if (event.outcome !== 'failure')
233
+ return null;
234
+ try {
235
+ const brain = await getBrainAccessor(cwd);
236
+ return await _recordFailurePatternWithAccessor(event, brain);
237
+ }
238
+ catch {
239
+ return null;
240
+ }
241
+ }
242
+ /**
243
+ * Internal implementation with injected accessor.
244
+ *
245
+ * @internal
246
+ */
247
+ export async function _recordFailurePatternWithAccessor(event, brain) {
248
+ const errorType = event.errorType ?? 'unknown';
249
+ const patternText = `Agent type "${event.agentType}" fails on task type "${event.taskType}" with ${errorType} error`;
250
+ const contextText = `Failure pattern detected: ${event.agentType} agent encountering ${errorType} errors on ${event.taskType} tasks`;
251
+ // Look for an existing matching pattern (search by prefix naming convention)
252
+ const existing = await brain.findPatterns({ type: 'failure', limit: 200 });
253
+ const match = existing.find((p) => p.pattern === patternText);
254
+ if (match) {
255
+ // Update frequency counter and keep success_rate at 0 (all failures)
256
+ const newFrequency = match.frequency + 1;
257
+ const suggestion = buildHealingSuggestion(event.agentType, event.taskType, errorType, newFrequency);
258
+ await brain.updatePattern(match.id, {
259
+ frequency: newFrequency,
260
+ successRate: 0,
261
+ mitigation: suggestion,
262
+ });
263
+ return {
264
+ ...match,
265
+ frequency: newFrequency,
266
+ successRate: 0,
267
+ mitigation: suggestion,
268
+ };
269
+ }
270
+ // Create new failure pattern
271
+ const id = generatePatternId();
272
+ const now = nowSql();
273
+ const suggestion = buildHealingSuggestion(event.agentType, event.taskType, errorType, 1);
274
+ return brain.addPattern({
275
+ id,
276
+ type: 'failure',
277
+ pattern: patternText,
278
+ context: contextText,
279
+ frequency: 1,
280
+ successRate: 0,
281
+ impact: 'medium',
282
+ antiPattern: `${event.agentType} assigned to ${event.taskType} task with ${errorType} error risk`,
283
+ mitigation: suggestion,
284
+ examplesJson: JSON.stringify([event.taskId]),
285
+ extractedAt: now,
286
+ updatedAt: null,
287
+ });
288
+ }
289
+ // ============================================================================
290
+ // Self-healing: observation storage
291
+ // ============================================================================
292
+ /**
293
+ * Store a healing strategy observation to brain_observations.
294
+ *
295
+ * When a failure pattern reaches a significant frequency threshold (≥ 3),
296
+ * a `change` observation is recorded to represent the healing action
297
+ * recommended for future recurrences of the same pattern.
298
+ *
299
+ * The call is best-effort and never throws.
300
+ *
301
+ * @param event - The failure event that triggered healing
302
+ * @param strategy - Human-readable healing strategy description
303
+ * @param cwd - Working directory
304
+ * @returns The stored observation row, or null on error
305
+ */
306
+ export async function storeHealingStrategy(event, strategy, cwd) {
307
+ if (event.outcome !== 'failure')
308
+ return null;
309
+ try {
310
+ const brain = await getBrainAccessor(cwd);
311
+ return await _storeHealingStrategyWithAccessor(event, strategy, brain);
312
+ }
313
+ catch {
314
+ return null;
315
+ }
316
+ }
317
+ /**
318
+ * Internal implementation with injected accessor.
319
+ *
320
+ * @internal
321
+ */
322
+ export async function _storeHealingStrategyWithAccessor(event, strategy, brain) {
323
+ const id = generateObservationId();
324
+ const now = nowSql();
325
+ return brain.addObservation({
326
+ id,
327
+ type: 'change',
328
+ title: `Healing strategy: ${event.agentType} on ${event.taskType}`,
329
+ subtitle: `Error type: ${event.errorType ?? 'unknown'}`,
330
+ narrative: strategy,
331
+ factsJson: JSON.stringify([
332
+ `Agent: ${event.agentType}`,
333
+ `Task type: ${event.taskType}`,
334
+ `Error: ${event.errorMessage ?? 'unspecified'}`,
335
+ `Suggested action: ${strategy}`,
336
+ ]),
337
+ conceptsJson: JSON.stringify([
338
+ 'self-healing',
339
+ 'agent-execution',
340
+ event.agentType,
341
+ event.taskType,
342
+ ]),
343
+ project: null,
344
+ filesReadJson: null,
345
+ filesModifiedJson: null,
346
+ sourceSessionId: event.sessionId ?? null,
347
+ sourceType: 'agent',
348
+ contentHash: null,
349
+ discoveryTokens: null,
350
+ createdAt: now,
351
+ updatedAt: null,
352
+ });
353
+ }
354
+ // ============================================================================
355
+ // Self-healing: suggestion retrieval
356
+ // ============================================================================
357
+ /**
358
+ * Get self-healing suggestions for a given agent type and task type.
359
+ *
360
+ * Queries brain_patterns for known failure patterns matching the
361
+ * (agentType, taskType) combination and returns healing suggestions
362
+ * ordered by frequency (most-seen first).
363
+ *
364
+ * Returns an empty array if no failure patterns are found or brain.db
365
+ * is unavailable.
366
+ *
367
+ * @param agentType - The agent type to check
368
+ * @param taskType - The task type to check
369
+ * @param cwd - Working directory
370
+ * @returns Array of healing suggestions, highest frequency first
371
+ */
372
+ export async function getSelfHealingSuggestions(agentType, taskType, cwd) {
373
+ try {
374
+ const brain = await getBrainAccessor(cwd);
375
+ return await _getSelfHealingSuggestionsWithAccessor(agentType, taskType, brain);
376
+ }
377
+ catch {
378
+ return [];
379
+ }
380
+ }
381
+ /**
382
+ * Internal implementation with injected accessor.
383
+ *
384
+ * @internal
385
+ */
386
+ export async function _getSelfHealingSuggestionsWithAccessor(agentType, taskType, brain) {
387
+ const patterns = await brain.findPatterns({ type: 'failure', limit: 200 });
388
+ const prefix = `Agent type "${agentType}" fails on task type "${taskType}"`;
389
+ const matches = patterns.filter((p) => p.pattern.startsWith(prefix) && p.mitigation);
390
+ return matches
391
+ .map((p) => ({
392
+ patternId: p.id,
393
+ failurePattern: p.pattern,
394
+ frequency: p.frequency,
395
+ suggestion: p.mitigation,
396
+ confidence: Math.min(0.3 + p.frequency * 0.1, 0.9),
397
+ }))
398
+ .sort((a, b) => b.frequency - a.frequency);
399
+ }
400
+ // ============================================================================
401
+ // Compound: record event + update patterns + suggest healing
402
+ // ============================================================================
403
+ /**
404
+ * Full agent lifecycle event processor.
405
+ *
406
+ * Convenience function that:
407
+ * 1. Records the execution event to brain_decisions
408
+ * 2. On failure: records/updates the failure pattern in brain_patterns
409
+ * 3. On failure with frequency ≥ 3: stores a healing strategy observation
410
+ *
411
+ * Returns a structured result with the recorded IDs and any healing
412
+ * suggestions that now apply.
413
+ *
414
+ * All operations are best-effort — the call never throws.
415
+ *
416
+ * @param event - The execution event
417
+ * @param cwd - Working directory
418
+ */
419
+ export async function processAgentLifecycleEvent(event, cwd) {
420
+ const result = {
421
+ decisionId: null,
422
+ patternId: null,
423
+ observationId: null,
424
+ healingSuggestions: [],
425
+ };
426
+ try {
427
+ const brain = await getBrainAccessor(cwd);
428
+ // 1. Record decision
429
+ const decision = await _recordAgentExecutionWithAccessor(event, brain);
430
+ if (decision)
431
+ result.decisionId = decision.id;
432
+ // 2. On failure: record/update failure pattern
433
+ if (event.outcome === 'failure') {
434
+ const pattern = await _recordFailurePatternWithAccessor(event, brain);
435
+ if (pattern) {
436
+ result.patternId = pattern.id;
437
+ // 3. When pattern is well-established, store a healing strategy observation
438
+ if (pattern.frequency >= 3 && pattern.mitigation) {
439
+ const obs = await _storeHealingStrategyWithAccessor(event, pattern.mitigation, brain);
440
+ if (obs)
441
+ result.observationId = obs.id;
442
+ }
443
+ }
444
+ }
445
+ // 4. Retrieve current suggestions (applies to all outcomes — useful for
446
+ // monitoring even successful retries of previously-failing patterns)
447
+ if (event.outcome !== 'success') {
448
+ result.healingSuggestions = await _getSelfHealingSuggestionsWithAccessor(event.agentType, event.taskType, brain);
449
+ }
450
+ }
451
+ catch {
452
+ // Best-effort — never propagate failures
453
+ }
454
+ return result;
455
+ }
456
+ // ============================================================================
457
+ // Internal: healing suggestion builder
458
+ // ============================================================================
459
+ /**
460
+ * Build a human-readable healing mitigation string for a failure pattern.
461
+ */
462
+ function buildHealingSuggestion(agentType, taskType, errorType, frequency) {
463
+ if (errorType === 'retriable') {
464
+ return `Retry with exponential backoff. Consider switching to a different ${agentType} agent instance if failures exceed threshold (seen ${frequency}x).`;
465
+ }
466
+ if (errorType === 'permanent') {
467
+ return `Reassign task to a different agent type — ${agentType} has a permanent failure on ${taskType} tasks. Check permissions, inputs, and dependencies (seen ${frequency}x).`;
468
+ }
469
+ if (frequency >= 5) {
470
+ return `High-frequency unknown failure (${frequency}x): escalate to orchestrator for manual investigation of ${agentType} on ${taskType} tasks.`;
471
+ }
472
+ return `Unknown failure for ${agentType} on ${taskType} tasks (${frequency}x). Check agent logs, retry with fresh context, or reassign to a different agent type.`;
473
+ }
474
+ //# sourceMappingURL=execution-learning.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execution-learning.js","sourceRoot":"","sources":["../../src/agents/execution-learning.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Agent Health Monitoring -- Heartbeat and crash detection for live agent instances.
3
+ *
4
+ * Provides the public-facing health API specified by T039:
5
+ * - `recordHeartbeat` — update last_heartbeat for a live agent
6
+ * - `checkAgentHealth` — check health of a specific agent by ID
7
+ * - `detectStaleAgents` — find agents whose heartbeat is older than threshold
8
+ * - `detectCrashedAgents` — find active agents with no heartbeat for >3 min
9
+ *
10
+ * These functions delegate to the lower-level `registry.ts` primitives
11
+ * (`heartbeat`, `checkAgentHealth`, `listAgentInstances`) and add the
12
+ * named, task-spec-aligned surface required for T039.
13
+ *
14
+ * @module agents/health-monitor
15
+ * @task T039
16
+ * @epic T038
17
+ */
18
+ import type { AgentInstanceRow, AgentInstanceStatus } from './agent-schema.js';
19
+ /** Default heartbeat interval (30 seconds) per BRAIN spec. */
20
+ export declare const HEARTBEAT_INTERVAL_MS = 30000;
21
+ /** Default staleness threshold: 3 minutes without a heartbeat. */
22
+ export declare const STALE_THRESHOLD_MS: number;
23
+ /**
24
+ * Health status of a specific agent instance.
25
+ */
26
+ export interface AgentHealthStatus {
27
+ /** Agent instance ID. */
28
+ agentId: string;
29
+ /** Current DB status. */
30
+ status: AgentInstanceStatus;
31
+ /** ISO timestamp of the last recorded heartbeat. */
32
+ lastHeartbeat: string;
33
+ /** Milliseconds since the last heartbeat (at call time). */
34
+ heartbeatAgeMs: number;
35
+ /** Whether the agent is considered healthy (heartbeat within threshold). */
36
+ healthy: boolean;
37
+ /** Whether the agent is considered stale (heartbeat older than threshold). */
38
+ stale: boolean;
39
+ /** Threshold used for staleness determination (ms). */
40
+ thresholdMs: number;
41
+ }
42
+ /**
43
+ * Record a heartbeat for an agent instance.
44
+ *
45
+ * Updates `last_heartbeat` to the current time and returns the agent's
46
+ * current {@link AgentInstanceStatus}. Returns `null` if the agent does not
47
+ * exist or is already in a terminal state (`stopped` / `crashed`).
48
+ *
49
+ * This is the primary mechanism by which long-running agents signal liveness.
50
+ * Call this every {@link HEARTBEAT_INTERVAL_MS} (30 s) from the agent loop.
51
+ *
52
+ * @param agentId - The agent instance ID (e.g. `agt_20260322120000_a1b2c3`)
53
+ * @param cwd - Working directory used to resolve the tasks.db path (optional)
54
+ * @returns The agent's current status, or `null` if not found / terminal
55
+ *
56
+ * @remarks
57
+ * Terminal agents (`stopped`, `crashed`) will NOT have their heartbeat
58
+ * updated — the existing status is returned as-is so callers can detect
59
+ * external shutdown signals.
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * // Inside the agent's main loop:
64
+ * const heartbeatTimer = setInterval(async () => {
65
+ * const status = await recordHeartbeat(agentId);
66
+ * if (status === 'stopped' || status === null) {
67
+ * // Orchestrator shut us down — exit cleanly
68
+ * clearInterval(heartbeatTimer);
69
+ * process.exit(0);
70
+ * }
71
+ * }, HEARTBEAT_INTERVAL_MS);
72
+ * ```
73
+ */
74
+ export declare function recordHeartbeat(agentId: string, cwd?: string): Promise<AgentInstanceStatus | null>;
75
+ /**
76
+ * Check the health of a specific agent instance by ID.
77
+ *
78
+ * Queries the agent's current record and returns a structured
79
+ * {@link AgentHealthStatus} describing staleness, heartbeat age, and
80
+ * whether the agent is considered healthy relative to `thresholdMs`.
81
+ *
82
+ * Returns `null` if the agent ID is not found in the database.
83
+ *
84
+ * @param agentId - The agent instance ID to check
85
+ * @param thresholdMs - Staleness threshold in milliseconds (default: 3 minutes)
86
+ * @param cwd - Working directory used to resolve the tasks.db path (optional)
87
+ * @returns {@link AgentHealthStatus} or `null` if the agent does not exist
88
+ *
89
+ * @remarks
90
+ * Returns null if the agent is not found. A non-null result includes
91
+ * staleness status based on the threshold comparison.
92
+ *
93
+ * @example
94
+ * ```ts
95
+ * const health = await checkAgentHealth('agt_20260322120000_a1b2c3');
96
+ * if (health && health.stale) {
97
+ * console.log(`Agent stale for ${health.heartbeatAgeMs}ms — presumed crashed`);
98
+ * }
99
+ * ```
100
+ */
101
+ export declare function checkAgentHealth(agentId: string, thresholdMs?: number, cwd?: string): Promise<AgentHealthStatus | null>;
102
+ /**
103
+ * Find all non-terminal agents whose last heartbeat is older than `thresholdMs`.
104
+ *
105
+ * "Stale" means an agent with status `starting`, `active`, or `idle` has
106
+ * not sent a heartbeat within the threshold window. This is a precursor to
107
+ * crash detection — a stale agent may still recover if it is under heavy load.
108
+ *
109
+ * Agents with status `stopped` or `crashed` are excluded — they are already
110
+ * in a terminal state and do not participate in the heartbeat protocol.
111
+ *
112
+ * @param thresholdMs - Staleness threshold in ms (default: 3 minutes / 180 000 ms)
113
+ * @param cwd - Working directory used to resolve the tasks.db path (optional)
114
+ * @returns Array of {@link AgentHealthStatus} for each stale agent, sorted by
115
+ * heartbeat age descending (most-stale first)
116
+ *
117
+ * @remarks
118
+ * The default threshold matches the crash-detection window specified in T039:
119
+ * "timeout detection after 3 minutes".
120
+ *
121
+ * @example
122
+ * ```ts
123
+ * const stale = await detectStaleAgents();
124
+ * for (const s of stale) {
125
+ * console.log(`${s.agentId} has been stale for ${s.heartbeatAgeMs / 1000}s`);
126
+ * }
127
+ * ```
128
+ */
129
+ export declare function detectStaleAgents(thresholdMs?: number, cwd?: string): Promise<AgentHealthStatus[]>;
130
+ /**
131
+ * Find agents with status `active` whose heartbeat has been silent for
132
+ * longer than `thresholdMs`, and mark them as `crashed` in the database.
133
+ *
134
+ * An agent is considered crashed when it:
135
+ * 1. Has status `active` (not `idle`, `starting`, `stopped`, or `crashed`)
136
+ * 2. Has not sent a heartbeat for longer than `thresholdMs`
137
+ *
138
+ * Each detected agent is immediately marked `crashed` via {@link markCrashed},
139
+ * incrementing its error count and writing a reason to `agent_error_log`.
140
+ *
141
+ * @param thresholdMs - Crash threshold in ms (default: 3 minutes / 180 000 ms)
142
+ * @param cwd - Working directory used to resolve the tasks.db path (optional)
143
+ * @returns Array of agent instance rows for each agent that was just marked
144
+ * `crashed`, sorted by last heartbeat ascending (oldest first).
145
+ *
146
+ * @remarks
147
+ * This function is WRITE-side: it mutates the database. Callers should run
148
+ * it on a schedule (e.g. every 60 s) from an orchestrator or health watchdog.
149
+ * For a read-only view, use {@link detectStaleAgents} instead.
150
+ *
151
+ * @example
152
+ * ```ts
153
+ * // Inside an orchestrator health watchdog:
154
+ * const crashed = await detectCrashedAgents();
155
+ * if (crashed.length > 0) {
156
+ * logger.warn({ crashed: crashed.map(a => a.id) }, 'Agents marked crashed');
157
+ * }
158
+ * ```
159
+ */
160
+ export declare function detectCrashedAgents(thresholdMs?: number, cwd?: string): Promise<AgentInstanceRow[]>;
161
+ //# sourceMappingURL=health-monitor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health-monitor.d.ts","sourceRoot":"","sources":["../../src/agents/health-monitor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAO/E,8DAA8D;AAC9D,eAAO,MAAM,qBAAqB,QAAS,CAAC;AAE5C,kEAAkE;AAClE,eAAO,MAAM,kBAAkB,QAAa,CAAC;AAS7C;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,yBAAyB;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,yBAAyB;IACzB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,oDAAoD;IACpD,aAAa,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,cAAc,EAAE,MAAM,CAAC;IACvB,4EAA4E;IAC5E,OAAO,EAAE,OAAO,CAAC;IACjB,8EAA8E;IAC9E,KAAK,EAAE,OAAO,CAAC;IACf,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAC;CACrB;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAErC;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,MAAM,EACf,WAAW,GAAE,MAA2B,EACxC,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAMnC;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,iBAAiB,CACrC,WAAW,GAAE,MAA2B,EACxC,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAO9B;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAsB,mBAAmB,CACvC,WAAW,GAAE,MAA2B,EACxC,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,gBAAgB,EAAE,CAAC,CA6B7B"}