mio-agent-runtime 0.13.4 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,365 +1,365 @@
1
- 'use strict'
2
-
3
- const path = require('path')
4
- const { readJsonlCached } = require('../memory-store.js')
5
-
6
- const PHASE0_THRESHOLDS = Object.freeze({
7
- minHosts: 2,
8
- minTaskOutcomes: 20,
9
- minVerifiedReuse: 5,
10
- minImprovementEvidence: 1,
11
- })
12
-
13
- const GENERIC_AGENT_IDS = new Set(['mcp'])
14
- const TASK_OUTCOME_EVENT = 'task_outcome'
15
- const HOST_ACTIVE_WINDOW_MS = 7 * 24 * 60 * 60 * 1000
16
-
17
- function asRecord(value) {
18
- if (typeof value !== 'object' || value === null || Array.isArray(value)) return null
19
- return value
20
- }
21
-
22
- function readString(record, key) {
23
- const value = record[key]
24
- if (typeof value === 'string' && value.trim()) return value.trim()
25
- if (typeof value === 'number') return String(value)
26
- return null
27
- }
28
-
29
- function readBoolean(record, key) {
30
- const value = record[key]
31
- if (typeof value === 'boolean') return value
32
- if (value === 'true') return true
33
- if (value === 'false') return false
34
- return null
35
- }
36
-
37
- function readTimestamp(record) {
38
- const raw = record && record.timestamp
39
- if (typeof raw === 'string' || typeof raw === 'number') {
40
- const time = new Date(raw).getTime()
41
- if (Number.isFinite(time)) return time
42
- }
43
- return null
44
- }
45
-
46
- function normalizeIdentity(value) {
47
- const identity = typeof value === 'string' ? value.trim().toLowerCase() : null
48
- if (!identity || GENERIC_AGENT_IDS.has(identity)) return null
49
- return identity
50
- }
51
-
52
- function normalizeEventType(value) {
53
- if (typeof value !== 'string') return null
54
- const eventType = value.trim().toLowerCase()
55
- return eventType || null
56
- }
57
-
58
- function filterByProject(records, project) {
59
- if (!project) return records
60
- return records.filter((record) => readString(record, 'project') === project)
61
- }
62
-
63
- function createOutcomeSummary() {
64
- return { success: 0, failure: 0, error: 0, aborted: 0, retry: 0, other: 0 }
65
- }
66
-
67
- function bumpOutcome(summary, outcome) {
68
- switch (outcome) {
69
- case 'success':
70
- summary.success += 1
71
- break
72
- case 'failure':
73
- summary.failure += 1
74
- break
75
- case 'error':
76
- summary.error += 1
77
- break
78
- case 'aborted':
79
- summary.aborted += 1
80
- break
81
- case 'retry':
82
- summary.retry += 1
83
- break
84
- default:
85
- summary.other += 1
86
- }
87
- }
88
-
89
- function isVerifiedReuse(record) {
90
- return (
91
- readBoolean(record, 'reuse') === true &&
92
- readBoolean(record, 'behaviorChanged') === true &&
93
- readBoolean(record, 'outcomeImproved') === true
94
- )
95
- }
96
-
97
- function analyzePhase0(dataset) {
98
- const memories = dataset.memories || []
99
- const traces = dataset.traces || []
100
- const reuseRecords = dataset.reuseRecords || []
101
-
102
- const hosts = new Set()
103
- const addHost = (value) => {
104
- const identity = normalizeIdentity(value)
105
- if (identity) hosts.add(identity)
106
- }
107
-
108
- for (const record of memories) addHost(record.source)
109
- for (const record of traces) {
110
- addHost(record.agent)
111
- addHost(record.host)
112
- }
113
- for (const record of reuseRecords) {
114
- addHost(record.sourceAgent)
115
- addHost(record.targetAgent)
116
- }
117
-
118
- const hostList = Array.from(hosts).sort()
119
- const outcomeByStatus = createOutcomeSummary()
120
- let taskOutcomes = 0
121
-
122
- for (const record of traces) {
123
- const outcome = readString(record, 'outcome')
124
- bumpOutcome(outcomeByStatus, outcome ? outcome.toLowerCase() : null)
125
- if (normalizeEventType(record.event_type) === TASK_OUTCOME_EVENT) {
126
- taskOutcomes += 1
127
- }
128
- }
129
-
130
- const crossAgentPairs = new Set()
131
- let claimedReuse = 0
132
- let behaviorChangedReuse = 0
133
- let outcomeImprovedReuse = 0
134
- let verifiedReuse = 0
135
- let autoClaimedReuse = 0
136
- let confirmedReuse = 0
137
- let pendingAutoClaims = 0
138
-
139
- for (const record of reuseRecords) {
140
- const sourceAgent = normalizeIdentity(record.sourceAgent)
141
- const targetAgent = normalizeIdentity(record.targetAgent)
142
- const reuse = readBoolean(record, 'reuse') === true
143
- const behaviorChanged = readBoolean(record, 'behaviorChanged') === true
144
- const outcomeImproved = readBoolean(record, 'outcomeImproved') === true
145
-
146
- if (reuse) claimedReuse += 1
147
- if (reuse && behaviorChanged) behaviorChangedReuse += 1
148
- if (reuse && outcomeImproved) outcomeImprovedReuse += 1
149
- if (reuse && behaviorChanged && outcomeImproved) verifiedReuse += 1
150
- if (readString(record, 'source') === 'auto_claim') {
151
- autoClaimedReuse += 1
152
- if (readBoolean(record, 'confirmed') !== true) pendingAutoClaims += 1
153
- }
154
- if (readBoolean(record, 'confirmed') === true) confirmedReuse += 1
155
-
156
- if (sourceAgent && targetAgent && sourceAgent !== targetAgent) {
157
- crossAgentPairs.add(`${sourceAgent}->${targetAgent}`)
158
- }
159
- }
160
-
161
- const agentCoverage = hostList.map((agent) => {
162
- const hostMemories = memories.filter((record) => normalizeIdentity(record.source) === agent)
163
- const hostTraces = traces.filter((record) => normalizeIdentity(record.agent) === agent)
164
- const hostReuse = reuseRecords.filter(
165
- (record) =>
166
- normalizeIdentity(record.sourceAgent) === agent ||
167
- normalizeIdentity(record.targetAgent) === agent,
168
- )
169
- const lastActiveTs = [...hostMemories, ...hostTraces, ...hostReuse]
170
- .map(readTimestamp)
171
- .filter(Boolean)
172
- .reduce((max, value) => Math.max(max, value), null)
173
- const dataSources = []
174
- if (hostMemories.length > 0) dataSources.push('memory')
175
- if (hostTraces.length > 0) dataSources.push('trace')
176
- if (hostReuse.length > 0) dataSources.push('reuse')
177
- return {
178
- agent,
179
- memoryRecords: hostMemories.length,
180
- traceEvents: hostTraces.length,
181
- taskOutcomes: hostTraces.filter(
182
- (record) => normalizeEventType(record.event_type) === TASK_OUTCOME_EVENT,
183
- ).length,
184
- verifiedReuseAsTarget: hostReuse.filter(
185
- (record) => normalizeIdentity(record.targetAgent) === agent && isVerifiedReuse(record),
186
- ).length,
187
- lastActiveAt: lastActiveTs === null ? null : new Date(lastActiveTs).toISOString(),
188
- lastActiveHoursAgo:
189
- lastActiveTs === null
190
- ? null
191
- : Number(((Date.now() - lastActiveTs) / 3600000).toFixed(1)),
192
- active: lastActiveTs !== null && Date.now() - lastActiveTs <= HOST_ACTIVE_WINDOW_MS,
193
- dataSources,
194
- }
195
- })
196
-
197
- const metrics = {
198
- memoryRecords: memories.length,
199
- traceEvents: traces.length,
200
- taskOutcomes,
201
- experienceReuseRecords: reuseRecords.length,
202
- autoClaimedReuse,
203
- confirmedReuse,
204
- pendingAutoClaims,
205
- claimedReuse,
206
- behaviorChangedReuse,
207
- outcomeImprovedReuse,
208
- verifiedReuse,
209
- hostCount: hostList.length,
210
- activeHostCount: agentCoverage.filter((coverage) => coverage.active).length,
211
- hosts: hostList,
212
- crossAgentPairs: Array.from(crossAgentPairs).sort(),
213
- outcomeByStatus,
214
- agentCoverage,
215
- }
216
-
217
- const criteria = [
218
- {
219
- key: 'hosts',
220
- label: 'Distinct agent hosts',
221
- current: metrics.hostCount,
222
- required: PHASE0_THRESHOLDS.minHosts,
223
- passed: metrics.hostCount >= PHASE0_THRESHOLDS.minHosts,
224
- },
225
- {
226
- key: 'tasks',
227
- label: 'Task outcomes',
228
- current: metrics.taskOutcomes,
229
- required: PHASE0_THRESHOLDS.minTaskOutcomes,
230
- passed: metrics.taskOutcomes >= PHASE0_THRESHOLDS.minTaskOutcomes,
231
- },
232
- {
233
- key: 'verified_reuse',
234
- label: 'Verified cross-agent reuses',
235
- current: metrics.verifiedReuse,
236
- required: PHASE0_THRESHOLDS.minVerifiedReuse,
237
- passed: metrics.verifiedReuse >= PHASE0_THRESHOLDS.minVerifiedReuse,
238
- },
239
- {
240
- key: 'measurable_improvement',
241
- label: 'Reuse events with improved outcome',
242
- current: metrics.outcomeImprovedReuse,
243
- required: PHASE0_THRESHOLDS.minImprovementEvidence,
244
- passed: metrics.outcomeImprovedReuse >= PHASE0_THRESHOLDS.minImprovementEvidence,
245
- },
246
- ]
247
-
248
- const totalEvidence =
249
- metrics.memoryRecords + metrics.traceEvents + metrics.experienceReuseRecords
250
- const status = criteria.every((criterion) => criterion.passed)
251
- ? 'passed'
252
- : totalEvidence === 0
253
- ? 'not_started'
254
- : 'in_progress'
255
-
256
- return {
257
- generatedAt: new Date().toISOString(),
258
- status,
259
- criteria,
260
- metrics,
261
- }
262
- }
263
-
264
- function loadPhase0(dataDir, project) {
265
- const normalizedProject = project && project.trim() ? project.trim() : undefined
266
- const memories = readJsonlCached(path.join(dataDir, 'memory.jsonl')).map(asRecord).filter(Boolean)
267
- const traces = readJsonlCached(path.join(dataDir, 'traces.jsonl')).map(asRecord).filter(Boolean)
268
- const reuseRecords = readJsonlCached(path.join(dataDir, 'experience_reuse.jsonl'))
269
- .map(asRecord)
270
- .filter(Boolean)
271
-
272
- return analyzePhase0({
273
- memories: filterByProject(memories, normalizedProject),
274
- traces: filterByProject(traces, normalizedProject),
275
- reuseRecords: filterByProject(reuseRecords, normalizedProject),
276
- })
277
- }
278
-
279
- function renderPhase0Markdown(report) {
280
- const lines = []
281
- lines.push('# Mio Phase 0 Validation Report')
282
- lines.push('')
283
- lines.push(`- Generated: ${report.generatedAt}`)
284
- lines.push(`- Status: **${report.status}**`)
285
- lines.push('')
286
- lines.push('## Gate Status')
287
- for (const criterion of report.criteria) {
288
- const mark = criterion.passed ? '✅' : '❌'
289
- lines.push(`- ${mark} ${criterion.label}: ${criterion.current}/${criterion.required}`)
290
- }
291
- lines.push('')
292
- lines.push('## Evidence Volume')
293
- lines.push(`- Memory records: ${report.metrics.memoryRecords}`)
294
- lines.push(`- Trace events: ${report.metrics.traceEvents}`)
295
- lines.push(`- Task outcomes: ${report.metrics.taskOutcomes}`)
296
- lines.push(`- Experience reuse records: ${report.metrics.experienceReuseRecords}`)
297
- lines.push(`- Active hosts: ${report.metrics.activeHostCount}/${report.metrics.hostCount}`)
298
- lines.push('')
299
- lines.push('## Reuse Funnel')
300
- lines.push(`- Auto-claimed reuse: ${report.metrics.autoClaimedReuse}`)
301
- lines.push(`- Pending auto-claims (awaiting confirmation): ${report.metrics.pendingAutoClaims}`)
302
- lines.push(`- Confirmed reuse: ${report.metrics.confirmedReuse}`)
303
- lines.push(`- Claimed reuse: ${report.metrics.claimedReuse}`)
304
- lines.push(`- Behavior changed: ${report.metrics.behaviorChangedReuse}`)
305
- lines.push(`- Outcome improved: ${report.metrics.outcomeImprovedReuse}`)
306
- lines.push(`- Verified reuse: ${report.metrics.verifiedReuse}`)
307
- lines.push('')
308
- lines.push('## Agent Coverage')
309
- if (report.metrics.agentCoverage.length === 0) {
310
- lines.push('- No agent identities found.')
311
- } else {
312
- for (const coverage of report.metrics.agentCoverage) {
313
- const activeLabel =
314
- coverage.active && coverage.lastActiveHoursAgo !== null
315
- ? `yes (${coverage.lastActiveHoursAgo}h ago)`
316
- : 'no'
317
- lines.push(
318
- `- ${coverage.agent}: ${coverage.memoryRecords} memories, ${coverage.traceEvents} traces, ${coverage.taskOutcomes} task outcomes, ${coverage.verifiedReuseAsTarget} verified reuse | active: ${activeLabel} | sources: ${coverage.dataSources.join(', ') || 'none'}`,
319
- )
320
- }
321
- }
322
- lines.push('')
323
- lines.push('## Cross-Agent Pairs')
324
- if (report.metrics.crossAgentPairs.length === 0) {
325
- lines.push('- No cross-agent reuse pairs found.')
326
- } else {
327
- for (const pair of report.metrics.crossAgentPairs) {
328
- lines.push(`- ${pair}`)
329
- }
330
- }
331
- lines.push('')
332
- lines.push('## Trace Outcomes')
333
- const outcome = report.metrics.outcomeByStatus
334
- lines.push(
335
- `- success: ${outcome.success}, failure: ${outcome.failure}, error: ${outcome.error}, aborted: ${outcome.aborted}, retry: ${outcome.retry}, other: ${outcome.other}`,
336
- )
337
- lines.push('')
338
- return lines.join('\n')
339
- }
340
-
341
- function summarizePhase0(report) {
342
- const remaining = (report.criteria || [])
343
- .filter((criterion) => !criterion.passed)
344
- .map((criterion) => ({
345
- key: criterion.key,
346
- label: criterion.label,
347
- current: criterion.current,
348
- required: criterion.required,
349
- gap: Math.max(0, criterion.required - criterion.current),
350
- }))
351
-
352
- return {
353
- status: report.status,
354
- passed: remaining.length === 0,
355
- remaining,
356
- }
357
- }
358
-
359
- module.exports = {
360
- PHASE0_THRESHOLDS,
361
- analyzePhase0,
362
- loadPhase0,
363
- renderPhase0Markdown,
364
- summarizePhase0,
365
- }
1
+ 'use strict'
2
+
3
+ const path = require('path')
4
+ const { readJsonlCached } = require('../memory-store.js')
5
+
6
+ const PHASE0_THRESHOLDS = Object.freeze({
7
+ minHosts: 2,
8
+ minTaskOutcomes: 20,
9
+ minVerifiedReuse: 5,
10
+ minImprovementEvidence: 1,
11
+ })
12
+
13
+ const GENERIC_AGENT_IDS = new Set(['mcp'])
14
+ const TASK_OUTCOME_EVENT = 'task_outcome'
15
+ const HOST_ACTIVE_WINDOW_MS = 7 * 24 * 60 * 60 * 1000
16
+
17
+ function asRecord(value) {
18
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) return null
19
+ return value
20
+ }
21
+
22
+ function readString(record, key) {
23
+ const value = record[key]
24
+ if (typeof value === 'string' && value.trim()) return value.trim()
25
+ if (typeof value === 'number') return String(value)
26
+ return null
27
+ }
28
+
29
+ function readBoolean(record, key) {
30
+ const value = record[key]
31
+ if (typeof value === 'boolean') return value
32
+ if (value === 'true') return true
33
+ if (value === 'false') return false
34
+ return null
35
+ }
36
+
37
+ function readTimestamp(record) {
38
+ const raw = record && record.timestamp
39
+ if (typeof raw === 'string' || typeof raw === 'number') {
40
+ const time = new Date(raw).getTime()
41
+ if (Number.isFinite(time)) return time
42
+ }
43
+ return null
44
+ }
45
+
46
+ function normalizeIdentity(value) {
47
+ const identity = typeof value === 'string' ? value.trim().toLowerCase() : null
48
+ if (!identity || GENERIC_AGENT_IDS.has(identity)) return null
49
+ return identity
50
+ }
51
+
52
+ function normalizeEventType(value) {
53
+ if (typeof value !== 'string') return null
54
+ const eventType = value.trim().toLowerCase()
55
+ return eventType || null
56
+ }
57
+
58
+ function filterByProject(records, project) {
59
+ if (!project) return records
60
+ return records.filter((record) => readString(record, 'project') === project)
61
+ }
62
+
63
+ function createOutcomeSummary() {
64
+ return { success: 0, failure: 0, error: 0, aborted: 0, retry: 0, other: 0 }
65
+ }
66
+
67
+ function bumpOutcome(summary, outcome) {
68
+ switch (outcome) {
69
+ case 'success':
70
+ summary.success += 1
71
+ break
72
+ case 'failure':
73
+ summary.failure += 1
74
+ break
75
+ case 'error':
76
+ summary.error += 1
77
+ break
78
+ case 'aborted':
79
+ summary.aborted += 1
80
+ break
81
+ case 'retry':
82
+ summary.retry += 1
83
+ break
84
+ default:
85
+ summary.other += 1
86
+ }
87
+ }
88
+
89
+ function isVerifiedReuse(record) {
90
+ return (
91
+ readBoolean(record, 'reuse') === true &&
92
+ readBoolean(record, 'behaviorChanged') === true &&
93
+ readBoolean(record, 'outcomeImproved') === true
94
+ )
95
+ }
96
+
97
+ function analyzePhase0(dataset) {
98
+ const memories = dataset.memories || []
99
+ const traces = dataset.traces || []
100
+ const reuseRecords = dataset.reuseRecords || []
101
+
102
+ const hosts = new Set()
103
+ const addHost = (value) => {
104
+ const identity = normalizeIdentity(value)
105
+ if (identity) hosts.add(identity)
106
+ }
107
+
108
+ for (const record of memories) addHost(record.source)
109
+ for (const record of traces) {
110
+ addHost(record.agent)
111
+ addHost(record.host)
112
+ }
113
+ for (const record of reuseRecords) {
114
+ addHost(record.sourceAgent)
115
+ addHost(record.targetAgent)
116
+ }
117
+
118
+ const hostList = Array.from(hosts).sort()
119
+ const outcomeByStatus = createOutcomeSummary()
120
+ let taskOutcomes = 0
121
+
122
+ for (const record of traces) {
123
+ const outcome = readString(record, 'outcome')
124
+ bumpOutcome(outcomeByStatus, outcome ? outcome.toLowerCase() : null)
125
+ if (normalizeEventType(record.event_type) === TASK_OUTCOME_EVENT) {
126
+ taskOutcomes += 1
127
+ }
128
+ }
129
+
130
+ const crossAgentPairs = new Set()
131
+ let claimedReuse = 0
132
+ let behaviorChangedReuse = 0
133
+ let outcomeImprovedReuse = 0
134
+ let verifiedReuse = 0
135
+ let autoClaimedReuse = 0
136
+ let confirmedReuse = 0
137
+ let pendingAutoClaims = 0
138
+
139
+ for (const record of reuseRecords) {
140
+ const sourceAgent = normalizeIdentity(record.sourceAgent)
141
+ const targetAgent = normalizeIdentity(record.targetAgent)
142
+ const reuse = readBoolean(record, 'reuse') === true
143
+ const behaviorChanged = readBoolean(record, 'behaviorChanged') === true
144
+ const outcomeImproved = readBoolean(record, 'outcomeImproved') === true
145
+
146
+ if (reuse) claimedReuse += 1
147
+ if (reuse && behaviorChanged) behaviorChangedReuse += 1
148
+ if (reuse && outcomeImproved) outcomeImprovedReuse += 1
149
+ if (reuse && behaviorChanged && outcomeImproved) verifiedReuse += 1
150
+ if (readString(record, 'source') === 'auto_claim') {
151
+ autoClaimedReuse += 1
152
+ if (readBoolean(record, 'confirmed') !== true) pendingAutoClaims += 1
153
+ }
154
+ if (readBoolean(record, 'confirmed') === true) confirmedReuse += 1
155
+
156
+ if (sourceAgent && targetAgent && sourceAgent !== targetAgent) {
157
+ crossAgentPairs.add(`${sourceAgent}->${targetAgent}`)
158
+ }
159
+ }
160
+
161
+ const agentCoverage = hostList.map((agent) => {
162
+ const hostMemories = memories.filter((record) => normalizeIdentity(record.source) === agent)
163
+ const hostTraces = traces.filter((record) => normalizeIdentity(record.agent) === agent)
164
+ const hostReuse = reuseRecords.filter(
165
+ (record) =>
166
+ normalizeIdentity(record.sourceAgent) === agent ||
167
+ normalizeIdentity(record.targetAgent) === agent,
168
+ )
169
+ const lastActiveTs = [...hostMemories, ...hostTraces, ...hostReuse]
170
+ .map(readTimestamp)
171
+ .filter(Boolean)
172
+ .reduce((max, value) => Math.max(max, value), null)
173
+ const dataSources = []
174
+ if (hostMemories.length > 0) dataSources.push('memory')
175
+ if (hostTraces.length > 0) dataSources.push('trace')
176
+ if (hostReuse.length > 0) dataSources.push('reuse')
177
+ return {
178
+ agent,
179
+ memoryRecords: hostMemories.length,
180
+ traceEvents: hostTraces.length,
181
+ taskOutcomes: hostTraces.filter(
182
+ (record) => normalizeEventType(record.event_type) === TASK_OUTCOME_EVENT,
183
+ ).length,
184
+ verifiedReuseAsTarget: hostReuse.filter(
185
+ (record) => normalizeIdentity(record.targetAgent) === agent && isVerifiedReuse(record),
186
+ ).length,
187
+ lastActiveAt: lastActiveTs === null ? null : new Date(lastActiveTs).toISOString(),
188
+ lastActiveHoursAgo:
189
+ lastActiveTs === null
190
+ ? null
191
+ : Number(((Date.now() - lastActiveTs) / 3600000).toFixed(1)),
192
+ active: lastActiveTs !== null && Date.now() - lastActiveTs <= HOST_ACTIVE_WINDOW_MS,
193
+ dataSources,
194
+ }
195
+ })
196
+
197
+ const metrics = {
198
+ memoryRecords: memories.length,
199
+ traceEvents: traces.length,
200
+ taskOutcomes,
201
+ experienceReuseRecords: reuseRecords.length,
202
+ autoClaimedReuse,
203
+ confirmedReuse,
204
+ pendingAutoClaims,
205
+ claimedReuse,
206
+ behaviorChangedReuse,
207
+ outcomeImprovedReuse,
208
+ verifiedReuse,
209
+ hostCount: hostList.length,
210
+ activeHostCount: agentCoverage.filter((coverage) => coverage.active).length,
211
+ hosts: hostList,
212
+ crossAgentPairs: Array.from(crossAgentPairs).sort(),
213
+ outcomeByStatus,
214
+ agentCoverage,
215
+ }
216
+
217
+ const criteria = [
218
+ {
219
+ key: 'hosts',
220
+ label: 'Distinct agent hosts',
221
+ current: metrics.hostCount,
222
+ required: PHASE0_THRESHOLDS.minHosts,
223
+ passed: metrics.hostCount >= PHASE0_THRESHOLDS.minHosts,
224
+ },
225
+ {
226
+ key: 'tasks',
227
+ label: 'Task outcomes',
228
+ current: metrics.taskOutcomes,
229
+ required: PHASE0_THRESHOLDS.minTaskOutcomes,
230
+ passed: metrics.taskOutcomes >= PHASE0_THRESHOLDS.minTaskOutcomes,
231
+ },
232
+ {
233
+ key: 'verified_reuse',
234
+ label: 'Verified cross-agent reuses',
235
+ current: metrics.verifiedReuse,
236
+ required: PHASE0_THRESHOLDS.minVerifiedReuse,
237
+ passed: metrics.verifiedReuse >= PHASE0_THRESHOLDS.minVerifiedReuse,
238
+ },
239
+ {
240
+ key: 'measurable_improvement',
241
+ label: 'Reuse events with improved outcome',
242
+ current: metrics.outcomeImprovedReuse,
243
+ required: PHASE0_THRESHOLDS.minImprovementEvidence,
244
+ passed: metrics.outcomeImprovedReuse >= PHASE0_THRESHOLDS.minImprovementEvidence,
245
+ },
246
+ ]
247
+
248
+ const totalEvidence =
249
+ metrics.memoryRecords + metrics.traceEvents + metrics.experienceReuseRecords
250
+ const status = criteria.every((criterion) => criterion.passed)
251
+ ? 'passed'
252
+ : totalEvidence === 0
253
+ ? 'not_started'
254
+ : 'in_progress'
255
+
256
+ return {
257
+ generatedAt: new Date().toISOString(),
258
+ status,
259
+ criteria,
260
+ metrics,
261
+ }
262
+ }
263
+
264
+ function loadPhase0(dataDir, project) {
265
+ const normalizedProject = project && project.trim() ? project.trim() : undefined
266
+ const memories = readJsonlCached(path.join(dataDir, 'memory.jsonl')).map(asRecord).filter(Boolean)
267
+ const traces = readJsonlCached(path.join(dataDir, 'traces.jsonl')).map(asRecord).filter(Boolean)
268
+ const reuseRecords = readJsonlCached(path.join(dataDir, 'experience_reuse.jsonl'))
269
+ .map(asRecord)
270
+ .filter(Boolean)
271
+
272
+ return analyzePhase0({
273
+ memories: filterByProject(memories, normalizedProject),
274
+ traces: filterByProject(traces, normalizedProject),
275
+ reuseRecords: filterByProject(reuseRecords, normalizedProject),
276
+ })
277
+ }
278
+
279
+ function renderPhase0Markdown(report) {
280
+ const lines = []
281
+ lines.push('# Mio Phase 0 Validation Report')
282
+ lines.push('')
283
+ lines.push(`- Generated: ${report.generatedAt}`)
284
+ lines.push(`- Status: **${report.status}**`)
285
+ lines.push('')
286
+ lines.push('## Gate Status')
287
+ for (const criterion of report.criteria) {
288
+ const mark = criterion.passed ? '✅' : '❌'
289
+ lines.push(`- ${mark} ${criterion.label}: ${criterion.current}/${criterion.required}`)
290
+ }
291
+ lines.push('')
292
+ lines.push('## Evidence Volume')
293
+ lines.push(`- Memory records: ${report.metrics.memoryRecords}`)
294
+ lines.push(`- Trace events: ${report.metrics.traceEvents}`)
295
+ lines.push(`- Task outcomes: ${report.metrics.taskOutcomes}`)
296
+ lines.push(`- Experience reuse records: ${report.metrics.experienceReuseRecords}`)
297
+ lines.push(`- Active hosts: ${report.metrics.activeHostCount}/${report.metrics.hostCount}`)
298
+ lines.push('')
299
+ lines.push('## Reuse Funnel')
300
+ lines.push(`- Auto-claimed reuse: ${report.metrics.autoClaimedReuse}`)
301
+ lines.push(`- Pending auto-claims (awaiting confirmation): ${report.metrics.pendingAutoClaims}`)
302
+ lines.push(`- Confirmed reuse: ${report.metrics.confirmedReuse}`)
303
+ lines.push(`- Claimed reuse: ${report.metrics.claimedReuse}`)
304
+ lines.push(`- Behavior changed: ${report.metrics.behaviorChangedReuse}`)
305
+ lines.push(`- Outcome improved: ${report.metrics.outcomeImprovedReuse}`)
306
+ lines.push(`- Verified reuse: ${report.metrics.verifiedReuse}`)
307
+ lines.push('')
308
+ lines.push('## Agent Coverage')
309
+ if (report.metrics.agentCoverage.length === 0) {
310
+ lines.push('- No agent identities found.')
311
+ } else {
312
+ for (const coverage of report.metrics.agentCoverage) {
313
+ const activeLabel =
314
+ coverage.active && coverage.lastActiveHoursAgo !== null
315
+ ? `yes (${coverage.lastActiveHoursAgo}h ago)`
316
+ : 'no'
317
+ lines.push(
318
+ `- ${coverage.agent}: ${coverage.memoryRecords} memories, ${coverage.traceEvents} traces, ${coverage.taskOutcomes} task outcomes, ${coverage.verifiedReuseAsTarget} verified reuse | active: ${activeLabel} | sources: ${coverage.dataSources.join(', ') || 'none'}`,
319
+ )
320
+ }
321
+ }
322
+ lines.push('')
323
+ lines.push('## Cross-Agent Pairs')
324
+ if (report.metrics.crossAgentPairs.length === 0) {
325
+ lines.push('- No cross-agent reuse pairs found.')
326
+ } else {
327
+ for (const pair of report.metrics.crossAgentPairs) {
328
+ lines.push(`- ${pair}`)
329
+ }
330
+ }
331
+ lines.push('')
332
+ lines.push('## Trace Outcomes')
333
+ const outcome = report.metrics.outcomeByStatus
334
+ lines.push(
335
+ `- success: ${outcome.success}, failure: ${outcome.failure}, error: ${outcome.error}, aborted: ${outcome.aborted}, retry: ${outcome.retry}, other: ${outcome.other}`,
336
+ )
337
+ lines.push('')
338
+ return lines.join('\n')
339
+ }
340
+
341
+ function summarizePhase0(report) {
342
+ const remaining = (report.criteria || [])
343
+ .filter((criterion) => !criterion.passed)
344
+ .map((criterion) => ({
345
+ key: criterion.key,
346
+ label: criterion.label,
347
+ current: criterion.current,
348
+ required: criterion.required,
349
+ gap: Math.max(0, criterion.required - criterion.current),
350
+ }))
351
+
352
+ return {
353
+ status: report.status,
354
+ passed: remaining.length === 0,
355
+ remaining,
356
+ }
357
+ }
358
+
359
+ module.exports = {
360
+ PHASE0_THRESHOLDS,
361
+ analyzePhase0,
362
+ loadPhase0,
363
+ renderPhase0Markdown,
364
+ summarizePhase0,
365
+ }