@arnilo/prism 0.7.0 → 0.8.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 +35 -0
- package/README.md +12 -11
- package/dist/agent-approval.d.ts +4 -0
- package/dist/agent-approval.js +5 -1
- package/dist/agent-run-lifecycle.js +29 -5
- package/dist/agent-run-state.d.ts +13 -1
- package/dist/agent-run-state.js +12 -2
- package/dist/agent-session/helpers.js +6 -1
- package/dist/agent-session/session/assemble.js +126 -5
- package/dist/agent-session/session/persist.d.ts +16 -0
- package/dist/agent-session/session/persist.js +58 -2
- package/dist/agent-session/session/provider-round.d.ts +3 -3
- package/dist/agent-session/session/provider-round.js +12 -6
- package/dist/agent-session/session/tool-round.js +4 -1
- package/dist/agent-session/session/types.d.ts +12 -1
- package/dist/agent-session/session.d.ts +2 -0
- package/dist/agent-session/session.js +2 -0
- package/dist/checkpoints.js +7 -11
- package/dist/contracts-core/content.d.ts +5 -0
- package/dist/contracts-core/loop.d.ts +42 -0
- package/dist/contracts-core/run-limits.d.ts +2 -0
- package/dist/contracts-protocol.d.ts +15 -3
- package/dist/contracts-run-state.d.ts +26 -5
- package/dist/evidence-grounding.d.ts +29 -0
- package/dist/evidence-grounding.js +162 -0
- package/dist/host-composition.d.ts +13 -0
- package/dist/host-composition.js +33 -2
- package/dist/index.d.ts +6 -2
- package/dist/index.js +3 -1
- package/dist/provider-events.d.ts +3 -1
- package/dist/provider-events.js +2 -2
- package/dist/providers/transport.d.ts +3 -1
- package/dist/providers/transport.js +36 -0
- package/dist/redaction.js +18 -2
- package/dist/run-bundle.d.ts +89 -0
- package/dist/run-bundle.js +149 -0
- package/dist/testing/state-concurrency-conformance.js +5 -12
- package/docs/ag-ui.md +5 -0
- package/docs/agent-loops.md +33 -0
- package/docs/agent-session-runtime.md +4 -3
- package/docs/coding-security.md +1 -0
- package/docs/coding-tools.md +0 -1
- package/docs/compaction-observational-memory.md +1 -1
- package/docs/connected-apps.md +116 -0
- package/docs/context-and-skills.md +13 -0
- package/docs/core.md +1 -1
- package/docs/diagrams.md +6 -6
- package/docs/document-reader.md +9 -9
- package/docs/documents.md +32 -11
- package/docs/durable-runs.md +87 -0
- package/docs/enterprise-postgres-state.md +4 -0
- package/docs/execution-timeline.md +6 -0
- package/docs/guardrails.md +33 -0
- package/docs/history/079-messaging-primitive-review.md +391 -0
- package/docs/history/080-messaging-followon-primitive-review.md +234 -0
- package/docs/history/081-connected-apps-primitive-review.md +74 -0
- package/docs/history/083-prism-work-primitive-review.md +84 -0
- package/docs/history/084-primitive-review.md +96 -0
- package/docs/history/085-honesty-and-cut-primitive-review.md +91 -0
- package/docs/history/README.md +5 -0
- package/docs/history/release-handoffs.md +38 -0
- package/docs/host-compositions.md +8 -6
- package/docs/host-security.md +2 -2
- package/docs/index.md +47 -29
- package/docs/live-testing.md +5 -3
- package/docs/mcp-tools.md +1 -0
- package/docs/messaging-channel-operations.md +166 -0
- package/docs/messaging-channels.md +150 -0
- package/docs/migrate-to-0.8.md +124 -0
- package/docs/migration.md +30 -0
- package/docs/model-registry.md +12 -2
- package/docs/openapi-tools.md +1 -1
- package/docs/operations.md +1 -3
- package/docs/options-index.md +34 -2
- package/docs/peer-dependencies.md +6 -6
- package/docs/postgres-persistence.md +1 -1
- package/docs/provider-layer.md +2 -2
- package/docs/provider-packages.md +20 -20
- package/docs/providers/neuralwatt.md +5 -1
- package/docs/rag.md +1 -1
- package/docs/release-and-install.md +52 -46
- package/docs/run-bundle.md +92 -0
- package/docs/runs-and-usage.md +14 -0
- package/docs/server.md +2 -0
- package/docs/sheets.md +9 -9
- package/docs/signal-channel.md +112 -0
- package/docs/speech.md +5 -1
- package/docs/sqlite-persistence.md +1 -1
- package/docs/telegram-channel.md +157 -0
- package/docs/testing.md +2 -2
- package/docs/wiki.md +1 -1
- package/docs/work-artifacts-and-review.md +1 -1
- package/docs/work-connectors.md +9 -9
- package/docs/work-sandbox.md +115 -0
- package/docs/work-tools.md +38 -16
- package/package.json +5 -3
- package/templates/business-worker/manifest.json +2 -1
- package/templates/business-worker/src/agent.ts.tmpl +1 -1
- package/templates/business-worker/src/tests/agent.test.ts.tmpl +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Provider-round phase of runInternal (plan 059). */
|
|
2
2
|
import { assertGuardrailsAllowed, GuardrailError, runGuardrails } from "../../guardrails.js";
|
|
3
3
|
import { createProviderTurnMetadata, readProviderHttpStatus } from "../../observability.js";
|
|
4
|
-
import { providerToolCallDeltaContent } from "../../provider-events.js";
|
|
4
|
+
import { providerError, providerToolCallDeltaContent } from "../../provider-events.js";
|
|
5
5
|
import { errorToErrorInfo, redactRunLedgerRecord, redactSecrets } from "../../redaction.js";
|
|
6
6
|
import { createDefaultRetryPolicy, waitForRetry } from "../../retry.js";
|
|
7
7
|
import { bridgeAbort, errorFromInfo, isSteerSoftInterrupt, jsonBytes, mergeRetry, ProviderTurnFailure, providerContent, randomId, reconstructMissingToolCalls, SteerSoftInterrupt, throwIfAborted, } from "../helpers.js";
|
|
@@ -70,13 +70,13 @@ export async function recordProviderUsage(ctx, turnUsage, turn, attempt) {
|
|
|
70
70
|
};
|
|
71
71
|
await session.activeLedger.appendUsage(redactRunLedgerRecord(usageRecord, session.activeRedactor));
|
|
72
72
|
}
|
|
73
|
-
export async function generateWithRetry(session, request, runId, options, signal, requestSecrets = [], turn = 1, recordUsage) {
|
|
73
|
+
export async function generateWithRetry(session, request, runId, options, signal, requestSecrets = [], turn = 1, recordUsage, toolResults = []) {
|
|
74
74
|
const retry = mergeRetry(session.agent.config.retry, options.retry);
|
|
75
75
|
const secrets = [...requestSecrets, ...(retry?.secrets ?? [])];
|
|
76
76
|
const policy = retry?.policy ?? (retry ? createDefaultRetryPolicy(retry) : undefined);
|
|
77
77
|
for (let attempt = 1;; attempt += 1) {
|
|
78
78
|
try {
|
|
79
|
-
return await generateProviderTurn(session, request, runId, signal, secrets, turn, attempt, recordUsage);
|
|
79
|
+
return await generateProviderTurn(session, request, runId, signal, secrets, turn, attempt, recordUsage, toolResults);
|
|
80
80
|
}
|
|
81
81
|
catch (error) {
|
|
82
82
|
if (error instanceof GuardrailError || isSteerSoftInterrupt(error))
|
|
@@ -100,7 +100,7 @@ export async function generateWithRetry(session, request, runId, options, signal
|
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
|
-
export async function generateProviderTurn(session, request, runId, signal, secrets = [], turn = 1, attempt = 1, recordUsage) {
|
|
103
|
+
export async function generateProviderTurn(session, request, runId, signal, secrets = [], turn = 1, attempt = 1, recordUsage, toolResults = []) {
|
|
104
104
|
session.activeLimits.charge("maxProviderAttempts");
|
|
105
105
|
session.activeLimits.charge("maxRequestBytes", jsonBytes(request));
|
|
106
106
|
const startedAt = performance.now();
|
|
@@ -189,7 +189,13 @@ export async function generateProviderTurn(session, request, runId, signal, secr
|
|
|
189
189
|
stage: "output",
|
|
190
190
|
guardrails: session.activeGuardrails,
|
|
191
191
|
value: { content, calls, messageId, started, usage },
|
|
192
|
-
context: {
|
|
192
|
+
context: {
|
|
193
|
+
sessionId: session.id,
|
|
194
|
+
runId,
|
|
195
|
+
metadata: session.activeMetadata ?? {},
|
|
196
|
+
signal: turnAbort.signal,
|
|
197
|
+
toolResults,
|
|
198
|
+
},
|
|
193
199
|
redactor: session.activeRedactor,
|
|
194
200
|
emit: (event) => session.emit(event),
|
|
195
201
|
}));
|
|
@@ -223,7 +229,7 @@ export async function generateProviderTurn(session, request, runId, signal, secr
|
|
|
223
229
|
throw new SteerSoftInterrupt();
|
|
224
230
|
}
|
|
225
231
|
const latencyMs = Math.round(performance.now() - startedAt);
|
|
226
|
-
const info = error instanceof ProviderTurnFailure ? redactSecrets(error.info, secrets) :
|
|
232
|
+
const info = error instanceof ProviderTurnFailure ? redactSecrets(error.info, secrets) : providerError(error, secrets).error;
|
|
227
233
|
await recordTurnUsage();
|
|
228
234
|
session.emit({
|
|
229
235
|
type: "provider_turn_finished",
|
|
@@ -251,8 +251,9 @@ export function bindDispatchToolCall(ctx) {
|
|
|
251
251
|
if (ctx.session.activeGatedRound?.has(call.id)) {
|
|
252
252
|
return { toolCallId: call.id, name: call.name, metadata: { approvalPending: true } };
|
|
253
253
|
}
|
|
254
|
+
ctx.toolCalls += 1;
|
|
254
255
|
try {
|
|
255
|
-
|
|
256
|
+
const result = await dispatchToolCall({
|
|
256
257
|
call,
|
|
257
258
|
registry: ctx.registry,
|
|
258
259
|
context: {
|
|
@@ -329,6 +330,8 @@ export function bindDispatchToolCall(ctx) {
|
|
|
329
330
|
},
|
|
330
331
|
validate: ctx.validate,
|
|
331
332
|
});
|
|
333
|
+
ctx.toolResults.push(result);
|
|
334
|
+
return result;
|
|
332
335
|
}
|
|
333
336
|
catch (error) {
|
|
334
337
|
if (error instanceof AgentDelegationSuspendedError && !error.toolCall)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { ActiveDurableRun } from "../../agent-approval.js";
|
|
3
3
|
import type { PendingToolCall } from "../../agent-run-state.js";
|
|
4
4
|
import type { AttentionStickyFrontier, PersistedAttentionStickyFrontier } from "../../attention-compiler.js";
|
|
5
|
-
import type { Agent, AgentEvent, AgentLoopStrategy, AgentRunResult, AIProvider, ErrorInfo, Guardrails, LoopContext, Message, ModelConfig, OwnershipScope, PendingDecision, PromptVersionRef, ProviderRequest, RunLedger, RunOptions, SessionEntry, SessionStore, Skill, ToolDefinition, ToolEffectStore, ToolRegistry, Usage } from "../../contracts.js";
|
|
5
|
+
import type { Agent, AgentEvent, AgentFinishReason, AgentLoopStrategy, AgentRunResult, AIProvider, ErrorInfo, Guardrails, LoopContext, Message, ModelConfig, OwnershipScope, PendingDecision, PromptVersionRef, ProviderRequest, RunLedger, RunOptions, SessionEntry, SessionStore, Skill, ToolDefinition, ToolEffectStore, ToolRegistry, ToolResult, Usage } from "../../contracts.js";
|
|
6
6
|
import type { AgentIdentity } from "../../identity.js";
|
|
7
7
|
import type { AgentInput } from "../../input.js";
|
|
8
8
|
import type { SecretRedactor } from "../../redaction.js";
|
|
@@ -87,6 +87,11 @@ export type SessionHost = {
|
|
|
87
87
|
snapshot(): Promise<SessionContextSnapshot>;
|
|
88
88
|
};
|
|
89
89
|
export declare function asSessionHost(session: unknown): SessionHost;
|
|
90
|
+
/** Why a run's loop ended, plus the host's stop detail when `RunOptions.turnPolicy` stopped it. */
|
|
91
|
+
export type RunStopInfo = {
|
|
92
|
+
readonly reason: AgentFinishReason;
|
|
93
|
+
readonly detail?: string;
|
|
94
|
+
};
|
|
90
95
|
export type RoundContext = {
|
|
91
96
|
session: SessionHost;
|
|
92
97
|
input: AgentInput;
|
|
@@ -117,6 +122,12 @@ export type RoundContext = {
|
|
|
117
122
|
message: string;
|
|
118
123
|
code?: string | number;
|
|
119
124
|
} | undefined;
|
|
125
|
+
/** Host tool calls dispatched in this run; the turn-boundary context's `toolCalls` (plan 084 Task 2). */
|
|
126
|
+
toolCalls: number;
|
|
127
|
+
/** Completed host tool results from this run, for output evidence guardrails (plan 084 Task 5). */
|
|
128
|
+
toolResults: ToolResult[];
|
|
129
|
+
/** Set when a `RunOptions.turnPolicy` stop ended the loop (plan 084 Task 2). */
|
|
130
|
+
runStop?: RunStopInfo;
|
|
120
131
|
runUsage: {
|
|
121
132
|
add(usage: Usage): void;
|
|
122
133
|
value(): Usage | undefined;
|
|
@@ -95,6 +95,8 @@ export declare class RuntimeAgentSession implements AgentSession {
|
|
|
95
95
|
readonly limit?: import("../contracts.js").RunLimitBreach;
|
|
96
96
|
readonly error?: ErrorInfo;
|
|
97
97
|
readonly abortReason?: string;
|
|
98
|
+
readonly stopReason?: import("../contracts.js").AgentFinishReason;
|
|
99
|
+
readonly stopDetail?: string;
|
|
98
100
|
readonly runState?: AgentRunState;
|
|
99
101
|
readonly interruption?: import("../contracts.js").AgentRunInterruption;
|
|
100
102
|
}): AgentRunResult;
|
|
@@ -232,6 +232,8 @@ export class RuntimeAgentSession {
|
|
|
232
232
|
limit: input.limit,
|
|
233
233
|
error: input.error,
|
|
234
234
|
abortReason: input.abortReason,
|
|
235
|
+
stopReason: input.stopReason,
|
|
236
|
+
stopDetail: input.stopDetail,
|
|
235
237
|
runState: input.runState,
|
|
236
238
|
interruption: input.interruption,
|
|
237
239
|
};
|
package/dist/checkpoints.js
CHANGED
|
@@ -23,8 +23,11 @@ export function createMemoryCheckpointStore(options = {}) {
|
|
|
23
23
|
}
|
|
24
24
|
const id = recordKey(input);
|
|
25
25
|
const existing = records.get(id);
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
// A foreign-owned record is indistinguishable from a CAS miss: never an
|
|
27
|
+
// ownership-shaped error (plan 080 Task 3).
|
|
28
|
+
if (existing && !ownershipMatches(input, existing)) {
|
|
29
|
+
throw new CheckpointConflictError(`Checkpoint compare-and-swap failed (expected ${input.expectedVersion ?? 0}, current ${existing.version})`);
|
|
30
|
+
}
|
|
28
31
|
if (input.expectedVersion !== undefined && input.expectedVersion !== (existing?.version ?? 0)) {
|
|
29
32
|
throw new CheckpointConflictError(`Checkpoint compare-and-swap failed (expected ${input.expectedVersion}, current ${existing?.version ?? 0})`);
|
|
30
33
|
}
|
|
@@ -66,8 +69,7 @@ export function createMemoryCheckpointStore(options = {}) {
|
|
|
66
69
|
const record = records.get(recordKey(input));
|
|
67
70
|
if (!record)
|
|
68
71
|
return null;
|
|
69
|
-
|
|
70
|
-
return record;
|
|
72
|
+
return ownershipMatches(input, record) ? record : null;
|
|
71
73
|
},
|
|
72
74
|
async listCheckpoints(query = {}) {
|
|
73
75
|
throwIfAborted(query.signal);
|
|
@@ -94,8 +96,7 @@ export function createMemoryCheckpointStore(options = {}) {
|
|
|
94
96
|
const record = records.get(id);
|
|
95
97
|
if (!record)
|
|
96
98
|
return false;
|
|
97
|
-
|
|
98
|
-
return records.delete(id);
|
|
99
|
+
return ownershipMatches(input, record) ? records.delete(id) : false;
|
|
99
100
|
},
|
|
100
101
|
};
|
|
101
102
|
}
|
|
@@ -121,11 +122,6 @@ function ownershipFilterMatches(expected, actual) {
|
|
|
121
122
|
(expected.accountId === undefined || expected.accountId === actual.accountId) &&
|
|
122
123
|
(expected.userId === undefined || expected.userId === actual.userId));
|
|
123
124
|
}
|
|
124
|
-
function assertOwnership(expected, actual) {
|
|
125
|
-
if (!ownershipMatches(expected, actual)) {
|
|
126
|
-
throw new CheckpointConflictError("Checkpoint ownership mismatch");
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
125
|
function cloneJson(value, label) {
|
|
130
126
|
try {
|
|
131
127
|
const encoded = JSON.stringify(value);
|
|
@@ -7,10 +7,13 @@ export type JsonValue = JsonPrimitive | JsonObject | JsonValue[];
|
|
|
7
7
|
export interface JsonObject {
|
|
8
8
|
readonly [key: string]: JsonValue;
|
|
9
9
|
}
|
|
10
|
+
export type ProviderFailureClass = "quota" | "auth" | "rate_limited" | "transient" | "permanent" | "unknown";
|
|
10
11
|
export interface ErrorInfo {
|
|
11
12
|
readonly name?: string;
|
|
12
13
|
readonly message: string;
|
|
13
14
|
readonly code?: string | number;
|
|
15
|
+
/** Advisory classification stamped only for provider failures; it never changes retry behavior. */
|
|
16
|
+
readonly failureClass?: ProviderFailureClass;
|
|
14
17
|
/** Provider backpressure hint (e.g. from a `Retry-After` header); retry policies
|
|
15
18
|
* honor it capped at their own `maxDelayMs`. */
|
|
16
19
|
readonly retryAfterMs?: number;
|
|
@@ -102,6 +105,8 @@ export interface ModelCapabilities {
|
|
|
102
105
|
readonly output?: readonly string[];
|
|
103
106
|
readonly reasoning?: boolean;
|
|
104
107
|
readonly tools?: boolean;
|
|
108
|
+
/** Advisory, conformance-derived tool-call behavior. Absent means unknown; it never changes runtime tool validation. */
|
|
109
|
+
readonly toolCallStrictness?: "strict" | "lenient" | "legacy";
|
|
105
110
|
readonly streaming?: boolean;
|
|
106
111
|
/** Native JSON-schema structured output support for this model. */
|
|
107
112
|
readonly structuredOutput?: boolean | "json_schema";
|
|
@@ -4,6 +4,48 @@ import type { AgentEvent, AgentFinishReason, ProviderTurnResult, ToolResult } fr
|
|
|
4
4
|
import type { AgentInput } from "../input.js";
|
|
5
5
|
import type { JsonValue, Message, ToolCallContent, Usage } from "./content.js";
|
|
6
6
|
import type { ProviderRequest, StructuredOutputOptions } from "./provider.js";
|
|
7
|
+
/**
|
|
8
|
+
* Metadata-only view of a run at a provider-turn boundary (plan 084 Task 2). Hosts branch on
|
|
9
|
+
* counters, never content: tool arguments, prompts, and tool results are not fields.
|
|
10
|
+
*/
|
|
11
|
+
export interface TurnBoundaryContext {
|
|
12
|
+
readonly sessionId: string;
|
|
13
|
+
readonly runId: string;
|
|
14
|
+
/** 1-based index of the provider turn this boundary precedes. */
|
|
15
|
+
readonly turn: number;
|
|
16
|
+
/** Provider turns already completed in this run (`turn - 1`; 0 at the first boundary). */
|
|
17
|
+
readonly turns: number;
|
|
18
|
+
/** Host tool calls dispatched so far in this run. */
|
|
19
|
+
readonly toolCalls: number;
|
|
20
|
+
/** Run-total usage so far, when the provider reported any. */
|
|
21
|
+
readonly usage?: Usage;
|
|
22
|
+
readonly metadata: Readonly<Record<string, unknown>>;
|
|
23
|
+
}
|
|
24
|
+
/** Synchronous decision at a turn boundary. `stop` ends the run cleanly; `continue` runs the turn. */
|
|
25
|
+
export type TurnStopDecision = {
|
|
26
|
+
readonly action: "continue";
|
|
27
|
+
} | {
|
|
28
|
+
readonly action: "stop";
|
|
29
|
+
readonly reason: string;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Host turn policy (plan 084 Task 2). Evaluated before every provider request, at the same
|
|
33
|
+
* boundary a `checkpointPolicy: "every-turn"` checkpoint is written. Omit it and the run keeps
|
|
34
|
+
* its exact 0.8.x turn structure (no callback, no reads).
|
|
35
|
+
*/
|
|
36
|
+
export interface TurnPolicyOptions {
|
|
37
|
+
/**
|
|
38
|
+
* Clean turn cap. Reaching it stops the run (`stopReason: "turn_limit"`) instead of failing it
|
|
39
|
+
* with a limit breach. A run overlay may only narrow `limits.maxTurns`; widening throws.
|
|
40
|
+
*/
|
|
41
|
+
readonly maxTurns?: number;
|
|
42
|
+
/**
|
|
43
|
+
* Consulted before every provider request. Returning `stop` ends the run cleanly with
|
|
44
|
+
* `stopReason: "host_policy"` and a resumable checkpoint (`decision: "continue"` resumes it).
|
|
45
|
+
* Must be synchronous and must not throw; a throw fails the run with `ERR_PRISM_TURN_POLICY`.
|
|
46
|
+
*/
|
|
47
|
+
readonly stop?: (context: TurnBoundaryContext) => TurnStopDecision;
|
|
48
|
+
}
|
|
7
49
|
export interface LoopContext {
|
|
8
50
|
readonly sessionId: string;
|
|
9
51
|
readonly runId: string;
|
|
@@ -70,6 +70,8 @@ export interface GuardrailContext<S extends GuardrailStage> {
|
|
|
70
70
|
readonly runId: string;
|
|
71
71
|
readonly toolCallId?: string;
|
|
72
72
|
readonly toolName?: string;
|
|
73
|
+
/** Same-run completed host tool results, available only at the output stage. */
|
|
74
|
+
readonly toolResults?: S extends "output" ? readonly ToolResult[] : never;
|
|
73
75
|
readonly metadata: Readonly<Record<string, unknown>>;
|
|
74
76
|
readonly signal: AbortSignal;
|
|
75
77
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentLoopOptions, AgentLoopStrategy, ArtifactValidation, CompactionOptions, ContentBlock, ErrorInfo, GuardrailRecord, Guardrails, InstructionInjector, JsonObject, Message, ModelConfig, OwnershipScope, ProviderRequestOptions, ProviderRequestPolicy, ProviderResolver, RetryOptions, RunLimitBreach, RunLimits, Skill, SubscriberOverflowPolicy, SystemPromptConfig, ToolCallAuthority, ToolCallContent, Usage } from "./contracts-core.js";
|
|
1
|
+
import type { AgentLoopOptions, AgentLoopStrategy, ArtifactValidation, CompactionOptions, ContentBlock, ErrorInfo, GuardrailRecord, Guardrails, InstructionInjector, JsonObject, Message, ModelConfig, OwnershipScope, ProviderRequestOptions, ProviderRequestPolicy, ProviderResolver, RetryOptions, RunLimitBreach, RunLimits, Skill, SubscriberOverflowPolicy, SystemPromptConfig, ToolCallAuthority, ToolCallContent, TurnPolicyOptions, Usage } from "./contracts-core.js";
|
|
2
2
|
import type { AgentRunInterruption, AgentRunStateOptions } from "./contracts-run-state.js";
|
|
3
3
|
import type { SecretRedactor } from "./redaction.js";
|
|
4
4
|
import type { ToolValidator } from "./tools.js";
|
|
@@ -125,6 +125,11 @@ export interface RunOptions {
|
|
|
125
125
|
readonly runState?: AgentRunStateOptions;
|
|
126
126
|
/** Prompt provenance: copied verbatim onto this run's start and finish ledger records. */
|
|
127
127
|
readonly promptVersion?: PromptVersionRef;
|
|
128
|
+
/**
|
|
129
|
+
* Host turn policy (plan 084 Task 2): a clean turn cap and/or a synchronous stop callback
|
|
130
|
+
* evaluated before every provider request. Omitted → no callback runs.
|
|
131
|
+
*/
|
|
132
|
+
readonly turnPolicy?: TurnPolicyOptions;
|
|
128
133
|
}
|
|
129
134
|
export interface ProviderTurnMetadata {
|
|
130
135
|
readonly providerId: string;
|
|
@@ -170,7 +175,8 @@ export interface DelegatedAgentStep {
|
|
|
170
175
|
readonly label?: string;
|
|
171
176
|
};
|
|
172
177
|
}
|
|
173
|
-
|
|
178
|
+
/** Why a run stopped cleanly. `host_policy` is a `RunOptions.turnPolicy` stop; the rest are loop ceilings (F4). */
|
|
179
|
+
export type AgentFinishReason = "turn_limit" | "token_limit" | "refusal" | "host_policy";
|
|
174
180
|
export type AgentEvent = {
|
|
175
181
|
readonly type: "agent_started";
|
|
176
182
|
readonly sessionId: string;
|
|
@@ -180,8 +186,10 @@ export type AgentEvent = {
|
|
|
180
186
|
readonly sessionId: string;
|
|
181
187
|
readonly runId: string;
|
|
182
188
|
readonly usage?: Usage;
|
|
183
|
-
/** Why the loop stopped, when a limit/ceiling ended the run cleanly (F4). Absent = natural end. */
|
|
189
|
+
/** Why the loop stopped, when a limit/ceiling or a host turn policy ended the run cleanly (F4). Absent = natural end. */
|
|
184
190
|
readonly finishReason?: AgentFinishReason;
|
|
191
|
+
/** Host stop detail from `TurnPolicyOptions.stop` (≤256 bytes, redacted). Present only with `finishReason: "host_policy"`. */
|
|
192
|
+
readonly stopDetail?: string;
|
|
185
193
|
} | {
|
|
186
194
|
readonly type: "agent_suspended";
|
|
187
195
|
readonly sessionId: string;
|
|
@@ -538,6 +546,10 @@ export interface RunRecord extends OwnershipScope {
|
|
|
538
546
|
readonly startedAt: string;
|
|
539
547
|
readonly finishedAt?: string;
|
|
540
548
|
readonly abortReason?: string;
|
|
549
|
+
/** Present when the loop stopped on a ceiling or host policy instead of a natural end. */
|
|
550
|
+
readonly stopReason?: AgentFinishReason;
|
|
551
|
+
/** Host stop detail from `TurnPolicyOptions.stop` (≤256 bytes, redacted). */
|
|
552
|
+
readonly stopDetail?: string;
|
|
541
553
|
readonly error?: ErrorInfo;
|
|
542
554
|
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
543
555
|
/** Provenance ref copied from `RunOptions.promptVersion` when the host supplied one. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AgentSessionCloneOptions, AgentSessionForkOptions, CheckpointStore, CompactionOptions, CompactionResult, ContentBlock, ErrorInfo, JsonObject, JsonValue, Message, ModelConfig, OwnershipScope, RunLimitBreach, SessionEntry, SubscribeOptions, ToolCallContent, Usage } from "./contracts-core.js";
|
|
2
|
-
import type { AgentEvent, RunOptions, ToolEffectKind } from "./contracts-protocol.js";
|
|
2
|
+
import type { AgentEvent, AgentFinishReason, RunOptions, ToolEffectKind } from "./contracts-protocol.js";
|
|
3
3
|
export type AgentRunStatus = "succeeded" | "failed" | "aborted" | "suspended" | "denied";
|
|
4
4
|
export type AgentRunInterruptionKind = "input_guardrail" | "tool_approval" | "elicitation";
|
|
5
5
|
export type ApprovalOutcome = "allow_once" | "allow_for_run" | "reject_once" | "reject_for_run";
|
|
@@ -143,6 +143,17 @@ export interface AgentRunStateOptions {
|
|
|
143
143
|
readonly checkpoints: CheckpointStore;
|
|
144
144
|
/** Host-authored immutable revision required for durable runs. */
|
|
145
145
|
readonly definitionRevision: string;
|
|
146
|
+
/**
|
|
147
|
+
* Durable checkpoint cadence (plan 084 Task 1). `"decision"` (default) persists only on
|
|
148
|
+
* suspension and terminal status. `"every-turn"` additionally persists a running-state
|
|
149
|
+
* checkpoint at each provider-turn boundary — after the previous turn's tool results are in
|
|
150
|
+
* the session store, before the next provider request — so a host process that dies mid-run
|
|
151
|
+
* can `resumeAgentRun(..., { decision: "continue" })` from the last turn instead of re-running
|
|
152
|
+
* the investigation. Costs one bounded, redacted checkpoint write per provider turn; the
|
|
153
|
+
* policy is recorded in the checkpoint, so a later resume keeps checkpointing without the host
|
|
154
|
+
* repeating the option.
|
|
155
|
+
*/
|
|
156
|
+
readonly checkpointPolicy?: "decision" | "every-turn";
|
|
146
157
|
/** Suspend every tool call before its side effect. */
|
|
147
158
|
readonly interruptBeforeTool?: boolean;
|
|
148
159
|
readonly maxStateBytes?: number;
|
|
@@ -183,8 +194,13 @@ export interface AgentRunState {
|
|
|
183
194
|
}
|
|
184
195
|
export interface AgentRunResume {
|
|
185
196
|
readonly expectedVersion: number;
|
|
186
|
-
/**
|
|
187
|
-
|
|
197
|
+
/**
|
|
198
|
+
* Legacy decision path. `approve` allows all pending decisions once; `deny` terminates the run
|
|
199
|
+
* as `denied`; `continue` resumes a running-state `"every-turn"` checkpoint that has no pending
|
|
200
|
+
* decisions (crash recovery). A suspended run still requires `approve`/`deny` or a decision
|
|
201
|
+
* batch — `continue` never bypasses an approval gate and is a host-API-only action.
|
|
202
|
+
*/
|
|
203
|
+
readonly decision?: "approve" | "deny" | "continue";
|
|
188
204
|
/** Batch decision path; exactly one of decision/decisions. Applied as one atomic CAS transition. */
|
|
189
205
|
readonly decisions?: readonly RunDecision[];
|
|
190
206
|
}
|
|
@@ -194,6 +210,8 @@ export interface AgentRunResumeOptions {
|
|
|
194
210
|
readonly definitionRevision: string;
|
|
195
211
|
readonly ownership?: OwnershipScope;
|
|
196
212
|
readonly fencingToken?: number;
|
|
213
|
+
/** Host abort for the resume: checked between steps and threaded into the resumed provider/tool turn. */
|
|
214
|
+
readonly signal?: AbortSignal;
|
|
197
215
|
/** Routes root decisions for nested-run approvals back to the child (e.g. supervisor). */
|
|
198
216
|
readonly resumeNestedRun?: ResumeNestedRun;
|
|
199
217
|
/**
|
|
@@ -207,9 +225,8 @@ export interface AgentRunResumeOptions {
|
|
|
207
225
|
/** Opt-in (plan 018 Task 6): restore persisted loaded-skill bodies (requires `persistSessionState` too). */
|
|
208
226
|
readonly includeSkillBodies?: boolean;
|
|
209
227
|
}
|
|
210
|
-
/** Bounded
|
|
228
|
+
/** Bounded live-event options for `resumeAgentRunStream()`; `signal` is inherited from the base resume options. */
|
|
211
229
|
export interface AgentRunResumeStreamOptions extends AgentRunResumeOptions, SubscribeOptions {
|
|
212
|
-
readonly signal?: AbortSignal;
|
|
213
230
|
}
|
|
214
231
|
export interface AgentRunRef {
|
|
215
232
|
readonly runId: string;
|
|
@@ -251,6 +268,10 @@ export interface AgentRunResult {
|
|
|
251
268
|
readonly error?: ErrorInfo;
|
|
252
269
|
/** String form of the abort reason when `status` is `"aborted"`. */
|
|
253
270
|
readonly abortReason?: string;
|
|
271
|
+
/** Present when the loop stopped on a ceiling or host turn policy instead of a natural end. */
|
|
272
|
+
readonly stopReason?: AgentFinishReason;
|
|
273
|
+
/** Host stop detail from `TurnPolicyOptions.stop` (≤256 bytes, redacted). */
|
|
274
|
+
readonly stopDetail?: string;
|
|
254
275
|
/** Present for durable suspended/terminal runs. Payload is redacted and bounded. */
|
|
255
276
|
readonly runState?: AgentRunState;
|
|
256
277
|
/** Present only while awaiting an operator decision. */
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Guardrail, ToolResult } from "./contracts.js";
|
|
2
|
+
export interface ClaimGroundingEvidence {
|
|
3
|
+
readonly value: number;
|
|
4
|
+
/** Refer to this governed figure as `[evidence:<ref>]` immediately after a claim. */
|
|
5
|
+
readonly ref?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ClaimGroundingEvidenceExtractorContext {
|
|
8
|
+
readonly sessionId: string;
|
|
9
|
+
readonly runId: string;
|
|
10
|
+
readonly metadata: Readonly<Record<string, unknown>>;
|
|
11
|
+
readonly toolResults: readonly ToolResult[];
|
|
12
|
+
}
|
|
13
|
+
/** Supplies host-governed figures without coupling this primitive to a host store or package. */
|
|
14
|
+
export type ClaimGroundingEvidenceExtractor = (context: ClaimGroundingEvidenceExtractorContext) => readonly ClaimGroundingEvidence[];
|
|
15
|
+
export interface ClaimGroundingGuardrailOptions {
|
|
16
|
+
/** False makes the returned guardrail a no-op. */
|
|
17
|
+
readonly requireEvidenceForNumbers: boolean;
|
|
18
|
+
/** Defaults to same-run tool results. A host extractor may return its own governed figures. */
|
|
19
|
+
readonly evidenceSources?: "tool_results" | ClaimGroundingEvidenceExtractor;
|
|
20
|
+
/** Defaults to block. Flag emits an allow record with `metadata.violation: true`. */
|
|
21
|
+
readonly onViolation?: "block" | "flag";
|
|
22
|
+
/** Exact numeric equality by default; rounded accepts half the final printed unit. */
|
|
23
|
+
readonly tolerance?: "exact" | "rounded";
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Deterministic output guardrail for figures that must be grounded in same-run tool results
|
|
27
|
+
* or host-governed evidence. It never calls a provider, store, or extractor asynchronously.
|
|
28
|
+
*/
|
|
29
|
+
export declare function createClaimGroundingGuardrail(options: ClaimGroundingGuardrailOptions): Guardrail<"output">;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
const MAX_EVIDENCE_FIGURES = 4096;
|
|
2
|
+
const MAX_EVIDENCE_DEPTH = 16;
|
|
3
|
+
const MAX_EVIDENCE_NODES = 16 * 1024;
|
|
4
|
+
const MAX_EVIDENCE_TEXT_CHARS = 128 * 1024;
|
|
5
|
+
const MAX_CLAIM_CHARS = 128;
|
|
6
|
+
const MAX_CITATION_DISTANCE = 96;
|
|
7
|
+
const EVIDENCE_CITATION = /\[evidence:([a-zA-Z0-9._:-]{1,128})\]/g;
|
|
8
|
+
const NUMERIC_CLAIM = /(?<![\p{L}\p{N}_])~?[$€£¥]?[-+]?(?:\d{1,3}(?:,\d{3})+|\d+)(?:\.\d+)?[kKmMbB]?%?(?![\p{L}\p{N}_])/gu;
|
|
9
|
+
/**
|
|
10
|
+
* Deterministic output guardrail for figures that must be grounded in same-run tool results
|
|
11
|
+
* or host-governed evidence. It never calls a provider, store, or extractor asynchronously.
|
|
12
|
+
*/
|
|
13
|
+
export function createClaimGroundingGuardrail(options) {
|
|
14
|
+
const resolved = resolveOptions(options);
|
|
15
|
+
return {
|
|
16
|
+
name: "claim-grounding",
|
|
17
|
+
stage: "output",
|
|
18
|
+
revision: "1",
|
|
19
|
+
evaluate(context) {
|
|
20
|
+
if (!resolved.requireEvidenceForNumbers)
|
|
21
|
+
return { action: "allow" };
|
|
22
|
+
const figures = evidenceFigures(resolved.evidenceSources, context);
|
|
23
|
+
const refs = new Set(figures.flatMap((figure) => (figure.ref === undefined ? [] : [figure.ref])));
|
|
24
|
+
for (const claim of outputClaims(context.value.content)) {
|
|
25
|
+
const block = context.value.content[claim.contentIndex];
|
|
26
|
+
if (matchesEvidence(claim, figures, resolved.tolerance) || (block?.type === "text" && citesEvidence(block.text, claim.end, refs)))
|
|
27
|
+
continue;
|
|
28
|
+
const metadata = {
|
|
29
|
+
violation: true,
|
|
30
|
+
claim: claim.text.slice(0, MAX_CLAIM_CHARS),
|
|
31
|
+
contentIndex: claim.contentIndex,
|
|
32
|
+
start: claim.end - claim.text.length,
|
|
33
|
+
end: claim.end,
|
|
34
|
+
};
|
|
35
|
+
return resolved.onViolation === "block"
|
|
36
|
+
? { action: "block", reason: "claim_ungrounded", metadata }
|
|
37
|
+
: { action: "allow", reason: "claim_ungrounded", metadata };
|
|
38
|
+
}
|
|
39
|
+
return { action: "allow" };
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function resolveOptions(options) {
|
|
44
|
+
if (!options || typeof options.requireEvidenceForNumbers !== "boolean")
|
|
45
|
+
throw new TypeError("Claim grounding requireEvidenceForNumbers must be boolean");
|
|
46
|
+
const evidenceSources = options.evidenceSources ?? "tool_results";
|
|
47
|
+
if (evidenceSources !== "tool_results" && typeof evidenceSources !== "function")
|
|
48
|
+
throw new TypeError('Claim grounding evidenceSources must be "tool_results" or an extractor');
|
|
49
|
+
const onViolation = options.onViolation ?? "block";
|
|
50
|
+
if (onViolation !== "block" && onViolation !== "flag")
|
|
51
|
+
throw new TypeError('Claim grounding onViolation must be "block" or "flag"');
|
|
52
|
+
const tolerance = options.tolerance ?? "exact";
|
|
53
|
+
if (tolerance !== "exact" && tolerance !== "rounded")
|
|
54
|
+
throw new TypeError('Claim grounding tolerance must be "exact" or "rounded"');
|
|
55
|
+
return { requireEvidenceForNumbers: options.requireEvidenceForNumbers, evidenceSources, onViolation, tolerance };
|
|
56
|
+
}
|
|
57
|
+
function evidenceFigures(source, context) {
|
|
58
|
+
if (source !== "tool_results")
|
|
59
|
+
return source({
|
|
60
|
+
sessionId: context.sessionId,
|
|
61
|
+
runId: context.runId,
|
|
62
|
+
metadata: context.metadata,
|
|
63
|
+
toolResults: context.toolResults ?? [],
|
|
64
|
+
})
|
|
65
|
+
.filter(validFigure)
|
|
66
|
+
.slice(0, MAX_EVIDENCE_FIGURES);
|
|
67
|
+
const figures = [];
|
|
68
|
+
const budget = { chars: MAX_EVIDENCE_TEXT_CHARS, nodes: MAX_EVIDENCE_NODES };
|
|
69
|
+
for (const result of context.toolResults ?? []) {
|
|
70
|
+
if (result.error)
|
|
71
|
+
continue;
|
|
72
|
+
const ref = `tool:${result.toolCallId}`;
|
|
73
|
+
collectFigures(result.value, figures, ref, new WeakSet(), 0, budget);
|
|
74
|
+
for (const block of result.content ?? [])
|
|
75
|
+
if (block.type === "text")
|
|
76
|
+
collectTextFigures(block.text, figures, ref, budget);
|
|
77
|
+
}
|
|
78
|
+
return figures;
|
|
79
|
+
}
|
|
80
|
+
function validFigure(value) {
|
|
81
|
+
return typeof value?.value === "number" && Number.isFinite(value.value) && (value.ref === undefined || citationRef(value.ref));
|
|
82
|
+
}
|
|
83
|
+
function collectFigures(value, figures, ref, seen = new WeakSet(), depth = 0, budget = { chars: MAX_EVIDENCE_TEXT_CHARS, nodes: MAX_EVIDENCE_NODES }) {
|
|
84
|
+
if (figures.length >= MAX_EVIDENCE_FIGURES || depth > MAX_EVIDENCE_DEPTH || budget.nodes-- < 1)
|
|
85
|
+
return;
|
|
86
|
+
if (typeof value === "number") {
|
|
87
|
+
if (Number.isFinite(value))
|
|
88
|
+
figures.push({ value, ref });
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (typeof value === "string") {
|
|
92
|
+
collectTextFigures(value, figures, ref, budget);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (!value || typeof value !== "object" || seen.has(value))
|
|
96
|
+
return;
|
|
97
|
+
seen.add(value);
|
|
98
|
+
if (Array.isArray(value)) {
|
|
99
|
+
for (const item of value)
|
|
100
|
+
collectFigures(item, figures, ref, seen, depth + 1, budget);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
for (const item of Object.values(value))
|
|
104
|
+
collectFigures(item, figures, ref, seen, depth + 1, budget);
|
|
105
|
+
}
|
|
106
|
+
function collectTextFigures(text, figures, ref, budget) {
|
|
107
|
+
const bounded = text.slice(0, budget.chars);
|
|
108
|
+
budget.chars -= bounded.length;
|
|
109
|
+
for (const claim of claims(bounded, 0)) {
|
|
110
|
+
if (figures.length >= MAX_EVIDENCE_FIGURES)
|
|
111
|
+
return;
|
|
112
|
+
figures.push({ value: claim.value, ref });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function outputClaims(content) {
|
|
116
|
+
const out = [];
|
|
117
|
+
for (let index = 0; index < content.length; index += 1) {
|
|
118
|
+
const block = content[index];
|
|
119
|
+
if (block?.type !== "text")
|
|
120
|
+
continue;
|
|
121
|
+
out.push(...claims(block.text, index));
|
|
122
|
+
}
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
125
|
+
function claims(text, contentIndex) {
|
|
126
|
+
const out = [];
|
|
127
|
+
NUMERIC_CLAIM.lastIndex = 0;
|
|
128
|
+
for (let match = NUMERIC_CLAIM.exec(text); match; match = NUMERIC_CLAIM.exec(text)) {
|
|
129
|
+
const token = match[0];
|
|
130
|
+
const parsed = parseNumber(token);
|
|
131
|
+
if (parsed === undefined)
|
|
132
|
+
continue;
|
|
133
|
+
out.push({ text: token, value: parsed.value, tolerance: parsed.tolerance, end: match.index + token.length, contentIndex });
|
|
134
|
+
}
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
137
|
+
function parseNumber(token) {
|
|
138
|
+
const compact = token.replace(/^~?[$€£¥]?[-+]?/, "").replace(/%$/, "");
|
|
139
|
+
const suffix = compact.at(-1)?.toLowerCase();
|
|
140
|
+
const multiplier = suffix === "k" ? 1_000 : suffix === "m" ? 1_000_000 : suffix === "b" ? 1_000_000_000 : 1;
|
|
141
|
+
const numberText = (multiplier === 1 ? compact : compact.slice(0, -1)).replace(/,/g, "");
|
|
142
|
+
const value = Number(numberText) * multiplier;
|
|
143
|
+
if (!Number.isFinite(value))
|
|
144
|
+
return undefined;
|
|
145
|
+
const fraction = numberText.split(".")[1]?.length ?? 0;
|
|
146
|
+
return { value, tolerance: 0.5 * multiplier * 10 ** -fraction };
|
|
147
|
+
}
|
|
148
|
+
function matchesEvidence(claim, figures, tolerance) {
|
|
149
|
+
return figures.some((figure) => Math.abs(figure.value - claim.value) <= (tolerance === "rounded" ? claim.tolerance : 0));
|
|
150
|
+
}
|
|
151
|
+
function citesEvidence(text, end, refs) {
|
|
152
|
+
const nearby = text.slice(end, end + MAX_CITATION_DISTANCE);
|
|
153
|
+
EVIDENCE_CITATION.lastIndex = 0;
|
|
154
|
+
for (let match = EVIDENCE_CITATION.exec(nearby); match; match = EVIDENCE_CITATION.exec(nearby))
|
|
155
|
+
if (refs.has(match[1] ?? ""))
|
|
156
|
+
return true;
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
function citationRef(value) {
|
|
160
|
+
return /^[a-zA-Z0-9._:-]{1,128}$/.test(value);
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=evidence-grounding.js.map
|
|
@@ -24,6 +24,10 @@ export interface HostCompositionOptions {
|
|
|
24
24
|
readonly workspaceRoot?: string;
|
|
25
25
|
readonly sandboxRoots?: readonly string[];
|
|
26
26
|
readonly credentialRefs?: readonly string[];
|
|
27
|
+
readonly connectedApps?: {
|
|
28
|
+
readonly appIds: readonly string[];
|
|
29
|
+
readonly serverIds: readonly string[];
|
|
30
|
+
};
|
|
27
31
|
readonly governance?: HostCompositionGovernance;
|
|
28
32
|
readonly redactor?: SecretRedactor;
|
|
29
33
|
/** Explicit opt-in for live network checks; inert by default. */
|
|
@@ -38,6 +42,10 @@ export interface HostCompositionReport {
|
|
|
38
42
|
readonly profile: "personal" | "business";
|
|
39
43
|
readonly effectiveTools: readonly HostCompositionToolReport[];
|
|
40
44
|
readonly credentialReferences: readonly string[];
|
|
45
|
+
readonly connectedApps?: {
|
|
46
|
+
readonly appIds: readonly string[];
|
|
47
|
+
readonly serverIds: readonly string[];
|
|
48
|
+
};
|
|
41
49
|
readonly ownership: {
|
|
42
50
|
readonly tenantId?: string;
|
|
43
51
|
readonly accountId?: string;
|
|
@@ -66,6 +74,11 @@ export interface HostCompositionReport {
|
|
|
66
74
|
readonly warnings: readonly string[];
|
|
67
75
|
};
|
|
68
76
|
}
|
|
77
|
+
/** Classifies a store by declared kind/durability and constructor name — never reads contents or connection strings (plan 084 Task 4 reuses it for run-bundle snapshots). */
|
|
78
|
+
export declare function describeStorage(store: unknown, checkpoints: unknown): {
|
|
79
|
+
kind: string;
|
|
80
|
+
durable: boolean;
|
|
81
|
+
};
|
|
69
82
|
/**
|
|
70
83
|
* Inspects a host composition in an inert, bounded, and secret-redacting manner.
|
|
71
84
|
* Performs zero network calls by default.
|