tinker-agent 2.3.0 → 2.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/CHANGELOG.md +22 -1
- package/package.json +1 -1
- package/src/agent/loop.ts +1 -1
- package/src/agent/runtime-session.ts +259 -16
- package/src/agent/session-ledger.ts +10 -3
- package/src/context/context-manager.ts +185 -8
- package/src/context/context-policy.ts +1 -1
- package/src/context/context-revision-compiler.ts +7 -2
- package/src/context/context-swap-label.ts +132 -0
- package/src/context/swap-planner.ts +219 -56
- package/src/events/stdout-event-printer.ts +1 -0
- package/src/events/types.ts +3 -3
- package/src/observation/observation-builder.ts +36 -0
- package/src/session/session-store.ts +300 -0
- package/src/tools/context-maintenance.ts +266 -0
- package/src/tools/registry.ts +18 -1
- package/src/tools/types.ts +83 -1
- package/src/tui/event-store.ts +15 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,26 @@ All notable user-facing changes to Tinker are documented here. The project follo
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [2.4.0] - 2026-09-02
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Add model-directed context management. Three new built-in tools open context
|
|
13
|
+
maintenance to the model itself: `ContextStatus` reports live input-token
|
|
14
|
+
usage and pressure, `ContextSwapCandidates` lists swappable historical tool
|
|
15
|
+
observations with short labels and byte savings, and `ContextSwap` schedules
|
|
16
|
+
selected observations for eviction. Scheduled swaps are validated immediately
|
|
17
|
+
and committed as one context revision when the iteration's tool frames close,
|
|
18
|
+
leaving Recall-recoverable placeholders behind. A one-shot lease pauses
|
|
19
|
+
automatic compaction for exactly one iteration after a candidate listing so
|
|
20
|
+
the model keeps the choice under real pressure.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- Lower the swap eligibility floor from 8 KiB to 2 KiB per observation. Both
|
|
25
|
+
automatic compaction and model-directed swaps can now evict medium-sized
|
|
26
|
+
tool observations, individually or in batches of up to 16 per swap.
|
|
27
|
+
|
|
8
28
|
## [2.3.0] - 2026-08-31
|
|
9
29
|
|
|
10
30
|
### Changed
|
|
@@ -277,7 +297,8 @@ All notable user-facing changes to Tinker are documented here. The project follo
|
|
|
277
297
|
- First formal npm release under the `tinker-agent` package name with the `tinker`
|
|
278
298
|
executable.
|
|
279
299
|
|
|
280
|
-
[Unreleased]: https://github.com/ishowshao/tinker/compare/v2.
|
|
300
|
+
[Unreleased]: https://github.com/ishowshao/tinker/compare/v2.4.0...HEAD
|
|
301
|
+
[2.4.0]: https://github.com/ishowshao/tinker/releases/tag/v2.4.0
|
|
281
302
|
[2.3.0]: https://github.com/ishowshao/tinker/releases/tag/v2.3.0
|
|
282
303
|
[2.2.0]: https://github.com/ishowshao/tinker/releases/tag/v2.2.0
|
|
283
304
|
[2.1.0]: https://github.com/ishowshao/tinker/releases/tag/v2.1.0
|
package/package.json
CHANGED
package/src/agent/loop.ts
CHANGED
|
@@ -56,7 +56,7 @@ export type RunAgentInput = {
|
|
|
56
56
|
preflight: ReturnType<ContextMeter["measure"]>;
|
|
57
57
|
}):
|
|
58
58
|
| {
|
|
59
|
-
trigger: Exclude<SwapPlanningTrigger, "manual">;
|
|
59
|
+
trigger: Exclude<SwapPlanningTrigger, "manual" | "model_directed">;
|
|
60
60
|
forcedTargetTokens?: number;
|
|
61
61
|
}
|
|
62
62
|
| undefined;
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
} from "../events/types";
|
|
12
12
|
import {
|
|
13
13
|
runtimeIdFactory,
|
|
14
|
+
type MessageId,
|
|
14
15
|
type RuntimeIdFactory,
|
|
15
16
|
type SessionId,
|
|
16
17
|
type TurnId,
|
|
@@ -27,6 +28,7 @@ import {
|
|
|
27
28
|
type MaterializedModelRequest,
|
|
28
29
|
type ModelClient,
|
|
29
30
|
} from "../model/model-client";
|
|
31
|
+
import type { ContextPressure } from "../model/model-request-preflight";
|
|
30
32
|
import { ImageAssetStore, type ImportedImageAsset } from "../image/image-asset-store";
|
|
31
33
|
import { IMAGE_INPUT_POLICY } from "../image/image-input-policy";
|
|
32
34
|
import {
|
|
@@ -72,7 +74,14 @@ import {
|
|
|
72
74
|
renderedMessageHash,
|
|
73
75
|
} from "../context/compiled-context-hash";
|
|
74
76
|
import { createDefaultTooling, type DefaultTooling } from "../tools/registry";
|
|
75
|
-
import
|
|
77
|
+
import {
|
|
78
|
+
ToolExecutionFatalError,
|
|
79
|
+
type ContextMaintenanceHandle,
|
|
80
|
+
type ContextStatusRawResult,
|
|
81
|
+
type ContextSwapCandidatesRawResult,
|
|
82
|
+
type ContextSwapRawResult,
|
|
83
|
+
type ToolExecutor,
|
|
84
|
+
} from "../tools/types";
|
|
76
85
|
import type { TurnUndoResult } from "../tools/turn-undo-manager";
|
|
77
86
|
import type { Refiner } from "../tools/web-fetch/refiner";
|
|
78
87
|
import type { ProjectInstructionManifest } from "../instructions/project-instructions";
|
|
@@ -105,6 +114,7 @@ import type { PublicToolingConfig } from "../cli/public-config-contract";
|
|
|
105
114
|
import type {
|
|
106
115
|
IterationIdentity,
|
|
107
116
|
RunAgentResult,
|
|
117
|
+
ToolCall,
|
|
108
118
|
ToolCallIdentity,
|
|
109
119
|
TurnIdentity,
|
|
110
120
|
} from "./types";
|
|
@@ -221,6 +231,7 @@ export type RuntimeSkillsSnapshot = {
|
|
|
221
231
|
|
|
222
232
|
export type RuntimeSessionContext = {
|
|
223
233
|
readonly sessionId: SessionId;
|
|
234
|
+
readonly contextMaintenance: ContextMaintenanceHandle;
|
|
224
235
|
createIteration(turn: TurnIdentity, iterationNumber: number): IterationIdentity;
|
|
225
236
|
createToolCall(
|
|
226
237
|
iteration: IterationIdentity,
|
|
@@ -379,6 +390,8 @@ type RuntimeSessionState =
|
|
|
379
390
|
|
|
380
391
|
type ActiveTurn = {
|
|
381
392
|
turn: TurnIdentity;
|
|
393
|
+
ledger: AgentTurnLedger;
|
|
394
|
+
consumedThroughOrdinal?: number;
|
|
382
395
|
controller: AbortController;
|
|
383
396
|
completion: Promise<RunAgentResult>;
|
|
384
397
|
};
|
|
@@ -467,6 +480,8 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
467
480
|
private contextManager?: ContextManager;
|
|
468
481
|
private contextAutomationDecision?: ContextAutomationDecision;
|
|
469
482
|
private pendingAutomaticContextMaintenance = false;
|
|
483
|
+
private pendingModelDirectedSwap?: Set<MessageId>;
|
|
484
|
+
private modelDirectedSwapLease = false;
|
|
470
485
|
private readonly skillCatalog: SkillCatalogSnapshot;
|
|
471
486
|
private skillCoordinator = new SkillActivationCoordinator();
|
|
472
487
|
private bashGuardMode: "guard" | "yolo";
|
|
@@ -517,6 +532,11 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
517
532
|
this.shadowPlanner = new SwapPlanner(input.modelClient);
|
|
518
533
|
this.context = {
|
|
519
534
|
sessionId: this.sessionId,
|
|
535
|
+
contextMaintenance: {
|
|
536
|
+
status: (call) => this.contextStatus(call),
|
|
537
|
+
candidates: (call, page) => this.contextSwapCandidates(call, page),
|
|
538
|
+
swap: (call, selection) => this.contextSwap(call, selection),
|
|
539
|
+
},
|
|
520
540
|
createIteration: (turn, iterationNumber) =>
|
|
521
541
|
this.createIteration(turn, iterationNumber),
|
|
522
542
|
createToolCall: (iteration, toolCallNumber) =>
|
|
@@ -1248,6 +1268,133 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
1248
1268
|
return this.contextAutomationDecision;
|
|
1249
1269
|
}
|
|
1250
1270
|
|
|
1271
|
+
private async contextStatus(call: ToolCall): Promise<ContextStatusRawResult> {
|
|
1272
|
+
const active = this.requireActiveContextTool(call, "ContextStatus");
|
|
1273
|
+
try {
|
|
1274
|
+
const usage = this.requireContextManager().measureActive(
|
|
1275
|
+
active.turn.turnId,
|
|
1276
|
+
active.ledger,
|
|
1277
|
+
);
|
|
1278
|
+
return Object.freeze({
|
|
1279
|
+
ok: true,
|
|
1280
|
+
operation: "status",
|
|
1281
|
+
usedInputTokens: usage.usedInputTokens,
|
|
1282
|
+
inputBudgetTokens: usage.inputBudgetTokens,
|
|
1283
|
+
pressure: toolContextPressure(usage.pressure),
|
|
1284
|
+
triggerTokens: usage.triggerTokens,
|
|
1285
|
+
source: usage.source,
|
|
1286
|
+
});
|
|
1287
|
+
} catch (error) {
|
|
1288
|
+
return {
|
|
1289
|
+
ok: false,
|
|
1290
|
+
operation: "status",
|
|
1291
|
+
error: this.contextToolFailure("status", error),
|
|
1292
|
+
};
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
private async contextSwapCandidates(
|
|
1297
|
+
call: ToolCall,
|
|
1298
|
+
page: { readonly limit: number; readonly offset: number },
|
|
1299
|
+
): Promise<ContextSwapCandidatesRawResult> {
|
|
1300
|
+
const active = this.requireActiveContextTool(call, "ContextSwapCandidates");
|
|
1301
|
+
try {
|
|
1302
|
+
const result = this.requireContextManager().listActiveSwapCandidates({
|
|
1303
|
+
turnId: active.turn.turnId,
|
|
1304
|
+
consumedThroughOrdinal: active.consumedThroughOrdinal,
|
|
1305
|
+
activeLedger: active.ledger,
|
|
1306
|
+
limit: page.limit,
|
|
1307
|
+
offset: page.offset,
|
|
1308
|
+
});
|
|
1309
|
+
if (result.total > 0 && result.usage.pressure !== "normal") {
|
|
1310
|
+
this.modelDirectedSwapLease = true;
|
|
1311
|
+
}
|
|
1312
|
+
return Object.freeze({
|
|
1313
|
+
ok: true,
|
|
1314
|
+
operation: "candidates",
|
|
1315
|
+
total: result.total,
|
|
1316
|
+
candidates: result.candidates,
|
|
1317
|
+
});
|
|
1318
|
+
} catch (error) {
|
|
1319
|
+
return {
|
|
1320
|
+
ok: false,
|
|
1321
|
+
operation: "candidates",
|
|
1322
|
+
error: this.contextToolFailure("candidate listing", error),
|
|
1323
|
+
};
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
private async contextSwap(
|
|
1328
|
+
call: ToolCall,
|
|
1329
|
+
selection: { readonly candidateIds: readonly MessageId[] },
|
|
1330
|
+
): Promise<ContextSwapRawResult> {
|
|
1331
|
+
const active = this.requireActiveContextTool(call, "ContextSwap");
|
|
1332
|
+
try {
|
|
1333
|
+
const result = this.requireContextManager().validateActiveSwapSelection({
|
|
1334
|
+
turnId: active.turn.turnId,
|
|
1335
|
+
consumedThroughOrdinal: active.consumedThroughOrdinal,
|
|
1336
|
+
activeLedger: active.ledger,
|
|
1337
|
+
messageIds: selection.candidateIds,
|
|
1338
|
+
});
|
|
1339
|
+
if (result.scheduled.length === 0) {
|
|
1340
|
+
return Object.freeze({
|
|
1341
|
+
ok: false,
|
|
1342
|
+
operation: "swap",
|
|
1343
|
+
scheduled: Object.freeze([]),
|
|
1344
|
+
rejected: result.rejected,
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1347
|
+
const pending = (this.pendingModelDirectedSwap ??= new Set<MessageId>());
|
|
1348
|
+
for (const candidate of result.scheduled) pending.add(candidate.candidateId);
|
|
1349
|
+
this.modelDirectedSwapLease = false;
|
|
1350
|
+
return Object.freeze({
|
|
1351
|
+
ok: true,
|
|
1352
|
+
operation: "swap",
|
|
1353
|
+
scheduled: result.scheduled,
|
|
1354
|
+
rejected: result.rejected,
|
|
1355
|
+
note: "Swap executes when this iteration's tool frames close.",
|
|
1356
|
+
});
|
|
1357
|
+
} catch (error) {
|
|
1358
|
+
return {
|
|
1359
|
+
ok: false,
|
|
1360
|
+
operation: "swap",
|
|
1361
|
+
scheduled: [],
|
|
1362
|
+
rejected: [],
|
|
1363
|
+
error: this.contextToolFailure("swap scheduling", error),
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
private requireActiveContextTool(
|
|
1369
|
+
call: ToolCall,
|
|
1370
|
+
expectedName: "ContextStatus" | "ContextSwapCandidates" | "ContextSwap",
|
|
1371
|
+
): ActiveTurn & { consumedThroughOrdinal: number } {
|
|
1372
|
+
this.requireToolCall(call);
|
|
1373
|
+
const active = this.activeTurn;
|
|
1374
|
+
if (
|
|
1375
|
+
call.name !== expectedName ||
|
|
1376
|
+
active === undefined ||
|
|
1377
|
+
active.turn.turnId !== call.turnId ||
|
|
1378
|
+
active.consumedThroughOrdinal === undefined ||
|
|
1379
|
+
this.state !== "executing"
|
|
1380
|
+
) {
|
|
1381
|
+
throw new ToolExecutionFatalError(
|
|
1382
|
+
`${expectedName} was called outside an active model iteration.`,
|
|
1383
|
+
);
|
|
1384
|
+
}
|
|
1385
|
+
return active as ActiveTurn & { consumedThroughOrdinal: number };
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
private contextToolFailure(operation: string, error: unknown): string {
|
|
1389
|
+
if (error instanceof ContextManagerError && !error.fatal) {
|
|
1390
|
+
return `Context ${operation} failed (${boundedContextErrorCode(error.code)}).`;
|
|
1391
|
+
}
|
|
1392
|
+
throw new ToolExecutionFatalError(
|
|
1393
|
+
`Context ${operation} required canonical session state that could not be read safely.`,
|
|
1394
|
+
{ cause: error },
|
|
1395
|
+
);
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1251
1398
|
private appendSkillsCatalogLoaded(): Promise<void> {
|
|
1252
1399
|
const activeNames = this.skillCoordinator
|
|
1253
1400
|
.activeEntries()
|
|
@@ -1307,6 +1454,15 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
1307
1454
|
iteration: IterationIdentity;
|
|
1308
1455
|
built: BuiltContextRequest;
|
|
1309
1456
|
}): void {
|
|
1457
|
+
const active = this.activeTurn;
|
|
1458
|
+
if (
|
|
1459
|
+
active === undefined ||
|
|
1460
|
+
active.turn.turnId !== input.iteration.turnId ||
|
|
1461
|
+
active.turn.sessionId !== input.iteration.sessionId
|
|
1462
|
+
) {
|
|
1463
|
+
throw new Error("Model dispatch does not belong to the active runtime turn.");
|
|
1464
|
+
}
|
|
1465
|
+
active.consumedThroughOrdinal = input.built.canonical.messages.length;
|
|
1310
1466
|
const pending = this.store.loadSkillActivations(["pending"]);
|
|
1311
1467
|
if (pending.length === 0) {
|
|
1312
1468
|
return;
|
|
@@ -1675,7 +1831,12 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
1675
1831
|
usage: admissionSnapshot,
|
|
1676
1832
|
},
|
|
1677
1833
|
});
|
|
1678
|
-
this.activeTurn = {
|
|
1834
|
+
this.activeTurn = {
|
|
1835
|
+
turn,
|
|
1836
|
+
ledger: pendingLedgerTurn.agent,
|
|
1837
|
+
controller,
|
|
1838
|
+
completion,
|
|
1839
|
+
};
|
|
1679
1840
|
this.notifyPromptScheduler();
|
|
1680
1841
|
return Object.freeze({
|
|
1681
1842
|
turnId: turn.turnId,
|
|
@@ -2192,6 +2353,8 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
2192
2353
|
this.activeTurn = undefined;
|
|
2193
2354
|
this.notifyPromptScheduler();
|
|
2194
2355
|
this.pendingAutomaticContextMaintenance = false;
|
|
2356
|
+
this.pendingModelDirectedSwap = undefined;
|
|
2357
|
+
this.modelDirectedSwapLease = false;
|
|
2195
2358
|
if (this.state === "executing") {
|
|
2196
2359
|
this.state = "ready";
|
|
2197
2360
|
}
|
|
@@ -2229,28 +2392,48 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
2229
2392
|
consumedThroughOrdinal: number;
|
|
2230
2393
|
ledger: AgentTurnLedger;
|
|
2231
2394
|
}): Promise<void> {
|
|
2232
|
-
const automation = this.requireContextAutomation();
|
|
2233
|
-
if (!automation.automaticSwapOnly) return;
|
|
2234
2395
|
if (this.state !== "executing") {
|
|
2235
2396
|
throw new Error(
|
|
2236
2397
|
`Cannot maintain active-turn context while RuntimeSession is ${this.state}.`,
|
|
2237
2398
|
);
|
|
2238
2399
|
}
|
|
2239
|
-
const
|
|
2240
|
-
|
|
2241
|
-
if (
|
|
2400
|
+
const pendingModelDirectedSwap = this.pendingModelDirectedSwap;
|
|
2401
|
+
this.pendingModelDirectedSwap = undefined;
|
|
2402
|
+
if (pendingModelDirectedSwap === undefined && this.modelDirectedSwapLease) {
|
|
2403
|
+
this.modelDirectedSwapLease = false;
|
|
2404
|
+
return;
|
|
2405
|
+
}
|
|
2242
2406
|
|
|
2243
|
-
const
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
consumedThroughOrdinal: input.consumedThroughOrdinal,
|
|
2249
|
-
},
|
|
2250
|
-
} as const;
|
|
2407
|
+
const automation = this.requireContextAutomation();
|
|
2408
|
+
if (pendingModelDirectedSwap === undefined && !automation.automaticSwapOnly) {
|
|
2409
|
+
return;
|
|
2410
|
+
}
|
|
2411
|
+
const manager = this.requireContextManager();
|
|
2251
2412
|
this.pendingAutomaticContextMaintenance = false;
|
|
2413
|
+
this.modelDirectedSwapLease = false;
|
|
2252
2414
|
this.state = "maintaining_context";
|
|
2253
2415
|
try {
|
|
2416
|
+
if (pendingModelDirectedSwap !== undefined) {
|
|
2417
|
+
await this.performModelDirectedCompaction({
|
|
2418
|
+
turn: input.turn,
|
|
2419
|
+
consumedThroughOrdinal: input.consumedThroughOrdinal,
|
|
2420
|
+
ledger: input.ledger,
|
|
2421
|
+
messageIds: Object.freeze([...pendingModelDirectedSwap]),
|
|
2422
|
+
});
|
|
2423
|
+
}
|
|
2424
|
+
if (!automation.automaticSwapOnly) return;
|
|
2425
|
+
|
|
2426
|
+
const usage = manager.measureCurrent(input.turn.turnId, input.ledger);
|
|
2427
|
+
if (usage.pressure === "normal") return;
|
|
2428
|
+
|
|
2429
|
+
const qualificationId = requireAutomationQualificationId(automation);
|
|
2430
|
+
const compactionTrigger = {
|
|
2431
|
+
kind: "runtime_pressure",
|
|
2432
|
+
activeTurn: {
|
|
2433
|
+
turnId: input.turn.turnId,
|
|
2434
|
+
consumedThroughOrdinal: input.consumedThroughOrdinal,
|
|
2435
|
+
},
|
|
2436
|
+
} as const;
|
|
2254
2437
|
await this.append({
|
|
2255
2438
|
type: "context.revision.started",
|
|
2256
2439
|
sessionId: this.sessionId,
|
|
@@ -2347,6 +2530,56 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
2347
2530
|
}
|
|
2348
2531
|
}
|
|
2349
2532
|
|
|
2533
|
+
private async performModelDirectedCompaction(input: {
|
|
2534
|
+
turn: TurnIdentity;
|
|
2535
|
+
consumedThroughOrdinal: number;
|
|
2536
|
+
ledger: AgentTurnLedger;
|
|
2537
|
+
messageIds: readonly MessageId[];
|
|
2538
|
+
}): Promise<void> {
|
|
2539
|
+
await this.append({
|
|
2540
|
+
type: "context.revision.started",
|
|
2541
|
+
sessionId: this.sessionId,
|
|
2542
|
+
data: {
|
|
2543
|
+
strategy: "swap",
|
|
2544
|
+
reason: "model_directed",
|
|
2545
|
+
policyVersion: "swap-only-v1",
|
|
2546
|
+
rendererFormat: "swap-observation-v1",
|
|
2547
|
+
},
|
|
2548
|
+
});
|
|
2549
|
+
try {
|
|
2550
|
+
const result = await this.requireContextManager().compact(
|
|
2551
|
+
{
|
|
2552
|
+
kind: "model_directed",
|
|
2553
|
+
messageIds: input.messageIds,
|
|
2554
|
+
activeTurn: {
|
|
2555
|
+
turnId: input.turn.turnId,
|
|
2556
|
+
consumedThroughOrdinal: input.consumedThroughOrdinal,
|
|
2557
|
+
},
|
|
2558
|
+
},
|
|
2559
|
+
input.ledger,
|
|
2560
|
+
);
|
|
2561
|
+
await this.append({
|
|
2562
|
+
type: "context.revision.finished",
|
|
2563
|
+
sessionId: this.sessionId,
|
|
2564
|
+
data: contextRevisionFinishedData(result, "model_directed"),
|
|
2565
|
+
});
|
|
2566
|
+
} catch (error) {
|
|
2567
|
+
const failure = automaticContextFailure(error, "compaction");
|
|
2568
|
+
await this.append({
|
|
2569
|
+
type: "context.revision.failed",
|
|
2570
|
+
sessionId: this.sessionId,
|
|
2571
|
+
data: {
|
|
2572
|
+
strategy: "swap",
|
|
2573
|
+
reason: "model_directed",
|
|
2574
|
+
stage: failure.stage,
|
|
2575
|
+
errorCode: boundedContextErrorCode(failure.code),
|
|
2576
|
+
error: `Model-directed context compaction failed at ${failure.stage}.`,
|
|
2577
|
+
},
|
|
2578
|
+
}).catch(() => undefined);
|
|
2579
|
+
if (failure.fatal) throw error;
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2350
2583
|
private notifyCompletedTurn(turn: TurnIdentity): void {
|
|
2351
2584
|
const hook = this.input.completedTurnHook;
|
|
2352
2585
|
if (hook === undefined) {
|
|
@@ -3086,7 +3319,7 @@ function errorMessage(error: unknown): string {
|
|
|
3086
3319
|
|
|
3087
3320
|
function contextRevisionFinishedData(
|
|
3088
3321
|
result: ContextCompactionResult,
|
|
3089
|
-
reason: "manual" | "runtime_pressure" = "manual",
|
|
3322
|
+
reason: "manual" | "runtime_pressure" | "model_directed" = "manual",
|
|
3090
3323
|
qualificationId?: string,
|
|
3091
3324
|
): ContextRevisionFinishedData {
|
|
3092
3325
|
if (result.status === "unchanged") {
|
|
@@ -3224,6 +3457,16 @@ function boundedContextErrorCode(code: string): string {
|
|
|
3224
3457
|
: "CONTEXT_COMPACTION_FAILED";
|
|
3225
3458
|
}
|
|
3226
3459
|
|
|
3460
|
+
function toolContextPressure(
|
|
3461
|
+
pressure: ContextPressure,
|
|
3462
|
+
): "normal" | "high" | "critical" {
|
|
3463
|
+
return pressure === "triggered"
|
|
3464
|
+
? "high"
|
|
3465
|
+
: pressure === "blocked"
|
|
3466
|
+
? "critical"
|
|
3467
|
+
: "normal";
|
|
3468
|
+
}
|
|
3469
|
+
|
|
3227
3470
|
function requirePositiveNumber(value: number, name: string): void {
|
|
3228
3471
|
if (!Number.isInteger(value) || value < 1) {
|
|
3229
3472
|
throw new Error(`${name} must be a positive integer; received ${value}.`);
|
|
@@ -116,7 +116,10 @@ export type AgentTurnLedger = {
|
|
|
116
116
|
commitToolCompletions(
|
|
117
117
|
completions: readonly ToolCompletionInput[],
|
|
118
118
|
): readonly CommittedToolCompletion[];
|
|
119
|
-
buildModelRequest(
|
|
119
|
+
buildModelRequest(
|
|
120
|
+
tools: readonly ToolDefinition[],
|
|
121
|
+
options?: { readonly allowOpenTail?: boolean },
|
|
122
|
+
): BuiltContextRequest;
|
|
120
123
|
activateContextSnapshot(snapshot: StoredContextSnapshotV8): void;
|
|
121
124
|
};
|
|
122
125
|
|
|
@@ -657,9 +660,10 @@ export class InMemorySessionLedger implements SessionLedger {
|
|
|
657
660
|
buildTurnModelRequest(
|
|
658
661
|
pending: InMemoryPendingLedgerTurn,
|
|
659
662
|
tools: readonly ToolDefinition[],
|
|
663
|
+
options: { readonly allowOpenTail?: boolean } = {},
|
|
660
664
|
): BuiltContextRequest {
|
|
661
665
|
this.requirePending(pending, "build a model request");
|
|
662
|
-
return this.buildRequest(tools);
|
|
666
|
+
return this.buildRequest(tools, undefined, options);
|
|
663
667
|
}
|
|
664
668
|
|
|
665
669
|
finishTurn(pending: InMemoryPendingLedgerTurn, result: RunAgentResult): void {
|
|
@@ -702,11 +706,13 @@ export class InMemorySessionLedger implements SessionLedger {
|
|
|
702
706
|
private buildRequest(
|
|
703
707
|
tools: readonly ToolDefinition[],
|
|
704
708
|
candidateUserMessage?: UserMessage,
|
|
709
|
+
options: { readonly allowOpenTail?: boolean } = {},
|
|
705
710
|
): BuiltContextRequest {
|
|
706
711
|
try {
|
|
707
712
|
const canonical = this.view;
|
|
708
713
|
const compiled = this.revisionCompiler.compileActive(
|
|
709
714
|
snapshotFor(canonical, this.revision, this.surface, this.activeOverrides),
|
|
715
|
+
{ allowOpenTail: options.allowOpenTail },
|
|
710
716
|
);
|
|
711
717
|
return this.contextBuilder.build({
|
|
712
718
|
canonical,
|
|
@@ -865,7 +871,8 @@ class InMemoryPendingLedgerTurn implements PendingLedgerTurn {
|
|
|
865
871
|
assertCanExecuteTool: (call) => this.ledger.assertCanExecuteTool(this, call),
|
|
866
872
|
commitToolCompletions: (completions) =>
|
|
867
873
|
this.ledger.commitToolCompletions(this, completions),
|
|
868
|
-
buildModelRequest: (tools) =>
|
|
874
|
+
buildModelRequest: (tools, options) =>
|
|
875
|
+
this.ledger.buildTurnModelRequest(this, tools, options),
|
|
869
876
|
activateContextSnapshot: (snapshot) =>
|
|
870
877
|
this.ledger.activateContextSnapshot(snapshot),
|
|
871
878
|
};
|