tinker-agent 2.8.0 → 2.9.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 +39 -1
- package/README.md +64 -10
- package/package.json +4 -3
- package/src/agent/runtime-context-capabilities.ts +19 -0
- package/src/agent/runtime-context-events.ts +127 -0
- package/src/agent/runtime-context-maintenance.ts +780 -0
- package/src/agent/runtime-interactions.ts +291 -0
- package/src/agent/runtime-prompt-scheduler.ts +182 -0
- package/src/agent/runtime-session-contracts.ts +317 -0
- package/src/agent/runtime-session.ts +250 -2130
- package/src/agent/runtime-skills.ts +544 -0
- package/src/cli/runner-dependencies.ts +6 -5
- package/src/context/context-automation-policy.ts +12 -118
- package/src/events/types.ts +12 -0
- package/src/memory/memory-get-tool.ts +1 -1
- package/src/observation/observation-builder.ts +41 -11
- package/src/session/resume-projection.ts +47 -21
- package/src/session/session-history-access.ts +238 -0
- package/src/session/session-store-context-readers.ts +183 -0
- package/src/session/session-store-ledger-writer.ts +315 -0
- package/src/session/session-store-record-writer.ts +318 -0
- package/src/session/session-store-recovery.ts +225 -0
- package/src/session/session-store-revisions.ts +1004 -0
- package/src/session/session-store-sql.ts +40 -0
- package/src/session/session-store-validation.ts +657 -0
- package/src/session/session-store.ts +756 -3186
- package/src/tools/bash-task.ts +20 -2
- package/src/tools/recall.ts +106 -50
- package/src/tools/registry.ts +4 -6
- package/src/tools/task-output-range.ts +146 -0
- package/src/tools/task-output-tool.ts +35 -5
- package/src/tools/task-output.ts +35 -0
- package/src/tools/task-tool-args.ts +34 -0
- package/src/tools/types.ts +9 -0
- package/src/tui/event-store.ts +8 -3
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
import {
|
|
2
|
+
canonicalSequenceHash,
|
|
3
|
+
renderedMessageHash,
|
|
4
|
+
} from "../context/compiled-context-hash";
|
|
5
|
+
import {
|
|
6
|
+
commitAgentSkillsContextUpdate,
|
|
7
|
+
ContextManagerError,
|
|
8
|
+
} from "../context/context-manager";
|
|
9
|
+
import type { BuiltContextRequest } from "../context/context-revision";
|
|
10
|
+
import { ContextRevisionCompiler } from "../context/context-revision-compiler";
|
|
11
|
+
import {
|
|
12
|
+
changedContextSurfaceComponents,
|
|
13
|
+
contextSurfaceChangeManifestHash,
|
|
14
|
+
contextSurfaceChanges,
|
|
15
|
+
createContextSurface,
|
|
16
|
+
sameContextSurface,
|
|
17
|
+
type StoredContextSurfaceV8,
|
|
18
|
+
} from "../context/context-surface";
|
|
19
|
+
import type { ToolCompletionInput } from "../context/protocol-frame";
|
|
20
|
+
import { CURRENT_RECALL_RETIREMENT_CONTRACT_VERSION } from "../context/recall-retirement-contract";
|
|
21
|
+
import type { AgentEventInput } from "../events/types";
|
|
22
|
+
import { type RuntimeIdFactory, type SessionId } from "../ids/runtime-id";
|
|
23
|
+
import { SessionError } from "../session/session-errors";
|
|
24
|
+
import type { SessionStore, StoredSkillActivation } from "../session/session-store";
|
|
25
|
+
import {
|
|
26
|
+
activeSkillManifestEntry,
|
|
27
|
+
skillCatalogManifest,
|
|
28
|
+
} from "../skills/skill-catalog";
|
|
29
|
+
import {
|
|
30
|
+
buildActiveSystemPrompt,
|
|
31
|
+
renderSkillActivationReceipt,
|
|
32
|
+
SkillActivationCoordinator,
|
|
33
|
+
} from "../skills/skill-context";
|
|
34
|
+
import type { SkillCatalogSnapshot } from "../skills/skill-loader";
|
|
35
|
+
import { type DefaultTooling } from "../tools/registry";
|
|
36
|
+
import type { ContextMeter } from "./context-meter";
|
|
37
|
+
import {
|
|
38
|
+
assertPreparedMatchesSurface,
|
|
39
|
+
boundedContextErrorCode,
|
|
40
|
+
elapsedMs,
|
|
41
|
+
} from "./runtime-context-events";
|
|
42
|
+
import {
|
|
43
|
+
type ContextSurfaceRefreshSummary,
|
|
44
|
+
type CreateRuntimeSessionInput,
|
|
45
|
+
type RuntimeSkillsSnapshot,
|
|
46
|
+
type SkillsUpdateSummary,
|
|
47
|
+
} from "./runtime-session-contracts";
|
|
48
|
+
import type { CommittedToolCompletion } from "./session-ledger";
|
|
49
|
+
import type { IterationIdentity } from "./types";
|
|
50
|
+
|
|
51
|
+
/** Coordinates skill activation and the corresponding persisted context surface. */
|
|
52
|
+
export class RuntimeSkills {
|
|
53
|
+
private skillCoordinator = new SkillActivationCoordinator();
|
|
54
|
+
get coordinator(): SkillActivationCoordinator {
|
|
55
|
+
return this.skillCoordinator;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
restoreCoordinator(coordinator: SkillActivationCoordinator): void {
|
|
59
|
+
this.skillCoordinator = coordinator;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
constructor(
|
|
63
|
+
private readonly sessionId: SessionId,
|
|
64
|
+
private readonly store: SessionStore,
|
|
65
|
+
private readonly input: Pick<
|
|
66
|
+
CreateRuntimeSessionInput,
|
|
67
|
+
"systemPrompt" | "projectInstruction" | "modelClient"
|
|
68
|
+
>,
|
|
69
|
+
private readonly skillCatalog: SkillCatalogSnapshot,
|
|
70
|
+
private readonly idFactory: RuntimeIdFactory,
|
|
71
|
+
private readonly contextMeter: ContextMeter,
|
|
72
|
+
private readonly toolDefinitions: DefaultTooling["registry"]["definitions"],
|
|
73
|
+
private readonly append: (event: AgentEventInput) => Promise<void>,
|
|
74
|
+
) {}
|
|
75
|
+
|
|
76
|
+
async refreshContextSurface(
|
|
77
|
+
candidateSurface: StoredContextSurfaceV8,
|
|
78
|
+
): Promise<ContextSurfaceRefreshSummary | undefined> {
|
|
79
|
+
const snapshot = this.store.loadContextSnapshot();
|
|
80
|
+
if (sameContextSurface(snapshot.surface, candidateSurface)) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const changes = contextSurfaceChanges(snapshot.surface, candidateSurface);
|
|
85
|
+
const changed = changedContextSurfaceComponents(changes);
|
|
86
|
+
if (changed.length === 0) {
|
|
87
|
+
throw new Error("Changed context surface has an empty change manifest.");
|
|
88
|
+
}
|
|
89
|
+
const startedAt = performance.now();
|
|
90
|
+
await this.append({
|
|
91
|
+
type: "context.revision.started",
|
|
92
|
+
sessionId: this.sessionId,
|
|
93
|
+
data: {
|
|
94
|
+
strategy: "surface_refresh",
|
|
95
|
+
reason: "resume",
|
|
96
|
+
baseRevisionNumber: snapshot.revision.revisionNumber,
|
|
97
|
+
changed,
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
let stage: "prepare" | "commit" | "activate" = "prepare";
|
|
102
|
+
let committed = false;
|
|
103
|
+
try {
|
|
104
|
+
const compiler = new ContextRevisionCompiler();
|
|
105
|
+
const active = compiler.compileActive(snapshot);
|
|
106
|
+
const candidateCompiled = compiler.compileProspective({
|
|
107
|
+
active,
|
|
108
|
+
canonical: snapshot.canonical,
|
|
109
|
+
activeOverrides: snapshot.activeOverrides,
|
|
110
|
+
addedOverrides: [],
|
|
111
|
+
activeSurface: snapshot.surface,
|
|
112
|
+
surface: candidateSurface,
|
|
113
|
+
});
|
|
114
|
+
const prepared = this.input.modelClient.prepare({
|
|
115
|
+
messages: candidateCompiled.entries.map((entry) => entry.message),
|
|
116
|
+
tools: [...candidateSurface.toolDefinitions],
|
|
117
|
+
});
|
|
118
|
+
assertPreparedMatchesSurface(prepared, candidateSurface);
|
|
119
|
+
|
|
120
|
+
stage = "commit";
|
|
121
|
+
const revision = this.store.commitSurfaceRefresh({
|
|
122
|
+
revisionId: this.idFactory.createContextRevisionId(),
|
|
123
|
+
expectedBaseRevisionId: snapshot.revision.revisionId,
|
|
124
|
+
expectedBaseRevisionNumber: snapshot.revision.revisionNumber,
|
|
125
|
+
expectedCanonicalThroughOrdinal: snapshot.canonical.messages.length,
|
|
126
|
+
expectedBaseActiveOverrideManifestSha256:
|
|
127
|
+
snapshot.revision.activeOverrideManifestSha256,
|
|
128
|
+
surface: candidateSurface,
|
|
129
|
+
changes,
|
|
130
|
+
changeManifestSha256: contextSurfaceChangeManifestHash(changes),
|
|
131
|
+
canonicalSequenceSha256: canonicalSequenceHash(snapshot.canonical),
|
|
132
|
+
renderedMessageSha256: renderedMessageHash(candidateCompiled.entries),
|
|
133
|
+
});
|
|
134
|
+
committed = true;
|
|
135
|
+
|
|
136
|
+
stage = "activate";
|
|
137
|
+
this.contextMeter.startRevision({
|
|
138
|
+
reason: "context_rebuilt",
|
|
139
|
+
requestConfigHash: prepared.requestConfigHash,
|
|
140
|
+
toolSchemaHash: prepared.toolSchemaHash,
|
|
141
|
+
});
|
|
142
|
+
const summary = Object.freeze({
|
|
143
|
+
previousRevisionNumber: snapshot.revision.revisionNumber,
|
|
144
|
+
revisionNumber: revision.revisionNumber,
|
|
145
|
+
changed,
|
|
146
|
+
toolCountBefore: snapshot.surface.toolDefinitions.length,
|
|
147
|
+
toolCountAfter: candidateSurface.toolDefinitions.length,
|
|
148
|
+
});
|
|
149
|
+
await this.append({
|
|
150
|
+
type: "context.revision.finished",
|
|
151
|
+
sessionId: this.sessionId,
|
|
152
|
+
data: {
|
|
153
|
+
strategy: "surface_refresh",
|
|
154
|
+
reason: "resume",
|
|
155
|
+
baseRevisionNumber: summary.previousRevisionNumber,
|
|
156
|
+
revisionNumber: summary.revisionNumber,
|
|
157
|
+
changed: summary.changed,
|
|
158
|
+
toolCountBefore: summary.toolCountBefore,
|
|
159
|
+
toolCountAfter: summary.toolCountAfter,
|
|
160
|
+
measuredAnchorCleared: true,
|
|
161
|
+
durationMs: elapsedMs(startedAt),
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
return summary;
|
|
165
|
+
} catch (error) {
|
|
166
|
+
await this.append({
|
|
167
|
+
type: "context.revision.failed",
|
|
168
|
+
sessionId: this.sessionId,
|
|
169
|
+
data: {
|
|
170
|
+
strategy: "surface_refresh",
|
|
171
|
+
reason: "resume",
|
|
172
|
+
stage,
|
|
173
|
+
errorCode: boundedContextErrorCode(
|
|
174
|
+
error instanceof SessionError
|
|
175
|
+
? error.code
|
|
176
|
+
: error instanceof Error
|
|
177
|
+
? error.name
|
|
178
|
+
: "CONTEXT_SURFACE_REFRESH_FAILED",
|
|
179
|
+
),
|
|
180
|
+
error: `Context surface refresh failed at ${stage}.`,
|
|
181
|
+
committed,
|
|
182
|
+
},
|
|
183
|
+
}).catch(() => undefined);
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
skills(): RuntimeSkillsSnapshot {
|
|
189
|
+
const activeNames = new Set(
|
|
190
|
+
this.skillCoordinator.activeEntries().map((entry) => entry.skill.name),
|
|
191
|
+
);
|
|
192
|
+
return Object.freeze({
|
|
193
|
+
skills: Object.freeze(
|
|
194
|
+
[...this.skillCatalog.skills.values()]
|
|
195
|
+
.sort((left, right) => compareText(left.name, right.name))
|
|
196
|
+
.map((skill) =>
|
|
197
|
+
Object.freeze({
|
|
198
|
+
name: skill.name,
|
|
199
|
+
description: skill.description,
|
|
200
|
+
scope: skill.scope,
|
|
201
|
+
active: activeNames.has(skill.name),
|
|
202
|
+
}),
|
|
203
|
+
),
|
|
204
|
+
),
|
|
205
|
+
shadowedNames: Object.freeze(
|
|
206
|
+
this.skillCatalog.shadowed.map((entry) => entry.name),
|
|
207
|
+
),
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
appendSkillsCatalogLoaded(): Promise<void> {
|
|
212
|
+
const activeNames = this.skillCoordinator
|
|
213
|
+
.activeEntries()
|
|
214
|
+
.map((entry) => entry.skill.name);
|
|
215
|
+
if (
|
|
216
|
+
this.skillCatalog.skills.size === 0 &&
|
|
217
|
+
activeNames.length === 0 &&
|
|
218
|
+
this.skillCatalog.shadowed.length === 0
|
|
219
|
+
) {
|
|
220
|
+
return Promise.resolve();
|
|
221
|
+
}
|
|
222
|
+
const skills = [...this.skillCatalog.skills.values()];
|
|
223
|
+
return this.append({
|
|
224
|
+
type: "skills.catalog.loaded",
|
|
225
|
+
sessionId: this.sessionId,
|
|
226
|
+
data: {
|
|
227
|
+
availableCount: skills.length,
|
|
228
|
+
projectCount: skills.filter((skill) => skill.scope === "project").length,
|
|
229
|
+
userCount: skills.filter((skill) => skill.scope === "user").length,
|
|
230
|
+
activeNames: Object.freeze(activeNames),
|
|
231
|
+
shadowedNames: Object.freeze(
|
|
232
|
+
this.skillCatalog.shadowed.map((entry) => entry.name),
|
|
233
|
+
),
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
onToolCompletionsCommitted(input: {
|
|
239
|
+
completions: readonly ToolCompletionInput[];
|
|
240
|
+
committed: readonly CommittedToolCompletion[];
|
|
241
|
+
}): void {
|
|
242
|
+
if (input.completions.length !== input.committed.length) {
|
|
243
|
+
throw new Error("Committed tool completion identity count does not match.");
|
|
244
|
+
}
|
|
245
|
+
for (let index = 0; index < input.completions.length; index += 1) {
|
|
246
|
+
const completion = input.completions[index];
|
|
247
|
+
const committed = input.committed[index];
|
|
248
|
+
if (
|
|
249
|
+
completion === undefined ||
|
|
250
|
+
committed === undefined ||
|
|
251
|
+
completion.call.toolCallId !== committed.toolCallId
|
|
252
|
+
) {
|
|
253
|
+
throw new Error("Committed tool completion identity is invalid.");
|
|
254
|
+
}
|
|
255
|
+
if (
|
|
256
|
+
completion.kind === "returned" &&
|
|
257
|
+
completion.raw.kind === "skill" &&
|
|
258
|
+
completion.raw.ok &&
|
|
259
|
+
completion.raw.status === "loaded"
|
|
260
|
+
) {
|
|
261
|
+
this.skillCoordinator.markPending(completion.raw.name);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async commitSkillSettlements(input: {
|
|
267
|
+
reason: "activation" | "resume";
|
|
268
|
+
unresolved: readonly StoredSkillActivation[];
|
|
269
|
+
candidateSurface?: StoredContextSurfaceV8;
|
|
270
|
+
activated?: readonly string[];
|
|
271
|
+
refreshed?: readonly string[];
|
|
272
|
+
deactivated?: readonly string[];
|
|
273
|
+
}): Promise<SkillsUpdateSummary> {
|
|
274
|
+
if (input.unresolved.length === 0) {
|
|
275
|
+
throw new Error("Agent Skills update requires unresolved activations.");
|
|
276
|
+
}
|
|
277
|
+
const snapshot = this.store.loadContextSnapshot();
|
|
278
|
+
const canonicalMessages = new Map(
|
|
279
|
+
snapshot.canonical.messages.map((message) => [message.messageId, message]),
|
|
280
|
+
);
|
|
281
|
+
const activeByName = new Map(
|
|
282
|
+
this.skillCoordinator
|
|
283
|
+
.activeEntries()
|
|
284
|
+
.map((entry) => [entry.skill.name, entry] as const),
|
|
285
|
+
);
|
|
286
|
+
const activated = new Set(input.activated ?? []);
|
|
287
|
+
const unavailable = new Set<string>();
|
|
288
|
+
const settlements: Array<{
|
|
289
|
+
activationMessageId: StoredSkillActivation["activationMessageId"];
|
|
290
|
+
name: string;
|
|
291
|
+
state: "promoted" | "rejected";
|
|
292
|
+
rejectionReason?: string;
|
|
293
|
+
}> = [];
|
|
294
|
+
const receipts = [];
|
|
295
|
+
for (const activation of [...input.unresolved].sort((left, right) =>
|
|
296
|
+
compareText(left.name, right.name),
|
|
297
|
+
)) {
|
|
298
|
+
const skill = this.skillCatalog.skills.get(activation.name);
|
|
299
|
+
const canPromote = activation.state === "dispatched" && skill !== undefined;
|
|
300
|
+
if (canPromote) {
|
|
301
|
+
const existing = activeByName.get(activation.name);
|
|
302
|
+
if (
|
|
303
|
+
existing !== undefined &&
|
|
304
|
+
existing.activationMessageId !== activation.activationMessageId
|
|
305
|
+
) {
|
|
306
|
+
throw new Error(
|
|
307
|
+
`Agent Skill ${activation.name} already has another active activation.`,
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
activeByName.set(activation.name, {
|
|
311
|
+
skill,
|
|
312
|
+
activationMessageId: activation.activationMessageId,
|
|
313
|
+
});
|
|
314
|
+
activated.add(activation.name);
|
|
315
|
+
}
|
|
316
|
+
const state = canPromote ? "promoted" : "rejected";
|
|
317
|
+
const rejectionReason =
|
|
318
|
+
state === "promoted"
|
|
319
|
+
? undefined
|
|
320
|
+
: activation.state === "pending"
|
|
321
|
+
? "not_dispatched"
|
|
322
|
+
: "unavailable";
|
|
323
|
+
if (rejectionReason === "unavailable") {
|
|
324
|
+
unavailable.add(activation.name);
|
|
325
|
+
}
|
|
326
|
+
settlements.push({
|
|
327
|
+
activationMessageId: activation.activationMessageId,
|
|
328
|
+
name: activation.name,
|
|
329
|
+
state,
|
|
330
|
+
...(rejectionReason === undefined ? {} : { rejectionReason }),
|
|
331
|
+
});
|
|
332
|
+
const message = canonicalMessages.get(activation.activationMessageId);
|
|
333
|
+
if (message?.role !== "tool") {
|
|
334
|
+
throw new Error(
|
|
335
|
+
`Agent Skill activation message ${activation.activationMessageId} is missing.`,
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
receipts.push(
|
|
339
|
+
renderSkillActivationReceipt({
|
|
340
|
+
message: {
|
|
341
|
+
messageId: message.messageId,
|
|
342
|
+
frameId: message.frameId,
|
|
343
|
+
ordinal: message.ordinal,
|
|
344
|
+
content: message.displayText,
|
|
345
|
+
contentSha256: message.contentSha256,
|
|
346
|
+
},
|
|
347
|
+
name: activation.name,
|
|
348
|
+
outcome:
|
|
349
|
+
state === "promoted"
|
|
350
|
+
? "promoted"
|
|
351
|
+
: rejectionReason === "unavailable"
|
|
352
|
+
? "unavailable"
|
|
353
|
+
: "rejected",
|
|
354
|
+
}),
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
const nextActive = Object.freeze(
|
|
358
|
+
[...activeByName.values()].sort((left, right) =>
|
|
359
|
+
compareText(left.skill.name, right.skill.name),
|
|
360
|
+
),
|
|
361
|
+
);
|
|
362
|
+
const createdAt = new Date().toISOString();
|
|
363
|
+
const definitions = this.toolDefinitions();
|
|
364
|
+
const renderedSystemPrompt = buildActiveSystemPrompt({
|
|
365
|
+
baseSystemPrompt: this.input.systemPrompt,
|
|
366
|
+
activeSkills: nextActive,
|
|
367
|
+
});
|
|
368
|
+
const surfacePrepared = this.input.modelClient.prepare({
|
|
369
|
+
messages: [{ role: "system", content: renderedSystemPrompt }],
|
|
370
|
+
tools: definitions,
|
|
371
|
+
});
|
|
372
|
+
const generatedSurface =
|
|
373
|
+
input.candidateSurface ??
|
|
374
|
+
createContextSurface({
|
|
375
|
+
surfaceId: this.idFactory.createContextSurfaceId(),
|
|
376
|
+
sessionId: this.sessionId,
|
|
377
|
+
systemPrompt: renderedSystemPrompt,
|
|
378
|
+
recallContractVersion: CURRENT_RECALL_RETIREMENT_CONTRACT_VERSION,
|
|
379
|
+
...(this.input.projectInstruction === undefined
|
|
380
|
+
? {}
|
|
381
|
+
: { projectInstruction: this.input.projectInstruction }),
|
|
382
|
+
skillCatalog: skillCatalogManifest(this.skillCatalog.skills.values()),
|
|
383
|
+
activeSkills: nextActive.map((entry) =>
|
|
384
|
+
activeSkillManifestEntry(entry.skill, entry.activationMessageId),
|
|
385
|
+
),
|
|
386
|
+
toolDefinitions: definitions,
|
|
387
|
+
prepared: surfacePrepared,
|
|
388
|
+
createdAt,
|
|
389
|
+
});
|
|
390
|
+
assertPreparedMatchesSurface(surfacePrepared, generatedSurface);
|
|
391
|
+
const surface = sameContextSurface(snapshot.surface, generatedSurface)
|
|
392
|
+
? snapshot.surface
|
|
393
|
+
: generatedSurface;
|
|
394
|
+
const startedAt = performance.now();
|
|
395
|
+
await this.append({
|
|
396
|
+
type: "context.revision.started",
|
|
397
|
+
sessionId: this.sessionId,
|
|
398
|
+
data: {
|
|
399
|
+
strategy: "skills_update",
|
|
400
|
+
reason: input.reason,
|
|
401
|
+
baseRevisionNumber: snapshot.revision.revisionNumber,
|
|
402
|
+
names: Object.freeze(
|
|
403
|
+
input.unresolved.map((entry) => entry.name).sort(compareText),
|
|
404
|
+
),
|
|
405
|
+
},
|
|
406
|
+
});
|
|
407
|
+
let stage: "prepare" | "commit" | "activate" = "prepare";
|
|
408
|
+
let committed = false;
|
|
409
|
+
try {
|
|
410
|
+
const revision = commitAgentSkillsContextUpdate({
|
|
411
|
+
store: this.store,
|
|
412
|
+
contextMeter: this.contextMeter,
|
|
413
|
+
idFactory: this.idFactory,
|
|
414
|
+
snapshot,
|
|
415
|
+
surface,
|
|
416
|
+
addedOverrides: receipts,
|
|
417
|
+
settlements,
|
|
418
|
+
});
|
|
419
|
+
committed = true;
|
|
420
|
+
stage = "activate";
|
|
421
|
+
this.skillCoordinator.replaceActive(nextActive);
|
|
422
|
+
this.skillCoordinator.settle(
|
|
423
|
+
input.unresolved.map((activation) => activation.name),
|
|
424
|
+
);
|
|
425
|
+
const summary = Object.freeze({
|
|
426
|
+
previousRevisionNumber: snapshot.revision.revisionNumber,
|
|
427
|
+
revisionNumber: revision.revisionNumber,
|
|
428
|
+
activated: Object.freeze([...activated].sort()),
|
|
429
|
+
refreshed: Object.freeze([...(input.refreshed ?? [])].sort()),
|
|
430
|
+
deactivated: Object.freeze([...(input.deactivated ?? [])].sort()),
|
|
431
|
+
unavailable: Object.freeze([...unavailable].sort()),
|
|
432
|
+
addedOverrideCount: receipts.length,
|
|
433
|
+
});
|
|
434
|
+
await this.append({
|
|
435
|
+
type: "context.revision.finished",
|
|
436
|
+
sessionId: this.sessionId,
|
|
437
|
+
data: {
|
|
438
|
+
strategy: "skills_update",
|
|
439
|
+
reason: input.reason,
|
|
440
|
+
baseRevisionNumber: summary.previousRevisionNumber,
|
|
441
|
+
revisionNumber: summary.revisionNumber,
|
|
442
|
+
activated: summary.activated,
|
|
443
|
+
refreshed: summary.refreshed,
|
|
444
|
+
deactivated: summary.deactivated,
|
|
445
|
+
unavailable: summary.unavailable,
|
|
446
|
+
addedOverrideCount: summary.addedOverrideCount,
|
|
447
|
+
measuredAnchorCleared: true,
|
|
448
|
+
durationMs: elapsedMs(startedAt),
|
|
449
|
+
},
|
|
450
|
+
});
|
|
451
|
+
return summary;
|
|
452
|
+
} catch (error) {
|
|
453
|
+
if (error instanceof ContextManagerError) {
|
|
454
|
+
committed = error.committed;
|
|
455
|
+
stage =
|
|
456
|
+
error.stage === "commit"
|
|
457
|
+
? "commit"
|
|
458
|
+
: error.stage === "activate"
|
|
459
|
+
? "activate"
|
|
460
|
+
: "prepare";
|
|
461
|
+
}
|
|
462
|
+
await this.append({
|
|
463
|
+
type: "context.revision.failed",
|
|
464
|
+
sessionId: this.sessionId,
|
|
465
|
+
data: {
|
|
466
|
+
strategy: "skills_update",
|
|
467
|
+
reason: input.reason,
|
|
468
|
+
stage,
|
|
469
|
+
errorCode: boundedContextErrorCode(
|
|
470
|
+
error instanceof ContextManagerError
|
|
471
|
+
? error.code
|
|
472
|
+
: error instanceof SessionError
|
|
473
|
+
? error.code
|
|
474
|
+
: error instanceof Error
|
|
475
|
+
? error.name
|
|
476
|
+
: "SKILLS_UPDATE_VALIDATION_FAILED",
|
|
477
|
+
),
|
|
478
|
+
error: `Agent Skills update failed at ${stage}.`,
|
|
479
|
+
committed,
|
|
480
|
+
},
|
|
481
|
+
}).catch(() => undefined);
|
|
482
|
+
throw error;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
async settleClosedTurnSkills(): Promise<void> {
|
|
487
|
+
const unresolved = this.store.loadSkillActivations(["pending", "dispatched"]);
|
|
488
|
+
if (unresolved.length === 0) {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
const summary = await this.commitSkillSettlements({
|
|
492
|
+
reason: "activation",
|
|
493
|
+
unresolved,
|
|
494
|
+
});
|
|
495
|
+
await this.append({
|
|
496
|
+
type: "skills.updated",
|
|
497
|
+
sessionId: this.sessionId,
|
|
498
|
+
data: {
|
|
499
|
+
reason: "activation",
|
|
500
|
+
activated: summary.activated,
|
|
501
|
+
refreshed: summary.refreshed,
|
|
502
|
+
deactivated: summary.deactivated,
|
|
503
|
+
unavailable: summary.unavailable,
|
|
504
|
+
revisionNumber: summary.revisionNumber,
|
|
505
|
+
},
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
markModelDispatch(input: {
|
|
510
|
+
iteration: IterationIdentity;
|
|
511
|
+
built: BuiltContextRequest;
|
|
512
|
+
}): void {
|
|
513
|
+
const pending = this.store.loadSkillActivations(["pending"]);
|
|
514
|
+
if (pending.length === 0) {
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
const visibleCanonicalMessageIds = new Set(
|
|
518
|
+
input.built.compiled.entries
|
|
519
|
+
.filter(
|
|
520
|
+
(entry) =>
|
|
521
|
+
entry.representation === "canonical" && entry.message.role === "tool",
|
|
522
|
+
)
|
|
523
|
+
.map((entry) => entry.messageId),
|
|
524
|
+
);
|
|
525
|
+
const included = pending.filter((activation) =>
|
|
526
|
+
visibleCanonicalMessageIds.has(activation.activationMessageId),
|
|
527
|
+
);
|
|
528
|
+
if (included.length === 0) {
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
const dispatched = this.store.markSkillActivationsDispatched({
|
|
532
|
+
iterationId: input.iteration.iterationId,
|
|
533
|
+
activationMessageIds: included.map(
|
|
534
|
+
(activation) => activation.activationMessageId,
|
|
535
|
+
),
|
|
536
|
+
});
|
|
537
|
+
this.skillCoordinator.markDispatched(
|
|
538
|
+
dispatched.map((activation) => activation.name),
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
function compareText(left: string, right: string): number {
|
|
543
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
544
|
+
}
|
|
@@ -34,13 +34,14 @@ Use WebSearch, when it is available, to look up current information on the web s
|
|
|
34
34
|
Use WebFetch to read the content of a specific URL, such as documentation pages found via WebSearch.
|
|
35
35
|
Prefer Read for reading files instead of using cat on large files.
|
|
36
36
|
Prefer Write or Edit for changing files instead of shell redirection.
|
|
37
|
-
Use run_in_background=true for dev servers
|
|
37
|
+
Use run_in_background=true for persistent processes such as dev servers and watch commands, or when you have independent work to do while a command runs.
|
|
38
|
+
For finite commands whose result is needed next, such as builds, tests, and checks, prefer foreground execution when no independent work remains. Set a sufficient foreground timeout; the call returns as soon as the command finishes.
|
|
38
39
|
Do not add & to Bash commands; background execution is handled by the Bash tool.
|
|
39
40
|
Use Bash with tty=true for REPLs, debuggers, interactive prompts, and terminal applications that require a controlling terminal.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
TaskList lists background shell tasks in the current session.
|
|
42
|
+
TaskOutput reports a task's current status, latest output, or current terminal screen. For non-PTY logs, offset (1-based) and limit select consecutive lines instead of the default head/tail preview; PTY tasks ignore them. Range truncated=true means byte limits shortened requested content, not that lines outside the range exist. The last observed line of a running log may still be growing; rereading it when polling captures further changes to that line.
|
|
43
|
+
TaskInput sends characters to a PTY task identified by the returned task ID. TaskInput does not append Enter; an explicit \\n sends Enter, \\u0003 sends Ctrl-C, and chars="" waits without writing.
|
|
44
|
+
TaskStop stops a background task that is no longer needed.
|
|
44
45
|
Do not use ad-hoc kill commands to manage tasks created by Bash.
|
|
45
46
|
Bash and TaskOutput return outputFilePath. Use Read on outputFilePath when you need complete or paginated output.
|
|
46
47
|
Do not send passwords, tokens, or other secrets through TaskInput because tool arguments are stored in session history.
|
|
@@ -1,122 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
renderRecallRetirementContract,
|
|
8
|
-
} from "./recall-retirement-contract";
|
|
9
|
-
|
|
10
|
-
export const I4_ACTIVE_RECALL_QUALIFICATION = Object.freeze({
|
|
11
|
-
qualificationId: "deepseek-v4-flash-floor-v1",
|
|
12
|
-
evaluatedProfile: "deepseek-v4-flash",
|
|
13
|
-
manifestVersion: "active-recall-manifest-v1",
|
|
14
|
-
manifestSha256: "093679e221e02b71ba5acf54693faa7a299d05dd25f6faabbeb84645d4db4d2d",
|
|
15
|
-
graderVersion: "active-recall-deterministic-grader-v1",
|
|
16
|
-
fixtureVersion: "active-recall-long-session-fixture-v1",
|
|
17
|
-
policyVersion: "active-recall-qualification-policy-v1",
|
|
18
|
-
policySha256: "77ca611594d4e9b7b5a597a3a33e35fcaaffae284dc2ac9953ce9a63cce1c009",
|
|
19
|
-
positiveReportSha256:
|
|
20
|
-
"e827e5e94171328bb2dd7fcaeff91881f04bdfc78361a45e2548da323229b02a",
|
|
21
|
-
negativeReportSha256:
|
|
22
|
-
"ed379843aee0f193f398a4dff9a18ed338f0edab2cbaf9b628d0dfc402d925a4",
|
|
23
|
-
resolvedModel: "deepseek-v4-flash",
|
|
24
|
-
recallContractVersion: CURRENT_RECALL_RETIREMENT_CONTRACT_VERSION,
|
|
25
|
-
recallContractSha256:
|
|
26
|
-
"3b6d1a452efea1db5920eb13542571b038667ef4f635551bea375bda6562a39f",
|
|
27
|
-
recallToolDefinitionSha256:
|
|
28
|
-
"e63ada7cdf9591d1e933cf5e190ea30586e02bbf73aeb5e648db449d75aae009",
|
|
29
|
-
metrics: Object.freeze({
|
|
30
|
-
fullHistoryTaskSuccessRate: 0.9667,
|
|
31
|
-
swapOnlyTaskSuccessRate: 0.9667,
|
|
32
|
-
recallOnlyTaskSuccessRate: 1,
|
|
33
|
-
recallOnlyActiveRecallRate: 1,
|
|
34
|
-
recallOnlySearchGetSuccessRate: 0.3333,
|
|
35
|
-
minimumCounterfactualGroupTaskSuccessRate: 1,
|
|
36
|
-
invalidRecallCallsPerRecallOnlyTrial: 0,
|
|
37
|
-
negativeUnnecessaryRecallRate: 0,
|
|
38
|
-
recallOnlyTokenRatioToFullHistory: 1.3739,
|
|
39
|
-
recallOnlyLatencyRatioToFullHistory: 1.207,
|
|
40
|
-
}),
|
|
41
|
-
passed: true,
|
|
42
|
-
} as const);
|
|
43
|
-
|
|
44
|
-
export const I4_SWAP_ONLY_QUALIFICATION_ID = "swap-only-engineering-v1";
|
|
45
|
-
|
|
46
|
-
export type ActiveRecallQualificationEvidence = {
|
|
47
|
-
readonly qualificationId: string;
|
|
48
|
-
readonly recallContractVersion: string;
|
|
49
|
-
readonly recallContractSha256: string;
|
|
50
|
-
readonly recallToolDefinitionSha256: string;
|
|
51
|
-
readonly passed: boolean;
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
export type ContextAutomationDecision = {
|
|
55
|
-
readonly automaticSwapOnly: boolean;
|
|
1
|
+
/** Product defaults only. Evaluation results and model identities never select these flags.
|
|
2
|
+
* Planners and session boundaries independently validate each operation before execution.
|
|
3
|
+
*/
|
|
4
|
+
export type ContextAutomationPolicy = {
|
|
5
|
+
readonly policyId: string;
|
|
6
|
+
readonly automaticSwap: boolean;
|
|
56
7
|
readonly automaticPrefixRetirement: boolean;
|
|
57
|
-
readonly reason:
|
|
58
|
-
| "qualified"
|
|
59
|
-
| "swap_only_qualified"
|
|
60
|
-
| "qualification_pending"
|
|
61
|
-
| "unprofiled_model"
|
|
62
|
-
| "recall_contract_mismatch"
|
|
63
|
-
| "recall_tool_mismatch";
|
|
64
|
-
readonly qualificationId?: string;
|
|
65
8
|
};
|
|
66
9
|
|
|
67
|
-
export
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
},
|
|
72
|
-
evidence: ActiveRecallQualificationEvidence = I4_ACTIVE_RECALL_QUALIFICATION,
|
|
73
|
-
): ContextAutomationDecision {
|
|
74
|
-
if (input.profileName === undefined) {
|
|
75
|
-
return disabled("unprofiled_model");
|
|
76
|
-
}
|
|
77
|
-
if (
|
|
78
|
-
input.surface.recallContractVersion !== evidence.recallContractVersion ||
|
|
79
|
-
sha256(renderRecallRetirementContract()) !== evidence.recallContractSha256
|
|
80
|
-
) {
|
|
81
|
-
return disabled("recall_contract_mismatch");
|
|
82
|
-
}
|
|
83
|
-
const recallTools = input.surface.toolDefinitions.filter(
|
|
84
|
-
(definition) =>
|
|
85
|
-
definition.name === "RecallSearch" || definition.name === "RecallGet",
|
|
86
|
-
);
|
|
87
|
-
if (
|
|
88
|
-
recallTools.length !== 2 ||
|
|
89
|
-
toolDefinitionsHash(recallTools) !== evidence.recallToolDefinitionSha256 ||
|
|
90
|
-
toolDefinitionsHash(RECALL_TOOL_DEFINITIONS) !== evidence.recallToolDefinitionSha256
|
|
91
|
-
) {
|
|
92
|
-
return disabled("recall_tool_mismatch");
|
|
93
|
-
}
|
|
94
|
-
if (!evidence.passed) {
|
|
95
|
-
return Object.freeze({
|
|
96
|
-
automaticSwapOnly: true,
|
|
97
|
-
automaticPrefixRetirement: false,
|
|
98
|
-
reason: "swap_only_qualified",
|
|
99
|
-
qualificationId: I4_SWAP_ONLY_QUALIFICATION_ID,
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
return Object.freeze({
|
|
103
|
-
automaticSwapOnly: true,
|
|
10
|
+
export const DEFAULT_CONTEXT_AUTOMATION_POLICY: ContextAutomationPolicy = Object.freeze(
|
|
11
|
+
{
|
|
12
|
+
policyId: "context-automation-v1",
|
|
13
|
+
automaticSwap: true,
|
|
104
14
|
automaticPrefixRetirement: true,
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function disabled(
|
|
111
|
-
reason: Exclude<ContextAutomationDecision["reason"], "qualified">,
|
|
112
|
-
): ContextAutomationDecision {
|
|
113
|
-
return Object.freeze({
|
|
114
|
-
automaticSwapOnly: false,
|
|
115
|
-
automaticPrefixRetirement: false,
|
|
116
|
-
reason,
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function toolDefinitionsHash(definitions: readonly ToolDefinition[]): string {
|
|
121
|
-
return sha256(stableJsonStringify(definitions));
|
|
122
|
-
}
|
|
15
|
+
},
|
|
16
|
+
);
|