@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.
- package/README.md +85 -0
- package/dist/capabilities/LivePromptAssembler.js +1 -0
- package/dist/flow/collectDigression.d.ts +5 -2
- package/dist/flow/collectDigression.js +48 -10
- package/dist/flow/collectUntilComplete.js +9 -1
- package/dist/flow/extraction.d.ts +1 -0
- package/dist/flow/extraction.js +4 -0
- package/dist/flow/runFlow.js +63 -5
- package/dist/index.d.ts +6 -1
- package/dist/index.js +4 -0
- package/dist/prompts/PromptBuilder.js +7 -0
- package/dist/prompts/types.d.ts +2 -0
- package/dist/runtime/InMemoryRetrievalCache.d.ts +25 -0
- package/dist/runtime/InMemoryRetrievalCache.js +59 -0
- package/dist/runtime/KnowledgeProvider.js +6 -1
- package/dist/runtime/Runtime.d.ts +44 -0
- package/dist/runtime/Runtime.js +196 -13
- package/dist/runtime/TokenAccumulator.d.ts +7 -0
- package/dist/runtime/TokenAccumulator.js +7 -0
- package/dist/runtime/TraceRecorder.d.ts +33 -0
- package/dist/runtime/TraceRecorder.js +290 -0
- package/dist/runtime/channels/TextDriver.js +45 -22
- package/dist/runtime/channels/executeModelTool.d.ts +8 -1
- package/dist/runtime/channels/executeModelTool.js +70 -1
- package/dist/runtime/channels/inputBuffer.d.ts +1 -0
- package/dist/runtime/channels/inputBuffer.js +9 -0
- package/dist/runtime/closeRun.js +11 -8
- package/dist/runtime/compaction.d.ts +2 -0
- package/dist/runtime/compaction.js +3 -2
- package/dist/runtime/ctx.js +110 -57
- package/dist/runtime/durable/RunStore.d.ts +16 -0
- package/dist/runtime/durable/RunStore.js +6 -0
- package/dist/runtime/durable/SessionRunStore.d.ts +7 -2
- package/dist/runtime/durable/SessionRunStore.js +136 -34
- package/dist/runtime/durable/idempotency.d.ts +1 -0
- package/dist/runtime/durable/idempotency.js +3 -0
- package/dist/runtime/durable/replay.js +7 -2
- package/dist/runtime/durable/types.d.ts +11 -0
- package/dist/runtime/goals.d.ts +18 -0
- package/dist/runtime/goals.js +158 -0
- package/dist/runtime/grounding/knowledge.js +1 -1
- package/dist/runtime/handoffContinuation.d.ts +20 -0
- package/dist/runtime/handoffContinuation.js +30 -0
- package/dist/runtime/handoffOscillation.d.ts +6 -0
- package/dist/runtime/handoffOscillation.js +17 -0
- package/dist/runtime/hostLoop.js +11 -0
- package/dist/runtime/index.d.ts +3 -1
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/openRun.d.ts +2 -0
- package/dist/runtime/openRun.js +46 -17
- package/dist/runtime/policies/limits.d.ts +1 -0
- package/dist/runtime/policies/limits.js +3 -0
- package/dist/runtime/select.d.ts +5 -1
- package/dist/runtime/select.js +30 -1
- package/dist/runtime/turnTokenUsage.d.ts +28 -0
- package/dist/runtime/turnTokenUsage.js +70 -0
- package/dist/session/SessionStore.d.ts +6 -0
- package/dist/session/SessionStore.js +12 -1
- package/dist/session/stores/MemoryStore.d.ts +1 -1
- package/dist/session/stores/MemoryStore.js +16 -2
- package/dist/session/testing.d.ts +6 -1
- package/dist/session/testing.js +34 -0
- package/dist/session/utils.d.ts +3 -0
- package/dist/session/utils.js +23 -0
- package/dist/tools/effect/ToolExecutor.js +4 -1
- package/dist/tools/effect/defineTool.d.ts +2 -0
- package/dist/tools/effect/defineTool.js +2 -0
- package/dist/tracing/MemoryTraceStore.d.ts +16 -0
- package/dist/tracing/MemoryTraceStore.js +56 -0
- package/dist/tracing/OtelTraceSink.d.ts +127 -0
- package/dist/tracing/OtelTraceSink.js +101 -0
- package/dist/tracing/TraceStore.d.ts +19 -0
- package/dist/tracing/TraceStore.js +32 -0
- package/dist/tracing/index.d.ts +3 -0
- package/dist/tracing/index.js +3 -0
- package/dist/tracing/testing.d.ts +3 -0
- package/dist/tracing/testing.js +45 -0
- package/dist/types/agentConfig.d.ts +2 -1
- package/dist/types/channel.d.ts +14 -1
- package/dist/types/effectTool.d.ts +4 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/run-context.d.ts +14 -1
- package/dist/types/session.d.ts +10 -0
- package/dist/types/stream.d.ts +8 -0
- package/dist/types/trace.d.ts +50 -0
- package/dist/types/trace.js +1 -0
- package/guides/EXAMPLE_VERIFICATION.md +4 -4
- package/package.json +13 -4
package/dist/runtime/Runtime.js
CHANGED
|
@@ -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
|
|
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({
|
|
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
|
-
|
|
341
|
-
if (latest) {
|
|
516
|
+
await mutateSessionWithRetry(this.sessionStore, runCtx.session.id, (latest) => {
|
|
342
517
|
latest.messages = [...result.messages];
|
|
343
|
-
|
|
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
|
|
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
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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>;
|