@monotykamary/pi-ledger 0.2.0 → 0.4.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.
- package/README.md +6 -2
- package/extensions/pi-ledger/__tests__/helpers.ts +8 -1
- package/extensions/pi-ledger/__tests__/in-process-session-observer.test.ts +555 -0
- package/extensions/pi-ledger/__tests__/ledger.test.ts +125 -0
- package/extensions/pi-ledger/__tests__/nested-agent-telemetry.test.ts +867 -0
- package/extensions/pi-ledger/__tests__/subprocess-observer.test.ts +364 -0
- package/extensions/pi-ledger/in-process-session-observer.ts +890 -0
- package/extensions/pi-ledger/index.ts +121 -1
- package/extensions/pi-ledger/nested-agent-telemetry.ts +1271 -0
- package/extensions/pi-ledger/subprocess-observer.ts +694 -0
- package/package.json +1 -1
|
@@ -0,0 +1,867 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
NESTED_AGENT_TELEMETRY_EVENT,
|
|
4
|
+
NESTED_AGENT_TELEMETRY_LIMITS,
|
|
5
|
+
NestedAgentTelemetryHarvester,
|
|
6
|
+
harvestNestedAgentTelemetry,
|
|
7
|
+
installNestedAgentTelemetryHarvester,
|
|
8
|
+
type NestedAgentTelemetryObservation,
|
|
9
|
+
} from '../nested-agent-telemetry';
|
|
10
|
+
|
|
11
|
+
function byPath(
|
|
12
|
+
observations: NestedAgentTelemetryObservation[],
|
|
13
|
+
suffix: string
|
|
14
|
+
): NestedAgentTelemetryObservation {
|
|
15
|
+
const observation = observations.find((candidate) => candidate.sourcePath.endsWith(suffix));
|
|
16
|
+
expect(observation, `missing observation ending in ${suffix}`).toBeDefined();
|
|
17
|
+
return observation!;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function usage(output: number, input = output * 2) {
|
|
21
|
+
return {
|
|
22
|
+
input,
|
|
23
|
+
output,
|
|
24
|
+
cacheRead: 3,
|
|
25
|
+
cacheWrite: 2,
|
|
26
|
+
totalTokens: input + output + 5,
|
|
27
|
+
cost: { input: 0.001, output: 0.002, cacheRead: 0, cacheWrite: 0, total: 0.003 },
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe('NestedAgentTelemetryHarvester structural formats', () => {
|
|
32
|
+
it('harvests the canonical Pi subagent aggregate and assistant usage', () => {
|
|
33
|
+
const observations = harvestNestedAgentTelemetry({
|
|
34
|
+
source: 'tool_execution_end.result',
|
|
35
|
+
toolName: 'subagent',
|
|
36
|
+
toolCallId: 'call-parent-1',
|
|
37
|
+
value: {
|
|
38
|
+
content: [{ type: 'text', text: 'raw child answer must not be retained' }],
|
|
39
|
+
details: {
|
|
40
|
+
mode: 'single',
|
|
41
|
+
results: [
|
|
42
|
+
{
|
|
43
|
+
agent: 'scout',
|
|
44
|
+
model: 'claude-sonnet-4-5',
|
|
45
|
+
sessionFile: '/tmp/pi-child/session.jsonl',
|
|
46
|
+
usage: {
|
|
47
|
+
input: 120,
|
|
48
|
+
output: 30,
|
|
49
|
+
cacheRead: 4,
|
|
50
|
+
cacheWrite: 1,
|
|
51
|
+
cost: 0.012,
|
|
52
|
+
turns: 1,
|
|
53
|
+
},
|
|
54
|
+
messages: [
|
|
55
|
+
{
|
|
56
|
+
role: 'assistant',
|
|
57
|
+
provider: 'anthropic',
|
|
58
|
+
model: 'claude-sonnet-4-5',
|
|
59
|
+
responseId: 'resp-canonical-1',
|
|
60
|
+
timestamp: 1_700_000_000_000,
|
|
61
|
+
content: [{ type: 'text', text: 'private response prose' }],
|
|
62
|
+
usage: usage(28, 110),
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
expect(observations).toHaveLength(2);
|
|
72
|
+
const aggregate = byPath(observations, 'details.results[0].usage');
|
|
73
|
+
expect(aggregate).toMatchObject({
|
|
74
|
+
outputTokens: 30,
|
|
75
|
+
inputTokens: 120,
|
|
76
|
+
cacheReadTokens: 4,
|
|
77
|
+
cacheWriteTokens: 1,
|
|
78
|
+
costUsd: 0.012,
|
|
79
|
+
model: 'claude-sonnet-4-5',
|
|
80
|
+
toolCallId: 'call-parent-1',
|
|
81
|
+
sessionFile: '/tmp/pi-child/session.jsonl',
|
|
82
|
+
measurement: 'cumulative',
|
|
83
|
+
cumulative: true,
|
|
84
|
+
confidence: 'high',
|
|
85
|
+
childAgentCorroborated: true,
|
|
86
|
+
billingEligible: false,
|
|
87
|
+
});
|
|
88
|
+
expect(aggregate.identityCandidates).toContainEqual({ kind: 'agent', value: 'scout' });
|
|
89
|
+
|
|
90
|
+
const assistant = byPath(observations, 'details.results[0].messages[0].usage');
|
|
91
|
+
expect(assistant).toMatchObject({
|
|
92
|
+
outputTokens: 28,
|
|
93
|
+
inputTokens: 110,
|
|
94
|
+
totalTokens: 143,
|
|
95
|
+
costUsd: 0.003,
|
|
96
|
+
provider: 'anthropic',
|
|
97
|
+
model: 'claude-sonnet-4-5',
|
|
98
|
+
responseId: 'resp-canonical-1',
|
|
99
|
+
timestamp: 1_700_000_000_000,
|
|
100
|
+
measurement: 'delta',
|
|
101
|
+
cumulative: false,
|
|
102
|
+
confidence: 'high',
|
|
103
|
+
});
|
|
104
|
+
expect(JSON.stringify(observations)).not.toContain('private response prose');
|
|
105
|
+
expect(JSON.stringify(observations)).not.toContain('raw child answer');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('recognizes pi-subagents totalChildUsage, results usage, and safe artifact metadata', () => {
|
|
109
|
+
const harvester = new NestedAgentTelemetryHarvester();
|
|
110
|
+
const partial = {
|
|
111
|
+
content: [{ type: 'text', text: 'Subagents running' }],
|
|
112
|
+
details: {
|
|
113
|
+
mode: 'parallel',
|
|
114
|
+
runId: 'run-subagents-42',
|
|
115
|
+
totalChildUsage: {
|
|
116
|
+
input: 300,
|
|
117
|
+
output: 80,
|
|
118
|
+
cacheRead: 20,
|
|
119
|
+
cacheWrite: 10,
|
|
120
|
+
cost: 0.08,
|
|
121
|
+
turns: 3,
|
|
122
|
+
},
|
|
123
|
+
results: [
|
|
124
|
+
{
|
|
125
|
+
agent: 'reviewer',
|
|
126
|
+
model: 'openai/gpt-5.4',
|
|
127
|
+
sessionFile: '/tmp/subagents/reviewer.jsonl',
|
|
128
|
+
artifactPaths: {
|
|
129
|
+
inputPath: '/tmp/subagents/input.md',
|
|
130
|
+
outputPath: '/tmp/subagents/output.md',
|
|
131
|
+
jsonlPath: '/tmp/subagents/events.jsonl',
|
|
132
|
+
transcriptPath: '/tmp/subagents/transcript.md',
|
|
133
|
+
metadataPath: '/tmp/subagents/metadata.json',
|
|
134
|
+
},
|
|
135
|
+
usage: {
|
|
136
|
+
input: 180,
|
|
137
|
+
output: 45,
|
|
138
|
+
cacheRead: 8,
|
|
139
|
+
cacheWrite: 4,
|
|
140
|
+
cost: 0.045,
|
|
141
|
+
turns: 2,
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const first = harvester.harvest({
|
|
149
|
+
source: 'tool_execution_update.partialResult',
|
|
150
|
+
toolName: 'subagent',
|
|
151
|
+
toolCallId: 'call-subagents',
|
|
152
|
+
value: partial,
|
|
153
|
+
});
|
|
154
|
+
expect(first).toHaveLength(2);
|
|
155
|
+
expect(byPath(first, 'details.totalChildUsage')).toMatchObject({
|
|
156
|
+
runId: 'run-subagents-42',
|
|
157
|
+
outputTokens: 80,
|
|
158
|
+
costUsd: 0.08,
|
|
159
|
+
confidence: 'high',
|
|
160
|
+
measurement: 'cumulative',
|
|
161
|
+
});
|
|
162
|
+
expect(byPath(first, 'details.results[0].usage')).toMatchObject({
|
|
163
|
+
runId: 'run-subagents-42',
|
|
164
|
+
outputTokens: 45,
|
|
165
|
+
artifactPath: '/tmp/subagents/output.md',
|
|
166
|
+
sessionFile: '/tmp/subagents/reviewer.jsonl',
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
expect(
|
|
170
|
+
harvester.harvest({
|
|
171
|
+
source: 'tool_execution_end.result',
|
|
172
|
+
toolName: 'subagent',
|
|
173
|
+
toolCallId: 'call-subagents',
|
|
174
|
+
value: partial,
|
|
175
|
+
})
|
|
176
|
+
).toEqual([]);
|
|
177
|
+
|
|
178
|
+
const advanced = structuredClone(partial);
|
|
179
|
+
advanced.details.totalChildUsage.output = 90;
|
|
180
|
+
expect(
|
|
181
|
+
harvester.harvest({
|
|
182
|
+
source: 'tool_execution_update.partialResult',
|
|
183
|
+
toolName: 'subagent',
|
|
184
|
+
toolCallId: 'call-subagents',
|
|
185
|
+
value: advanced,
|
|
186
|
+
})
|
|
187
|
+
).toEqual([
|
|
188
|
+
expect.objectContaining({
|
|
189
|
+
sourcePath: expect.stringContaining('totalChildUsage'),
|
|
190
|
+
outputTokens: 90,
|
|
191
|
+
}),
|
|
192
|
+
]);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('accepts pi-agents run-event metadata with inputTokens/outputTokens/cost', () => {
|
|
196
|
+
const observations = harvestNestedAgentTelemetry({
|
|
197
|
+
source: 'artifact.metadata',
|
|
198
|
+
customType: 'pi-agents:run-event:v3',
|
|
199
|
+
artifactPath: '/tmp/pi-agents/parent.pi-agents.jsonl',
|
|
200
|
+
value: {
|
|
201
|
+
type: 'node_completed',
|
|
202
|
+
at: 1_700_000_010_000,
|
|
203
|
+
runId: 'workflow-run-7',
|
|
204
|
+
path: '$.branches.review',
|
|
205
|
+
instance: '$.branches.review@0',
|
|
206
|
+
agent: 'reviewer',
|
|
207
|
+
sessionFile: '/tmp/pi-agents/reviewer.jsonl',
|
|
208
|
+
usage: {
|
|
209
|
+
inputTokens: 900,
|
|
210
|
+
outputTokens: 111,
|
|
211
|
+
cost: 0.07,
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
expect(observations).toEqual([
|
|
217
|
+
expect.objectContaining({
|
|
218
|
+
source: 'artifact.metadata',
|
|
219
|
+
sourcePath: 'artifact.metadata.usage',
|
|
220
|
+
runId: 'workflow-run-7',
|
|
221
|
+
outputTokens: 111,
|
|
222
|
+
inputTokens: 900,
|
|
223
|
+
costUsd: 0.07,
|
|
224
|
+
artifactPath: '/tmp/pi-agents/parent.pi-agents.jsonl',
|
|
225
|
+
sessionFile: '/tmp/pi-agents/reviewer.jsonl',
|
|
226
|
+
confidence: 'high',
|
|
227
|
+
childAgentCorroborated: true,
|
|
228
|
+
}),
|
|
229
|
+
]);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('normalizes pi-tidy flattened child counters and observed runtime identity', () => {
|
|
233
|
+
const observations = harvestNestedAgentTelemetry({
|
|
234
|
+
source: 'tool_execution_update.partialResult',
|
|
235
|
+
toolName: 'subagent',
|
|
236
|
+
toolCallId: 'tidy-call-1',
|
|
237
|
+
value: {
|
|
238
|
+
content: [{ type: 'text', text: 'Subagents running' }],
|
|
239
|
+
details: {
|
|
240
|
+
schemaVersion: 3,
|
|
241
|
+
runId: 'tidy-run-1',
|
|
242
|
+
runDir: '/tmp/tidy/run-1',
|
|
243
|
+
children: [
|
|
244
|
+
{
|
|
245
|
+
id: 'child-001',
|
|
246
|
+
target: 'tidy-run-1:child-001',
|
|
247
|
+
agent: 'researcher',
|
|
248
|
+
status: 'running',
|
|
249
|
+
input: 75,
|
|
250
|
+
output: 25,
|
|
251
|
+
cacheRead: 6,
|
|
252
|
+
cacheWrite: 4,
|
|
253
|
+
providerTraffic: 110,
|
|
254
|
+
tokens: 110,
|
|
255
|
+
model: 'claude-sonnet-4-5',
|
|
256
|
+
runtimePlan: {
|
|
257
|
+
observed: {
|
|
258
|
+
provider: 'anthropic',
|
|
259
|
+
modelId: 'claude-sonnet-4-5',
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
artifactPath: '/tmp/tidy/run-1/child-001.md',
|
|
263
|
+
response: 'raw answer',
|
|
264
|
+
},
|
|
265
|
+
],
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
expect(observations).toEqual([
|
|
271
|
+
expect.objectContaining({
|
|
272
|
+
sourcePath: 'tool_execution_update.partialResult.details.children[0]',
|
|
273
|
+
runId: 'tidy-run-1',
|
|
274
|
+
outputTokens: 25,
|
|
275
|
+
inputTokens: 75,
|
|
276
|
+
cacheReadTokens: 6,
|
|
277
|
+
cacheWriteTokens: 4,
|
|
278
|
+
provider: 'anthropic',
|
|
279
|
+
model: 'claude-sonnet-4-5',
|
|
280
|
+
artifactPath: '/tmp/tidy/run-1/child-001.md',
|
|
281
|
+
measurement: 'cumulative',
|
|
282
|
+
confidence: 'high',
|
|
283
|
+
}),
|
|
284
|
+
]);
|
|
285
|
+
expect(JSON.stringify(observations)).not.toContain('raw answer');
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('finds Fabric live audit agent results without trusting unrelated Fabric calls', () => {
|
|
289
|
+
const observations = harvestNestedAgentTelemetry({
|
|
290
|
+
source: 'tool_execution_update.partialResult',
|
|
291
|
+
toolName: 'fabric_exec',
|
|
292
|
+
toolCallId: 'fabric-parent-call',
|
|
293
|
+
value: {
|
|
294
|
+
content: [{ type: 'text', text: 'Calling agents.run' }],
|
|
295
|
+
details: {
|
|
296
|
+
progress: 'Calling agents.run',
|
|
297
|
+
audits: [
|
|
298
|
+
{
|
|
299
|
+
ref: 'pi.read',
|
|
300
|
+
provider: 'pi',
|
|
301
|
+
tool: 'read',
|
|
302
|
+
result: { usage: { input: 1, output: 999, cost: 100 } },
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
ref: 'agents.run',
|
|
306
|
+
provider: 'agents',
|
|
307
|
+
tool: 'run',
|
|
308
|
+
result: {
|
|
309
|
+
id: 'fabric-child-1',
|
|
310
|
+
name: 'security-review',
|
|
311
|
+
status: 'completed',
|
|
312
|
+
runner: 'pi',
|
|
313
|
+
model: 'openai/gpt-5.4',
|
|
314
|
+
sessionId: 'fabric-session-1',
|
|
315
|
+
logFile: '/tmp/fabric/fabric-child-1/events.jsonl',
|
|
316
|
+
usage: {
|
|
317
|
+
input: 400,
|
|
318
|
+
output: 123,
|
|
319
|
+
cacheRead: 20,
|
|
320
|
+
cacheWrite: 5,
|
|
321
|
+
cost: 0.12,
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
],
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
expect(observations).toHaveLength(1);
|
|
331
|
+
expect(observations[0]).toMatchObject({
|
|
332
|
+
sourcePath: 'tool_execution_update.partialResult.details.audits[1].result.usage',
|
|
333
|
+
runId: 'fabric-child-1',
|
|
334
|
+
sessionId: 'fabric-session-1',
|
|
335
|
+
toolCallId: 'fabric-parent-call',
|
|
336
|
+
logFile: '/tmp/fabric/fabric-child-1/events.jsonl',
|
|
337
|
+
outputTokens: 123,
|
|
338
|
+
costUsd: 0.12,
|
|
339
|
+
confidence: 'high',
|
|
340
|
+
childAgentCorroborated: true,
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it('handles async completion custom-message details and suppresses their replay', () => {
|
|
345
|
+
const harvester = new NestedAgentTelemetryHarvester();
|
|
346
|
+
const completion = {
|
|
347
|
+
runId: 'async-run-9',
|
|
348
|
+
state: 'complete',
|
|
349
|
+
results: [
|
|
350
|
+
{
|
|
351
|
+
agent: 'planner',
|
|
352
|
+
sessionId: 'async-child-session',
|
|
353
|
+
sessionFile: '/tmp/async/planner.jsonl',
|
|
354
|
+
usage: {
|
|
355
|
+
inputTokens: 250,
|
|
356
|
+
outputTokens: 64,
|
|
357
|
+
costUsd: 0.032,
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
],
|
|
361
|
+
};
|
|
362
|
+
const first = harvester.harvest({
|
|
363
|
+
source: 'custom_message.details',
|
|
364
|
+
customType: 'pi-subagents:async-complete',
|
|
365
|
+
value: completion,
|
|
366
|
+
});
|
|
367
|
+
expect(first).toEqual([
|
|
368
|
+
expect.objectContaining({
|
|
369
|
+
runId: 'async-run-9',
|
|
370
|
+
sessionId: 'async-child-session',
|
|
371
|
+
outputTokens: 64,
|
|
372
|
+
measurement: 'cumulative',
|
|
373
|
+
confidence: 'high',
|
|
374
|
+
}),
|
|
375
|
+
]);
|
|
376
|
+
expect(
|
|
377
|
+
harvester.harvest({
|
|
378
|
+
source: 'custom_message.details',
|
|
379
|
+
customType: 'pi-subagents:async-complete',
|
|
380
|
+
value: structuredClone(completion),
|
|
381
|
+
})
|
|
382
|
+
).toEqual([]);
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
describe('NestedAgentTelemetryHarvester safety and deduplication', () => {
|
|
387
|
+
it('does not invoke getters and survives cycles and hostile proxies', () => {
|
|
388
|
+
let getterCalls = 0;
|
|
389
|
+
const cyclic: Record<string, unknown> = {
|
|
390
|
+
children: [
|
|
391
|
+
{
|
|
392
|
+
agent: 'safe-child',
|
|
393
|
+
usage: { input: 20, output: 10, cost: 0.01 },
|
|
394
|
+
},
|
|
395
|
+
],
|
|
396
|
+
};
|
|
397
|
+
Object.defineProperty(cyclic, 'trap', {
|
|
398
|
+
enumerable: true,
|
|
399
|
+
get() {
|
|
400
|
+
getterCalls++;
|
|
401
|
+
throw new Error('getter should not run');
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
cyclic.self = cyclic;
|
|
405
|
+
cyclic.hostile = new Proxy(
|
|
406
|
+
{},
|
|
407
|
+
{
|
|
408
|
+
ownKeys() {
|
|
409
|
+
throw new Error('proxy ownKeys failed');
|
|
410
|
+
},
|
|
411
|
+
}
|
|
412
|
+
);
|
|
413
|
+
const revoked = Proxy.revocable({}, {});
|
|
414
|
+
revoked.revoke();
|
|
415
|
+
(cyclic.children as unknown[]).push(revoked.proxy);
|
|
416
|
+
|
|
417
|
+
const observations = harvestNestedAgentTelemetry({
|
|
418
|
+
source: 'artifact.metadata',
|
|
419
|
+
value: cyclic,
|
|
420
|
+
});
|
|
421
|
+
expect(getterCalls).toBe(0);
|
|
422
|
+
expect(observations).toEqual([
|
|
423
|
+
expect.objectContaining({ outputTokens: 10, confidence: 'medium' }),
|
|
424
|
+
]);
|
|
425
|
+
expect(() =>
|
|
426
|
+
harvestNestedAgentTelemetry({ source: 'artifact.metadata', value: revoked.proxy })
|
|
427
|
+
).not.toThrow();
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
it('does not invoke accessors on the harvest envelope', () => {
|
|
431
|
+
let getterCalls = 0;
|
|
432
|
+
const input = { source: 'artifact.metadata' } as Record<string, unknown>;
|
|
433
|
+
Object.defineProperty(input, 'value', {
|
|
434
|
+
enumerable: true,
|
|
435
|
+
get() {
|
|
436
|
+
getterCalls++;
|
|
437
|
+
return { children: [{ agent: 'spoof', usage: { input: 2, output: 1 } }] };
|
|
438
|
+
},
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
expect(new NestedAgentTelemetryHarvester().harvest(input as any)).toEqual([]);
|
|
442
|
+
expect(getterCalls).toBe(0);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it('requires recognized provenance for plausible nested usage', () => {
|
|
446
|
+
const validSpoof = {
|
|
447
|
+
results: [{ agent: 'spoof', usage: { input: 10, output: 5, cost: 0.5 } }],
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
expect(
|
|
451
|
+
harvestNestedAgentTelemetry({
|
|
452
|
+
source: 'tool_execution_end.result',
|
|
453
|
+
toolName: 'user-agent-reporter',
|
|
454
|
+
value: validSpoof,
|
|
455
|
+
})
|
|
456
|
+
).toEqual([]);
|
|
457
|
+
expect(
|
|
458
|
+
harvestNestedAgentTelemetry({
|
|
459
|
+
source: 'custom_message.details',
|
|
460
|
+
customType: 'user-agent-report',
|
|
461
|
+
value: validSpoof,
|
|
462
|
+
})
|
|
463
|
+
).toEqual([]);
|
|
464
|
+
expect(
|
|
465
|
+
harvestNestedAgentTelemetry({
|
|
466
|
+
source: 'tool_execution_end.result',
|
|
467
|
+
toolName: 'fabric_exec',
|
|
468
|
+
value: {
|
|
469
|
+
audits: [
|
|
470
|
+
{
|
|
471
|
+
ref: 'agents.run',
|
|
472
|
+
provider: 'pi',
|
|
473
|
+
tool: 'run',
|
|
474
|
+
result: validSpoof.results[0],
|
|
475
|
+
},
|
|
476
|
+
],
|
|
477
|
+
},
|
|
478
|
+
})
|
|
479
|
+
).toEqual([]);
|
|
480
|
+
expect(
|
|
481
|
+
harvestNestedAgentTelemetry({
|
|
482
|
+
source: 'tool_execution_end.result',
|
|
483
|
+
toolName: 'subagent',
|
|
484
|
+
value: {
|
|
485
|
+
results: [
|
|
486
|
+
{
|
|
487
|
+
agent: 'worker',
|
|
488
|
+
messages: [
|
|
489
|
+
{
|
|
490
|
+
role: 'user',
|
|
491
|
+
provider: 'openai',
|
|
492
|
+
model: 'gpt-5.4',
|
|
493
|
+
timestamp: 1_700_000_000_000,
|
|
494
|
+
usage: { input: 10, output: 5, cost: 0.5 },
|
|
495
|
+
},
|
|
496
|
+
],
|
|
497
|
+
},
|
|
498
|
+
],
|
|
499
|
+
},
|
|
500
|
+
})
|
|
501
|
+
).toEqual([]);
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
it('ignores raw text, sensitive branches, malformed numbers, and arbitrary root usage', () => {
|
|
505
|
+
const harvester = new NestedAgentTelemetryHarvester();
|
|
506
|
+
const observations = harvester.harvest({
|
|
507
|
+
source: 'tool_execution_end.result',
|
|
508
|
+
toolName: 'bash',
|
|
509
|
+
toolCallId: 'bash-call',
|
|
510
|
+
value: {
|
|
511
|
+
usage: { input: 1, output: 500, cost: 10 },
|
|
512
|
+
content: {
|
|
513
|
+
children: [{ agent: 'spoof', usage: { input: 1, output: 900, cost: 90 } }],
|
|
514
|
+
},
|
|
515
|
+
apiKey: {
|
|
516
|
+
children: [{ agent: 'secret', usage: { input: 1, output: 800, cost: 80 } }],
|
|
517
|
+
},
|
|
518
|
+
children: [
|
|
519
|
+
{ agent: 'negative', usage: { input: 1, output: -1, cost: 1 } },
|
|
520
|
+
{ agent: 'fractional', usage: { input: 1, output: 1.5, cost: 1 } },
|
|
521
|
+
{ agent: 'infinite', usage: { input: 1, output: Number.POSITIVE_INFINITY, cost: 1 } },
|
|
522
|
+
{ agent: 'string', usage: { input: 1, output: '1000', cost: 1 } },
|
|
523
|
+
],
|
|
524
|
+
},
|
|
525
|
+
});
|
|
526
|
+
expect(observations).toEqual([]);
|
|
527
|
+
|
|
528
|
+
const noCorroboration = harvester.harvest({
|
|
529
|
+
source: 'tool_execution_end.result',
|
|
530
|
+
toolName: 'bash',
|
|
531
|
+
value: { usage: { input: 10, output: 5, cost: 0.5 } },
|
|
532
|
+
});
|
|
533
|
+
expect(noCorroboration).toEqual([]);
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
it('does not retain prose disguised as metadata or property names', () => {
|
|
537
|
+
const privateModelText = 'private model prose must not survive';
|
|
538
|
+
const privatePathKey = 'private response prose must not survive';
|
|
539
|
+
const observations = harvestNestedAgentTelemetry({
|
|
540
|
+
source: 'tool_execution_end.result',
|
|
541
|
+
toolName: 'subagent',
|
|
542
|
+
value: {
|
|
543
|
+
results: [
|
|
544
|
+
{
|
|
545
|
+
agent: 'worker',
|
|
546
|
+
model: privateModelText,
|
|
547
|
+
[privatePathKey]: {
|
|
548
|
+
input: 10,
|
|
549
|
+
output: 5,
|
|
550
|
+
cost: 0.01,
|
|
551
|
+
provider: privateModelText,
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
],
|
|
555
|
+
},
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
expect(observations).toHaveLength(1);
|
|
559
|
+
expect(observations[0]).not.toHaveProperty('provider');
|
|
560
|
+
expect(observations[0]).not.toHaveProperty('model');
|
|
561
|
+
expect(JSON.stringify(observations)).not.toContain(privateModelText);
|
|
562
|
+
expect(JSON.stringify(observations)).not.toContain(privatePathKey);
|
|
563
|
+
expect(observations[0]!.sourcePath).toMatch(/\['#[0-9a-f]{8}'\]$/);
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
it('validates metadata paths lexically without reading them', () => {
|
|
567
|
+
const observations = harvestNestedAgentTelemetry({
|
|
568
|
+
source: 'artifact.metadata',
|
|
569
|
+
customType: 'child-agent-completion',
|
|
570
|
+
value: {
|
|
571
|
+
runId: 'path-run',
|
|
572
|
+
children: [
|
|
573
|
+
{
|
|
574
|
+
agent: 'worker',
|
|
575
|
+
logFile: 'https://user:secret@example.test/events.jsonl',
|
|
576
|
+
sessionFile: 'file:///tmp/session.jsonl',
|
|
577
|
+
artifactPath: '/definitely/not/present/output.md',
|
|
578
|
+
usage: { input: 10, output: 5, cost: 0.01 },
|
|
579
|
+
},
|
|
580
|
+
],
|
|
581
|
+
},
|
|
582
|
+
});
|
|
583
|
+
expect(observations[0]).toMatchObject({
|
|
584
|
+
artifactPath: '/definitely/not/present/output.md',
|
|
585
|
+
});
|
|
586
|
+
expect(observations[0]).not.toHaveProperty('logFile');
|
|
587
|
+
expect(observations[0]).not.toHaveProperty('sessionFile');
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
it('dedupes mirrored assistant responses by responseId before source path', () => {
|
|
591
|
+
const harvester = new NestedAgentTelemetryHarvester();
|
|
592
|
+
const assistant = (output: number) => ({
|
|
593
|
+
role: 'assistant',
|
|
594
|
+
provider: 'openai',
|
|
595
|
+
model: 'gpt-5.4',
|
|
596
|
+
responseId: 'resp-shared',
|
|
597
|
+
timestamp: 1_700_000_020_000,
|
|
598
|
+
usage: usage(output, 40),
|
|
599
|
+
content: [{ type: 'text', text: 'ignored' }],
|
|
600
|
+
});
|
|
601
|
+
const first = harvester.harvest({
|
|
602
|
+
source: 'tool_execution_update.partialResult',
|
|
603
|
+
toolName: 'subagent',
|
|
604
|
+
value: {
|
|
605
|
+
results: [
|
|
606
|
+
{ agent: 'a', messages: [assistant(10)] },
|
|
607
|
+
{ agent: 'b', messages: [assistant(10)] },
|
|
608
|
+
],
|
|
609
|
+
},
|
|
610
|
+
});
|
|
611
|
+
expect(first).toHaveLength(1);
|
|
612
|
+
expect(first[0]!.responseId).toBe('resp-shared');
|
|
613
|
+
|
|
614
|
+
const advanced = harvester.harvest({
|
|
615
|
+
source: 'tool_execution_update.partialResult',
|
|
616
|
+
toolName: 'subagent',
|
|
617
|
+
value: { results: [{ agent: 'a', messages: [assistant(12)] }] },
|
|
618
|
+
});
|
|
619
|
+
expect(advanced).toEqual([expect.objectContaining({ outputTokens: 12 })]);
|
|
620
|
+
|
|
621
|
+
const stale = harvester.harvest({
|
|
622
|
+
source: 'tool_execution_end.result',
|
|
623
|
+
toolName: 'subagent',
|
|
624
|
+
value: { results: [{ agent: 'a', messages: [assistant(11)] }] },
|
|
625
|
+
});
|
|
626
|
+
expect(stale).toEqual([]);
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
it('accepts monotonic cumulative progress and rejects mixed regressions', () => {
|
|
630
|
+
const harvester = new NestedAgentTelemetryHarvester();
|
|
631
|
+
const result = (input: number, output: number) => ({
|
|
632
|
+
runId: 'vector-run',
|
|
633
|
+
results: [{ agent: 'worker', usage: { input, output, cost: 0.02 } }],
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
expect(
|
|
637
|
+
harvester.harvest({
|
|
638
|
+
source: 'tool_execution_update.partialResult',
|
|
639
|
+
toolName: 'subagent',
|
|
640
|
+
value: result(30, 15),
|
|
641
|
+
})
|
|
642
|
+
).toHaveLength(1);
|
|
643
|
+
expect(
|
|
644
|
+
harvester.harvest({
|
|
645
|
+
source: 'tool_execution_update.partialResult',
|
|
646
|
+
toolName: 'subagent',
|
|
647
|
+
value: result(35, 15),
|
|
648
|
+
})
|
|
649
|
+
).toEqual([expect.objectContaining({ inputTokens: 35, outputTokens: 15 })]);
|
|
650
|
+
expect(
|
|
651
|
+
harvester.harvest({
|
|
652
|
+
source: 'tool_execution_update.partialResult',
|
|
653
|
+
toolName: 'subagent',
|
|
654
|
+
value: result(34, 16),
|
|
655
|
+
})
|
|
656
|
+
).toEqual([]);
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
it('does not dedupe unrelated observations that lack a stable identity', () => {
|
|
660
|
+
const harvester = new NestedAgentTelemetryHarvester();
|
|
661
|
+
const value = {
|
|
662
|
+
children: [{ agent: 'worker', usage: { input: 10, output: 5, cost: 0.01 } }],
|
|
663
|
+
};
|
|
664
|
+
const harvest = () =>
|
|
665
|
+
harvester.harvest({
|
|
666
|
+
source: 'artifact.metadata',
|
|
667
|
+
value: structuredClone(value),
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
expect(harvest()).toHaveLength(1);
|
|
671
|
+
expect(harvest()).toHaveLength(1);
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
it('dedupes cumulative update/end/tool-result mirrors by run, session, and semantic path', () => {
|
|
675
|
+
const harvester = new NestedAgentTelemetryHarvester();
|
|
676
|
+
const details = {
|
|
677
|
+
runId: 'stable-run',
|
|
678
|
+
sessionId: 'stable-session',
|
|
679
|
+
results: [{ agent: 'worker', usage: { input: 30, output: 15, cost: 0.02 } }],
|
|
680
|
+
};
|
|
681
|
+
expect(
|
|
682
|
+
harvester.harvest({
|
|
683
|
+
source: 'tool_execution_update.partialResult',
|
|
684
|
+
toolName: 'subagent',
|
|
685
|
+
toolCallId: 'stable-call',
|
|
686
|
+
value: { details },
|
|
687
|
+
})
|
|
688
|
+
).toHaveLength(1);
|
|
689
|
+
expect(
|
|
690
|
+
harvester.harvest({
|
|
691
|
+
source: 'tool_execution_end.result',
|
|
692
|
+
toolName: 'subagent',
|
|
693
|
+
toolCallId: 'stable-call',
|
|
694
|
+
value: { details: structuredClone(details) },
|
|
695
|
+
})
|
|
696
|
+
).toEqual([]);
|
|
697
|
+
expect(
|
|
698
|
+
harvester.harvest({
|
|
699
|
+
source: 'tool_result.details',
|
|
700
|
+
toolName: 'subagent',
|
|
701
|
+
toolCallId: 'stable-call',
|
|
702
|
+
value: structuredClone(details),
|
|
703
|
+
})
|
|
704
|
+
).toEqual([]);
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
it('bounds descriptor work, freezes limits, and byte-bounds short paths', () => {
|
|
708
|
+
let descriptorReads = 0;
|
|
709
|
+
const values: Record<string, number> = {
|
|
710
|
+
outputTokens: 5,
|
|
711
|
+
inputTokens: 10,
|
|
712
|
+
ignoredA: 1,
|
|
713
|
+
ignoredB: 2,
|
|
714
|
+
};
|
|
715
|
+
const proxy = new Proxy(
|
|
716
|
+
{},
|
|
717
|
+
{
|
|
718
|
+
ownKeys: () => Object.keys(values),
|
|
719
|
+
getOwnPropertyDescriptor: (_target, key) => {
|
|
720
|
+
descriptorReads++;
|
|
721
|
+
return {
|
|
722
|
+
configurable: true,
|
|
723
|
+
enumerable: true,
|
|
724
|
+
writable: true,
|
|
725
|
+
value: values[String(key)],
|
|
726
|
+
};
|
|
727
|
+
},
|
|
728
|
+
}
|
|
729
|
+
);
|
|
730
|
+
const bounded = new NestedAgentTelemetryHarvester({ limits: { maxObjectKeys: 2 } });
|
|
731
|
+
|
|
732
|
+
expect(
|
|
733
|
+
bounded.harvest({
|
|
734
|
+
source: 'tool_execution_end.result',
|
|
735
|
+
toolName: 'subagent',
|
|
736
|
+
value: proxy,
|
|
737
|
+
})
|
|
738
|
+
).toEqual([expect.objectContaining({ inputTokens: 10, outputTokens: 5 })]);
|
|
739
|
+
expect(descriptorReads).toBe(2);
|
|
740
|
+
expect(Object.isFrozen(bounded.limits)).toBe(true);
|
|
741
|
+
expect(Reflect.set(bounded.limits, 'maxNodes', Number.MAX_SAFE_INTEGER)).toBe(false);
|
|
742
|
+
|
|
743
|
+
const shortPath = new NestedAgentTelemetryHarvester({ limits: { maxPathBytes: 1 } });
|
|
744
|
+
const observations = shortPath.harvest({
|
|
745
|
+
source: 'tool_execution_end.result',
|
|
746
|
+
toolName: 'subagent',
|
|
747
|
+
value: { outputTokens: 1 },
|
|
748
|
+
});
|
|
749
|
+
expect(observations).toHaveLength(1);
|
|
750
|
+
expect(Buffer.byteLength(observations[0]!.sourcePath, 'utf8')).toBeLessThanOrEqual(1);
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
it('enforces depth, node, array, observation, string, and path limits', () => {
|
|
754
|
+
const harvester = new NestedAgentTelemetryHarvester({
|
|
755
|
+
limits: {
|
|
756
|
+
maxDepth: 3,
|
|
757
|
+
maxNodes: 12,
|
|
758
|
+
maxArrayItems: 1,
|
|
759
|
+
maxObjectKeys: 8,
|
|
760
|
+
maxObservations: 1,
|
|
761
|
+
maxStringBytes: 32,
|
|
762
|
+
maxPathBytes: 80,
|
|
763
|
+
maxIdentityCandidates: 2,
|
|
764
|
+
maxDedupeEntries: 2,
|
|
765
|
+
},
|
|
766
|
+
});
|
|
767
|
+
const tooDeep = { child: { child: { child: { usage: { input: 2, output: 1 } } } } };
|
|
768
|
+
expect(
|
|
769
|
+
harvester.harvest({
|
|
770
|
+
source: 'artifact.metadata',
|
|
771
|
+
customType: 'child-agent',
|
|
772
|
+
value: tooDeep,
|
|
773
|
+
})
|
|
774
|
+
).toEqual([]);
|
|
775
|
+
|
|
776
|
+
const bounded = harvester.harvest({
|
|
777
|
+
source: 'artifact.metadata',
|
|
778
|
+
customType: 'child-agent',
|
|
779
|
+
value: {
|
|
780
|
+
children: [
|
|
781
|
+
{
|
|
782
|
+
agent: 'first',
|
|
783
|
+
runId: 'run-first',
|
|
784
|
+
sessionId: 'session-first',
|
|
785
|
+
toolCallId: 'tool-first',
|
|
786
|
+
usage: { input: 2, output: 1 },
|
|
787
|
+
},
|
|
788
|
+
{ agent: 'second', usage: { input: 200, output: 100 } },
|
|
789
|
+
],
|
|
790
|
+
},
|
|
791
|
+
});
|
|
792
|
+
expect(bounded).toHaveLength(1);
|
|
793
|
+
expect(bounded[0]!.outputTokens).toBe(1);
|
|
794
|
+
expect(bounded[0]!.identityCandidates.length).toBeLessThanOrEqual(2);
|
|
795
|
+
expect(Buffer.byteLength(bounded[0]!.sourcePath, 'utf8')).toBeLessThanOrEqual(80);
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
it('publishes observations from Pi lifecycle surfaces without changing billing state', () => {
|
|
799
|
+
const handlers = new Map<string, Array<(event: any) => void>>();
|
|
800
|
+
const emit = vi.fn();
|
|
801
|
+
const pi = {
|
|
802
|
+
on: vi.fn((name: string, handler: (event: any) => void) => {
|
|
803
|
+
const list = handlers.get(name) ?? [];
|
|
804
|
+
list.push(handler);
|
|
805
|
+
handlers.set(name, list);
|
|
806
|
+
}),
|
|
807
|
+
events: { emit },
|
|
808
|
+
} as any;
|
|
809
|
+
installNestedAgentTelemetryHarvester(pi);
|
|
810
|
+
|
|
811
|
+
for (const handler of handlers.get('message_end') ?? []) {
|
|
812
|
+
handler({
|
|
813
|
+
type: 'message_end',
|
|
814
|
+
message: {
|
|
815
|
+
role: 'custom',
|
|
816
|
+
customType: 'pi-subagents:async-complete',
|
|
817
|
+
content: 'raw completion text',
|
|
818
|
+
details: {
|
|
819
|
+
runId: 'event-run',
|
|
820
|
+
results: [{ agent: 'worker', usage: { input: 8, output: 4, cost: 0.01 } }],
|
|
821
|
+
},
|
|
822
|
+
},
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
let messageGetterCalls = 0;
|
|
827
|
+
for (const handler of handlers.get('message_end') ?? []) {
|
|
828
|
+
const event = {};
|
|
829
|
+
Object.defineProperty(event, 'message', {
|
|
830
|
+
enumerable: true,
|
|
831
|
+
get() {
|
|
832
|
+
messageGetterCalls++;
|
|
833
|
+
throw new Error('message getter should not run');
|
|
834
|
+
},
|
|
835
|
+
});
|
|
836
|
+
expect(() => handler(event)).not.toThrow();
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
expect(messageGetterCalls).toBe(0);
|
|
840
|
+
expect(emit).toHaveBeenCalledTimes(1);
|
|
841
|
+
expect(emit).toHaveBeenCalledWith(
|
|
842
|
+
NESTED_AGENT_TELEMETRY_EVENT,
|
|
843
|
+
expect.objectContaining({
|
|
844
|
+
source: 'custom_message.details',
|
|
845
|
+
runId: 'event-run',
|
|
846
|
+
outputTokens: 4,
|
|
847
|
+
billingEligible: false,
|
|
848
|
+
})
|
|
849
|
+
);
|
|
850
|
+
});
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
describe('nested telemetry public limits', () => {
|
|
854
|
+
it('exposes finite conservative defaults', () => {
|
|
855
|
+
expect(NESTED_AGENT_TELEMETRY_LIMITS).toEqual({
|
|
856
|
+
maxDepth: 12,
|
|
857
|
+
maxNodes: 2_048,
|
|
858
|
+
maxArrayItems: 64,
|
|
859
|
+
maxObjectKeys: 96,
|
|
860
|
+
maxObservations: 128,
|
|
861
|
+
maxStringBytes: 512,
|
|
862
|
+
maxPathBytes: 4_096,
|
|
863
|
+
maxIdentityCandidates: 12,
|
|
864
|
+
maxDedupeEntries: 2_048,
|
|
865
|
+
});
|
|
866
|
+
});
|
|
867
|
+
});
|