@arnilo/prism 0.8.0 → 0.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 +38 -0
- package/README.md +11 -11
- package/dist/agent-approval.d.ts +11 -2
- package/dist/agent-event-source.d.ts +9 -1
- package/dist/agent-event-source.js +10 -3
- package/dist/agent-loops.js +7 -4
- package/dist/agent-run-lifecycle.d.ts +15 -1
- package/dist/agent-run-lifecycle.js +63 -6
- package/dist/agent-run-state.d.ts +22 -2
- package/dist/agent-run-state.js +57 -5
- package/dist/agent-session/helpers.js +14 -0
- package/dist/agent-session/session/assemble.js +126 -24
- package/dist/agent-session/session/persist.d.ts +11 -0
- package/dist/agent-session/session/persist.js +37 -11
- package/dist/agent-session/session/provider-round.d.ts +14 -4
- package/dist/agent-session/session/provider-round.js +185 -19
- package/dist/agent-session/session/tool-round.js +20 -1
- package/dist/agent-session/session/types.d.ts +25 -2
- package/dist/agent-session/session.d.ts +38 -4
- package/dist/agent-session/session.js +76 -5
- package/dist/attention-compiler.d.ts +51 -2
- package/dist/attention-compiler.js +282 -21
- package/dist/cache-helpers.d.ts +4 -2
- package/dist/cache-helpers.js +8 -6
- package/dist/checkpoint-restore.d.ts +45 -0
- package/dist/checkpoint-restore.js +54 -0
- package/dist/context-budget.d.ts +2 -1
- package/dist/context-budget.js +24 -2
- package/dist/contracts-core/agent.d.ts +30 -0
- package/dist/contracts-core/attention.d.ts +95 -0
- package/dist/contracts-core/content.d.ts +10 -0
- package/dist/contracts-core/guardrail-packs.d.ts +41 -0
- package/dist/contracts-core/guardrail-packs.js +2 -0
- package/dist/contracts-core/provider.d.ts +25 -0
- package/dist/contracts-core/run-limits.d.ts +19 -0
- package/dist/contracts-core/session.d.ts +23 -5
- package/dist/contracts-core/session.js +21 -2
- package/dist/contracts-core/usage.d.ts +40 -0
- package/dist/contracts-core/usage.js +8 -0
- package/dist/contracts-core.d.ts +2 -0
- package/dist/contracts-core.js +2 -0
- package/dist/contracts-protocol.d.ts +76 -2
- package/dist/contracts-run-state.d.ts +56 -1
- package/dist/guardrail-packs/coding-standard.d.ts +3 -0
- package/dist/guardrail-packs/coding-standard.js +63 -0
- package/dist/guardrail-packs/destructive-commands.d.ts +3 -0
- package/dist/guardrail-packs/destructive-commands.js +46 -0
- package/dist/guardrail-packs/errors.d.ts +7 -0
- package/dist/guardrail-packs/errors.js +9 -0
- package/dist/guardrail-packs/index.d.ts +4 -0
- package/dist/guardrail-packs/index.js +15 -0
- package/dist/guardrail-packs/secrets-hygiene.d.ts +3 -0
- package/dist/guardrail-packs/secrets-hygiene.js +23 -0
- package/dist/guardrail-packs/types.d.ts +16 -0
- package/dist/guardrail-packs/types.js +2 -0
- package/dist/guardrail-packs/validation-respect.d.ts +3 -0
- package/dist/guardrail-packs/validation-respect.js +53 -0
- package/dist/guardrails.d.ts +20 -1
- package/dist/guardrails.js +268 -0
- package/dist/index.d.ts +14 -9
- package/dist/index.js +9 -6
- package/dist/input.d.ts +8 -1
- package/dist/input.js +68 -6
- package/dist/middleware.d.ts +37 -2
- package/dist/middleware.js +41 -0
- package/dist/node/session-store-jsonl.js +18 -3
- package/dist/observability.js +6 -0
- package/dist/provider-events.d.ts +8 -2
- package/dist/provider-events.js +60 -2
- package/dist/providers/openai-compatible.js +6 -3
- package/dist/run-bundle.js +2 -1
- package/dist/run-limits.d.ts +11 -1
- package/dist/run-limits.js +46 -0
- package/dist/session-stores.d.ts +12 -1
- package/dist/session-stores.js +21 -4
- package/dist/testing/agent-event-source-conformance.js +41 -2
- package/dist/testing/prefix-stability-conformance.d.ts +30 -0
- package/dist/testing/prefix-stability-conformance.js +104 -0
- package/dist/testing/session-store-conformance.d.ts +3 -2
- package/dist/testing/session-store-conformance.js +48 -0
- package/dist/tools.d.ts +5 -0
- package/dist/tools.js +11 -3
- package/dist/usage-estimation.d.ts +29 -0
- package/dist/usage-estimation.js +79 -0
- package/docs/agent-events.md +68 -1
- package/docs/agent-session-runtime.md +1 -0
- package/docs/attention-compiler.md +89 -8
- package/docs/coding-agent-tools.md +1 -1
- package/docs/compaction-and-retry.md +1 -1
- package/docs/compaction-observational-memory.md +33 -6
- package/docs/durable-runs.md +42 -0
- package/docs/embeddings.md +5 -0
- package/docs/evaluations.md +5 -0
- package/docs/execution-timeline.md +78 -1
- package/docs/guardrails.md +38 -2
- package/docs/index.md +32 -13
- package/docs/input-and-prompt-assembly.md +3 -3
- package/docs/knowledge-sync.md +4 -0
- package/docs/middleware-hooks.md +38 -2
- package/docs/migrate-to-0.9.md +210 -0
- package/docs/migration.md +13 -0
- package/docs/multi-agent-patterns.md +25 -2
- package/docs/node-jsonl-session-store.md +7 -1
- package/docs/observability.md +7 -3
- package/docs/options-index.md +2 -1
- package/docs/policy-and-audit.md +13 -1
- package/docs/prefix-stability-conformance.md +93 -0
- package/docs/provider-caching.md +4 -4
- package/docs/provider-conformance.md +16 -0
- package/docs/provider-packages.md +20 -20
- package/docs/public-contracts.md +2 -2
- package/docs/rag.md +101 -3
- package/docs/release-and-install.md +39 -37
- package/docs/runs-and-usage.md +43 -6
- package/docs/scoped-agent-memory.md +262 -0
- package/docs/session-store-conformance.md +1 -2
- package/docs/session-stores.md +17 -17
- package/docs/supervisors.md +32 -12
- package/docs/tools.md +17 -0
- package/docs/workflows.md +5 -0
- package/package.json +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.9.0] - 2026-09-20 (attention budget axes, turn traces, cache-stable disclosure, tool narrowing, guardrail packs, background agents, session search, deterministic turns, shared scopes)
|
|
4
|
+
|
|
5
|
+
> **Eleven publishable packages.** All 086–098 features ship behind their documented options, joined by the live-stream terminal fix (plan 100). Predecessor published line is **0.8.0**. Registry/tag writes stay operator-authorized.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **Attention budget axes and durable folding.** `attentionCompiler.trigger` accepts `input_ratio` (the legacy axis), `run_input_ratio` (fires against `RunLimits.maxInputTokens` instead of the model window), `token_floor`, and a host predicate, alone or as an any-of array; `attentionCompiler.durable: true` writes the fold ledger and sticky frontier into the checkpoint so a resumed run keeps its stubs. See [docs/attention-compiler.md](docs/attention-compiler.md).
|
|
9
|
+
- **Turn traces and exhaustion attribution.** `provider_turn_finished` carries a closed `stopReason` taxonomy, a `budgets` snapshot, the effective tool menu (`tools.count` / `tools.idsHash`), and provider-reported cache counts with a per-turn `hitRate`; `agent_finished` carries `finishReason` / `stopDetail`, `AgentRunResult.stopReason` names host-policy and loop-ceiling stops, and the execution timeline adds `turns[i].stopReason` plus `timeline.exhaustion`. See [docs/agent-events.md](docs/agent-events.md), [docs/execution-timeline.md](docs/execution-timeline.md).
|
|
10
|
+
- **Cache-stable progressive disclosure and cache metrics.** Skill bodies, deferred tool schemas, and loaded references land at cache-stable positions (append-only tail or explicit invalidation); cache read/write tokens and per-turn hit rate are recorded on usage records and turn events. See [docs/provider-caching.md](docs/provider-caching.md), [docs/prefix-stability-conformance.md](docs/prefix-stability-conformance.md).
|
|
11
|
+
- **Per-turn tool narrowing.** `AgentConfig.toolNarrowing` / `RunOptions.toolNarrowing` returns the effective tool subset before each provider turn; out-of-grant names are dropped and reported on `tool_narrowing_clamped`. See [docs/tools.md](docs/tools.md).
|
|
12
|
+
- **Usage estimation and the context meter.** `estimateMessageTokens` / `MODEL_FAMILY_TOKENS` / `resolveModelFamily` produce labeled `TokenEstimate`s, `AgentConfig.usageEstimation: "fallback"` (default) charges one labeled estimate for usage-less providers, and `session.contextMeter()` exposes `{ inputTokens, source, inputCap?, runInputBudget?, usedRatio? }`. See [docs/runs-and-usage.md](docs/runs-and-usage.md).
|
|
13
|
+
- **Execution guardrail packs.** `guardrailPacks` compiles four built-in restrictive rule sets (`coding-standard`, `destructive-commands`, `validation-respect`, `secrets-hygiene`) onto existing tool stages, each with a trajectory scorer (`createGuardrailPackScorer`). See [docs/guardrails.md](docs/guardrails.md).
|
|
14
|
+
- **Background child agents and child-event passthrough.** `lifetime: "session"`, `report: "on-complete" | "milestones" | "stream"`, `milestone`, and `budgetShare` on `delegate` / `delegateAsync` / `spawn_agent`, with `child_milestone`, `child_failed`, and rate-coalesced child events. See [docs/supervisors.md](docs/supervisors.md), [docs/multi-agent-patterns.md](docs/multi-agent-patterns.md).
|
|
15
|
+
- **Checkpoint sidecar metadata and restore hooks.** A redacted ≤4 KB `checkpointMetadata` map rides every checkpoint record without charging `maxStateBytes`, and `restoreHooks` revert external layers before a resume claims the run. See [docs/durable-runs.md](docs/durable-runs.md).
|
|
16
|
+
- **Bounded workspace session search.** `SessionStore.searchSessions?(query)` over workspace/time/provider/label/kind/ownership filters with an indexed full-text path (SQLite FTS5, Postgres `tsvector`, migration 004) and a shared linear matcher for memory and JSONL stores. See [docs/session-stores.md](docs/session-stores.md).
|
|
17
|
+
- **Deterministic no-model turns.** The `beforeProviderTurn` middleware hook answers a turn from host data (`BeforeProviderTurnPayload` → `DeterministicTurnAnswer`) with no provider request, recorded as `deterministic` on the timeline and graded by `createDeterministicTurnScorer`. See [docs/middleware-hooks.md](docs/middleware-hooks.md).
|
|
18
|
+
- **Shared work scopes for observational memory.** `sharedScopes` at attach time plus owner-branch grants (`grant` / `revoke`) let granted sessions contribute to and read one scope, with deny-by-default rechecks, `onScopeAccess` auditing, and revocation at the next read. See [docs/compaction-observational-memory.md](docs/compaction-observational-memory.md).
|
|
19
|
+
- **Retrieval revocation and a zero-service default reranker.** `createDeletionPropagator` (+ `collectInvalidationIds` / `listInvalidatedIds`, `repointSource`, `retireWikiSources`, RAG/wiki handlers) propagates deletion and revocation through derived artifacts, `createAccessRecheck` rechecks governed sources per query, and `resolveReranker({ kind: "local" })` / `createLocalReranker()` run an in-process cross-encoder behind the `LocalRerankRuntime` seam with no declared inference dependency. See [docs/rag.md](docs/rag.md), [docs/embeddings.md](docs/embeddings.md).
|
|
20
|
+
- **Examples.** `examples/attention-budget-axes.ts`, `examples/tool-narrowing-planes.ts`, `examples/session-search.ts`.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- **Lockstep `0.8.0` → `0.9.0`.** All eleven publishable manifests move together with `^0.9.0` internal ranges; the lockfile, the `src/index.ts` version constant, the docs index banner, the release-workflow tag lists, and the generated package-truth artifact agree (enforced by `scripts/version-literal-gate.test.mjs`).
|
|
24
|
+
- **A limit death no longer ends a stream early.** The terminal set is exactly `agent_finished` / `agent_denied` / `error`, decided by one exported `isTerminalAgentEventType` predicate shared by the memory, NATS, and Postgres sources, AG-UI replay, the A2A stream break, AG-UI `filterRun`, and conversation replay; `run_limit_exceeded` → `budget_exhausted` → `error` now reaches page, subscribe, and replay consumers in order. See [docs/migrate-to-0.9.md](docs/migrate-to-0.9.md).
|
|
25
|
+
- **Usage-less providers are charged a labeled estimate by default.** `AgentConfig.usageEstimation: "fallback"` records one `TokenEstimate` (`estimated: true` + `confidence`) at the existing usage seam; reported usage always wins, and `"off"` keeps the previous zero-for-no-usage behavior.
|
|
26
|
+
- **Compat baselines regenerated at 0.9.0**: `+119` public names with **zero removals** and zero renames (`@arnilo/prism` +49, `@arnilo/prism-memory` +60, `@arnilo/prism-core` +10). One type-level consumer change: the `recordUsage` callback of `generateProviderTurn` / `generateWithRetry` returns `Promise<Usage | undefined>` instead of `Promise<void>`.
|
|
27
|
+
- **Budgets rebaselined with recorded reasons**: root packed/unpacked/file count, per-package export ceilings, and the non-null assertion ratchet carry the measured 0.9.0 values and the plans that moved them.
|
|
28
|
+
- **Migration guide for 0.8.0 hosts**: [docs/migrate-to-0.9.md](docs/migrate-to-0.9.md) — four behavior changes inside existing surfaces, every new option with defaults and sizing lines, upgrade steps, and rollback — indexed from [docs/migration.md](docs/migration.md) and [docs/index.md](docs/index.md).
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
- **Streams ended one record early on a run-limit death.** In-memory, NATS, Postgres, and AG-UI replay consumers treated `run_limit_exceeded` as terminal, so a subscriber that stopped at the first breach record never saw the `budget_exhausted` attribution or the terminal `error` (`assertAgentEventSourceConforms` now asserts the delivery order against every source implementation).
|
|
32
|
+
- **Attention folding was inert when the run input cap sat below the model window.** The new `run_input_ratio` axis evaluates cumulative run spend plus the assembled request, so a 500k run cap under a 1M-window model folds instead of dying at the cap.
|
|
33
|
+
- **Unindexed session search could scan an unbounded store.** Memory and JSONL stores share `searchLinearSessions` with contract linear caps, overridable only within their hard bounds.
|
|
34
|
+
|
|
35
|
+
### Security
|
|
36
|
+
- **Guardrail packs fail closed on secrets and destructive commands.** `secrets-hygiene` scans tool arguments for credential shapes, `destructive-commands` matches shell commands, and `coding-standard` keeps edits inside `options.roots`; every pack is restrictive-only, so a pack can never widen a host policy. Cap: 8 packs, 64 rules per pack.
|
|
37
|
+
- **Shared-scope access is deny-by-default and audited.** Absent, unknown, revoked, unreachable, or not-opened-locally scope state denies the read, every decision reaches `onScopeAccess`, revocation lands on the next read, and the host `entries(sessionId)` callback stays inside one `OwnershipScope`.
|
|
38
|
+
- **Checkpoint metadata and restore hooks are redacted and bounded.** The sidecar map is redacted unconditionally (no public-key exemption) and capped at 4 KiB; a failing or timed-out restore hook aborts the restore rather than claiming the run.
|
|
39
|
+
- **Estimates can never masquerade as provider truth.** Every estimated token count carries `estimated: true` (and `confidence`), reported usage is never overwritten, and estimates never produce a price — a configured `maxCost` stays the fail-closed envelope.
|
|
40
|
+
|
|
3
41
|
## [0.8.0] - 2026-09-18 (messaging channels, connected apps, work family, durable runs, honesty gates)
|
|
4
42
|
|
|
5
43
|
> **Eleven publishable packages.** `@arnilo/prism-channels` is new; `@arnilo/prism-work` replaces `@arnilo/prism-office`. Predecessor published line is **0.7.0**. Registry/tag writes stay operator-authorized.
|
package/README.md
CHANGED
|
@@ -162,17 +162,17 @@ printf '{"id":"1","command":"prompt","params":{"input":"Hi"}}\n' \
|
|
|
162
162
|
|
|
163
163
|
| package | version | notes |
|
|
164
164
|
| --- | --- | --- |
|
|
165
|
-
| `@arnilo/prism` | 0.
|
|
166
|
-
| `@arnilo/prism-channels` | 0.
|
|
167
|
-
| `@arnilo/prism-coding-tools` | 0.
|
|
168
|
-
| `@arnilo/prism-core` | 0.
|
|
169
|
-
| `@arnilo/prism-providers` | 0.
|
|
170
|
-
| `@arnilo/prism-acp-agent` | 0.
|
|
171
|
-
| `@arnilo/prism-ag-ui` | 0.
|
|
172
|
-
| `@arnilo/prism-mcp` | 0.
|
|
173
|
-
| `@arnilo/prism-memory` | 0.
|
|
174
|
-
| `@arnilo/prism-web-tools` | 0.
|
|
175
|
-
| `@arnilo/prism-work` | 0.
|
|
165
|
+
| `@arnilo/prism` | 0.9.0 | core — runtime, CLI/RPC, templates, docs |
|
|
166
|
+
| `@arnilo/prism-channels` | 0.9.0 | family — transport-neutral messaging runtime, durable journal, pairing and one-use approvals; official /telegram (private DMs, opt-in granted groups/topics) and experimental pinned signal-cli /signal |
|
|
167
|
+
| `@arnilo/prism-coding-tools` | 0.9.0 | family — /agent, /security, /openapi, /computer-use-linux, /dev, /caveman, /ponytail, /impeccable subpaths |
|
|
168
|
+
| `@arnilo/prism-core` | 0.9.0 | family — /runtime, /sessions, /governance, /credentials, /enterprise, /validation subpaths |
|
|
169
|
+
| `@arnilo/prism-providers` | 0.9.0 | family — all provider adapters as `/<adapter>` subpaths |
|
|
170
|
+
| `@arnilo/prism-acp-agent` | 0.9.0 | capability — ACP adapter |
|
|
171
|
+
| `@arnilo/prism-ag-ui` | 0.9.0 | capability — AG-UI/A2A/A2UI adapter |
|
|
172
|
+
| `@arnilo/prism-mcp` | 0.9.0 | capability — MCP client/server/OAuth interop |
|
|
173
|
+
| `@arnilo/prism-memory` | 0.9.0 | capability — memory plus /rag, /compaction/*, /fabric, /graft, /wiki subpaths |
|
|
174
|
+
| `@arnilo/prism-web-tools` | 0.9.0 | capability — Brave/Exa/Firecrawl plus peer-gated /browser and /obscura subpaths |
|
|
175
|
+
| `@arnilo/prism-work` | 0.9.0 | capability — /connectors, /documents, /sheets, /diagrams, /document-reader, /sandbox, /skills, /tools subpaths |
|
|
176
176
|
<!-- generated:package-truth:inventory end -->
|
|
177
177
|
|
|
178
178
|
## Scripts
|
package/dist/agent-approval.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import type { CheckpointRestoreAudit } from "./checkpoint-restore.js";
|
|
1
2
|
import type { StoredAgentRunState } from "./agent-run-state.js";
|
|
2
|
-
import { Agent, AgentRunResume, AgentRunState, AgentRunStateOptions, DecisionScope, NestedRunOutcome, PendingDecision, RunDecision, StickyDecision, ToolResult } from "./contracts.js";
|
|
3
|
+
import { Agent, AgentRunCheckpointMetadata, AgentRunResume, AgentRunState, AgentRunStateOptions, DecisionScope, NestedRunOutcome, PendingDecision, RunDecision, StickyDecision, ToolResult } from "./contracts.js";
|
|
3
4
|
import type { AgentIdentity } from "./identity.js";
|
|
4
5
|
/** Pending decisions of a suspended state, synthesizing the legacy single-approval shape. */
|
|
5
6
|
export declare function pendingDecisionsOf(state: StoredAgentRunState): readonly PendingDecision[] | undefined;
|
|
@@ -50,7 +51,15 @@ export declare function decisionScopesEqual(a: DecisionScope, b: DecisionScope):
|
|
|
50
51
|
export declare function nestedOutcomeToolResult(outcome: Exclude<NestedRunOutcome, {
|
|
51
52
|
status: "suspended";
|
|
52
53
|
}>, toolCallId: string, name: string): ToolResult;
|
|
53
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Non-state extras carried into a resumed run: the sidecar metadata seed (so later checkpoint
|
|
56
|
+
* writes preserve the record's map) and the restore audit emitted on `agent_resumed`.
|
|
57
|
+
*/
|
|
58
|
+
export interface ActiveDurableRunExtras {
|
|
59
|
+
readonly checkpointMetadata?: AgentRunCheckpointMetadata;
|
|
60
|
+
readonly restore?: CheckpointRestoreAudit;
|
|
61
|
+
}
|
|
62
|
+
export interface ActiveDurableRun extends ActiveDurableRunExtras {
|
|
54
63
|
readonly options: AgentRunStateOptions;
|
|
55
64
|
state?: StoredAgentRunState;
|
|
56
65
|
version: number;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentEventSource, AgentEventSourceOptions } from "./contracts.js";
|
|
1
|
+
import type { AgentEvent, AgentEventSource, AgentEventSourceOptions } from "./contracts.js";
|
|
2
2
|
export type AgentEventSourceErrorCode = "ERR_PRISM_AGENT_EVENT_SOURCE_INPUT" | "ERR_PRISM_AGENT_EVENT_SOURCE_CURSOR" | "ERR_PRISM_AGENT_EVENT_SOURCE_RETENTION" | "ERR_PRISM_AGENT_EVENT_SOURCE_OVERFLOW" | "ERR_PRISM_AGENT_EVENT_SOURCE_CLOSED";
|
|
3
3
|
export declare class AgentEventSourceError extends Error {
|
|
4
4
|
readonly code: AgentEventSourceErrorCode;
|
|
@@ -9,3 +9,11 @@ export declare class AgentEventSourceError extends Error {
|
|
|
9
9
|
* wakeup or persistence; use a database-backed source for distributed delivery.
|
|
10
10
|
*/
|
|
11
11
|
export declare function createMemoryAgentEventSource(options?: AgentEventSourceOptions): AgentEventSource;
|
|
12
|
+
/**
|
|
13
|
+
* Terminal-set membership for agent event streams: a run's stream ends on its outcome record.
|
|
14
|
+
*
|
|
15
|
+
* Attribution records — `run_limit_exceeded` and `budget_exhausted` — are delivered *before* that
|
|
16
|
+
* outcome (the `error` that follows a run-limit death), so a page, subscription, or replay must
|
|
17
|
+
* keep reading past them. Every stream-ending site routes through this one predicate.
|
|
18
|
+
*/
|
|
19
|
+
export declare function isTerminalAgentEventType(type: AgentEvent["type"]): boolean;
|
|
@@ -439,12 +439,19 @@ function positionAfter(records, cursor, stream) {
|
|
|
439
439
|
}
|
|
440
440
|
function terminalAt(records, after) {
|
|
441
441
|
const last = records.at(-1);
|
|
442
|
-
if (!last || !
|
|
442
|
+
if (!last || !isTerminalAgentEventType(last.type))
|
|
443
443
|
return false;
|
|
444
444
|
return after === undefined || after.sequence <= last.sequence;
|
|
445
445
|
}
|
|
446
|
-
|
|
447
|
-
|
|
446
|
+
/**
|
|
447
|
+
* Terminal-set membership for agent event streams: a run's stream ends on its outcome record.
|
|
448
|
+
*
|
|
449
|
+
* Attribution records — `run_limit_exceeded` and `budget_exhausted` — are delivered *before* that
|
|
450
|
+
* outcome (the `error` that follows a run-limit death), so a page, subscription, or replay must
|
|
451
|
+
* keep reading past them. Every stream-ending site routes through this one predicate.
|
|
452
|
+
*/
|
|
453
|
+
export function isTerminalAgentEventType(type) {
|
|
454
|
+
return type === "agent_finished" || type === "agent_denied" || type === "error";
|
|
448
455
|
}
|
|
449
456
|
function sameRecord(existing, input) {
|
|
450
457
|
const { sequence: _existingSequence, ...existingWithoutSequence } = existing;
|
package/dist/agent-loops.js
CHANGED
|
@@ -25,12 +25,14 @@ export const singleShotLoop = {
|
|
|
25
25
|
ctx.emit({ type: "turn_started", sessionId: ctx.sessionId, runId: ctx.runId, turn });
|
|
26
26
|
const request = await ctx.assemble(nextInput, undefined, turn);
|
|
27
27
|
throwIfAborted(ctx.signal);
|
|
28
|
-
const { content, calls, messageId, started, usage: turnUsage } = await ctx.generate(request);
|
|
28
|
+
const { content, calls, messageId, started, usage: turnUsage, metadata: turnMetadata } = await ctx.generate(request);
|
|
29
29
|
usage = turnUsage ?? usage;
|
|
30
30
|
if (turn === 1)
|
|
31
31
|
ctx.history.push(...ctx.inputMessages);
|
|
32
32
|
if (started) {
|
|
33
|
-
|
|
33
|
+
// Turn provenance (plan 096): a deterministic answer carries `{ deterministic: { middleware } }`
|
|
34
|
+
// so the persisted transcript proves the turn had no model behind it.
|
|
35
|
+
const message = { id: messageId, role: "assistant", content, ...(turnMetadata ? { metadata: turnMetadata } : {}) };
|
|
34
36
|
ctx.history.push(message);
|
|
35
37
|
await ctx.appendMessage(message);
|
|
36
38
|
ctx.emit({ type: "message_finished", sessionId: ctx.sessionId, runId: ctx.runId, message });
|
|
@@ -140,7 +142,7 @@ export function generateValidateReviseLoop(opts) {
|
|
|
140
142
|
}
|
|
141
143
|
}
|
|
142
144
|
throwIfAborted(ctx.signal);
|
|
143
|
-
const { content, calls, messageId, started, usage: turnUsage } = await ctx.generate(request);
|
|
145
|
+
const { content, calls, messageId, started, usage: turnUsage, metadata: turnMetadata } = await ctx.generate(request);
|
|
144
146
|
usage = turnUsage ?? usage;
|
|
145
147
|
if (pendingHistory.length > 0) {
|
|
146
148
|
ctx.history.push(...pendingHistory);
|
|
@@ -149,7 +151,8 @@ export function generateValidateReviseLoop(opts) {
|
|
|
149
151
|
if (turn === 1)
|
|
150
152
|
ctx.history.push(...ctx.inputMessages);
|
|
151
153
|
if (started) {
|
|
152
|
-
|
|
154
|
+
// Same provenance contract as singleShotLoop (plan 096).
|
|
155
|
+
const message = { id: messageId, role: "assistant", content, ...(turnMetadata ? { metadata: turnMetadata } : {}) };
|
|
153
156
|
ctx.history.push(message);
|
|
154
157
|
await ctx.appendMessage(message);
|
|
155
158
|
ctx.emit({ type: "message_finished", sessionId: ctx.sessionId, runId: ctx.runId, message });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Agent, AgentEvent, AgentRunRef, AgentRunResult, AgentRunResume, AgentRunResumeOptions, AgentRunResumeStreamOptions, AgentRunStatusResult, CheckpointStore, OwnershipScope, SubscribeOptions } from "./contracts.js";
|
|
1
|
+
import type { Agent, AgentCheckpointRestoreHook, AgentEvent, AgentRunCheckpointMetadataSource, AgentRunRef, AgentRunResult, AgentRunResume, AgentRunResumeOptions, AgentRunResumeStreamOptions, AgentRunStatusResult, CheckpointStore, OwnershipScope, SubscribeOptions } from "./contracts.js";
|
|
2
2
|
export interface AgentRunLifecycleAgent {
|
|
3
3
|
readonly agent: Agent;
|
|
4
4
|
/** Current host-authored revision; it must match the stored revision. */
|
|
@@ -12,6 +12,14 @@ export interface AgentRunLifecycleOptions {
|
|
|
12
12
|
readonly signal?: AbortSignal;
|
|
13
13
|
}) => AgentRunLifecycleAgent | Promise<AgentRunLifecycleAgent>;
|
|
14
14
|
readonly fencingToken?: number;
|
|
15
|
+
/**
|
|
16
|
+
* Plan 094 Task 3: external-state restore hooks, run on every claiming resume before the
|
|
17
|
+
* checkpoint is claimed. Registered once here because a resume builds its session from the
|
|
18
|
+
* stored state (there is no live session to register against beforehand).
|
|
19
|
+
*/
|
|
20
|
+
readonly restoreHooks?: readonly AgentCheckpointRestoreHook[];
|
|
21
|
+
/** Per-hook restore ceiling in ms; defaults to `DEFAULT_CHECKPOINT_RESTORE_TIMEOUT_MS`. */
|
|
22
|
+
readonly restoreHookTimeoutMs?: number;
|
|
15
23
|
}
|
|
16
24
|
export interface AgentRunLifecycleRequest {
|
|
17
25
|
readonly ownership?: OwnershipScope;
|
|
@@ -22,6 +30,12 @@ export interface AgentRunLifecycleRequest {
|
|
|
22
30
|
readonly persistSessionState?: boolean;
|
|
23
31
|
/** Opt-in (plan 018 Task 6): restore persisted loaded-skill bodies on resume (requires `persistSessionState` too). */
|
|
24
32
|
readonly includeSkillBodies?: boolean;
|
|
33
|
+
/** Checkpoint sidecar metadata applied on resume (and to the resumed run's later checkpoints). */
|
|
34
|
+
readonly checkpointMetadata?: AgentRunCheckpointMetadataSource;
|
|
35
|
+
/** Plan 094 Task 3: restore hooks for this resume; the lifecycle's own hooks are used when omitted. */
|
|
36
|
+
readonly restoreHooks?: readonly AgentCheckpointRestoreHook[];
|
|
37
|
+
/** Per-hook restore ceiling in ms; defaults to `DEFAULT_CHECKPOINT_RESTORE_TIMEOUT_MS`. */
|
|
38
|
+
readonly restoreHookTimeoutMs?: number;
|
|
25
39
|
}
|
|
26
40
|
/** Bounded live-event options for a durable lifecycle resume. */
|
|
27
41
|
export interface AgentRunLifecycleStreamRequest extends AgentRunLifecycleRequest, SubscribeOptions {
|
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
import { assertValidAgentRunResume, pendingDecisionsOf, resolveRunDecisions } from "./agent-approval.js";
|
|
2
|
-
import { agentFingerprint, loadAgentRunState, publicState, saveAgentRunState } from "./agent-run-state.js";
|
|
2
|
+
import { agentFingerprint, loadAgentRunState, publicState, resolveCheckpointMetadata, saveAgentRunState } from "./agent-run-state.js";
|
|
3
3
|
import { RuntimeAgentSession, throwIfAbortedSignal } from "./agent-session.js";
|
|
4
|
-
import { parseAttentionStickyFrontier } from "./attention-compiler.js";
|
|
4
|
+
import { parseAttentionStickyFrontier, restoreAttentionFoldLedger } from "./attention-compiler.js";
|
|
5
|
+
import { runCheckpointRestoreHooks } from "./checkpoint-restore.js";
|
|
5
6
|
import { AgentRunStateError } from "./contracts.js";
|
|
6
7
|
function assertAgentId(actual, expected) {
|
|
7
8
|
if (expected !== undefined && actual !== expected)
|
|
8
9
|
throw new AgentRunStateError("Agent run capability mismatch");
|
|
9
10
|
}
|
|
11
|
+
/** Lifecycle-registered hooks run first, then per-request ones; either timeout setting wins for both. */
|
|
12
|
+
function restoreHookOptions(lifecycle, request) {
|
|
13
|
+
const hooks = [...(lifecycle.restoreHooks ?? []), ...(request.restoreHooks ?? [])];
|
|
14
|
+
const timeoutMs = request.restoreHookTimeoutMs ?? lifecycle.restoreHookTimeoutMs;
|
|
15
|
+
return {
|
|
16
|
+
...(hooks.length > 0 ? { restoreHooks: hooks } : {}),
|
|
17
|
+
...(timeoutMs === undefined ? {} : { restoreHookTimeoutMs: timeoutMs }),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
10
20
|
/** Host capability for durable agent status/resume. Adapters supply authorized ownership only. */
|
|
11
21
|
export function createAgentRunLifecycle(options) {
|
|
12
22
|
return {
|
|
13
23
|
async status(ref, request = {}) {
|
|
14
24
|
request.signal?.throwIfAborted();
|
|
15
|
-
const { state, record } = await loadAgentRunState(options.checkpoints, ref, request.ownership);
|
|
25
|
+
const { state, record, metadata } = await loadAgentRunState(options.checkpoints, ref, request.ownership);
|
|
16
26
|
assertAgentId(state.agentId, request.agentId);
|
|
17
27
|
request.signal?.throwIfAborted();
|
|
18
|
-
return { state: publicState({ ...state, version: record.version }), version: record.version };
|
|
28
|
+
return { state: publicState({ ...state, version: record.version }), version: record.version, ...(metadata ? { metadata } : {}) };
|
|
19
29
|
},
|
|
20
30
|
async resume(ref, resume, request = {}) {
|
|
21
31
|
request.signal?.throwIfAborted();
|
|
@@ -31,6 +41,8 @@ export function createAgentRunLifecycle(options) {
|
|
|
31
41
|
signal: request.signal,
|
|
32
42
|
persistSessionState: request.persistSessionState,
|
|
33
43
|
includeSkillBodies: request.includeSkillBodies,
|
|
44
|
+
...(request.checkpointMetadata === undefined ? {} : { checkpointMetadata: request.checkpointMetadata }),
|
|
45
|
+
...restoreHookOptions(options, request),
|
|
34
46
|
});
|
|
35
47
|
},
|
|
36
48
|
async *resumeStream(ref, resume, request = {}) {
|
|
@@ -49,6 +61,8 @@ export function createAgentRunLifecycle(options) {
|
|
|
49
61
|
overflow: request.overflow,
|
|
50
62
|
persistSessionState: request.persistSessionState,
|
|
51
63
|
includeSkillBodies: request.includeSkillBodies,
|
|
64
|
+
...(request.checkpointMetadata === undefined ? {} : { checkpointMetadata: request.checkpointMetadata }),
|
|
65
|
+
...restoreHookOptions(options, request),
|
|
52
66
|
});
|
|
53
67
|
},
|
|
54
68
|
};
|
|
@@ -99,7 +113,10 @@ async function prepareAgentRunResume(agent, ref, resume, options, signal) {
|
|
|
99
113
|
// and malformed untyped batches fail closed here instead of falling through to approval.
|
|
100
114
|
assertValidAgentRunResume(resume);
|
|
101
115
|
const continuing = resume.decision === "continue";
|
|
102
|
-
const { record, state } = await loadAgentRunState(options.checkpoints, ref, options.ownership);
|
|
116
|
+
const { record, state, metadata: recordMetadata } = await loadAgentRunState(options.checkpoints, ref, options.ownership);
|
|
117
|
+
// Sidecar metadata: a resume-time source wins; otherwise the record's existing map is
|
|
118
|
+
// preserved on every write below, so a non-durable resume cannot wipe it.
|
|
119
|
+
const checkpointMetadata = resolveCheckpointMetadata(options.checkpointMetadata) ?? recordMetadata;
|
|
103
120
|
if (state.definitionRevision !== options.definitionRevision ||
|
|
104
121
|
state.agentId !== (agent.config.id ?? agent.config.name) ||
|
|
105
122
|
state.fingerprint !== agentFingerprint(agent, options.definitionRevision)) {
|
|
@@ -137,6 +154,21 @@ async function prepareAgentRunResume(agent, ref, resume, options, signal) {
|
|
|
137
154
|
if (frontier)
|
|
138
155
|
session.restoreAttentionSticky(frontier);
|
|
139
156
|
}
|
|
157
|
+
// Plan 086 T3: durable folding writes its own ledger (with the frontier it belongs to), so it
|
|
158
|
+
// is restored whenever the checkpoint carries one — the `durable` opt-in was the host's
|
|
159
|
+
// consent, and a run without it never has this key. Without the frontier the ledger's rows
|
|
160
|
+
// would not be re-applied on an under-ratio turn, so the two ride together.
|
|
161
|
+
if (state.sessionState?.attentionFold) {
|
|
162
|
+
const ledger = restoreAttentionFoldLedger(state.sessionState.attentionFold);
|
|
163
|
+
if (ledger) {
|
|
164
|
+
session.restoreAttentionFold(ledger);
|
|
165
|
+
if (!options.persistSessionState && state.sessionState.attentionSticky) {
|
|
166
|
+
const frontier = parseAttentionStickyFrontier(state.sessionState.attentionSticky);
|
|
167
|
+
if (frontier)
|
|
168
|
+
session.restoreAttentionSticky(frontier);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
140
172
|
// Plan 018 Task 6 (closeout `checkpoint-bodies`): restore exact instructions so the
|
|
141
173
|
// resumed session renders them registry-independently (no load_skill round-trip).
|
|
142
174
|
if (options.persistSessionState && options.includeSkillBodies && state.sessionState?.loadedSkillBodies) {
|
|
@@ -231,6 +263,7 @@ async function prepareAgentRunResume(agent, ref, resume, options, signal) {
|
|
|
231
263
|
expectedVersion: record.version,
|
|
232
264
|
ownership: options.ownership,
|
|
233
265
|
fencingToken: options.fencingToken,
|
|
266
|
+
...(checkpointMetadata ? { metadata: checkpointMetadata } : {}),
|
|
234
267
|
});
|
|
235
268
|
return {
|
|
236
269
|
kind: "deny",
|
|
@@ -258,6 +291,21 @@ async function prepareAgentRunResume(agent, ref, resume, options, signal) {
|
|
|
258
291
|
throw new AgentRunStateError("Agent durable run-state configuration mismatch on resume");
|
|
259
292
|
}
|
|
260
293
|
throwIfAbortedSignal(signal);
|
|
294
|
+
// Plan 094 Task 3: restore external state (git commit, document versions) before the claim
|
|
295
|
+
// write. Every hook must succeed — a throw here leaves the checkpoint exactly as it was, and
|
|
296
|
+
// the conversation restore below never runs, so no half-restored world is claimed as resumed.
|
|
297
|
+
const restoreHooks = options.restoreHooks ?? [];
|
|
298
|
+
const restore = restoreHooks.length
|
|
299
|
+
? await runCheckpointRestoreHooks(restoreHooks, {
|
|
300
|
+
runId: state.runId,
|
|
301
|
+
sessionId: state.sessionId,
|
|
302
|
+
version: record.version,
|
|
303
|
+
status: state.status,
|
|
304
|
+
...(recordMetadata ? { metadata: recordMetadata } : {}),
|
|
305
|
+
checkpoint: record,
|
|
306
|
+
}, { timeoutMs: options.restoreHookTimeoutMs, signal })
|
|
307
|
+
: undefined;
|
|
308
|
+
throwIfAbortedSignal(signal);
|
|
261
309
|
const claimed = await saveAgentRunState({
|
|
262
310
|
checkpoints: options.checkpoints,
|
|
263
311
|
state: {
|
|
@@ -269,6 +317,7 @@ async function prepareAgentRunResume(agent, ref, resume, options, signal) {
|
|
|
269
317
|
expectedVersion: record.version,
|
|
270
318
|
ownership: options.ownership,
|
|
271
319
|
fencingToken: options.fencingToken,
|
|
320
|
+
...(checkpointMetadata ? { metadata: checkpointMetadata } : {}),
|
|
272
321
|
});
|
|
273
322
|
return {
|
|
274
323
|
kind: "claim",
|
|
@@ -276,6 +325,9 @@ async function prepareAgentRunResume(agent, ref, resume, options, signal) {
|
|
|
276
325
|
state: claimed.state,
|
|
277
326
|
decisions: resolved?.decisionsById,
|
|
278
327
|
ownership: options.ownership,
|
|
328
|
+
// The configured object must be passed by identity (agent-session assemble rejects a
|
|
329
|
+
// replaced config); its own `checkpointMetadata` provider wins, and the resolved map rides
|
|
330
|
+
// the session as a seed so later writes preserve a record's existing sidecar.
|
|
279
331
|
runState: configured ?? {
|
|
280
332
|
checkpoints: options.checkpoints,
|
|
281
333
|
definitionRevision: options.definitionRevision,
|
|
@@ -286,6 +338,8 @@ async function prepareAgentRunResume(agent, ref, resume, options, signal) {
|
|
|
286
338
|
// turn checkpoints without the host repeating the option on resume.
|
|
287
339
|
...(state.checkpointPolicy ? { checkpointPolicy: state.checkpointPolicy } : {}),
|
|
288
340
|
},
|
|
341
|
+
...(checkpointMetadata ? { checkpointMetadata } : {}),
|
|
342
|
+
...(restore ? { restore } : {}),
|
|
289
343
|
};
|
|
290
344
|
}
|
|
291
345
|
/** Pending decisions of a suspended state, synthesizing the legacy single-approval shape. */
|
|
@@ -298,6 +352,9 @@ async function executePreparedAgentRunResume(prepared, signal) {
|
|
|
298
352
|
await prepared.session.recordDurableResumption(prepared.result.runId, prepared.interruption, prepared.version, prepared.ownership);
|
|
299
353
|
return prepared.result;
|
|
300
354
|
}
|
|
301
|
-
return prepared.session.resumeDurable(prepared.state, prepared.runState, prepared.ownership, signal, prepared.decisions
|
|
355
|
+
return prepared.session.resumeDurable(prepared.state, prepared.runState, prepared.ownership, signal, prepared.decisions, {
|
|
356
|
+
...(prepared.checkpointMetadata ? { checkpointMetadata: prepared.checkpointMetadata } : {}),
|
|
357
|
+
...(prepared.restore ? { restore: prepared.restore } : {}),
|
|
358
|
+
});
|
|
302
359
|
}
|
|
303
360
|
//# sourceMappingURL=agent-run-lifecycle.js.map
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { type PersistedAttentionStickyFrontier } from "./attention-compiler.js";
|
|
2
|
-
import type { Agent, AgentRunInterruption, AgentRunRef, AgentRunState, AgentRunStateOptions, CheckpointRecord, CheckpointStore, JsonValue, Message, ModelConfig, NestedRunRef, OwnershipScope, RunDecision, RunLimitCounters, StickyDecision, ToolCallContent } from "./contracts.js";
|
|
1
|
+
import { type PersistedAttentionFoldLedger, type PersistedAttentionStickyFrontier } from "./attention-compiler.js";
|
|
2
|
+
import type { Agent, AgentRunCheckpointMetadata, AgentRunCheckpointMetadataSource, AgentRunInterruption, AgentRunRef, AgentRunState, AgentRunStateOptions, CheckpointRecord, CheckpointStore, JsonValue, Message, ModelConfig, NestedRunRef, OwnershipScope, RunDecision, RunLimitCounters, StickyDecision, ToolCallContent } from "./contracts.js";
|
|
3
3
|
import type { SecretRedactor } from "./redaction.js";
|
|
4
4
|
import { type LoadedSkillBodiesEntry } from "./skill-load.js";
|
|
5
5
|
export declare const AGENT_RUN_STATE_NAMESPACE = "prism.agent-run";
|
|
6
6
|
export declare const AGENT_RUN_STATE_SCHEMA_VERSION: 1;
|
|
7
7
|
export declare const DEFAULT_MAX_AGENT_RUN_STATE_BYTES: number;
|
|
8
8
|
export declare const HARD_MAX_AGENT_RUN_STATE_BYTES: number;
|
|
9
|
+
/** Sidecar metadata ceiling per checkpoint record (not the run-state value). */
|
|
10
|
+
export declare const MAX_AGENT_RUN_METADATA_BYTES: number;
|
|
9
11
|
/** One gated tool call awaiting or holding a decision inside a suspended durable run. */
|
|
10
12
|
export interface PendingToolCall {
|
|
11
13
|
readonly call: ToolCallContent;
|
|
@@ -50,6 +52,10 @@ export interface StoredAgentRunState extends AgentRunState {
|
|
|
50
52
|
/** Plan 074 P3: sticky attention mutations (thinking hashes + tool-call ids), so a durable
|
|
51
53
|
* resume keeps its stubs instead of re-deciding on the first turn. Validated on load. */
|
|
52
54
|
readonly attentionSticky?: PersistedAttentionStickyFrontier;
|
|
55
|
+
/** Plan 086 T3: folded bodies (`attention.compiler.durable`), so a resumed fold re-applies
|
|
56
|
+
* the same stub bytes instead of re-summarizing. Written and restored independently of
|
|
57
|
+
* `persistSessionState`. Validated on load. */
|
|
58
|
+
readonly attentionFold?: PersistedAttentionFoldLedger;
|
|
53
59
|
};
|
|
54
60
|
/** Per-run allow-list (Task 21). Absent = full registered set (legacy checkpoints). */
|
|
55
61
|
readonly toolNames?: readonly string[];
|
|
@@ -81,7 +87,20 @@ export declare function validateRunStateOptions(options: AgentRunStateOptions):
|
|
|
81
87
|
export declare function loadAgentRunState(checkpoints: CheckpointStore, ref: AgentRunRef, ownership?: OwnershipScope): Promise<{
|
|
82
88
|
readonly record: CheckpointRecord;
|
|
83
89
|
readonly state: StoredAgentRunState;
|
|
90
|
+
readonly metadata?: AgentRunCheckpointMetadata;
|
|
84
91
|
}>;
|
|
92
|
+
/** Resolve a host metadata source. A throwing provider fails the checkpoint write (fail closed). */
|
|
93
|
+
export declare function resolveCheckpointMetadata(source: AgentRunCheckpointMetadataSource | undefined): AgentRunCheckpointMetadata | undefined;
|
|
94
|
+
/**
|
|
95
|
+
* Redact + bound a sidecar metadata map for a checkpoint write. Values must be strings;
|
|
96
|
+
* redaction runs first so a replacement marker is still charged against the 4 KiB ceiling.
|
|
97
|
+
*/
|
|
98
|
+
export declare function boundCheckpointMetadata(metadata: AgentRunCheckpointMetadata, redactor?: SecretRedactor): AgentRunCheckpointMetadata;
|
|
99
|
+
/**
|
|
100
|
+
* Read-side normalization (legacy tolerance): absent, oversize, or non-string entries are
|
|
101
|
+
* dropped, never thrown — a malformed sidecar must not block a resume.
|
|
102
|
+
*/
|
|
103
|
+
export declare function readCheckpointMetadata(metadata: unknown): AgentRunCheckpointMetadata | undefined;
|
|
85
104
|
export declare function saveAgentRunState(input: {
|
|
86
105
|
readonly checkpoints: CheckpointStore;
|
|
87
106
|
readonly state: StoredAgentRunState;
|
|
@@ -90,6 +109,7 @@ export declare function saveAgentRunState(input: {
|
|
|
90
109
|
readonly fencingToken?: number;
|
|
91
110
|
readonly redactor?: SecretRedactor;
|
|
92
111
|
readonly maxStateBytes?: number;
|
|
112
|
+
readonly metadata?: AgentRunCheckpointMetadata;
|
|
93
113
|
}): Promise<{
|
|
94
114
|
readonly record: CheckpointRecord;
|
|
95
115
|
readonly state: StoredAgentRunState;
|
package/dist/agent-run-state.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { parseAttentionStickyFrontier } from "./attention-compiler.js";
|
|
2
|
+
import { parseAttentionStickyFrontier, restoreAttentionFoldLedger, } from "./attention-compiler.js";
|
|
3
3
|
import { AgentLoopStateError, AgentRunStateError } from "./contracts.js";
|
|
4
4
|
import { validateLoadedSkillBodies } from "./skill-load.js";
|
|
5
5
|
import { HARD_RUN_TOOL_NAMES } from "./tools.js";
|
|
@@ -7,6 +7,8 @@ export const AGENT_RUN_STATE_NAMESPACE = "prism.agent-run";
|
|
|
7
7
|
export const AGENT_RUN_STATE_SCHEMA_VERSION = 1;
|
|
8
8
|
export const DEFAULT_MAX_AGENT_RUN_STATE_BYTES = 256 * 1024;
|
|
9
9
|
export const HARD_MAX_AGENT_RUN_STATE_BYTES = 1024 * 1024;
|
|
10
|
+
/** Sidecar metadata ceiling per checkpoint record (not the run-state value). */
|
|
11
|
+
export const MAX_AGENT_RUN_METADATA_BYTES = 4 * 1024;
|
|
10
12
|
const MAX_DEPTH = 32;
|
|
11
13
|
const MAX_PROPERTIES = 256;
|
|
12
14
|
/** Session-state caps (plan 015 Task 4): bounded names charged against the run-state byte budget. */
|
|
@@ -116,10 +118,55 @@ export async function loadAgentRunState(checkpoints, ref, ownership) {
|
|
|
116
118
|
record.value.sessionId !== ref.sessionId) {
|
|
117
119
|
throw new AgentRunStateError("Agent run session mismatch");
|
|
118
120
|
}
|
|
119
|
-
|
|
121
|
+
const metadata = readCheckpointMetadata(record.metadata);
|
|
122
|
+
return { record, state: parseAgentRunState(record.value, record.version), ...(metadata ? { metadata } : {}) };
|
|
123
|
+
}
|
|
124
|
+
/** Resolve a host metadata source. A throwing provider fails the checkpoint write (fail closed). */
|
|
125
|
+
export function resolveCheckpointMetadata(source) {
|
|
126
|
+
return typeof source === "function" ? source() : source;
|
|
127
|
+
}
|
|
128
|
+
function checkpointMetadataBytes(metadata) {
|
|
129
|
+
return Buffer.byteLength(JSON.stringify(metadata), "utf8");
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Redact + bound a sidecar metadata map for a checkpoint write. Values must be strings;
|
|
133
|
+
* redaction runs first so a replacement marker is still charged against the 4 KiB ceiling.
|
|
134
|
+
*/
|
|
135
|
+
export function boundCheckpointMetadata(metadata, redactor) {
|
|
136
|
+
const redacted = redactor?.redact(metadata) ?? metadata;
|
|
137
|
+
if (!redacted || typeof redacted !== "object" || Array.isArray(redacted)) {
|
|
138
|
+
throw new AgentRunStateError("Checkpoint metadata must be an object");
|
|
139
|
+
}
|
|
140
|
+
const bounded = {};
|
|
141
|
+
for (const [key, value] of Object.entries(redacted)) {
|
|
142
|
+
if (typeof value !== "string")
|
|
143
|
+
throw new AgentRunStateError(`Checkpoint metadata value for ${key} must be a string`);
|
|
144
|
+
bounded[key] = value;
|
|
145
|
+
}
|
|
146
|
+
if (checkpointMetadataBytes(bounded) > MAX_AGENT_RUN_METADATA_BYTES) {
|
|
147
|
+
throw new AgentRunStateError(`Checkpoint metadata exceeds ${MAX_AGENT_RUN_METADATA_BYTES} bytes`);
|
|
148
|
+
}
|
|
149
|
+
return Object.freeze(bounded);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Read-side normalization (legacy tolerance): absent, oversize, or non-string entries are
|
|
153
|
+
* dropped, never thrown — a malformed sidecar must not block a resume.
|
|
154
|
+
*/
|
|
155
|
+
export function readCheckpointMetadata(metadata) {
|
|
156
|
+
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata))
|
|
157
|
+
return undefined;
|
|
158
|
+
const bounded = {};
|
|
159
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
160
|
+
if (typeof value === "string")
|
|
161
|
+
bounded[key] = value;
|
|
162
|
+
}
|
|
163
|
+
if (Object.keys(bounded).length === 0 || checkpointMetadataBytes(bounded) > MAX_AGENT_RUN_METADATA_BYTES)
|
|
164
|
+
return undefined;
|
|
165
|
+
return Object.freeze(bounded);
|
|
120
166
|
}
|
|
121
167
|
export async function saveAgentRunState(input) {
|
|
122
168
|
const bounded = boundState(input.redactor?.redact(input.state) ?? input.state, input.maxStateBytes ?? DEFAULT_MAX_AGENT_RUN_STATE_BYTES);
|
|
169
|
+
const metadata = input.metadata === undefined ? undefined : boundCheckpointMetadata(input.metadata, input.redactor);
|
|
123
170
|
const record = await input.checkpoints.saveCheckpoint({
|
|
124
171
|
namespace: AGENT_RUN_STATE_NAMESPACE,
|
|
125
172
|
key: bounded.runId,
|
|
@@ -128,6 +175,7 @@ export async function saveAgentRunState(input) {
|
|
|
128
175
|
fencingToken: input.fencingToken,
|
|
129
176
|
value: bounded,
|
|
130
177
|
category: "agent-run",
|
|
178
|
+
...(metadata ? { metadata } : {}),
|
|
131
179
|
...input.ownership,
|
|
132
180
|
});
|
|
133
181
|
return { record, state: { ...bounded, version: record.version } };
|
|
@@ -294,12 +342,16 @@ function validateSessionState(sessionState) {
|
|
|
294
342
|
}
|
|
295
343
|
}
|
|
296
344
|
const attention = sessionState.attentionSticky;
|
|
297
|
-
if (attention === undefined)
|
|
298
|
-
return;
|
|
299
345
|
// Both arrays are capped by the parser, and a malformed frontier is dropped rather than
|
|
300
346
|
// failing the resume: re-deciding a mutation is safe, refusing to resume is not.
|
|
301
|
-
if (parseAttentionStickyFrontier(attention) === undefined) {
|
|
347
|
+
if (attention !== undefined && parseAttentionStickyFrontier(attention) === undefined) {
|
|
302
348
|
throw new AgentRunStateError("Malformed agent run attention frontier");
|
|
303
349
|
}
|
|
350
|
+
// Plan 086 T3: the fold ledger gets the same treatment — malformed entries are dropped by the
|
|
351
|
+
// parser, a malformed shape fails the load rather than the first provider turn.
|
|
352
|
+
const fold = sessionState.attentionFold;
|
|
353
|
+
if (fold !== undefined && restoreAttentionFoldLedger(fold) === undefined) {
|
|
354
|
+
throw new AgentRunStateError("Malformed agent run attention fold ledger");
|
|
355
|
+
}
|
|
304
356
|
}
|
|
305
357
|
//# sourceMappingURL=agent-run-state.js.map
|
|
@@ -160,6 +160,8 @@ export function createUsageAccumulator() {
|
|
|
160
160
|
const sums = new Map();
|
|
161
161
|
let costCurrency;
|
|
162
162
|
let costCompatible = true;
|
|
163
|
+
let estimated = false;
|
|
164
|
+
let confidence;
|
|
163
165
|
return {
|
|
164
166
|
add(usage) {
|
|
165
167
|
for (const key of ["inputTokens", "outputTokens", "cacheReadTokens", "cacheWriteTokens"]) {
|
|
@@ -181,6 +183,13 @@ export function createUsageAccumulator() {
|
|
|
181
183
|
if (costCompatible)
|
|
182
184
|
sums.set("cost", (sums.get("cost") ?? 0) + usage.cost);
|
|
183
185
|
}
|
|
186
|
+
// Run totals keep estimate provenance (plan 091 T2): one estimated turn
|
|
187
|
+
// labels the total, and the weakest confidence wins.
|
|
188
|
+
if (usage.estimated === true) {
|
|
189
|
+
estimated = true;
|
|
190
|
+
if (usage.confidence !== undefined && (confidence === undefined || usage.confidence === "low"))
|
|
191
|
+
confidence = usage.confidence;
|
|
192
|
+
}
|
|
184
193
|
},
|
|
185
194
|
value() {
|
|
186
195
|
if (sums.size === 0)
|
|
@@ -192,6 +201,11 @@ export function createUsageAccumulator() {
|
|
|
192
201
|
}
|
|
193
202
|
if (costCompatible && sums.has("cost") && costCurrency !== undefined)
|
|
194
203
|
usage.currency = costCurrency;
|
|
204
|
+
if (estimated) {
|
|
205
|
+
usage.estimated = true;
|
|
206
|
+
if (confidence !== undefined)
|
|
207
|
+
usage.confidence = confidence;
|
|
208
|
+
}
|
|
195
209
|
return Object.keys(usage).length > 0 ? usage : undefined;
|
|
196
210
|
},
|
|
197
211
|
};
|