@kuralle-agents/core 0.12.0 → 0.13.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.
Files changed (89) hide show
  1. package/README.md +85 -0
  2. package/dist/capabilities/LivePromptAssembler.js +1 -0
  3. package/dist/flow/collectDigression.d.ts +5 -2
  4. package/dist/flow/collectDigression.js +48 -10
  5. package/dist/flow/collectUntilComplete.js +9 -1
  6. package/dist/flow/extraction.d.ts +1 -0
  7. package/dist/flow/extraction.js +4 -0
  8. package/dist/flow/runFlow.js +63 -5
  9. package/dist/index.d.ts +6 -1
  10. package/dist/index.js +4 -0
  11. package/dist/prompts/PromptBuilder.js +7 -0
  12. package/dist/prompts/types.d.ts +2 -0
  13. package/dist/runtime/InMemoryRetrievalCache.d.ts +25 -0
  14. package/dist/runtime/InMemoryRetrievalCache.js +59 -0
  15. package/dist/runtime/KnowledgeProvider.js +6 -1
  16. package/dist/runtime/Runtime.d.ts +44 -0
  17. package/dist/runtime/Runtime.js +196 -13
  18. package/dist/runtime/TokenAccumulator.d.ts +7 -0
  19. package/dist/runtime/TokenAccumulator.js +7 -0
  20. package/dist/runtime/TraceRecorder.d.ts +33 -0
  21. package/dist/runtime/TraceRecorder.js +290 -0
  22. package/dist/runtime/channels/TextDriver.js +45 -22
  23. package/dist/runtime/channels/executeModelTool.d.ts +8 -1
  24. package/dist/runtime/channels/executeModelTool.js +70 -1
  25. package/dist/runtime/channels/inputBuffer.d.ts +1 -0
  26. package/dist/runtime/channels/inputBuffer.js +9 -0
  27. package/dist/runtime/closeRun.js +11 -8
  28. package/dist/runtime/compaction.d.ts +2 -0
  29. package/dist/runtime/compaction.js +3 -2
  30. package/dist/runtime/ctx.js +110 -57
  31. package/dist/runtime/durable/RunStore.d.ts +16 -0
  32. package/dist/runtime/durable/RunStore.js +6 -0
  33. package/dist/runtime/durable/SessionRunStore.d.ts +7 -2
  34. package/dist/runtime/durable/SessionRunStore.js +136 -34
  35. package/dist/runtime/durable/idempotency.d.ts +1 -0
  36. package/dist/runtime/durable/idempotency.js +3 -0
  37. package/dist/runtime/durable/replay.js +7 -2
  38. package/dist/runtime/durable/types.d.ts +11 -0
  39. package/dist/runtime/goals.d.ts +18 -0
  40. package/dist/runtime/goals.js +158 -0
  41. package/dist/runtime/grounding/knowledge.js +1 -1
  42. package/dist/runtime/handoffContinuation.d.ts +20 -0
  43. package/dist/runtime/handoffContinuation.js +30 -0
  44. package/dist/runtime/handoffOscillation.d.ts +6 -0
  45. package/dist/runtime/handoffOscillation.js +17 -0
  46. package/dist/runtime/hostLoop.js +11 -0
  47. package/dist/runtime/index.d.ts +3 -1
  48. package/dist/runtime/index.js +1 -0
  49. package/dist/runtime/openRun.d.ts +2 -0
  50. package/dist/runtime/openRun.js +46 -17
  51. package/dist/runtime/policies/limits.d.ts +1 -0
  52. package/dist/runtime/policies/limits.js +3 -0
  53. package/dist/runtime/select.d.ts +5 -1
  54. package/dist/runtime/select.js +30 -1
  55. package/dist/runtime/turnTokenUsage.d.ts +28 -0
  56. package/dist/runtime/turnTokenUsage.js +70 -0
  57. package/dist/session/SessionStore.d.ts +6 -0
  58. package/dist/session/SessionStore.js +12 -1
  59. package/dist/session/stores/MemoryStore.d.ts +1 -1
  60. package/dist/session/stores/MemoryStore.js +16 -2
  61. package/dist/session/testing.d.ts +6 -1
  62. package/dist/session/testing.js +34 -0
  63. package/dist/session/utils.d.ts +3 -0
  64. package/dist/session/utils.js +23 -0
  65. package/dist/tools/effect/ToolExecutor.js +4 -1
  66. package/dist/tools/effect/defineTool.d.ts +2 -0
  67. package/dist/tools/effect/defineTool.js +2 -0
  68. package/dist/tracing/MemoryTraceStore.d.ts +16 -0
  69. package/dist/tracing/MemoryTraceStore.js +56 -0
  70. package/dist/tracing/OtelTraceSink.d.ts +127 -0
  71. package/dist/tracing/OtelTraceSink.js +101 -0
  72. package/dist/tracing/TraceStore.d.ts +19 -0
  73. package/dist/tracing/TraceStore.js +32 -0
  74. package/dist/tracing/index.d.ts +3 -0
  75. package/dist/tracing/index.js +3 -0
  76. package/dist/tracing/testing.d.ts +3 -0
  77. package/dist/tracing/testing.js +45 -0
  78. package/dist/types/agentConfig.d.ts +2 -1
  79. package/dist/types/channel.d.ts +14 -1
  80. package/dist/types/effectTool.d.ts +4 -0
  81. package/dist/types/index.d.ts +1 -0
  82. package/dist/types/index.js +1 -0
  83. package/dist/types/run-context.d.ts +14 -1
  84. package/dist/types/session.d.ts +10 -0
  85. package/dist/types/stream.d.ts +8 -0
  86. package/dist/types/trace.d.ts +50 -0
  87. package/dist/types/trace.js +1 -0
  88. package/guides/EXAMPLE_VERIFICATION.md +4 -4
  89. package/package.json +13 -4
@@ -7,10 +7,16 @@ import { createEventBus, createTurnHandle } from '../events/TurnHandle.js';
7
7
  import { CoreToolExecutor } from '../tools/effect/index.js';
8
8
  import { buildAgentToolSurface } from './buildAgentToolSurface.js';
9
9
  import { hostLoop } from './hostLoop.js';
10
+ import { isHandoffOscillating } from './handoffOscillation.js';
11
+ import { applyHandoffContinuation } from './handoffContinuation.js';
10
12
  import { isDegradableRuntimeError } from '../flow/degradableErrors.js';
11
13
  import { SAFE_DEGRADED_MESSAGE } from '../flow/degrade.js';
12
14
  import { adaptHostSelect } from './hostClassifyAdapter.js';
13
15
  import { openRun, sessionDerivedRunId } from './openRun.js';
16
+ function resolveOutOfBandControl(agent) {
17
+ const hasFlows = (agent.flows?.length ?? 0) > 0;
18
+ return agent.experimental?.outOfBandControl ?? hasFlows;
19
+ }
14
20
  import { closeRun } from './closeRun.js';
15
21
  import { SessionRunStore } from './durable/SessionRunStore.js';
16
22
  import { loadRecordedSteps } from './durable/replay.js';
@@ -19,8 +25,15 @@ import { resolveAgentPolicies } from './policies/resolvePolicies.js';
19
25
  import { buildAutoRetrieveProvider, buildKnowledgeProvider, buildMemoryService, runMemoryIngest, } from './grounding/index.js';
20
26
  import { SessionMutex } from './SessionMutex.js';
21
27
  import { compactMessages } from './compaction.js';
28
+ import { readLastPromptTokens, readCumulativeUsage, computeTurnTraceUsage } from './turnTokenUsage.js';
22
29
  import { isContextOverflowError, recoverFromContextOverflow } from './contextOverflow.js';
30
+ import { projectGoalsPromptFromState, updateGoalsFromTurn } from './goals.js';
23
31
  import { buildEscalationRequest, recordEscalationOutcome, ESCALATION_NOTIFIED_KEY, } from '../escalation/escalation.js';
32
+ import { runOnce as recordRunOnce } from './TraceRecorder.js';
33
+ import { TraceRecorder } from './TraceRecorder.js';
34
+ import { MemoryTraceStore } from '../tracing/MemoryTraceStore.js';
35
+ import { mutateSessionWithRetry } from '../session/utils.js';
36
+ import { isTraceStore } from '../tracing/TraceStore.js';
24
37
  export class Runtime {
25
38
  config;
26
39
  agentsById;
@@ -31,6 +44,9 @@ export class Runtime {
31
44
  hooks;
32
45
  activeTurnAborts = new Map();
33
46
  sessionMutex = new SessionMutex();
47
+ traceStore;
48
+ traceSinks;
49
+ pendingTraceWrites = new Set();
34
50
  constructor(config) {
35
51
  this.config = config;
36
52
  this.agentsById = indexAgents(config.agents);
@@ -39,12 +55,27 @@ export class Runtime {
39
55
  this.maxHandoffs = config.maxHandoffs ?? 5;
40
56
  this.terminalHandoffTargets = new Set(config.terminalHandoffTargets ?? ['human']);
41
57
  this.hooks = config.hooks;
58
+ const configuredSinks = config.tracing?.sinks ?? [];
59
+ const configuredStore = config.tracing?.store ?? configuredSinks.find(isTraceStore);
60
+ this.traceStore = config.tracing?.enabled === false
61
+ ? undefined
62
+ : configuredStore ?? new MemoryTraceStore();
63
+ this.traceSinks = this.traceStore
64
+ ? [this.traceStore, ...configuredSinks.filter((sink) => sink !== this.traceStore)]
65
+ : [];
42
66
  }
43
67
  run(opts) {
44
68
  if (opts.wake && opts.input !== undefined) {
45
69
  throw new Error('RunOptions.wake and RunOptions.input are mutually exclusive');
46
70
  }
47
71
  const sessionId = opts.sessionId || randomUUID();
72
+ const recorder = this.shouldTrace(sessionId, opts.input)
73
+ ? new TraceRecorder({
74
+ sessionId,
75
+ input: opts.input,
76
+ onSpan: (span) => this.writeSpan(span),
77
+ })
78
+ : undefined;
48
79
  const bus = createEventBus();
49
80
  const abortController = new AbortController();
50
81
  this.activeTurnAborts.set(sessionId, abortController);
@@ -54,6 +85,9 @@ export class Runtime {
54
85
  const execute = async () => {
55
86
  let runCtx;
56
87
  const emit = (part) => {
88
+ recorder?.record(part);
89
+ if (part.type === 'done')
90
+ this.flushTraceSinks();
57
91
  bus.emit(part);
58
92
  void this.hooks?.onStreamPart?.(runCtx, part);
59
93
  };
@@ -67,6 +101,7 @@ export class Runtime {
67
101
  seedMessages: opts.seedMessages,
68
102
  historyDelta: opts.historyDelta,
69
103
  signalDelivery: opts.signalDelivery,
104
+ idempotencyKey: opts.idempotencyKey,
70
105
  transcriptionModel: this.config.transcriptionModel,
71
106
  defaultAgentId: this.config.defaultAgentId,
72
107
  sessionStore: this.sessionStore,
@@ -93,6 +128,10 @@ export class Runtime {
93
128
  });
94
129
  const steps = await loadRecordedSteps(opened.runStore, opened.runState.runId);
95
130
  const freshRunState = (await opened.runStore.getRunState(opened.runState.runId)) ?? opened.runState;
131
+ // Snapshot cumulative token usage as this turn opens, so the trace can report
132
+ // THIS turn's consumption (delta), not the running session total (see the
133
+ // per-turn scope requirement in the observability guide).
134
+ const usageBaseline = readCumulativeUsage(freshRunState.state);
96
135
  const model = opened.agent.model ?? this.defaultModel;
97
136
  if (!model) {
98
137
  throw new Error('Runtime requires agent.model or config.defaultModel');
@@ -120,12 +159,16 @@ export class Runtime {
120
159
  : undefined,
121
160
  fs: openingSurface.resolvedWorkspace?.fs,
122
161
  });
162
+ // Session retrieval cache (G6): created once per run, persists across
163
+ // in-session handoffs (runCtx survives the handoff branch). RAG-only —
164
+ // a knowledge-less runtime leaves it undefined.
165
+ runCtx.retrievalCache = knowledgeProvider?.createSessionCache();
123
166
  // Agent base layer (ADR 0001): composed into every node turn by the drivers.
124
167
  runCtx.baseInstructions = opened.agent.instructions;
125
168
  runCtx.globalTools = openingSurface.globalTools;
126
- runCtx.outOfBandControl = opened.agent.experimental?.outOfBandControl ?? false;
169
+ runCtx.outOfBandControl = resolveOutOfBandControl(opened.agent);
127
170
  runCtx.skillPrompt = openingSurface.skillPrompt;
128
- runCtx.workingMemoryPrompt = openingSurface.workingMemoryPrompt;
171
+ runCtx.workingMemoryPrompt = appendGoalsPrompt(openingSurface.workingMemoryPrompt, opened.session.workingMemory);
129
172
  runCtx.workingMemoryTools = openingSurface.workingMemoryTools;
130
173
  await this.hooks?.onStart?.(runCtx);
131
174
  if (opts.wake) {
@@ -169,6 +212,14 @@ export class Runtime {
169
212
  if (handoffCount > this.maxHandoffs) {
170
213
  throw new Error(`maxHandoffs exceeded (${this.maxHandoffs})`);
171
214
  }
215
+ // Cross-turn ping-pong safeguard (handoffCount resets each turn, so it
216
+ // can't catch A↔B oscillation spread across turns). This is a bound
217
+ // ABOVE maxHandoffs: within-run runaway is caught by the maxHandoffs
218
+ // check above; oscillation only fires for same-pair accumulation in the
219
+ // persisted handoffHistory beyond that, so it never pre-empts maxHandoffs.
220
+ if (isHandoffOscillating(opened.session.handoffHistory, runCtx.runState.activeAgentId, loopResult.to, this.maxHandoffs + 1)) {
221
+ throw new Error(`Handoff oscillation detected between "${runCtx.runState.activeAgentId}" and "${loopResult.to}"`);
222
+ }
172
223
  const target = this.agentsById.get(loopResult.to);
173
224
  if (!target) {
174
225
  throw new Error(`Handoff target agent not found: ${loopResult.to}`);
@@ -179,6 +230,20 @@ export class Runtime {
179
230
  reason: loopResult.reason ?? 'handoff',
180
231
  timestamp: new Date(),
181
232
  });
233
+ const handoffTarget = loopResult.to;
234
+ const routeFilter = activeAgent.routes?.find((r) => r.agent === handoffTarget)?.filter;
235
+ const inputFilter = routeFilter ?? this.config.handoffInputFilter;
236
+ if (inputFilter) {
237
+ const filtered = await inputFilter({
238
+ messages: runCtx.runState.messages,
239
+ workingMemory: runCtx.session.workingMemory,
240
+ sourceAgentId: runCtx.runState.activeAgentId,
241
+ targetAgentId: handoffTarget,
242
+ reason: loopResult.reason,
243
+ });
244
+ runCtx.runState.messages = filtered.messages;
245
+ runCtx.session.workingMemory = filtered.workingMemory;
246
+ }
182
247
  runCtx.runState.activeAgentId = loopResult.to;
183
248
  activeAgent = target;
184
249
  const targetSurface = await buildAgentToolSurface(target, opened.session, {
@@ -191,12 +256,40 @@ export class Runtime {
191
256
  : undefined;
192
257
  runCtx.globalTools = targetSurface.globalTools;
193
258
  runCtx.skillPrompt = targetSurface.skillPrompt;
194
- runCtx.workingMemoryPrompt = targetSurface.workingMemoryPrompt;
259
+ runCtx.workingMemoryPrompt = appendGoalsPrompt(targetSurface.workingMemoryPrompt, runCtx.session.workingMemory);
195
260
  runCtx.workingMemoryTools = targetSurface.workingMemoryTools;
196
261
  runCtx.fs = targetSurface.resolvedWorkspace?.fs;
197
262
  runCtx.memoryService = this.config.memoryService
198
263
  ? buildMemoryService(this.config.memoryService, target)
199
264
  : undefined;
265
+ const targetPolicies = resolveAgentPolicies(target);
266
+ const targetModel = target.model ?? this.defaultModel;
267
+ if (!targetModel) {
268
+ throw new Error('Runtime requires agent.model or config.defaultModel');
269
+ }
270
+ runCtx.baseInstructions =
271
+ (this.config.silentHandoff ?? true)
272
+ ? applyHandoffContinuation(target.instructions)
273
+ : target.instructions;
274
+ runCtx.model = targetModel;
275
+ runCtx.controlModel = target.controlModel ?? targetModel;
276
+ runCtx.outOfBandControl = resolveOutOfBandControl(target);
277
+ runCtx.limits = targetPolicies.limits;
278
+ runCtx.refinementPolicies = targetPolicies.refinementPolicies;
279
+ runCtx.validationPolicies = targetPolicies.validationPolicies;
280
+ runCtx.inputProcessors = targetPolicies.inputProcessors;
281
+ runCtx.outputProcessors = targetPolicies.outputProcessors;
282
+ runCtx.toolExecutor = new CoreToolExecutor({
283
+ tools: targetSurface.executorTools,
284
+ enforcer: targetPolicies.enforcer,
285
+ agentId: target.id,
286
+ onInterim: (message) => {
287
+ const id = crypto.randomUUID();
288
+ emit({ type: 'text-start', id });
289
+ emit({ type: 'text-delta', id, delta: message });
290
+ emit({ type: 'text-end', id });
291
+ },
292
+ });
200
293
  await runCtx.runStore.putRunState(runCtx.runState);
201
294
  continue;
202
295
  }
@@ -254,10 +347,19 @@ export class Runtime {
254
347
  outcomeReason: loopResult.kind === 'ended' ? loopResult.reason : undefined,
255
348
  memoryIngest: async () => {
256
349
  await runMemoryIngest(runCtx);
350
+ if (this.config.trackGoals) {
351
+ const controlModel = this.agentsById.get(runCtx.runState.activeAgentId)?.controlModel ??
352
+ runCtx.controlModel;
353
+ await updateGoalsFromTurn(runCtx, controlModel);
354
+ }
257
355
  },
258
356
  });
259
357
  await this.hooks?.onEnd?.(runCtx);
260
- emit({ type: 'done', sessionId: opened.session.id });
358
+ emit({
359
+ type: 'done',
360
+ sessionId: opened.session.id,
361
+ usage: computeTurnTraceUsage(usageBaseline, runCtx.runState.state),
362
+ });
261
363
  }
262
364
  return { text: collectAssistantText(runCtx.runState.messages), toolResults: [] };
263
365
  };
@@ -276,12 +378,26 @@ export class Runtime {
276
378
  run: gated,
277
379
  });
278
380
  }
381
+ runOnce(opts) {
382
+ return recordRunOnce(this, opts);
383
+ }
279
384
  stream(opts) {
280
385
  return this.run(opts);
281
386
  }
282
387
  async getSession(sessionId) {
283
388
  return this.sessionStore.get(sessionId);
284
389
  }
390
+ async getTrace(traceId) {
391
+ await this.settleTraceWrites();
392
+ return this.traceStore?.getTrace(traceId) ?? null;
393
+ }
394
+ async listTraces(sessionId) {
395
+ await this.settleTraceWrites();
396
+ return this.traceStore?.listTraces(sessionId) ?? [];
397
+ }
398
+ getTraceStore() {
399
+ return this.traceStore;
400
+ }
285
401
  getSessionStore() {
286
402
  return this.sessionStore;
287
403
  }
@@ -308,6 +424,65 @@ export class Runtime {
308
424
  markedBy: opts?.markedBy ?? 'http',
309
425
  });
310
426
  }
427
+ shouldTrace(sessionId, input) {
428
+ if (this.traceSinks.length === 0)
429
+ return false;
430
+ const sampling = this.config.tracing?.sampling;
431
+ if (typeof sampling === 'function') {
432
+ try {
433
+ return sampling({ sessionId, input });
434
+ }
435
+ catch {
436
+ return false;
437
+ }
438
+ }
439
+ if (sampling === undefined)
440
+ return true;
441
+ return sampling > 0 && (sampling >= 1 || Math.random() < sampling);
442
+ }
443
+ writeSpan(original) {
444
+ let span = original;
445
+ try {
446
+ if (this.config.tracing?.redact) {
447
+ span = this.config.tracing.redact(structuredClone(original));
448
+ }
449
+ }
450
+ catch {
451
+ return;
452
+ }
453
+ if (!span)
454
+ return;
455
+ for (const sink of this.traceSinks) {
456
+ try {
457
+ const result = sink.write(span);
458
+ if (result instanceof Promise) {
459
+ const pending = result.catch(() => { }).finally(() => this.pendingTraceWrites.delete(pending));
460
+ this.pendingTraceWrites.add(pending);
461
+ }
462
+ }
463
+ catch {
464
+ // Traces are derived observability and never participate in run correctness.
465
+ }
466
+ }
467
+ }
468
+ async settleTraceWrites() {
469
+ await Promise.allSettled([...this.pendingTraceWrites]);
470
+ await Promise.allSettled(this.traceSinks.map((sink) => sink.flush?.()));
471
+ }
472
+ flushTraceSinks() {
473
+ for (const sink of this.traceSinks) {
474
+ try {
475
+ const result = sink.flush?.();
476
+ if (result) {
477
+ const pending = result.catch(() => { }).finally(() => this.pendingTraceWrites.delete(pending));
478
+ this.pendingTraceWrites.add(pending);
479
+ }
480
+ }
481
+ catch {
482
+ // Export flushes are observational and never affect the run.
483
+ }
484
+ }
485
+ }
311
486
  /**
312
487
  * Compact `runState.messages` when over the configured trigger (or always,
313
488
  * when `force`). Persists both the run state and the session message mirror.
@@ -327,6 +502,7 @@ export class Runtime {
327
502
  model,
328
503
  config,
329
504
  force,
505
+ lastPromptTokens: readLastPromptTokens(runCtx.runState.state),
330
506
  });
331
507
  if (!result.compacted) {
332
508
  if (force) {
@@ -337,11 +513,9 @@ export class Runtime {
337
513
  runCtx.runState.messages = result.messages;
338
514
  runCtx.runState.updatedAt = Date.now();
339
515
  await runCtx.runStore.putRunState(runCtx.runState);
340
- const latest = await this.sessionStore.get(runCtx.session.id);
341
- if (latest) {
516
+ await mutateSessionWithRetry(this.sessionStore, runCtx.session.id, (latest) => {
342
517
  latest.messages = [...result.messages];
343
- await this.sessionStore.save(latest);
344
- }
518
+ });
345
519
  runCtx.session.messages = [...result.messages];
346
520
  emit({
347
521
  type: 'context-compacted',
@@ -409,12 +583,14 @@ export class Runtime {
409
583
  error: error instanceof Error ? error.message : String(error),
410
584
  };
411
585
  }
412
- recordEscalationOutcome(runCtx.session, info.category ?? 'user-request', outcome);
413
586
  if (opts.setLatch) {
414
587
  runCtx.runState.state[ESCALATION_NOTIFIED_KEY] = true;
415
588
  }
416
589
  await runCtx.runStore.putRunState(runCtx.runState);
417
- await this.sessionStore.save(runCtx.session);
590
+ const latestSession = await mutateSessionWithRetry(this.sessionStore, runCtx.session.id, (latest) => {
591
+ recordEscalationOutcome(latest, info.category ?? 'user-request', outcome);
592
+ });
593
+ runCtx.session.metadata = latestSession.metadata;
418
594
  emit({
419
595
  type: 'escalation',
420
596
  reason: info.reason,
@@ -451,9 +627,9 @@ export class Runtime {
451
627
  delete runState.state[ESCALATION_NOTIFIED_KEY];
452
628
  runState.updatedAt = Date.now();
453
629
  await runStore.putRunState(runState);
454
- const latest = (await this.sessionStore.get(sessionId)) ?? session;
455
- latest.messages = [...runState.messages];
456
- await this.sessionStore.save(latest);
630
+ await mutateSessionWithRetry(this.sessionStore, sessionId, (latest) => {
631
+ latest.messages = [...runState.messages];
632
+ });
457
633
  }
458
634
  }
459
635
  export function createRuntime(config) {
@@ -476,3 +652,10 @@ function collectAssistantText(messages) {
476
652
  }
477
653
  return '';
478
654
  }
655
+ function appendGoalsPrompt(workingMemoryPrompt, workingMemory) {
656
+ const goalsPrompt = projectGoalsPromptFromState(workingMemory);
657
+ if (!goalsPrompt) {
658
+ return workingMemoryPrompt;
659
+ }
660
+ return [workingMemoryPrompt, goalsPrompt].filter((part) => part && part.trim()).join('\n\n');
661
+ }
@@ -12,6 +12,13 @@ export declare class TokenAccumulator {
12
12
  private _peakUtil;
13
13
  private _cumCacheRead;
14
14
  constructor(contextWindow?: number | undefined);
15
+ /** Rehydrate cumulative totals from persisted session state (cross-turn). */
16
+ restoreCumulative(saved: {
17
+ inputTokens: number;
18
+ outputTokens: number;
19
+ totalTokens: number;
20
+ cacheReadTokens?: number;
21
+ }): void;
15
22
  record(usage: TurnUsageInput): TurnUsage;
16
23
  get cumulative(): {
17
24
  inputTokens: number;
@@ -12,6 +12,13 @@ export class TokenAccumulator {
12
12
  constructor(contextWindow) {
13
13
  this.contextWindow = contextWindow;
14
14
  }
15
+ /** Rehydrate cumulative totals from persisted session state (cross-turn). */
16
+ restoreCumulative(saved) {
17
+ this._cumInput = saved.inputTokens;
18
+ this._cumOutput = saved.outputTokens;
19
+ this._cumTotal = saved.totalTokens;
20
+ this._cumCacheRead = saved.cacheReadTokens ?? 0;
21
+ }
15
22
  record(usage) {
16
23
  this._cumInput += usage.inputTokens;
17
24
  this._cumOutput += usage.outputTokens;
@@ -0,0 +1,33 @@
1
+ import type { TurnResult } from '../types/channel.js';
2
+ import type { HarnessStreamPart, TurnHandle } from '../types/stream.js';
3
+ import type { AgentSpan, AgentTrace } from '../types/trace.js';
4
+ import type { RunOptions } from './Runtime.js';
5
+ export interface TraceRecorderOptions {
6
+ sessionId?: string;
7
+ input?: unknown;
8
+ onSpan?: (span: AgentSpan) => void;
9
+ }
10
+ export declare class TraceRecorder {
11
+ private readonly trace;
12
+ private readonly root;
13
+ private currentFlow?;
14
+ private currentNode?;
15
+ private readonly openTools;
16
+ private readonly toolCallIds;
17
+ private readonly emitted;
18
+ private readonly onSpan?;
19
+ constructor(options?: TraceRecorderOptions);
20
+ record(part: HarnessStreamPart): void;
21
+ finish(result: TurnResult): AgentTrace;
22
+ private openSpan;
23
+ private takeToolSpan;
24
+ private closeTools;
25
+ private closeNode;
26
+ private closeFlow;
27
+ private setSessionId;
28
+ private close;
29
+ private emitSpan;
30
+ }
31
+ export declare function runOnce(runtime: {
32
+ run(opts: RunOptions): TurnHandle;
33
+ }, opts: RunOptions): Promise<AgentTrace>;