@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.
- package/dist/agents/agent-registry.d.ts +206 -0
- package/dist/agents/agent-registry.d.ts.map +1 -0
- package/dist/agents/agent-registry.js +288 -0
- package/dist/agents/agent-registry.js.map +1 -0
- package/dist/agents/agent-schema.js +5 -0
- package/dist/agents/agent-schema.js.map +1 -1
- package/dist/agents/execution-learning.js +474 -0
- package/dist/agents/execution-learning.js.map +1 -0
- package/dist/agents/health-monitor.d.ts +161 -0
- package/dist/agents/health-monitor.d.ts.map +1 -0
- package/dist/agents/health-monitor.js +217 -0
- package/dist/agents/health-monitor.js.map +1 -0
- package/dist/agents/index.d.ts +3 -1
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js +9 -1
- package/dist/agents/index.js.map +1 -1
- package/dist/agents/retry.d.ts +57 -4
- package/dist/agents/retry.d.ts.map +1 -1
- package/dist/agents/retry.js +57 -4
- package/dist/agents/retry.js.map +1 -1
- package/dist/backfill/index.d.ts +27 -0
- package/dist/backfill/index.d.ts.map +1 -1
- package/dist/backfill/index.js +229 -0
- package/dist/backfill/index.js.map +1 -0
- package/dist/bootstrap.d.ts +2 -1
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +135 -28
- package/dist/bootstrap.js.map +1 -1
- package/dist/cleo.d.ts +40 -0
- package/dist/cleo.d.ts.map +1 -1
- package/dist/config.js +83 -0
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1036 -536
- package/dist/index.js.map +4 -4
- package/dist/intelligence/adaptive-validation.js +497 -0
- package/dist/intelligence/adaptive-validation.js.map +1 -0
- package/dist/intelligence/impact.d.ts +34 -1
- package/dist/intelligence/impact.d.ts.map +1 -1
- package/dist/intelligence/impact.js +176 -0
- package/dist/intelligence/impact.js.map +1 -1
- package/dist/intelligence/index.d.ts +2 -2
- package/dist/intelligence/index.d.ts.map +1 -1
- package/dist/intelligence/index.js +6 -1
- package/dist/intelligence/index.js.map +1 -1
- package/dist/intelligence/types.d.ts +60 -0
- package/dist/intelligence/types.d.ts.map +1 -1
- package/dist/internal.d.ts +5 -4
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +11 -2
- package/dist/internal.js.map +1 -1
- package/dist/lib/index.d.ts +10 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +10 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/retry.d.ts +128 -0
- package/dist/lib/retry.d.ts.map +1 -0
- package/dist/lib/retry.js +152 -0
- package/dist/lib/retry.js.map +1 -0
- package/dist/nexus/sharing/index.d.ts +48 -2
- package/dist/nexus/sharing/index.d.ts.map +1 -1
- package/dist/nexus/sharing/index.js +110 -1
- package/dist/nexus/sharing/index.js.map +1 -1
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +22 -2
- package/dist/scaffold.js.map +1 -1
- package/dist/sessions/session-enforcement.js +4 -0
- package/dist/sessions/session-enforcement.js.map +1 -1
- package/dist/stats/index.js +2 -0
- package/dist/stats/index.js.map +1 -1
- package/dist/stats/workflow-telemetry.d.ts +15 -0
- package/dist/stats/workflow-telemetry.d.ts.map +1 -1
- package/dist/stats/workflow-telemetry.js +400 -0
- package/dist/stats/workflow-telemetry.js.map +1 -0
- package/dist/store/brain-schema.js +4 -1
- package/dist/store/brain-schema.js.map +1 -1
- package/dist/store/converters.js +2 -0
- package/dist/store/converters.js.map +1 -1
- package/dist/store/cross-db-cleanup.d.ts +35 -0
- package/dist/store/cross-db-cleanup.d.ts.map +1 -1
- package/dist/store/cross-db-cleanup.js +169 -0
- package/dist/store/cross-db-cleanup.js.map +1 -0
- package/dist/store/db-helpers.js +2 -0
- package/dist/store/db-helpers.js.map +1 -1
- package/dist/store/migration-sqlite.js +5 -0
- package/dist/store/migration-sqlite.js.map +1 -1
- package/dist/store/sqlite-data-accessor.js +20 -28
- package/dist/store/sqlite-data-accessor.js.map +1 -1
- package/dist/store/sqlite.js +13 -2
- package/dist/store/sqlite.js.map +1 -1
- package/dist/store/task-store.js +4 -0
- package/dist/store/task-store.js.map +1 -1
- package/dist/store/tasks-schema.js +50 -20
- package/dist/store/tasks-schema.js.map +1 -1
- package/dist/tasks/add.js +87 -3
- package/dist/tasks/add.js.map +1 -1
- package/dist/tasks/complete.d.ts.map +1 -1
- package/dist/tasks/complete.js +15 -4
- package/dist/tasks/complete.js.map +1 -1
- package/dist/tasks/enforcement.d.ts.map +1 -1
- package/dist/tasks/enforcement.js +8 -1
- package/dist/tasks/enforcement.js.map +1 -1
- package/dist/tasks/epic-enforcement.d.ts +61 -0
- package/dist/tasks/epic-enforcement.d.ts.map +1 -1
- package/dist/tasks/epic-enforcement.js +294 -0
- package/dist/tasks/epic-enforcement.js.map +1 -0
- package/dist/tasks/index.js +1 -1
- package/dist/tasks/index.js.map +1 -1
- package/dist/tasks/pipeline-stage.d.ts +70 -1
- package/dist/tasks/pipeline-stage.d.ts.map +1 -1
- package/dist/tasks/pipeline-stage.js +248 -0
- package/dist/tasks/pipeline-stage.js.map +1 -0
- package/dist/tasks/update.js +28 -0
- package/dist/tasks/update.js.map +1 -1
- package/package.json +5 -5
- package/schemas/config.schema.json +37 -1547
- package/src/__tests__/sharing.test.ts +24 -0
- package/src/agents/__tests__/agent-registry.test.ts +351 -0
- package/src/agents/__tests__/health-monitor.test.ts +332 -0
- package/src/agents/agent-registry.ts +394 -0
- package/src/agents/health-monitor.ts +279 -0
- package/src/agents/index.ts +24 -1
- package/src/agents/retry.ts +57 -4
- package/src/backfill/index.ts +27 -0
- package/src/bootstrap.ts +171 -30
- package/src/cleo.ts +103 -2
- package/src/config.ts +3 -3
- package/src/index.ts +1 -0
- package/src/intelligence/__tests__/impact.test.ts +165 -1
- package/src/intelligence/impact.ts +203 -0
- package/src/intelligence/index.ts +3 -0
- package/src/intelligence/types.ts +76 -0
- package/src/internal.ts +20 -0
- package/src/lib/__tests__/retry.test.ts +321 -0
- package/src/lib/index.ts +16 -0
- package/src/lib/retry.ts +224 -0
- package/src/nexus/sharing/index.ts +142 -2
- package/src/scaffold.ts +24 -2
- package/src/stats/workflow-telemetry.ts +15 -0
- package/src/store/__tests__/session-store.test.ts +43 -7
- package/src/store/__tests__/task-store.test.ts +1 -1
- package/src/store/__tests__/test-db-helper.ts +7 -3
- package/src/store/cross-db-cleanup.ts +35 -0
- package/src/tasks/__tests__/epic-enforcement.test.ts +9 -4
- package/src/tasks/__tests__/minimal-test.test.ts +2 -2
- package/src/tasks/__tests__/update.test.ts +25 -25
- package/src/tasks/complete.ts +11 -6
- package/src/tasks/enforcement.ts +6 -3
- package/src/tasks/epic-enforcement.ts +61 -0
- package/src/tasks/pipeline-stage.ts +70 -1
- package/templates/config.template.json +5 -116
- 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"}
|