@agentguard-run/burn 0.2.5 → 0.2.6
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/CHANGELOG.md +10 -0
- package/README.md +48 -17
- package/dist/src/calibrate.js +2 -3
- package/dist/src/cli.js +14 -3
- package/dist/src/conformance.d.ts +5 -2
- package/dist/src/conformance.js +30 -17
- package/dist/src/defaults.d.ts +5 -4
- package/dist/src/defaults.js +7 -6
- package/dist/src/detectors/evaluate.d.ts +4 -5
- package/dist/src/detectors/evaluate.js +13 -11
- package/dist/src/gateway.js +2 -8
- package/dist/src/history/claude-transcript.d.ts +20 -2
- package/dist/src/history/claude-transcript.js +56 -15
- package/dist/src/hook/pre-tool-use.d.ts +13 -9
- package/dist/src/hook/pre-tool-use.js +55 -31
- package/dist/src/insights/blocks.d.ts +61 -0
- package/dist/src/insights/blocks.js +243 -0
- package/dist/src/insights/transcript.d.ts +3 -1
- package/dist/src/insights/transcript.js +14 -1
- package/dist/src/insights/types.d.ts +7 -0
- package/dist/src/policy.d.ts +4 -0
- package/dist/src/policy.js +57 -0
- package/dist/src/replay/render.js +4 -2
- package/dist/src/replay/simulate.d.ts +5 -0
- package/dist/src/replay/simulate.js +23 -8
- package/dist/src/state/reservations.d.ts +2 -0
- package/dist/src/state/reservations.js +5 -1
- package/dist/src/state/spawn-window.d.ts +10 -0
- package/dist/src/state/spawn-window.js +25 -0
- package/dist/src/types.d.ts +2 -1
- package/fixtures/usage-dedup-session/subagents/agent-synthetic-first.jsonl +5 -0
- package/fixtures/usage-dedup-session/subagents/agent-synthetic-second.jsonl +4 -0
- package/fixtures/usage-dedup-session.jsonl +4 -0
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.providerResponseIdentity = void 0;
|
|
3
4
|
exports.parseInsightTranscript = parseInsightTranscript;
|
|
4
5
|
exports.readInsightTranscript = readInsightTranscript;
|
|
5
6
|
exports.readInsightSession = readInsightSession;
|
|
@@ -11,6 +12,9 @@ const object = (value) => value !== null && typeof value === 'object' && !Array.
|
|
|
11
12
|
const count = (value) => typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 ? value : 0;
|
|
12
13
|
const hasCount = (value) => typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
|
|
13
14
|
const digest = (value) => (0, node_crypto_1.createHash)('sha256').update(JSON.stringify(value)).digest('hex');
|
|
15
|
+
/** Provider response identity is shared by reporting and enforcement readers. */
|
|
16
|
+
const providerResponseIdentity = (host, id) => digest(['provider-response', host, id]);
|
|
17
|
+
exports.providerResponseIdentity = providerResponseIdentity;
|
|
14
18
|
const identifier = (value) => typeof value === 'string' && /^[A-Za-z0-9_.:/-]{1,128}$/.test(value) ? value : undefined;
|
|
15
19
|
const modelName = identifier;
|
|
16
20
|
function millis(value) {
|
|
@@ -260,9 +264,18 @@ function parseInsightTranscript(text, options = {}) {
|
|
|
260
264
|
const message = object(raw.message), payload = object(raw.payload), response = object(raw.response);
|
|
261
265
|
const extracted = usageOf(raw), usage = extracted.usage;
|
|
262
266
|
const at = millis(raw.timestamp ?? raw.created_at ?? payload.timestamp);
|
|
267
|
+
if (raw.type === 'fork-context-ref') {
|
|
268
|
+
state.lineage = { ...state.lineage, forkContextRef: true, forked: true,
|
|
269
|
+
...(identifier(raw.parentSessionId) ? { parentSessionId: identifier(raw.parentSessionId) } : {}) };
|
|
270
|
+
}
|
|
263
271
|
if (raw.type === 'session_meta' && !state.sessionMetadataSeen) {
|
|
264
272
|
state.sessionMetadataSeen = true;
|
|
265
273
|
state.host = 'codex';
|
|
274
|
+
const spawned = object(object(object(payload.source).subagent).thread_spawn);
|
|
275
|
+
state.lineage = { ...state.lineage,
|
|
276
|
+
...(identifier(payload.id ?? payload.session_id) ? { sessionId: identifier(payload.id ?? payload.session_id) } : {}),
|
|
277
|
+
...(identifier(payload.parent_thread_id ?? spawned.parent_thread_id) ? { parentSessionId: identifier(payload.parent_thread_id ?? spawned.parent_thread_id) } : {}),
|
|
278
|
+
...(identifier(payload.forked_from_id) ? { forked: true } : {}) };
|
|
266
279
|
if ('subagent' in object(payload.source) || identifier(payload.parent_thread_id) || identifier(payload.agent_path))
|
|
267
280
|
state.spawnedSession = true;
|
|
268
281
|
if (hasCount(payload.subagent_history_start_ordinal))
|
|
@@ -371,7 +384,7 @@ function parseInsightTranscript(text, options = {}) {
|
|
|
371
384
|
// Provider response ids remain the same when a host copies history into a
|
|
372
385
|
// forked session. Preserve global identity instead of charging it twice.
|
|
373
386
|
const providerId = message.id !== undefined || response.id !== undefined;
|
|
374
|
-
const id =
|
|
387
|
+
const id = providerId ? (0, exports.providerResponseIdentity)(host, nativeId) : digest([extracted.codex && state.codexTurnIdDigest ? 'codex-turn' : state.sessionId, host, nativeId]);
|
|
375
388
|
const existing = state.seen[id];
|
|
376
389
|
const creation = object(usage.cache_creation);
|
|
377
390
|
const split5m = creation.ephemeral_5m_input_tokens, split1h = creation.ephemeral_1h_input_tokens;
|
|
@@ -77,6 +77,13 @@ export interface InsightParserState {
|
|
|
77
77
|
fileKey?: string;
|
|
78
78
|
}>;
|
|
79
79
|
sessionMetadataSeen?: boolean;
|
|
80
|
+
/** Host-recorded lineage identifiers only, never inherited prompt content. */
|
|
81
|
+
lineage?: {
|
|
82
|
+
sessionId?: string;
|
|
83
|
+
parentSessionId?: string;
|
|
84
|
+
forkContextRef?: boolean;
|
|
85
|
+
forked?: boolean;
|
|
86
|
+
};
|
|
80
87
|
inheritedBeforeOrdinal?: number;
|
|
81
88
|
forkHistoryBoundaryUnknown?: boolean;
|
|
82
89
|
inheritedTotalDigest?: string;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizePolicy = normalizePolicy;
|
|
4
|
+
exports.loadPolicy = loadPolicy;
|
|
5
|
+
/** Load additive policy defaults without rewriting the operator's policy. */
|
|
6
|
+
const node_fs_1 = require("node:fs");
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
const defaults_1 = require("./defaults");
|
|
9
|
+
const noticed = new Set();
|
|
10
|
+
/** Normalize before evaluating or hashing so receipts bind the actual policy. */
|
|
11
|
+
function normalizePolicy(policy) {
|
|
12
|
+
const thresholds = policy.thresholds;
|
|
13
|
+
const normalized = {
|
|
14
|
+
fanout: { ...defaults_1.DEFAULT_THRESHOLDS.fanout, ...thresholds.fanout },
|
|
15
|
+
sustained: { ...defaults_1.DEFAULT_THRESHOLDS.sustained, ...thresholds.sustained },
|
|
16
|
+
burnDebt: { ...defaults_1.DEFAULT_THRESHOLDS.burnDebt, ...thresholds.burnDebt },
|
|
17
|
+
spawnRate: { ...defaults_1.DEFAULT_THRESHOLDS.spawnRate, ...thresholds.spawnRate },
|
|
18
|
+
duplicate: { ...defaults_1.DEFAULT_THRESHOLDS.duplicate, ...thresholds.duplicate },
|
|
19
|
+
account: { ...defaults_1.DEFAULT_THRESHOLDS.account, ...thresholds.account },
|
|
20
|
+
localCompute: { ...defaults_1.DEFAULT_THRESHOLDS.localCompute, ...thresholds.localCompute },
|
|
21
|
+
};
|
|
22
|
+
return { ...policy, thresholds: normalized };
|
|
23
|
+
}
|
|
24
|
+
function noticeOnce(home, fields) {
|
|
25
|
+
if (noticed.has(home))
|
|
26
|
+
return;
|
|
27
|
+
noticed.add(home);
|
|
28
|
+
try {
|
|
29
|
+
(0, node_fs_1.mkdirSync)(home, { recursive: true, mode: 0o700 });
|
|
30
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(home, '.burn-policy-0.2.6-notice'), fields.join(', ') + '\n', { flag: 'wx', mode: 0o600 });
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
if (error.code === 'EEXIST')
|
|
34
|
+
return;
|
|
35
|
+
// A read-only home still gets the notice once in this process.
|
|
36
|
+
}
|
|
37
|
+
process.stderr.write(`AgentGuard loaded missing policy fields from defaults: ${fields.join(', ')}; existing overrides are unchanged.\n`);
|
|
38
|
+
}
|
|
39
|
+
function loadPolicy(home) {
|
|
40
|
+
try {
|
|
41
|
+
const parsed = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(home, 'burn-policy.json'), 'utf8'));
|
|
42
|
+
if (parsed && (parsed.mode === 'shadow' || parsed.mode === 'enforce') && parsed.thresholds) {
|
|
43
|
+
const missing = [];
|
|
44
|
+
if (parsed.thresholds.fanout?.windowActiveMinutes === undefined)
|
|
45
|
+
missing.push('fanout.windowActiveMinutes=120');
|
|
46
|
+
if (parsed.thresholds.spawnRate?.enforce === undefined)
|
|
47
|
+
missing.push('spawnRate.enforce=true');
|
|
48
|
+
if (missing.length)
|
|
49
|
+
noticeOnce(home, missing);
|
|
50
|
+
return normalizePolicy(parsed);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
/* no policy yet: shadow defaults */
|
|
55
|
+
}
|
|
56
|
+
return normalizePolicy(defaults_1.DEFAULT_POLICY);
|
|
57
|
+
}
|
|
@@ -150,13 +150,15 @@ function renderReplay(summary, opts = {}) {
|
|
|
150
150
|
paint(on, C.dim, ` across ${summary.sessions.length} sessions, ${summary.totalSpawns} spawns`)));
|
|
151
151
|
out.push(MID);
|
|
152
152
|
for (const s of summary.sessions.slice(0, top)) {
|
|
153
|
-
const tag = s.fanoutStop ? paint(on, C.red, 'FAN-OUT STOP') : s.sustainedStop ? paint(on, C.red, 'SUSTAINED STOP') : s.firstWarn ? paint(on, C.yellow, 'WARN') : paint(on, C.green, 'clean');
|
|
153
|
+
const tag = s.fanoutStop ? paint(on, C.red, 'FAN-OUT STOP') : s.sustainedStop ? paint(on, C.red, 'SUSTAINED STOP') : s.spawnRateStop ? paint(on, C.red, 'SPAWN-RATE STOP') : s.firstWarn ? paint(on, C.yellow, 'WARN') : paint(on, C.green, 'clean');
|
|
154
154
|
out.push(row(`${sparkline(s.curve, s.stopAtIndex, on)} ${tag}`));
|
|
155
155
|
const detail = s.fanoutStop
|
|
156
156
|
? `before spawn ${s.fanoutStop.atSpawn}, tail ${(0, evaluate_1.fmt)(s.catchableTail)}`
|
|
157
157
|
: s.sustainedStop
|
|
158
158
|
? `near ${(0, evaluate_1.fmt)(s.sustainedStop.tokensAtStop)}, tail ${(0, evaluate_1.fmt)(s.catchableTail)}`
|
|
159
|
-
:
|
|
159
|
+
: s.spawnRateStop
|
|
160
|
+
? `before spawn ${s.spawnRateStop.atSpawn}, tail ${(0, evaluate_1.fmt)(s.catchableTail)}`
|
|
161
|
+
: `${(0, evaluate_1.fmt)(s.totalTokens)} · ${s.spawns} spawns`;
|
|
160
162
|
out.push(row(paint(on, C.dim, `${s.sessionId.slice(0, 8)} ${(0, evaluate_1.fmt)(s.totalTokens).padStart(6)} · ${String(s.spawns).padStart(3)} spawns ${detail}`)));
|
|
161
163
|
}
|
|
162
164
|
if (summary.sessions.length > top) {
|
|
@@ -30,6 +30,11 @@ export interface SessionReplay {
|
|
|
30
30
|
atSpawn: number;
|
|
31
31
|
tokensAtStop: number;
|
|
32
32
|
} | null;
|
|
33
|
+
/** Additive boundary for policies that enforce short-window spawn rate. */
|
|
34
|
+
spawnRateStop?: {
|
|
35
|
+
atSpawn: number;
|
|
36
|
+
tokensAtStop: number;
|
|
37
|
+
} | null;
|
|
33
38
|
/** Tokens observed after the earliest STOP boundary. */
|
|
34
39
|
catchableTail: number;
|
|
35
40
|
firstWarn: {
|
|
@@ -58,6 +58,7 @@ function replayEvents(sessionId, path, events, thresholds) {
|
|
|
58
58
|
const state = (0, session_1.newSessionState)(sessionId, first.at);
|
|
59
59
|
let fanoutStop = null;
|
|
60
60
|
let sustainedStop = null;
|
|
61
|
+
let spawnRateStop = null;
|
|
61
62
|
let firstWarn = null;
|
|
62
63
|
let tokensAtEarliestStop = null;
|
|
63
64
|
// Cumulative tokens after every event, for the sparkline.
|
|
@@ -66,25 +67,38 @@ function replayEvents(sessionId, path, events, thresholds) {
|
|
|
66
67
|
for (const event of events) {
|
|
67
68
|
// A hook boundary exists only where a spawn was attempted. Evaluate the
|
|
68
69
|
// *proposal* before applying the event, then apply it.
|
|
70
|
+
const candidateState = event.spawns.length > 0
|
|
71
|
+
? { ...state, spawnsByActiveMinute: new Map(state.spawnsByActiveMinute) }
|
|
72
|
+
: state;
|
|
73
|
+
if (event.spawns.length > 0) {
|
|
74
|
+
// One assistant row can propose several agents. Advance its active-time
|
|
75
|
+
// clock once, then account for each earlier proposal in this row. Usage
|
|
76
|
+
// and the durable state are still folded only once below.
|
|
77
|
+
(0, session_1.applyEvent)(candidateState, { at: event.at, tokens: 0, cacheRead: 0, spawns: [], surfaces: [], sidechain: event.sidechain });
|
|
78
|
+
}
|
|
69
79
|
for (const spawn of event.spawns) {
|
|
70
|
-
const report = (0, evaluate_1.evaluate)(
|
|
80
|
+
const report = (0, evaluate_1.evaluate)(candidateState, thresholds, spawn.issuerDepth + 1);
|
|
71
81
|
if (!firstWarn && report.verdict !== 'OK') {
|
|
72
|
-
firstWarn = { detector: report.findings[0]?.detector ?? 'unknown', tokensAt: state.totalTokens, spawnsAt:
|
|
82
|
+
firstWarn = { detector: report.findings[0]?.detector ?? 'unknown', tokensAt: state.totalTokens, spawnsAt: candidateState.spawnCount + 1 };
|
|
73
83
|
}
|
|
74
84
|
for (const finding of report.findings) {
|
|
75
85
|
if (finding.verdict !== 'STOP')
|
|
76
86
|
continue;
|
|
77
87
|
if (finding.detector === 'fanout' && !fanoutStop) {
|
|
78
|
-
fanoutStop = { atSpawn:
|
|
88
|
+
fanoutStop = { atSpawn: candidateState.spawnCount + 1, tokensAtStop: state.totalTokens };
|
|
89
|
+
}
|
|
90
|
+
if (finding.detector === 'spawn_rate' && !spawnRateStop) {
|
|
91
|
+
spawnRateStop = { atSpawn: candidateState.spawnCount + 1, tokensAtStop: state.totalTokens };
|
|
79
92
|
}
|
|
80
93
|
if ((finding.detector === 'sustained_burn' || finding.detector === 'burn_debt') && !sustainedStop) {
|
|
81
|
-
sustainedStop = { atSpawn:
|
|
94
|
+
sustainedStop = { atSpawn: candidateState.spawnCount + 1, tokensAtStop: state.totalTokens };
|
|
82
95
|
}
|
|
83
96
|
}
|
|
84
|
-
if ((fanoutStop || sustainedStop) && tokensAtEarliestStop === null) {
|
|
97
|
+
if ((fanoutStop || sustainedStop || spawnRateStop) && tokensAtEarliestStop === null) {
|
|
85
98
|
tokensAtEarliestStop = state.totalTokens;
|
|
86
99
|
stopEventIndex = timeline.length;
|
|
87
100
|
}
|
|
101
|
+
(0, session_1.applyEvent)(candidateState, { at: event.at, tokens: 0, cacheRead: 0, spawns: [spawn], surfaces: [], sidechain: event.sidechain });
|
|
88
102
|
}
|
|
89
103
|
// The sustained plane also has a boundary at every tool call, not just
|
|
90
104
|
// spawns. Approximate: any event with surfaces is a tool boundary.
|
|
@@ -118,6 +132,7 @@ function replayEvents(sessionId, path, events, thresholds) {
|
|
|
118
132
|
finalVerdict: final.verdict,
|
|
119
133
|
fanoutStop,
|
|
120
134
|
sustainedStop,
|
|
135
|
+
spawnRateStop,
|
|
121
136
|
catchableTail: tokensAtEarliestStop === null ? 0 : Math.max(0, state.totalTokens - tokensAtEarliestStop),
|
|
122
137
|
firstWarn,
|
|
123
138
|
curve,
|
|
@@ -160,8 +175,8 @@ function replayAll(paths, thresholds, minTokens = 0) {
|
|
|
160
175
|
totalSpawns: sessions.reduce((s, r) => s + r.spawns, 0),
|
|
161
176
|
catchableTail,
|
|
162
177
|
catchableShare: totalTokens > 0 ? catchableTail / totalTokens : 0,
|
|
163
|
-
stops: sessions.filter((r) => r.fanoutStop || r.sustainedStop).length,
|
|
164
|
-
warns: sessions.filter((r) => !r.fanoutStop && !r.sustainedStop && r.firstWarn).length,
|
|
165
|
-
clean: sessions.filter((r) => !r.fanoutStop && !r.sustainedStop && !r.firstWarn).length,
|
|
178
|
+
stops: sessions.filter((r) => r.fanoutStop || r.sustainedStop || r.spawnRateStop).length,
|
|
179
|
+
warns: sessions.filter((r) => !r.fanoutStop && !r.sustainedStop && !r.spawnRateStop && r.firstWarn).length,
|
|
180
|
+
clean: sessions.filter((r) => !r.fanoutStop && !r.sustainedStop && !r.spawnRateStop && !r.firstWarn).length,
|
|
166
181
|
};
|
|
167
182
|
}
|
|
@@ -152,6 +152,8 @@ export declare class Transaction {
|
|
|
152
152
|
private readonly data;
|
|
153
153
|
dirty: boolean;
|
|
154
154
|
constructor(data: ReservationFile);
|
|
155
|
+
/** Pending proposals participate in every spawn window under the same lock. */
|
|
156
|
+
pendingSpawns(sessionId: string, toolUseId: string, now: number): number;
|
|
155
157
|
reserve(args: ReserveArgs): ReserveResult;
|
|
156
158
|
reconcile(sessionId: string, observedSpawns: number, previouslyObserved: number): void;
|
|
157
159
|
/**
|
|
@@ -310,6 +310,10 @@ class Transaction {
|
|
|
310
310
|
if (!this.data.calls)
|
|
311
311
|
this.data.calls = [];
|
|
312
312
|
}
|
|
313
|
+
/** Pending proposals participate in every spawn window under the same lock. */
|
|
314
|
+
pendingSpawns(sessionId, toolUseId, now) {
|
|
315
|
+
return this.data.reservations.filter(r => r.sessionId === sessionId && r.toolUseId !== toolUseId && r.expiresAt > now).length;
|
|
316
|
+
}
|
|
313
317
|
reserve(args) {
|
|
314
318
|
const now = args.now ?? Date.now();
|
|
315
319
|
const data = this.data;
|
|
@@ -318,7 +322,7 @@ class Transaction {
|
|
|
318
322
|
if (data.reservations.length !== before)
|
|
319
323
|
this.dirty = true;
|
|
320
324
|
// Idempotent: the same tool_use_id evaluated twice must not double-count.
|
|
321
|
-
const existing = data.reservations.find((r) => r.toolUseId === args.toolUseId);
|
|
325
|
+
const existing = data.reservations.find((r) => r.sessionId === args.sessionId && r.toolUseId === args.toolUseId);
|
|
322
326
|
const pendingForSession = data.reservations.filter((r) => r.sessionId === args.sessionId && r.toolUseId !== args.toolUseId).length;
|
|
323
327
|
const effective = args.observedSpawns + pendingForSession + 1;
|
|
324
328
|
if (existing) {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { BurnReport, SessionState, Thresholds } from '../types';
|
|
2
|
+
import type { ReserveResult, Transaction } from './reservations';
|
|
3
|
+
/** Evaluate and reserve together. Pending forks must not bypass either window. */
|
|
4
|
+
export declare function evaluateSpawnReservation(tx: Transaction, state: SessionState, thresholds: Thresholds, proposedDepth: number, toolUseId: string, now: number, account?: {
|
|
5
|
+
sessions: SessionState[];
|
|
6
|
+
now: number;
|
|
7
|
+
}): {
|
|
8
|
+
report: BurnReport;
|
|
9
|
+
reservation: ReserveResult;
|
|
10
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.evaluateSpawnReservation = evaluateSpawnReservation;
|
|
4
|
+
const evaluate_1 = require("../detectors/evaluate");
|
|
5
|
+
const defaults_1 = require("../defaults");
|
|
6
|
+
const session_1 = require("./session");
|
|
7
|
+
/** Evaluate and reserve together. Pending forks must not bypass either window. */
|
|
8
|
+
function evaluateSpawnReservation(tx, state, thresholds, proposedDepth, toolUseId, now, account) {
|
|
9
|
+
const pending = tx.pendingSpawns(state.sessionId, toolUseId, now);
|
|
10
|
+
const minute = Math.floor(state.activeMinutes);
|
|
11
|
+
const spawns = new Map(state.spawnsByActiveMinute);
|
|
12
|
+
if (pending)
|
|
13
|
+
spawns.set(minute, (spawns.get(minute) ?? 0) + pending);
|
|
14
|
+
const candidateState = { ...state, spawnCount: state.spawnCount + pending, spawnsByActiveMinute: spawns };
|
|
15
|
+
const report = (0, evaluate_1.evaluate)(candidateState, thresholds, proposedDepth, account);
|
|
16
|
+
// Receipts retain the lifetime ordinal, while admission uses the active window.
|
|
17
|
+
const effectiveSpawns = state.spawnCount + pending + 1;
|
|
18
|
+
if (report.verdict === 'STOP') {
|
|
19
|
+
// A refused proposal never consumes a reservation. Its detector explains why.
|
|
20
|
+
return { report, reservation: { allowed: true, effectiveSpawns, pending } };
|
|
21
|
+
}
|
|
22
|
+
const recent = (0, session_1.windowSum)(state.spawnsByActiveMinute, state.activeMinutes, thresholds.fanout.windowActiveMinutes ?? defaults_1.DEFAULT_THRESHOLDS.fanout.windowActiveMinutes);
|
|
23
|
+
const reservation = tx.reserve({ sessionId: state.sessionId, toolUseId, observedSpawns: recent, ceiling: thresholds.fanout.stop, now });
|
|
24
|
+
return { report, reservation: { ...reservation, effectiveSpawns } };
|
|
25
|
+
}
|
package/dist/src/types.d.ts
CHANGED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
{"type":"fork-context-ref"}
|
|
2
|
+
{"type":"assistant","timestamp":"2026-09-01T00:00:02Z","uuid":"synthetic-root-block-3","message":{"id":"synthetic-response-a","model":"claude-sonnet-5","usage":{"input_tokens":10,"cache_creation_input_tokens":20,"cache_read_input_tokens":100,"output_tokens":9},"content":[]}}
|
|
3
|
+
{"type":"assistant","timestamp":"2026-09-01T00:00:03Z","uuid":"synthetic-root-spawn","message":{"id":"synthetic-response-b","model":"claude-sonnet-5","usage":{"input_tokens":11,"cache_read_input_tokens":130,"output_tokens":2},"content":[{"type":"tool_use","id":"synthetic-spawn-1","name":"Agent","input":{"description":"synthetic task"}}]}}
|
|
4
|
+
{"type":"assistant","timestamp":"2026-09-01T00:00:04Z","uuid":"synthetic-child-block-1","message":{"id":"synthetic-response-c","model":"claude-sonnet-5","usage":{"input_tokens":5,"cache_creation_input_tokens":30,"cache_read_input_tokens":150,"output_tokens":10},"content":[]}}
|
|
5
|
+
{"type":"assistant","timestamp":"2026-09-01T00:00:05Z","uuid":"synthetic-child-block-2","message":{"id":"synthetic-response-c","model":"claude-sonnet-5","usage":{"input_tokens":5,"cache_creation_input_tokens":30,"cache_read_input_tokens":150,"output_tokens":12},"content":[]}}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
{"type":"fork-context-ref"}
|
|
2
|
+
{"type":"assistant","timestamp":"2026-09-01T00:00:02Z","uuid":"synthetic-root-block-3","message":{"id":"synthetic-response-a","model":"claude-sonnet-5","usage":{"input_tokens":10,"cache_creation_input_tokens":20,"cache_read_input_tokens":100,"output_tokens":9},"content":[]}}
|
|
3
|
+
{"type":"assistant","timestamp":"2026-09-01T00:00:05Z","uuid":"synthetic-copy-c","message":{"id":"synthetic-response-c","model":"claude-sonnet-5","usage":{"input_tokens":5,"cache_creation_input_tokens":30,"cache_read_input_tokens":150,"output_tokens":12},"content":[]}}
|
|
4
|
+
{"type":"assistant","timestamp":"2026-09-01T00:00:06Z","uuid":"synthetic-child-d","message":{"id":"synthetic-response-d","model":"claude-sonnet-5","usage":{"input_tokens":2,"cache_read_input_tokens":200,"output_tokens":3},"content":[]}}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
{"type":"assistant","timestamp":"2026-09-01T00:00:00Z","uuid":"synthetic-root-block-1","message":{"id":"synthetic-response-a","model":"claude-sonnet-5","usage":{"input_tokens":10,"cache_creation_input_tokens":20,"cache_read_input_tokens":100,"output_tokens":5},"content":[]}}
|
|
2
|
+
{"type":"assistant","timestamp":"2026-09-01T00:00:01Z","uuid":"synthetic-root-block-2","message":{"id":"synthetic-response-a","model":"claude-sonnet-5","usage":{"input_tokens":10,"cache_creation_input_tokens":20,"cache_read_input_tokens":100,"output_tokens":5},"content":[]}}
|
|
3
|
+
{"type":"assistant","timestamp":"2026-09-01T00:00:02Z","uuid":"synthetic-root-block-3","message":{"id":"synthetic-response-a","model":"claude-sonnet-5","usage":{"input_tokens":10,"cache_creation_input_tokens":20,"cache_read_input_tokens":100,"output_tokens":9},"content":[]}}
|
|
4
|
+
{"type":"assistant","timestamp":"2026-09-01T00:00:03Z","uuid":"synthetic-root-spawn","message":{"id":"synthetic-response-b","model":"claude-sonnet-5","usage":{"input_tokens":11,"cache_read_input_tokens":130,"output_tokens":2},"content":[{"type":"tool_use","id":"synthetic-spawn-1","name":"Agent","input":{"description":"synthetic task"}}]}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentguard-run/burn",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "Local session usage and runaway-agent circuit breaker. Explain tokens, cache rewrites and API list cost, track pace, warn on heavy turns, and gate agent fan-out with signed receipts. Nothing leaves the machine.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "commonjs",
|