@kindgi/agents 0.1.3 → 0.1.4-rc.1
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/dist/blocks.d.ts +103 -0
- package/dist/blocks.d.ts.map +1 -0
- package/dist/blocks.js +149 -0
- package/dist/blocks.js.map +1 -0
- package/dist/define.d.ts +17 -2
- package/dist/define.d.ts.map +1 -1
- package/dist/define.js +73 -6
- package/dist/define.js.map +1 -1
- package/dist/handlers/compose-result.d.ts.map +1 -1
- package/dist/handlers/compose-result.js +3 -0
- package/dist/handlers/compose-result.js.map +1 -1
- package/dist/handlers/context.d.ts +10 -0
- package/dist/handlers/context.d.ts.map +1 -1
- package/dist/handlers/dispatch-tools.d.ts.map +1 -1
- package/dist/handlers/dispatch-tools.js +51 -1
- package/dist/handlers/dispatch-tools.js.map +1 -1
- package/dist/handlers/errors.d.ts +12 -1
- package/dist/handlers/errors.d.ts.map +1 -1
- package/dist/handlers/errors.js.map +1 -1
- package/dist/handlers/evaluate-guardrails.d.ts.map +1 -1
- package/dist/handlers/evaluate-guardrails.js +2 -0
- package/dist/handlers/evaluate-guardrails.js.map +1 -1
- package/dist/handlers/model-call.d.ts.map +1 -1
- package/dist/handlers/model-call.js +11 -0
- package/dist/handlers/model-call.js.map +1 -1
- package/dist/handlers/public-types.d.ts +21 -1
- package/dist/handlers/public-types.d.ts.map +1 -1
- package/dist/handlers/rehydrate.d.ts.map +1 -1
- package/dist/handlers/rehydrate.js +6 -1
- package/dist/handlers/rehydrate.js.map +1 -1
- package/dist/handlers/render-prompt.d.ts.map +1 -1
- package/dist/handlers/render-prompt.js +4 -1
- package/dist/handlers/render-prompt.js.map +1 -1
- package/dist/handlers/replay.d.ts +143 -0
- package/dist/handlers/replay.d.ts.map +1 -0
- package/dist/handlers/replay.js +177 -0
- package/dist/handlers/replay.js.map +1 -0
- package/dist/handlers/resolve-blocks.d.ts +32 -0
- package/dist/handlers/resolve-blocks.d.ts.map +1 -0
- package/dist/handlers/resolve-blocks.js +129 -0
- package/dist/handlers/resolve-blocks.js.map +1 -0
- package/dist/handlers/resolve-tools.d.ts +6 -2
- package/dist/handlers/resolve-tools.d.ts.map +1 -1
- package/dist/handlers/resolve-tools.js +59 -27
- package/dist/handlers/resolve-tools.js.map +1 -1
- package/dist/handlers/result-shape.d.ts +7 -0
- package/dist/handlers/result-shape.d.ts.map +1 -1
- package/dist/handlers/result-shape.js.map +1 -1
- package/dist/handlers/run-retrievals.d.ts.map +1 -1
- package/dist/handlers/run-retrievals.js +34 -17
- package/dist/handlers/run-retrievals.js.map +1 -1
- package/dist/handlers/run-snapshot.d.ts.map +1 -1
- package/dist/handlers/run-snapshot.js +1 -0
- package/dist/handlers/run-snapshot.js.map +1 -1
- package/dist/handlers/setup.d.ts +2 -0
- package/dist/handlers/setup.d.ts.map +1 -1
- package/dist/handlers/setup.js +14 -2
- package/dist/handlers/setup.js.map +1 -1
- package/dist/handlers/turn-environment.d.ts +13 -2
- package/dist/handlers/turn-environment.d.ts.map +1 -1
- package/dist/handlers/turn-environment.js +12 -3
- package/dist/handlers/turn-environment.js.map +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/invoke.d.ts.map +1 -1
- package/dist/invoke.js +3 -0
- package/dist/invoke.js.map +1 -1
- package/dist/pins.d.ts +54 -0
- package/dist/pins.d.ts.map +1 -0
- package/dist/pins.js +51 -0
- package/dist/pins.js.map +1 -0
- package/dist/prompt.d.ts +12 -2
- package/dist/prompt.d.ts.map +1 -1
- package/dist/prompt.js +42 -5
- package/dist/prompt.js.map +1 -1
- package/dist/run-snapshot-binding.d.ts +5 -0
- package/dist/run-snapshot-binding.d.ts.map +1 -1
- package/dist/schema.d.ts +17 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +5 -0
- package/dist/schema.js.map +1 -1
- package/dist/streaming.d.ts +5 -0
- package/dist/streaming.d.ts.map +1 -1
- package/dist/streaming.js.map +1 -1
- package/dist/types.d.ts +50 -1
- package/dist/types.d.ts.map +1 -1
- package/migrations/0004_stormy_moondragon.sql +1 -0
- package/migrations/meta/0004_snapshot.json +327 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +15 -15
- package/src/blocks.ts +233 -0
- package/src/define.ts +84 -7
- package/src/handlers/compose-result.ts +3 -0
- package/src/handlers/context.ts +10 -0
- package/src/handlers/dispatch-tools.ts +54 -1
- package/src/handlers/errors.ts +13 -0
- package/src/handlers/evaluate-guardrails.ts +2 -0
- package/src/handlers/model-call.ts +14 -0
- package/src/handlers/public-types.ts +22 -1
- package/src/handlers/rehydrate.ts +21 -6
- package/src/handlers/render-prompt.ts +13 -7
- package/src/handlers/replay.ts +314 -0
- package/src/handlers/resolve-blocks.ts +188 -0
- package/src/handlers/resolve-tools.ts +76 -28
- package/src/handlers/result-shape.ts +8 -0
- package/src/handlers/run-retrievals.ts +44 -23
- package/src/handlers/run-snapshot.ts +1 -0
- package/src/handlers/setup.ts +13 -2
- package/src/handlers/turn-environment.ts +28 -3
- package/src/index.ts +37 -0
- package/src/invoke.ts +3 -0
- package/src/pins.ts +98 -0
- package/src/prompt.ts +53 -5
- package/src/run-snapshot-binding.ts +6 -0
- package/src/schema.ts +5 -0
- package/src/streaming.ts +5 -0
- package/src/types.ts +53 -1
package/src/define.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
loadZodConverterSync,
|
|
10
10
|
toJSONSchemaSync,
|
|
11
11
|
} from '@kindgi/schema';
|
|
12
|
+
import { pickVersion } from '@kindgi/tools';
|
|
12
13
|
import type { Result, Semver } from '@kindgi/types';
|
|
13
14
|
|
|
14
15
|
import type { InvalidAgentError } from './errors.js';
|
|
@@ -16,8 +17,10 @@ import type {
|
|
|
16
17
|
Agent,
|
|
17
18
|
AgentId,
|
|
18
19
|
AgentOutputSpec,
|
|
20
|
+
BlockRef,
|
|
19
21
|
ConversationPolicy,
|
|
20
22
|
PromptParameter,
|
|
23
|
+
PromptRef,
|
|
21
24
|
RetrievalIntent,
|
|
22
25
|
ToolRef,
|
|
23
26
|
TurnBudget,
|
|
@@ -48,9 +51,10 @@ export function defineAgent(spec: DefineAgentSpec): Result<Agent, InvalidAgentEr
|
|
|
48
51
|
const issues: Issue[] = [
|
|
49
52
|
...validateIdentity(spec),
|
|
50
53
|
...validateContent(spec),
|
|
54
|
+
...validateBlockRefs(spec),
|
|
51
55
|
...validateArrays(spec),
|
|
52
56
|
...validateRetrieval(spec),
|
|
53
|
-
...
|
|
57
|
+
...validatePromptParameters(spec.parameters),
|
|
54
58
|
...validateBudget(spec.budget),
|
|
55
59
|
...validateConversationPolicy(spec.conversationPolicy),
|
|
56
60
|
...validateToolErrors(spec.toolErrors),
|
|
@@ -126,8 +130,16 @@ export interface DefineAgentSpec {
|
|
|
126
130
|
* The system prompt. Sent to the model with every turn as the
|
|
127
131
|
* baseline instructions. Load-bearing — this is where you shape
|
|
128
132
|
* the agent's behavior (persona, output format, tool-use policy).
|
|
133
|
+
*
|
|
134
|
+
* Or a prompt block by range (`{ prompt: 'acme.intake-prompt',
|
|
135
|
+
* version: '^1.0.0' }`), whose template and parameters are used
|
|
136
|
+
* instead; then `parameters` stays unset (the block declares them).
|
|
129
137
|
*/
|
|
130
|
-
readonly instructions: string;
|
|
138
|
+
readonly instructions: string | PromptRef;
|
|
139
|
+
/** Settings blocks the agent reads, by range (see `Agent.settings`). */
|
|
140
|
+
readonly settings?: readonly BlockRef[];
|
|
141
|
+
/** A model-settings block, by range (see `Agent.modelSettings`). */
|
|
142
|
+
readonly modelSettings?: BlockRef;
|
|
131
143
|
/**
|
|
132
144
|
* Required capabilities the agent needs from a `ModelProvider`.
|
|
133
145
|
* Typically one entry: `[{ needs: [{ feature: 'tool-use' }] }]`
|
|
@@ -248,8 +260,19 @@ function validateIdentity(spec: DefineAgentSpec): Issue[] {
|
|
|
248
260
|
|
|
249
261
|
function validateContent(spec: DefineAgentSpec): Issue[] {
|
|
250
262
|
const out: Issue[] = [];
|
|
251
|
-
if (typeof spec.instructions
|
|
252
|
-
out.push(
|
|
263
|
+
if (typeof spec.instructions === 'object' && spec.instructions !== null) {
|
|
264
|
+
out.push(...blockRefIssues(spec.instructions, '/instructions', 'prompt'));
|
|
265
|
+
if (spec.parameters !== undefined) {
|
|
266
|
+
out.push({
|
|
267
|
+
path: '/parameters',
|
|
268
|
+
message: 'the prompt block declares the parameters: leave parameters unset',
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
} else if (typeof spec.instructions !== 'string' || spec.instructions.trim().length === 0) {
|
|
272
|
+
out.push({
|
|
273
|
+
path: '/instructions',
|
|
274
|
+
message: 'instructions must be a non-empty string, or a prompt block { prompt, version }',
|
|
275
|
+
});
|
|
253
276
|
}
|
|
254
277
|
if (!Array.isArray(spec.capabilities) || spec.capabilities.length === 0) {
|
|
255
278
|
out.push({
|
|
@@ -260,6 +283,56 @@ function validateContent(spec: DefineAgentSpec): Issue[] {
|
|
|
260
283
|
return out;
|
|
261
284
|
}
|
|
262
285
|
|
|
286
|
+
const BLOCK_ID = /^[a-z0-9][a-z0-9-]*(?:\.[a-z0-9][a-z0-9-]*)+$/;
|
|
287
|
+
|
|
288
|
+
/** A block reference: a dotted block id and a valid semver range. */
|
|
289
|
+
function blockRefIssues(ref: unknown, path: string, idKey: 'prompt' | 'id'): Issue[] {
|
|
290
|
+
if (ref === null || typeof ref !== 'object') {
|
|
291
|
+
return [{ path, message: `must be { ${idKey}, version }` }];
|
|
292
|
+
}
|
|
293
|
+
const r = ref as Record<string, unknown>;
|
|
294
|
+
const out: Issue[] = [];
|
|
295
|
+
if (typeof r[idKey] !== 'string' || !BLOCK_ID.test(r[idKey] as string)) {
|
|
296
|
+
out.push({
|
|
297
|
+
path: `${path}/${idKey}`,
|
|
298
|
+
message: 'must be a dotted lowercase block id, e.g. "acme.weights"',
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
if (typeof r.version !== 'string' || pickVersion([], r.version).kind === 'invalid-range') {
|
|
302
|
+
out.push({
|
|
303
|
+
path: `${path}/version`,
|
|
304
|
+
message: 'must be a semver range, e.g. "^1.0.0" or "1.2.0"',
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
return out;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** Settings and model-settings references: valid, and each block named once. */
|
|
311
|
+
function validateBlockRefs(spec: DefineAgentSpec): Issue[] {
|
|
312
|
+
const out: Issue[] = [];
|
|
313
|
+
const seen = new Set<string>();
|
|
314
|
+
const once = (id: unknown, path: string) => {
|
|
315
|
+
if (typeof id !== 'string') return;
|
|
316
|
+
if (seen.has(id)) out.push({ path, message: `settings block "${id}" is referenced twice` });
|
|
317
|
+
seen.add(id);
|
|
318
|
+
};
|
|
319
|
+
if (spec.settings !== undefined) {
|
|
320
|
+
if (!Array.isArray(spec.settings)) {
|
|
321
|
+
out.push({ path: '/settings', message: 'settings must be an array of { id, version }' });
|
|
322
|
+
} else {
|
|
323
|
+
spec.settings.forEach((ref, i) => {
|
|
324
|
+
out.push(...blockRefIssues(ref, `/settings/${i}`, 'id'));
|
|
325
|
+
once((ref as { id?: unknown }).id, `/settings/${i}/id`);
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (spec.modelSettings !== undefined) {
|
|
330
|
+
out.push(...blockRefIssues(spec.modelSettings, '/modelSettings', 'id'));
|
|
331
|
+
once((spec.modelSettings as { id?: unknown }).id, '/modelSettings/id');
|
|
332
|
+
}
|
|
333
|
+
return out;
|
|
334
|
+
}
|
|
335
|
+
|
|
263
336
|
function validateArrays(spec: DefineAgentSpec): Issue[] {
|
|
264
337
|
const out: Issue[] = [];
|
|
265
338
|
if (!Array.isArray(spec.tools)) {
|
|
@@ -334,9 +407,10 @@ function validateIntent(intent: RetrievalIntent, i: number): Issue[] {
|
|
|
334
407
|
}
|
|
335
408
|
|
|
336
409
|
const VALID_PARAM_TYPES = new Set(['string', 'number', 'boolean', 'date']);
|
|
337
|
-
const AUTO_INJECTED_NAMES = new Set(['today', 'now', 'agent', 'conversation']);
|
|
410
|
+
const AUTO_INJECTED_NAMES = new Set(['today', 'now', 'agent', 'conversation', 'settings']);
|
|
338
411
|
|
|
339
|
-
|
|
412
|
+
/** An agent's or a prompt block's declared parameters: the problems, none when they're valid. */
|
|
413
|
+
export function validatePromptParameters(parameters?: readonly PromptParameter[]): Issue[] {
|
|
340
414
|
if (parameters === undefined) return [];
|
|
341
415
|
const out: Issue[] = [];
|
|
342
416
|
const seen = new Set<string>();
|
|
@@ -566,7 +640,10 @@ function buildAgent(spec: DefineAgentSpec, output: AgentOutputSpec | undefined):
|
|
|
566
640
|
version: spec.version as Semver,
|
|
567
641
|
name: spec.name,
|
|
568
642
|
...(spec.description !== undefined && { description: spec.description }),
|
|
569
|
-
instructions:
|
|
643
|
+
instructions:
|
|
644
|
+
typeof spec.instructions === 'string' ? spec.instructions : { ...spec.instructions },
|
|
645
|
+
...(spec.settings !== undefined && { settings: spec.settings.map((r) => ({ ...r })) }),
|
|
646
|
+
...(spec.modelSettings !== undefined && { modelSettings: { ...spec.modelSettings } }),
|
|
570
647
|
capabilities: spec.capabilities.map((c) => ({ ...c })),
|
|
571
648
|
tools: [...spec.tools],
|
|
572
649
|
retrieval: spec.retrieval.map((r) => ({ ...r })),
|
|
@@ -8,6 +8,7 @@ import type { AgentTurnResult, AgentTurnUsage } from './result-shape.js';
|
|
|
8
8
|
|
|
9
9
|
import type { TurnContext } from './context.js';
|
|
10
10
|
import { throwAgentTurnFailure } from './errors.js';
|
|
11
|
+
import { replayReport } from './replay.js';
|
|
11
12
|
import { parseJsonAnswer } from './structured-output.js';
|
|
12
13
|
|
|
13
14
|
/** The final answer as JSON — `budget-check` already validated it against the schema. */
|
|
@@ -45,6 +46,7 @@ export function buildComposeResultHandler(ctx: TurnContext): NodeHandler {
|
|
|
45
46
|
model: ctx.model?.name ?? 'unknown',
|
|
46
47
|
};
|
|
47
48
|
|
|
49
|
+
const replay = replayReport(ctx);
|
|
48
50
|
const result: AgentTurnResult = {
|
|
49
51
|
runId: kctx.runId,
|
|
50
52
|
conversationId: ctx.input.conversationId,
|
|
@@ -70,6 +72,7 @@ export function buildComposeResultHandler(ctx: TurnContext): NodeHandler {
|
|
|
70
72
|
}),
|
|
71
73
|
...(ctx.persistedProvenance !== undefined && { provenance: ctx.persistedProvenance }),
|
|
72
74
|
...(kctx.dryRun && { dryRun: true }),
|
|
75
|
+
...(replay !== undefined && { replay }),
|
|
73
76
|
};
|
|
74
77
|
|
|
75
78
|
await emitTurnEvent(ctx.bindings.onEvent, {
|
package/src/handlers/context.ts
CHANGED
|
@@ -19,6 +19,7 @@ import type { ProvenanceBindings } from '../provenance-emit.js';
|
|
|
19
19
|
import type { Agent, Conversation, ConversationMessage, RetrievedFact } from '../types.js';
|
|
20
20
|
|
|
21
21
|
import type { HitlBindings, InvokeAgentBindings, InvokeAgentInput } from './public-types.js';
|
|
22
|
+
import type { TurnBlocks } from './resolve-blocks.js';
|
|
22
23
|
import type { ToolErrorPolicy } from './tool-errors.js';
|
|
23
24
|
|
|
24
25
|
/**
|
|
@@ -53,6 +54,8 @@ export interface TurnContext {
|
|
|
53
54
|
* Populated by `setup` — resolved tools (per-name map + model
|
|
54
55
|
* definitions).
|
|
55
56
|
*/
|
|
57
|
+
/** The data blocks the turn runs with (set by setup; none when the agent references none). */
|
|
58
|
+
blocks?: TurnBlocks;
|
|
56
59
|
tools?: {
|
|
57
60
|
readonly definitions: readonly ModelToolDefinition[];
|
|
58
61
|
/**
|
|
@@ -189,6 +192,13 @@ export interface TurnContext {
|
|
|
189
192
|
* `AgentTurnResult.violations`.
|
|
190
193
|
*/
|
|
191
194
|
nonBlockingViolations?: readonly EvaluationResult[];
|
|
195
|
+
/**
|
|
196
|
+
* A replay turn's tool calls so far, and what happened to each
|
|
197
|
+
* (`decideReplayTool`). Rebuilt from the journal on resume.
|
|
198
|
+
*/
|
|
199
|
+
replayTrace?: import('./replay.js').ReplayToolTrace[];
|
|
200
|
+
/** A replay turn's session approval, when it reached the gate (`replaySessionApproval`). */
|
|
201
|
+
replayApproval?: 'followed' | 'skipped';
|
|
192
202
|
/**
|
|
193
203
|
* Reason recorded when `turnAbort` fires. Used to distinguish
|
|
194
204
|
* external cancellation from wall-clock timeout in the projected
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
throwAgentTurnFailure,
|
|
21
21
|
} from './errors.js';
|
|
22
22
|
import { TOOL_CALL_GATE_SUBJECT, readGateDecision } from './gate-decision.js';
|
|
23
|
+
import { decideReplayTool } from './replay.js';
|
|
23
24
|
import {
|
|
24
25
|
effectiveToolErrorPolicy,
|
|
25
26
|
toolErrorKindOf,
|
|
@@ -244,6 +245,50 @@ export function buildDispatchToolsHandler(ctx: TurnContext): NodeHandler {
|
|
|
244
245
|
// skip the reviewer's answer. Cross-turn `ask_on_first_use` caching
|
|
245
246
|
// (via conversation metadata) is not implemented.
|
|
246
247
|
const gate = await decideToolGate(ctx, kctx, call, tool);
|
|
248
|
+
|
|
249
|
+
// A replay turn decides the call first: a recorded or refused call
|
|
250
|
+
// runs nothing, so it asks for no approval either.
|
|
251
|
+
const replayed =
|
|
252
|
+
ctx.input.replay === undefined
|
|
253
|
+
? undefined
|
|
254
|
+
: await decideReplayTool(ctx, kctx, {
|
|
255
|
+
step: partial.step,
|
|
256
|
+
callId: call.id,
|
|
257
|
+
tool,
|
|
258
|
+
version: resolvedVersion,
|
|
259
|
+
arguments: call.arguments,
|
|
260
|
+
gated: gate !== undefined,
|
|
261
|
+
});
|
|
262
|
+
if (replayed !== undefined && replayed.kind !== 'live') {
|
|
263
|
+
const replayStarted = Date.now();
|
|
264
|
+
await emitTurnEvent(ctx.bindings.onEvent, {
|
|
265
|
+
kind: 'tool.started',
|
|
266
|
+
step: partial.step,
|
|
267
|
+
toolId: call.name,
|
|
268
|
+
toolVersion: resolvedVersion,
|
|
269
|
+
toolVersionRange: requestedRange,
|
|
270
|
+
invocationId: call.id,
|
|
271
|
+
arguments: call.arguments,
|
|
272
|
+
});
|
|
273
|
+
nextMessages = await appendToolResult(ctx, call, replayed.result, {
|
|
274
|
+
toolId: tool.id as unknown as string,
|
|
275
|
+
nextMessages,
|
|
276
|
+
iterationAppended,
|
|
277
|
+
});
|
|
278
|
+
await emitTurnEvent(ctx.bindings.onEvent, {
|
|
279
|
+
kind: 'tool.completed',
|
|
280
|
+
step: partial.step,
|
|
281
|
+
toolId: call.name,
|
|
282
|
+
toolVersion: resolvedVersion,
|
|
283
|
+
toolVersionRange: requestedRange,
|
|
284
|
+
invocationId: call.id,
|
|
285
|
+
output: replayed.result as never,
|
|
286
|
+
durationMs: Date.now() - replayStarted,
|
|
287
|
+
replay: replayed.kind,
|
|
288
|
+
});
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
|
|
247
292
|
let toolRejectionPayload: { readonly rationale?: string } | null = null;
|
|
248
293
|
if (gate !== undefined) {
|
|
249
294
|
const { argsHash, waitTokenId, timeoutMs } = gate;
|
|
@@ -376,6 +421,7 @@ export function buildDispatchToolsHandler(ctx: TurnContext): NodeHandler {
|
|
|
376
421
|
invocationId: call.id,
|
|
377
422
|
output: dispatched.value.persisted.content,
|
|
378
423
|
durationMs: Date.now() - toolStarted,
|
|
424
|
+
...(replayed !== undefined && { replay: replayed.kind }),
|
|
379
425
|
});
|
|
380
426
|
}
|
|
381
427
|
|
|
@@ -495,7 +541,12 @@ async function appendToolResult(
|
|
|
495
541
|
target.iterationAppended.push(persisted.value);
|
|
496
542
|
return [
|
|
497
543
|
...target.nextMessages,
|
|
498
|
-
{
|
|
544
|
+
{
|
|
545
|
+
role: 'tool',
|
|
546
|
+
// As a tool's own result reaches the model: a string as it is.
|
|
547
|
+
content: typeof output === 'string' ? output : JSON.stringify(output),
|
|
548
|
+
toolCallId: call.id,
|
|
549
|
+
},
|
|
499
550
|
];
|
|
500
551
|
}
|
|
501
552
|
|
|
@@ -557,6 +608,8 @@ async function dispatchOne(
|
|
|
557
608
|
// secret_refs at invoke time. Present iff the caller wired
|
|
558
609
|
// `bindings.resolveSecret` from a tenant-scoped `SecretBinding`.
|
|
559
610
|
...(ctx.bindings.resolveSecret !== undefined && { resolveSecret: ctx.bindings.resolveSecret }),
|
|
611
|
+
// The pinned settings blocks' values, by block id.
|
|
612
|
+
...(ctx.blocks !== undefined && { settings: ctx.blocks.settings }),
|
|
560
613
|
};
|
|
561
614
|
const result = await invokeTool(tool, call.arguments, toolCtx);
|
|
562
615
|
if (result.kind === 'err') {
|
package/src/handlers/errors.ts
CHANGED
|
@@ -16,6 +16,7 @@ export type InvokeAgentError =
|
|
|
16
16
|
| AgentError
|
|
17
17
|
| UnresolvedToolError
|
|
18
18
|
| ToolVersionUnresolvableError
|
|
19
|
+
| BlockUnresolvableError
|
|
19
20
|
| CapabilityRoutingError
|
|
20
21
|
| ModelInvocationError
|
|
21
22
|
| ToolInvocationError
|
|
@@ -75,6 +76,18 @@ export interface ToolVersionUnresolvableError {
|
|
|
75
76
|
readonly availableVersions?: readonly string[];
|
|
76
77
|
}
|
|
77
78
|
|
|
79
|
+
/**
|
|
80
|
+
* A data block the agent references can't be loaded: no version in its
|
|
81
|
+
* range, a pinned version that's gone, the wrong kind, model settings
|
|
82
|
+
* that aren't, or a runtime that serves no blocks.
|
|
83
|
+
*/
|
|
84
|
+
export interface BlockUnresolvableError {
|
|
85
|
+
readonly code: 'block-unresolvable';
|
|
86
|
+
readonly message: string;
|
|
87
|
+
readonly blockId: string;
|
|
88
|
+
readonly requestedRange?: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
78
91
|
export interface CapabilityRoutingError {
|
|
79
92
|
readonly code: 'capability-routing-failed';
|
|
80
93
|
readonly message: string;
|
|
@@ -18,6 +18,7 @@ import type { ConversationMessage } from '../types.js';
|
|
|
18
18
|
import type { TurnContext } from './context.js';
|
|
19
19
|
import { throwAgentTurnFailure } from './errors.js';
|
|
20
20
|
import { finalIteration } from './final-iteration.js';
|
|
21
|
+
import { replayTag } from './replay.js';
|
|
21
22
|
import { parseJsonAnswer } from './structured-output.js';
|
|
22
23
|
|
|
23
24
|
/**
|
|
@@ -169,6 +170,7 @@ function judgeUsageSink(ctx: TurnContext, kctx: NodeContext): UsageSink | undefi
|
|
|
169
170
|
sink.record({
|
|
170
171
|
nodeId: kctx.nodeId as unknown as string,
|
|
171
172
|
agentVersion: ctx.input.agent.version,
|
|
173
|
+
...(ctx.input.replay !== undefined && { replay: replayTag(ctx.input.replay) }),
|
|
172
174
|
...call,
|
|
173
175
|
}),
|
|
174
176
|
};
|
|
@@ -18,6 +18,7 @@ import { emitTurnEvent } from '../streaming.js';
|
|
|
18
18
|
|
|
19
19
|
import type { AgentTurnIterationOutput, TurnContext } from './context.js';
|
|
20
20
|
import { throwAgentTurnFailure } from './errors.js';
|
|
21
|
+
import { replayTag } from './replay.js';
|
|
21
22
|
import { addModelCallNode } from './turn-provenance.js';
|
|
22
23
|
|
|
23
24
|
/**
|
|
@@ -83,6 +84,7 @@ export function buildModelCallHandler(ctx: TurnContext): NodeHandler {
|
|
|
83
84
|
...(ctx.tools.definitions.length > 0 && {
|
|
84
85
|
tools: ctx.tools.definitions,
|
|
85
86
|
}),
|
|
87
|
+
...modelSettingsOf(ctx),
|
|
86
88
|
abortSignal: ctx.turnAbort.signal,
|
|
87
89
|
};
|
|
88
90
|
|
|
@@ -251,6 +253,7 @@ async function recordCall(
|
|
|
251
253
|
providerId: ctx.provider.metadata.id,
|
|
252
254
|
model: ctx.model.name,
|
|
253
255
|
...(ctx.provider.metadata.fallback === true && { fallback: true }),
|
|
256
|
+
...(input.replay !== undefined && { replay: replayTag(input.replay) }),
|
|
254
257
|
occurredAt: new Date().toISOString(),
|
|
255
258
|
});
|
|
256
259
|
return recorded.kind === 'err' ? (recorded.error ?? new Error('no detail')) : undefined;
|
|
@@ -265,3 +268,14 @@ function describeCause(cause: unknown): string {
|
|
|
265
268
|
if (text.length === 0) return cause instanceof Error ? cause.name : 'no detail';
|
|
266
269
|
return text.length > MAX_CAUSE_CHARS ? `${text.slice(0, MAX_CAUSE_CHARS - 1)}…` : text;
|
|
267
270
|
}
|
|
271
|
+
|
|
272
|
+
/** The pinned model-settings block's values, as model-call fields; none when it has none. */
|
|
273
|
+
function modelSettingsOf(
|
|
274
|
+
ctx: TurnContext,
|
|
275
|
+
): Pick<ModelCallInput, 'temperature' | 'maxOutputTokens'> {
|
|
276
|
+
const settings = ctx.blocks?.modelSettings;
|
|
277
|
+
return {
|
|
278
|
+
...(settings?.temperature !== undefined && { temperature: settings.temperature }),
|
|
279
|
+
...(settings?.maxOutputTokens !== undefined && { maxOutputTokens: settings.maxOutputTokens }),
|
|
280
|
+
};
|
|
281
|
+
}
|
|
@@ -6,10 +6,11 @@ import type { ProviderRegistry, TenantPolicy, UsageSink } from '@kindgi/capabili
|
|
|
6
6
|
import type { EmbeddingProviderRegistry } from '@kindgi/embedding';
|
|
7
7
|
import type { MemoryQueryBinding } from '@kindgi/memory';
|
|
8
8
|
import type { PolicyRegistry } from '@kindgi/policy-contract';
|
|
9
|
-
import type { ParentRunRef, RunBinding } from '@kindgi/runtime';
|
|
9
|
+
import type { ParentRunRef, RunBinding, RunReplayRef } from '@kindgi/runtime';
|
|
10
10
|
import type { ToolRegistry, ToolSecretRef } from '@kindgi/tools';
|
|
11
11
|
import type { OrgId, ProjectId, ProvenanceId, RunId, TenantId, Timestamp } from '@kindgi/types';
|
|
12
12
|
|
|
13
|
+
import type { BlockReader } from '../blocks.js';
|
|
13
14
|
import type { ConversationBinding } from '../conversation-binding.js';
|
|
14
15
|
import type { GuardrailsBindings } from '../guardrails-gate.js';
|
|
15
16
|
import type { ProvenanceBindings } from '../provenance-emit.js';
|
|
@@ -17,6 +18,8 @@ import type { RunSnapshotBinding } from '../run-snapshot-binding.js';
|
|
|
17
18
|
import type { OnTurnEvent } from '../streaming.js';
|
|
18
19
|
import type { Agent, ConversationId } from '../types.js';
|
|
19
20
|
|
|
21
|
+
import type { ReplayBinding } from './replay.js';
|
|
22
|
+
|
|
20
23
|
/**
|
|
21
24
|
* The user-facing input to `invokeAgent`. Split out from `invoke.ts` so
|
|
22
25
|
* the handler modules can depend on the shape without pulling in the
|
|
@@ -54,6 +57,13 @@ export interface InvokeAgentInput {
|
|
|
54
57
|
* step. Recorded on the turn's run, so the flow can find its child.
|
|
55
58
|
*/
|
|
56
59
|
readonly parent?: ParentRunRef;
|
|
60
|
+
/**
|
|
61
|
+
* Marks the turn as a replay: an eval run re-running a past run (`of`)
|
|
62
|
+
* on this agent version. Recorded on the turn's run and in its snapshot.
|
|
63
|
+
* Its tool calls are decided by `InvokeAgentBindings.replay` (every call
|
|
64
|
+
* is refused when that isn't wired), and only a read-only tool can run.
|
|
65
|
+
*/
|
|
66
|
+
readonly replay?: RunReplayRef;
|
|
57
67
|
readonly participantId?: string;
|
|
58
68
|
readonly abortSignal?: AbortSignal;
|
|
59
69
|
/**
|
|
@@ -101,6 +111,12 @@ export interface InvokeAgentBindings extends GuardrailsBindings {
|
|
|
101
111
|
* sees another tenant's tools.
|
|
102
112
|
*/
|
|
103
113
|
readonly toolRegistry: ToolRegistry;
|
|
114
|
+
/**
|
|
115
|
+
* Data blocks (prompts and settings). Setup loads the blocks the agent
|
|
116
|
+
* references at their pinned versions. Optional: an agent that
|
|
117
|
+
* references blocks fails its turn (`block-unresolvable`) without it.
|
|
118
|
+
*/
|
|
119
|
+
readonly blockReader?: BlockReader;
|
|
104
120
|
/**
|
|
105
121
|
* Caller-plugged data-access surface for memory reads.
|
|
106
122
|
* The Kindgi runtime supplies a Postgres-backed implementation;
|
|
@@ -175,6 +191,11 @@ export interface InvokeAgentBindings extends GuardrailsBindings {
|
|
|
175
191
|
* unaffected.
|
|
176
192
|
*/
|
|
177
193
|
readonly resolveSecret?: (ref: ToolSecretRef) => Promise<string>;
|
|
194
|
+
/**
|
|
195
|
+
* How replay turns (`InvokeAgentInput.replay`) decide their tool calls,
|
|
196
|
+
* retrievals and session approval. Consulted only for a replay turn.
|
|
197
|
+
*/
|
|
198
|
+
readonly replay?: ReplayBinding;
|
|
178
199
|
}
|
|
179
200
|
|
|
180
201
|
export interface HitlBindings {
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
*
|
|
11
11
|
* - the environment — conversation, approval rules, guardrails, tools,
|
|
12
12
|
* policies — is resolved again, routed to the provider and model
|
|
13
|
-
* `setup` journaled
|
|
13
|
+
* `setup` journaled, with the tool versions it journaled (a range
|
|
14
|
+
* isn't resolved again: a version published meanwhile doesn't run
|
|
15
|
+
* mid-turn);
|
|
14
16
|
* - the messages the turn stored come back from the conversation, from
|
|
15
17
|
* the turn's user message (`persist-user-message` journals its
|
|
16
18
|
* sequence);
|
|
@@ -46,6 +48,8 @@ import type { ConversationMessage, RetrievedFact } from '../types.js';
|
|
|
46
48
|
import type { TurnContext } from './context.js';
|
|
47
49
|
import { throwAgentTurnFailure } from './errors.js';
|
|
48
50
|
import { readGateDecision } from './gate-decision.js';
|
|
51
|
+
import { rehydrateReplay } from './replay.js';
|
|
52
|
+
import type { PinnedBlockVersions } from './resolve-blocks.js';
|
|
49
53
|
import { TOOL_GATE_RECORD_PREFIX } from './tool-hitl.js';
|
|
50
54
|
import {
|
|
51
55
|
loadTurnConversation,
|
|
@@ -107,10 +111,12 @@ export async function rehydrateTurnContext(
|
|
|
107
111
|
journal: readonly JournalEntry[],
|
|
108
112
|
): Promise<boolean> {
|
|
109
113
|
const steps = completedSteps(journal);
|
|
110
|
-
const setup = outputOf<{
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
+
const setup = outputOf<{
|
|
115
|
+
readonly providerId: string;
|
|
116
|
+
readonly providerModel: string;
|
|
117
|
+
readonly toolVersions?: Readonly<Record<string, string>>;
|
|
118
|
+
readonly blockVersions?: PinnedBlockVersions;
|
|
119
|
+
}>(steps, 'setup');
|
|
114
120
|
if (setup === undefined) return false;
|
|
115
121
|
|
|
116
122
|
if (ctx.bindings.provenance?.newBuilder !== undefined) {
|
|
@@ -119,7 +125,15 @@ export async function rehydrateTurnContext(
|
|
|
119
125
|
}
|
|
120
126
|
await loadTurnConversation(ctx);
|
|
121
127
|
ctx.hitlPolicy = await resolveTurnHitlPolicy(ctx);
|
|
122
|
-
|
|
128
|
+
// The route and the tool versions `setup` resolved: a resumed turn
|
|
129
|
+
// runs those, never a range resolved again (a journal from before
|
|
130
|
+
// `toolVersions` was recorded resolves the ranges, as it did).
|
|
131
|
+
await resolveTurnEnvironment(
|
|
132
|
+
ctx,
|
|
133
|
+
{ providerId: setup.providerId, model: setup.providerModel },
|
|
134
|
+
setup.toolVersions,
|
|
135
|
+
setup.blockVersions,
|
|
136
|
+
);
|
|
123
137
|
|
|
124
138
|
const userMessage = outputOf<{ readonly sequence: number }>(steps, 'persist-user-message');
|
|
125
139
|
if (userMessage !== undefined) {
|
|
@@ -151,6 +165,7 @@ export async function rehydrateTurnContext(
|
|
|
151
165
|
}
|
|
152
166
|
ctx.toolApprovals = toolApprovalsOf(journal);
|
|
153
167
|
rebuildProvenance(ctx, steps, retrievals?.retrieved);
|
|
168
|
+
rehydrateReplay(ctx, journal);
|
|
154
169
|
return true;
|
|
155
170
|
}
|
|
156
171
|
|
|
@@ -25,14 +25,20 @@ export function buildRenderPromptHandler(ctx: TurnContext): NodeHandler {
|
|
|
25
25
|
cause: null,
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
|
-
const rendered = renderInstructions(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
const rendered = renderInstructions(
|
|
29
|
+
ctx.input.agent,
|
|
30
|
+
{
|
|
31
|
+
parameters: ctx.input.parameters ?? {},
|
|
32
|
+
...(ctx.input.input !== undefined && { input: ctx.input.input }),
|
|
33
|
+
conversation: {
|
|
34
|
+
id: ctx.input.conversationId,
|
|
35
|
+
turn: ctx.conversation.turnCount + 1,
|
|
36
|
+
},
|
|
37
|
+
...(ctx.blocks !== undefined && { settings: ctx.blocks.settings }),
|
|
34
38
|
},
|
|
35
|
-
|
|
39
|
+
// The pinned prompt block, when the instructions come from one.
|
|
40
|
+
ctx.blocks?.prompt?.content,
|
|
41
|
+
);
|
|
36
42
|
if (!rendered.ok) {
|
|
37
43
|
throwAgentTurnFailure({
|
|
38
44
|
code: 'model-invocation-failed',
|