mio-agent-runtime 0.5.15 → 0.5.16
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/README.md +144 -236
- package/adapters/codex.js +122 -134
- package/adapters/hermes.js +122 -140
- package/adapters/host-utils.js +61 -61
- package/adapters/opencode.js +176 -215
- package/adapters/workbuddy.js +196 -242
- package/bin/mio.js +423 -424
- package/observe/observer.js +1326 -1335
- package/package.json +27 -16
- package/scripts/verify-packed-runtime.js +371 -0
- package/server/evolution-cutover.js +119 -0
- package/server/host-capabilities.js +50 -0
- package/server/mio-intelligence-mcp/README.md +184 -173
- package/server/mio-intelligence-mcp/index.js +116 -298
- package/server/mio-intelligence-mcp/package.json +17 -17
- package/server/mio-intelligence-mcp/phase0.js +381 -381
- package/server/runtime-modules.js +111 -0
- package/server/mio-intelligence-mcp/dashboard-server.js +0 -171
|
@@ -1,381 +1,381 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const fs = require('fs')
|
|
4
|
-
const path = require('path')
|
|
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 readJsonl(filePath) {
|
|
59
|
-
if (!fs.existsSync(filePath)) return []
|
|
60
|
-
return fs
|
|
61
|
-
.readFileSync(filePath, 'utf8')
|
|
62
|
-
.split(/\r?\n/)
|
|
63
|
-
.filter(Boolean)
|
|
64
|
-
.map((line) => {
|
|
65
|
-
try {
|
|
66
|
-
return JSON.parse(line)
|
|
67
|
-
} catch (_) {
|
|
68
|
-
return null
|
|
69
|
-
}
|
|
70
|
-
})
|
|
71
|
-
.filter(Boolean)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function filterByProject(records, project) {
|
|
75
|
-
if (!project) return records
|
|
76
|
-
return records.filter((record) => readString(record, 'project') === project)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function createOutcomeSummary() {
|
|
80
|
-
return { success: 0, failure: 0, error: 0, aborted: 0, retry: 0, other: 0 }
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function bumpOutcome(summary, outcome) {
|
|
84
|
-
switch (outcome) {
|
|
85
|
-
case 'success':
|
|
86
|
-
summary.success += 1
|
|
87
|
-
break
|
|
88
|
-
case 'failure':
|
|
89
|
-
summary.failure += 1
|
|
90
|
-
break
|
|
91
|
-
case 'error':
|
|
92
|
-
summary.error += 1
|
|
93
|
-
break
|
|
94
|
-
case 'aborted':
|
|
95
|
-
summary.aborted += 1
|
|
96
|
-
break
|
|
97
|
-
case 'retry':
|
|
98
|
-
summary.retry += 1
|
|
99
|
-
break
|
|
100
|
-
default:
|
|
101
|
-
summary.other += 1
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function isVerifiedReuse(record) {
|
|
106
|
-
return (
|
|
107
|
-
readBoolean(record, 'reuse') === true &&
|
|
108
|
-
readBoolean(record, 'behaviorChanged') === true &&
|
|
109
|
-
readBoolean(record, 'outcomeImproved') === true
|
|
110
|
-
)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function analyzePhase0(dataset) {
|
|
114
|
-
const memories = dataset.memories || []
|
|
115
|
-
const traces = dataset.traces || []
|
|
116
|
-
const reuseRecords = dataset.reuseRecords || []
|
|
117
|
-
|
|
118
|
-
const hosts = new Set()
|
|
119
|
-
const addHost = (value) => {
|
|
120
|
-
const identity = normalizeIdentity(value)
|
|
121
|
-
if (identity) hosts.add(identity)
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
for (const record of memories) addHost(record.source)
|
|
125
|
-
for (const record of traces) {
|
|
126
|
-
addHost(record.agent)
|
|
127
|
-
addHost(record.host)
|
|
128
|
-
}
|
|
129
|
-
for (const record of reuseRecords) {
|
|
130
|
-
addHost(record.sourceAgent)
|
|
131
|
-
addHost(record.targetAgent)
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const hostList = Array.from(hosts).sort()
|
|
135
|
-
const outcomeByStatus = createOutcomeSummary()
|
|
136
|
-
let taskOutcomes = 0
|
|
137
|
-
|
|
138
|
-
for (const record of traces) {
|
|
139
|
-
const outcome = readString(record, 'outcome')
|
|
140
|
-
bumpOutcome(outcomeByStatus, outcome ? outcome.toLowerCase() : null)
|
|
141
|
-
if (normalizeEventType(record.event_type) === TASK_OUTCOME_EVENT) {
|
|
142
|
-
taskOutcomes += 1
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const crossAgentPairs = new Set()
|
|
147
|
-
let claimedReuse = 0
|
|
148
|
-
let behaviorChangedReuse = 0
|
|
149
|
-
let outcomeImprovedReuse = 0
|
|
150
|
-
let verifiedReuse = 0
|
|
151
|
-
let autoClaimedReuse = 0
|
|
152
|
-
let confirmedReuse = 0
|
|
153
|
-
let pendingAutoClaims = 0
|
|
154
|
-
|
|
155
|
-
for (const record of reuseRecords) {
|
|
156
|
-
const sourceAgent = normalizeIdentity(record.sourceAgent)
|
|
157
|
-
const targetAgent = normalizeIdentity(record.targetAgent)
|
|
158
|
-
const reuse = readBoolean(record, 'reuse') === true
|
|
159
|
-
const behaviorChanged = readBoolean(record, 'behaviorChanged') === true
|
|
160
|
-
const outcomeImproved = readBoolean(record, 'outcomeImproved') === true
|
|
161
|
-
|
|
162
|
-
if (reuse) claimedReuse += 1
|
|
163
|
-
if (reuse && behaviorChanged) behaviorChangedReuse += 1
|
|
164
|
-
if (reuse && outcomeImproved) outcomeImprovedReuse += 1
|
|
165
|
-
if (reuse && behaviorChanged && outcomeImproved) verifiedReuse += 1
|
|
166
|
-
if (readString(record, 'source') === 'auto_claim') {
|
|
167
|
-
autoClaimedReuse += 1
|
|
168
|
-
if (readBoolean(record, 'confirmed') !== true) pendingAutoClaims += 1
|
|
169
|
-
}
|
|
170
|
-
if (readBoolean(record, 'confirmed') === true) confirmedReuse += 1
|
|
171
|
-
|
|
172
|
-
if (sourceAgent && targetAgent && sourceAgent !== targetAgent) {
|
|
173
|
-
crossAgentPairs.add(`${sourceAgent}->${targetAgent}`)
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const agentCoverage = hostList.map((agent) => {
|
|
178
|
-
const hostMemories = memories.filter((record) => normalizeIdentity(record.source) === agent)
|
|
179
|
-
const hostTraces = traces.filter((record) => normalizeIdentity(record.agent) === agent)
|
|
180
|
-
const hostReuse = reuseRecords.filter(
|
|
181
|
-
(record) =>
|
|
182
|
-
normalizeIdentity(record.sourceAgent) === agent ||
|
|
183
|
-
normalizeIdentity(record.targetAgent) === agent,
|
|
184
|
-
)
|
|
185
|
-
const lastActiveTs = [...hostMemories, ...hostTraces, ...hostReuse]
|
|
186
|
-
.map(readTimestamp)
|
|
187
|
-
.filter(Boolean)
|
|
188
|
-
.reduce((max, value) => Math.max(max, value), null)
|
|
189
|
-
const dataSources = []
|
|
190
|
-
if (hostMemories.length > 0) dataSources.push('memory')
|
|
191
|
-
if (hostTraces.length > 0) dataSources.push('trace')
|
|
192
|
-
if (hostReuse.length > 0) dataSources.push('reuse')
|
|
193
|
-
return {
|
|
194
|
-
agent,
|
|
195
|
-
memoryRecords: hostMemories.length,
|
|
196
|
-
traceEvents: hostTraces.length,
|
|
197
|
-
taskOutcomes: hostTraces.filter(
|
|
198
|
-
(record) => normalizeEventType(record.event_type) === TASK_OUTCOME_EVENT,
|
|
199
|
-
).length,
|
|
200
|
-
verifiedReuseAsTarget: hostReuse.filter(
|
|
201
|
-
(record) => normalizeIdentity(record.targetAgent) === agent && isVerifiedReuse(record),
|
|
202
|
-
).length,
|
|
203
|
-
lastActiveAt: lastActiveTs === null ? null : new Date(lastActiveTs).toISOString(),
|
|
204
|
-
lastActiveHoursAgo:
|
|
205
|
-
lastActiveTs === null
|
|
206
|
-
? null
|
|
207
|
-
: Number(((Date.now() - lastActiveTs) / 3600000).toFixed(1)),
|
|
208
|
-
active: lastActiveTs !== null && Date.now() - lastActiveTs <= HOST_ACTIVE_WINDOW_MS,
|
|
209
|
-
dataSources,
|
|
210
|
-
}
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
const metrics = {
|
|
214
|
-
memoryRecords: memories.length,
|
|
215
|
-
traceEvents: traces.length,
|
|
216
|
-
taskOutcomes,
|
|
217
|
-
experienceReuseRecords: reuseRecords.length,
|
|
218
|
-
autoClaimedReuse,
|
|
219
|
-
confirmedReuse,
|
|
220
|
-
pendingAutoClaims,
|
|
221
|
-
claimedReuse,
|
|
222
|
-
behaviorChangedReuse,
|
|
223
|
-
outcomeImprovedReuse,
|
|
224
|
-
verifiedReuse,
|
|
225
|
-
hostCount: hostList.length,
|
|
226
|
-
activeHostCount: agentCoverage.filter((coverage) => coverage.active).length,
|
|
227
|
-
hosts: hostList,
|
|
228
|
-
crossAgentPairs: Array.from(crossAgentPairs).sort(),
|
|
229
|
-
outcomeByStatus,
|
|
230
|
-
agentCoverage,
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
const criteria = [
|
|
234
|
-
{
|
|
235
|
-
key: 'hosts',
|
|
236
|
-
label: 'Distinct agent hosts',
|
|
237
|
-
current: metrics.hostCount,
|
|
238
|
-
required: PHASE0_THRESHOLDS.minHosts,
|
|
239
|
-
passed: metrics.hostCount >= PHASE0_THRESHOLDS.minHosts,
|
|
240
|
-
},
|
|
241
|
-
{
|
|
242
|
-
key: 'tasks',
|
|
243
|
-
label: 'Task outcomes',
|
|
244
|
-
current: metrics.taskOutcomes,
|
|
245
|
-
required: PHASE0_THRESHOLDS.minTaskOutcomes,
|
|
246
|
-
passed: metrics.taskOutcomes >= PHASE0_THRESHOLDS.minTaskOutcomes,
|
|
247
|
-
},
|
|
248
|
-
{
|
|
249
|
-
key: 'verified_reuse',
|
|
250
|
-
label: 'Verified cross-agent reuses',
|
|
251
|
-
current: metrics.verifiedReuse,
|
|
252
|
-
required: PHASE0_THRESHOLDS.minVerifiedReuse,
|
|
253
|
-
passed: metrics.verifiedReuse >= PHASE0_THRESHOLDS.minVerifiedReuse,
|
|
254
|
-
},
|
|
255
|
-
{
|
|
256
|
-
key: 'measurable_improvement',
|
|
257
|
-
label: 'Reuse events with improved outcome',
|
|
258
|
-
current: metrics.outcomeImprovedReuse,
|
|
259
|
-
required: PHASE0_THRESHOLDS.minImprovementEvidence,
|
|
260
|
-
passed: metrics.outcomeImprovedReuse >= PHASE0_THRESHOLDS.minImprovementEvidence,
|
|
261
|
-
},
|
|
262
|
-
]
|
|
263
|
-
|
|
264
|
-
const totalEvidence =
|
|
265
|
-
metrics.memoryRecords + metrics.traceEvents + metrics.experienceReuseRecords
|
|
266
|
-
const status = criteria.every((criterion) => criterion.passed)
|
|
267
|
-
? 'passed'
|
|
268
|
-
: totalEvidence === 0
|
|
269
|
-
? 'not_started'
|
|
270
|
-
: 'in_progress'
|
|
271
|
-
|
|
272
|
-
return {
|
|
273
|
-
generatedAt: new Date().toISOString(),
|
|
274
|
-
status,
|
|
275
|
-
criteria,
|
|
276
|
-
metrics,
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
function loadPhase0(dataDir, project) {
|
|
281
|
-
const normalizedProject = project && project.trim() ? project.trim() : undefined
|
|
282
|
-
const memories = readJsonl(path.join(dataDir, 'memory.jsonl')).map(asRecord).filter(Boolean)
|
|
283
|
-
const traces = readJsonl(path.join(dataDir, 'traces.jsonl')).map(asRecord).filter(Boolean)
|
|
284
|
-
const reuseRecords = readJsonl(path.join(dataDir, 'experience_reuse.jsonl'))
|
|
285
|
-
.map(asRecord)
|
|
286
|
-
.filter(Boolean)
|
|
287
|
-
|
|
288
|
-
return analyzePhase0({
|
|
289
|
-
memories: filterByProject(memories, normalizedProject),
|
|
290
|
-
traces: filterByProject(traces, normalizedProject),
|
|
291
|
-
reuseRecords: filterByProject(reuseRecords, normalizedProject),
|
|
292
|
-
})
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
function renderPhase0Markdown(report) {
|
|
296
|
-
const lines = []
|
|
297
|
-
lines.push('# Mio Phase 0 Validation Report')
|
|
298
|
-
lines.push('')
|
|
299
|
-
lines.push(`- Generated: ${report.generatedAt}`)
|
|
300
|
-
lines.push(`- Status: **${report.status}**`)
|
|
301
|
-
lines.push('')
|
|
302
|
-
lines.push('## Gate Status')
|
|
303
|
-
for (const criterion of report.criteria) {
|
|
304
|
-
const mark = criterion.passed ? '✅' : '❌'
|
|
305
|
-
lines.push(`- ${mark} ${criterion.label}: ${criterion.current}/${criterion.required}`)
|
|
306
|
-
}
|
|
307
|
-
lines.push('')
|
|
308
|
-
lines.push('## Evidence Volume')
|
|
309
|
-
lines.push(`- Memory records: ${report.metrics.memoryRecords}`)
|
|
310
|
-
lines.push(`- Trace events: ${report.metrics.traceEvents}`)
|
|
311
|
-
lines.push(`- Task outcomes: ${report.metrics.taskOutcomes}`)
|
|
312
|
-
lines.push(`- Experience reuse records: ${report.metrics.experienceReuseRecords}`)
|
|
313
|
-
lines.push(`- Active hosts: ${report.metrics.activeHostCount}/${report.metrics.hostCount}`)
|
|
314
|
-
lines.push('')
|
|
315
|
-
lines.push('## Reuse Funnel')
|
|
316
|
-
lines.push(`- Auto-claimed reuse: ${report.metrics.autoClaimedReuse}`)
|
|
317
|
-
lines.push(`- Pending auto-claims (awaiting confirmation): ${report.metrics.pendingAutoClaims}`)
|
|
318
|
-
lines.push(`- Confirmed reuse: ${report.metrics.confirmedReuse}`)
|
|
319
|
-
lines.push(`- Claimed reuse: ${report.metrics.claimedReuse}`)
|
|
320
|
-
lines.push(`- Behavior changed: ${report.metrics.behaviorChangedReuse}`)
|
|
321
|
-
lines.push(`- Outcome improved: ${report.metrics.outcomeImprovedReuse}`)
|
|
322
|
-
lines.push(`- Verified reuse: ${report.metrics.verifiedReuse}`)
|
|
323
|
-
lines.push('')
|
|
324
|
-
lines.push('## Agent Coverage')
|
|
325
|
-
if (report.metrics.agentCoverage.length === 0) {
|
|
326
|
-
lines.push('- No agent identities found.')
|
|
327
|
-
} else {
|
|
328
|
-
for (const coverage of report.metrics.agentCoverage) {
|
|
329
|
-
const activeLabel =
|
|
330
|
-
coverage.active && coverage.lastActiveHoursAgo !== null
|
|
331
|
-
? `yes (${coverage.lastActiveHoursAgo}h ago)`
|
|
332
|
-
: 'no'
|
|
333
|
-
lines.push(
|
|
334
|
-
`- ${coverage.agent}: ${coverage.memoryRecords} memories, ${coverage.traceEvents} traces, ${coverage.taskOutcomes} task outcomes, ${coverage.verifiedReuseAsTarget} verified reuse | active: ${activeLabel} | sources: ${coverage.dataSources.join(', ') || 'none'}`,
|
|
335
|
-
)
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
lines.push('')
|
|
339
|
-
lines.push('## Cross-Agent Pairs')
|
|
340
|
-
if (report.metrics.crossAgentPairs.length === 0) {
|
|
341
|
-
lines.push('- No cross-agent reuse pairs found.')
|
|
342
|
-
} else {
|
|
343
|
-
for (const pair of report.metrics.crossAgentPairs) {
|
|
344
|
-
lines.push(`- ${pair}`)
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
lines.push('')
|
|
348
|
-
lines.push('## Trace Outcomes')
|
|
349
|
-
const outcome = report.metrics.outcomeByStatus
|
|
350
|
-
lines.push(
|
|
351
|
-
`- success: ${outcome.success}, failure: ${outcome.failure}, error: ${outcome.error}, aborted: ${outcome.aborted}, retry: ${outcome.retry}, other: ${outcome.other}`,
|
|
352
|
-
)
|
|
353
|
-
lines.push('')
|
|
354
|
-
return lines.join('\n')
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
function summarizePhase0(report) {
|
|
358
|
-
const remaining = (report.criteria || [])
|
|
359
|
-
.filter((criterion) => !criterion.passed)
|
|
360
|
-
.map((criterion) => ({
|
|
361
|
-
key: criterion.key,
|
|
362
|
-
label: criterion.label,
|
|
363
|
-
current: criterion.current,
|
|
364
|
-
required: criterion.required,
|
|
365
|
-
gap: Math.max(0, criterion.required - criterion.current),
|
|
366
|
-
}))
|
|
367
|
-
|
|
368
|
-
return {
|
|
369
|
-
status: report.status,
|
|
370
|
-
passed: remaining.length === 0,
|
|
371
|
-
remaining,
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
module.exports = {
|
|
376
|
-
PHASE0_THRESHOLDS,
|
|
377
|
-
analyzePhase0,
|
|
378
|
-
loadPhase0,
|
|
379
|
-
renderPhase0Markdown,
|
|
380
|
-
summarizePhase0,
|
|
381
|
-
}
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const path = require('path')
|
|
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 readJsonl(filePath) {
|
|
59
|
+
if (!fs.existsSync(filePath)) return []
|
|
60
|
+
return fs
|
|
61
|
+
.readFileSync(filePath, 'utf8')
|
|
62
|
+
.split(/\r?\n/)
|
|
63
|
+
.filter(Boolean)
|
|
64
|
+
.map((line) => {
|
|
65
|
+
try {
|
|
66
|
+
return JSON.parse(line)
|
|
67
|
+
} catch (_) {
|
|
68
|
+
return null
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
.filter(Boolean)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function filterByProject(records, project) {
|
|
75
|
+
if (!project) return records
|
|
76
|
+
return records.filter((record) => readString(record, 'project') === project)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function createOutcomeSummary() {
|
|
80
|
+
return { success: 0, failure: 0, error: 0, aborted: 0, retry: 0, other: 0 }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function bumpOutcome(summary, outcome) {
|
|
84
|
+
switch (outcome) {
|
|
85
|
+
case 'success':
|
|
86
|
+
summary.success += 1
|
|
87
|
+
break
|
|
88
|
+
case 'failure':
|
|
89
|
+
summary.failure += 1
|
|
90
|
+
break
|
|
91
|
+
case 'error':
|
|
92
|
+
summary.error += 1
|
|
93
|
+
break
|
|
94
|
+
case 'aborted':
|
|
95
|
+
summary.aborted += 1
|
|
96
|
+
break
|
|
97
|
+
case 'retry':
|
|
98
|
+
summary.retry += 1
|
|
99
|
+
break
|
|
100
|
+
default:
|
|
101
|
+
summary.other += 1
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function isVerifiedReuse(record) {
|
|
106
|
+
return (
|
|
107
|
+
readBoolean(record, 'reuse') === true &&
|
|
108
|
+
readBoolean(record, 'behaviorChanged') === true &&
|
|
109
|
+
readBoolean(record, 'outcomeImproved') === true
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function analyzePhase0(dataset) {
|
|
114
|
+
const memories = dataset.memories || []
|
|
115
|
+
const traces = dataset.traces || []
|
|
116
|
+
const reuseRecords = dataset.reuseRecords || []
|
|
117
|
+
|
|
118
|
+
const hosts = new Set()
|
|
119
|
+
const addHost = (value) => {
|
|
120
|
+
const identity = normalizeIdentity(value)
|
|
121
|
+
if (identity) hosts.add(identity)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
for (const record of memories) addHost(record.source)
|
|
125
|
+
for (const record of traces) {
|
|
126
|
+
addHost(record.agent)
|
|
127
|
+
addHost(record.host)
|
|
128
|
+
}
|
|
129
|
+
for (const record of reuseRecords) {
|
|
130
|
+
addHost(record.sourceAgent)
|
|
131
|
+
addHost(record.targetAgent)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const hostList = Array.from(hosts).sort()
|
|
135
|
+
const outcomeByStatus = createOutcomeSummary()
|
|
136
|
+
let taskOutcomes = 0
|
|
137
|
+
|
|
138
|
+
for (const record of traces) {
|
|
139
|
+
const outcome = readString(record, 'outcome')
|
|
140
|
+
bumpOutcome(outcomeByStatus, outcome ? outcome.toLowerCase() : null)
|
|
141
|
+
if (normalizeEventType(record.event_type) === TASK_OUTCOME_EVENT) {
|
|
142
|
+
taskOutcomes += 1
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const crossAgentPairs = new Set()
|
|
147
|
+
let claimedReuse = 0
|
|
148
|
+
let behaviorChangedReuse = 0
|
|
149
|
+
let outcomeImprovedReuse = 0
|
|
150
|
+
let verifiedReuse = 0
|
|
151
|
+
let autoClaimedReuse = 0
|
|
152
|
+
let confirmedReuse = 0
|
|
153
|
+
let pendingAutoClaims = 0
|
|
154
|
+
|
|
155
|
+
for (const record of reuseRecords) {
|
|
156
|
+
const sourceAgent = normalizeIdentity(record.sourceAgent)
|
|
157
|
+
const targetAgent = normalizeIdentity(record.targetAgent)
|
|
158
|
+
const reuse = readBoolean(record, 'reuse') === true
|
|
159
|
+
const behaviorChanged = readBoolean(record, 'behaviorChanged') === true
|
|
160
|
+
const outcomeImproved = readBoolean(record, 'outcomeImproved') === true
|
|
161
|
+
|
|
162
|
+
if (reuse) claimedReuse += 1
|
|
163
|
+
if (reuse && behaviorChanged) behaviorChangedReuse += 1
|
|
164
|
+
if (reuse && outcomeImproved) outcomeImprovedReuse += 1
|
|
165
|
+
if (reuse && behaviorChanged && outcomeImproved) verifiedReuse += 1
|
|
166
|
+
if (readString(record, 'source') === 'auto_claim') {
|
|
167
|
+
autoClaimedReuse += 1
|
|
168
|
+
if (readBoolean(record, 'confirmed') !== true) pendingAutoClaims += 1
|
|
169
|
+
}
|
|
170
|
+
if (readBoolean(record, 'confirmed') === true) confirmedReuse += 1
|
|
171
|
+
|
|
172
|
+
if (sourceAgent && targetAgent && sourceAgent !== targetAgent) {
|
|
173
|
+
crossAgentPairs.add(`${sourceAgent}->${targetAgent}`)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const agentCoverage = hostList.map((agent) => {
|
|
178
|
+
const hostMemories = memories.filter((record) => normalizeIdentity(record.source) === agent)
|
|
179
|
+
const hostTraces = traces.filter((record) => normalizeIdentity(record.agent) === agent)
|
|
180
|
+
const hostReuse = reuseRecords.filter(
|
|
181
|
+
(record) =>
|
|
182
|
+
normalizeIdentity(record.sourceAgent) === agent ||
|
|
183
|
+
normalizeIdentity(record.targetAgent) === agent,
|
|
184
|
+
)
|
|
185
|
+
const lastActiveTs = [...hostMemories, ...hostTraces, ...hostReuse]
|
|
186
|
+
.map(readTimestamp)
|
|
187
|
+
.filter(Boolean)
|
|
188
|
+
.reduce((max, value) => Math.max(max, value), null)
|
|
189
|
+
const dataSources = []
|
|
190
|
+
if (hostMemories.length > 0) dataSources.push('memory')
|
|
191
|
+
if (hostTraces.length > 0) dataSources.push('trace')
|
|
192
|
+
if (hostReuse.length > 0) dataSources.push('reuse')
|
|
193
|
+
return {
|
|
194
|
+
agent,
|
|
195
|
+
memoryRecords: hostMemories.length,
|
|
196
|
+
traceEvents: hostTraces.length,
|
|
197
|
+
taskOutcomes: hostTraces.filter(
|
|
198
|
+
(record) => normalizeEventType(record.event_type) === TASK_OUTCOME_EVENT,
|
|
199
|
+
).length,
|
|
200
|
+
verifiedReuseAsTarget: hostReuse.filter(
|
|
201
|
+
(record) => normalizeIdentity(record.targetAgent) === agent && isVerifiedReuse(record),
|
|
202
|
+
).length,
|
|
203
|
+
lastActiveAt: lastActiveTs === null ? null : new Date(lastActiveTs).toISOString(),
|
|
204
|
+
lastActiveHoursAgo:
|
|
205
|
+
lastActiveTs === null
|
|
206
|
+
? null
|
|
207
|
+
: Number(((Date.now() - lastActiveTs) / 3600000).toFixed(1)),
|
|
208
|
+
active: lastActiveTs !== null && Date.now() - lastActiveTs <= HOST_ACTIVE_WINDOW_MS,
|
|
209
|
+
dataSources,
|
|
210
|
+
}
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
const metrics = {
|
|
214
|
+
memoryRecords: memories.length,
|
|
215
|
+
traceEvents: traces.length,
|
|
216
|
+
taskOutcomes,
|
|
217
|
+
experienceReuseRecords: reuseRecords.length,
|
|
218
|
+
autoClaimedReuse,
|
|
219
|
+
confirmedReuse,
|
|
220
|
+
pendingAutoClaims,
|
|
221
|
+
claimedReuse,
|
|
222
|
+
behaviorChangedReuse,
|
|
223
|
+
outcomeImprovedReuse,
|
|
224
|
+
verifiedReuse,
|
|
225
|
+
hostCount: hostList.length,
|
|
226
|
+
activeHostCount: agentCoverage.filter((coverage) => coverage.active).length,
|
|
227
|
+
hosts: hostList,
|
|
228
|
+
crossAgentPairs: Array.from(crossAgentPairs).sort(),
|
|
229
|
+
outcomeByStatus,
|
|
230
|
+
agentCoverage,
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const criteria = [
|
|
234
|
+
{
|
|
235
|
+
key: 'hosts',
|
|
236
|
+
label: 'Distinct agent hosts',
|
|
237
|
+
current: metrics.hostCount,
|
|
238
|
+
required: PHASE0_THRESHOLDS.minHosts,
|
|
239
|
+
passed: metrics.hostCount >= PHASE0_THRESHOLDS.minHosts,
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
key: 'tasks',
|
|
243
|
+
label: 'Task outcomes',
|
|
244
|
+
current: metrics.taskOutcomes,
|
|
245
|
+
required: PHASE0_THRESHOLDS.minTaskOutcomes,
|
|
246
|
+
passed: metrics.taskOutcomes >= PHASE0_THRESHOLDS.minTaskOutcomes,
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
key: 'verified_reuse',
|
|
250
|
+
label: 'Verified cross-agent reuses',
|
|
251
|
+
current: metrics.verifiedReuse,
|
|
252
|
+
required: PHASE0_THRESHOLDS.minVerifiedReuse,
|
|
253
|
+
passed: metrics.verifiedReuse >= PHASE0_THRESHOLDS.minVerifiedReuse,
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
key: 'measurable_improvement',
|
|
257
|
+
label: 'Reuse events with improved outcome',
|
|
258
|
+
current: metrics.outcomeImprovedReuse,
|
|
259
|
+
required: PHASE0_THRESHOLDS.minImprovementEvidence,
|
|
260
|
+
passed: metrics.outcomeImprovedReuse >= PHASE0_THRESHOLDS.minImprovementEvidence,
|
|
261
|
+
},
|
|
262
|
+
]
|
|
263
|
+
|
|
264
|
+
const totalEvidence =
|
|
265
|
+
metrics.memoryRecords + metrics.traceEvents + metrics.experienceReuseRecords
|
|
266
|
+
const status = criteria.every((criterion) => criterion.passed)
|
|
267
|
+
? 'passed'
|
|
268
|
+
: totalEvidence === 0
|
|
269
|
+
? 'not_started'
|
|
270
|
+
: 'in_progress'
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
generatedAt: new Date().toISOString(),
|
|
274
|
+
status,
|
|
275
|
+
criteria,
|
|
276
|
+
metrics,
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function loadPhase0(dataDir, project) {
|
|
281
|
+
const normalizedProject = project && project.trim() ? project.trim() : undefined
|
|
282
|
+
const memories = readJsonl(path.join(dataDir, 'memory.jsonl')).map(asRecord).filter(Boolean)
|
|
283
|
+
const traces = readJsonl(path.join(dataDir, 'traces.jsonl')).map(asRecord).filter(Boolean)
|
|
284
|
+
const reuseRecords = readJsonl(path.join(dataDir, 'experience_reuse.jsonl'))
|
|
285
|
+
.map(asRecord)
|
|
286
|
+
.filter(Boolean)
|
|
287
|
+
|
|
288
|
+
return analyzePhase0({
|
|
289
|
+
memories: filterByProject(memories, normalizedProject),
|
|
290
|
+
traces: filterByProject(traces, normalizedProject),
|
|
291
|
+
reuseRecords: filterByProject(reuseRecords, normalizedProject),
|
|
292
|
+
})
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function renderPhase0Markdown(report) {
|
|
296
|
+
const lines = []
|
|
297
|
+
lines.push('# Mio Phase 0 Validation Report')
|
|
298
|
+
lines.push('')
|
|
299
|
+
lines.push(`- Generated: ${report.generatedAt}`)
|
|
300
|
+
lines.push(`- Status: **${report.status}**`)
|
|
301
|
+
lines.push('')
|
|
302
|
+
lines.push('## Gate Status')
|
|
303
|
+
for (const criterion of report.criteria) {
|
|
304
|
+
const mark = criterion.passed ? '✅' : '❌'
|
|
305
|
+
lines.push(`- ${mark} ${criterion.label}: ${criterion.current}/${criterion.required}`)
|
|
306
|
+
}
|
|
307
|
+
lines.push('')
|
|
308
|
+
lines.push('## Evidence Volume')
|
|
309
|
+
lines.push(`- Memory records: ${report.metrics.memoryRecords}`)
|
|
310
|
+
lines.push(`- Trace events: ${report.metrics.traceEvents}`)
|
|
311
|
+
lines.push(`- Task outcomes: ${report.metrics.taskOutcomes}`)
|
|
312
|
+
lines.push(`- Experience reuse records: ${report.metrics.experienceReuseRecords}`)
|
|
313
|
+
lines.push(`- Active hosts: ${report.metrics.activeHostCount}/${report.metrics.hostCount}`)
|
|
314
|
+
lines.push('')
|
|
315
|
+
lines.push('## Reuse Funnel')
|
|
316
|
+
lines.push(`- Auto-claimed reuse: ${report.metrics.autoClaimedReuse}`)
|
|
317
|
+
lines.push(`- Pending auto-claims (awaiting confirmation): ${report.metrics.pendingAutoClaims}`)
|
|
318
|
+
lines.push(`- Confirmed reuse: ${report.metrics.confirmedReuse}`)
|
|
319
|
+
lines.push(`- Claimed reuse: ${report.metrics.claimedReuse}`)
|
|
320
|
+
lines.push(`- Behavior changed: ${report.metrics.behaviorChangedReuse}`)
|
|
321
|
+
lines.push(`- Outcome improved: ${report.metrics.outcomeImprovedReuse}`)
|
|
322
|
+
lines.push(`- Verified reuse: ${report.metrics.verifiedReuse}`)
|
|
323
|
+
lines.push('')
|
|
324
|
+
lines.push('## Agent Coverage')
|
|
325
|
+
if (report.metrics.agentCoverage.length === 0) {
|
|
326
|
+
lines.push('- No agent identities found.')
|
|
327
|
+
} else {
|
|
328
|
+
for (const coverage of report.metrics.agentCoverage) {
|
|
329
|
+
const activeLabel =
|
|
330
|
+
coverage.active && coverage.lastActiveHoursAgo !== null
|
|
331
|
+
? `yes (${coverage.lastActiveHoursAgo}h ago)`
|
|
332
|
+
: 'no'
|
|
333
|
+
lines.push(
|
|
334
|
+
`- ${coverage.agent}: ${coverage.memoryRecords} memories, ${coverage.traceEvents} traces, ${coverage.taskOutcomes} task outcomes, ${coverage.verifiedReuseAsTarget} verified reuse | active: ${activeLabel} | sources: ${coverage.dataSources.join(', ') || 'none'}`,
|
|
335
|
+
)
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
lines.push('')
|
|
339
|
+
lines.push('## Cross-Agent Pairs')
|
|
340
|
+
if (report.metrics.crossAgentPairs.length === 0) {
|
|
341
|
+
lines.push('- No cross-agent reuse pairs found.')
|
|
342
|
+
} else {
|
|
343
|
+
for (const pair of report.metrics.crossAgentPairs) {
|
|
344
|
+
lines.push(`- ${pair}`)
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
lines.push('')
|
|
348
|
+
lines.push('## Trace Outcomes')
|
|
349
|
+
const outcome = report.metrics.outcomeByStatus
|
|
350
|
+
lines.push(
|
|
351
|
+
`- success: ${outcome.success}, failure: ${outcome.failure}, error: ${outcome.error}, aborted: ${outcome.aborted}, retry: ${outcome.retry}, other: ${outcome.other}`,
|
|
352
|
+
)
|
|
353
|
+
lines.push('')
|
|
354
|
+
return lines.join('\n')
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function summarizePhase0(report) {
|
|
358
|
+
const remaining = (report.criteria || [])
|
|
359
|
+
.filter((criterion) => !criterion.passed)
|
|
360
|
+
.map((criterion) => ({
|
|
361
|
+
key: criterion.key,
|
|
362
|
+
label: criterion.label,
|
|
363
|
+
current: criterion.current,
|
|
364
|
+
required: criterion.required,
|
|
365
|
+
gap: Math.max(0, criterion.required - criterion.current),
|
|
366
|
+
}))
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
status: report.status,
|
|
370
|
+
passed: remaining.length === 0,
|
|
371
|
+
remaining,
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
module.exports = {
|
|
376
|
+
PHASE0_THRESHOLDS,
|
|
377
|
+
analyzePhase0,
|
|
378
|
+
loadPhase0,
|
|
379
|
+
renderPhase0Markdown,
|
|
380
|
+
summarizePhase0,
|
|
381
|
+
}
|