@arnilo/prism 0.1.4 → 0.1.6

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 CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.6] - 2026-08-11
4
+
5
+ ### Changed
6
+ - **Release 0.1.6 (plan 018)** is the coding-agent capability-closeouts patch on the frozen 0.1.x line — five demand-gated closeouts, all shipped, additive-only vs 0.1.5 (freeze manifest `scripts/phase18-freeze-manifest.json`; every closeout flipped to `demanded` by named demand evidence before its task landed, then the demand-gate registry validated demanded ⇒ implemented, deferred ⇒ untouched). (1) **Durable ACP session store** (`acp-session-store`): `@arnilo/prism-ag-ui` gains the host-owned `AcpSessionStore` seam on `CreatePrismAcpAgentOptions` — `save` (upsert on session/new, set_mode, set_config_option, never on cancel/prompt-end), `loadAll` (lazy, once per agent instance, after authorization, cross-tenant entries refused `ERR_PRISM_ACP_INPUT`), `evict` (on close/delete); the persisted entry shape `{sessionId, ownership, modeId, configValues, cwd, additionalDirectories, updatedAt}` deliberately excludes client/controller/budget/pending state; fail-closed restore drops corrupt/oversized entries, re-validates modes/config options, keeps the in-memory registry caps (32 default / 128 hard), and re-resolves the live session binding; absent seam = byte-identical 0.1.5 in-memory behavior; the whole persisted entry rides the optional `SecretRedactor` at the save boundary. (2) **Network-free native sandbox backend** (`native-sandbox`): `@arnilo/prism-coding-security` gains `createNativeSandbox` — spawn + POSIX rlimits + existing path containment, zero new dependencies; every command runs in a fresh network namespace via the OS `unshare` binary (plain or `--map-root-user` preflighted once at creation, fail-closed on macOS/Windows and where netns cannot be created), ulimit chains (`-v`/`-t`/`-n`) with `|| exit 126`, argv-only `exec` (never shell-interpolated), cwd containment via `assertPathInsideRoots`, process-group kill on timeout/abort, env allow-list (host env never inherited), output cap, `close({export})` tar parity, and a documented honest boundary (runs as the invoking OS user; egress denial + rlimits + cwd containment only). (3) **Bounded PDF/Office document reader** (`doc-reader`): new optional package `@arnilo/prism-document-reader` (the 50th publishable manifest) — `createDocumentReader({ maxBytes, maxPages, maxTextBytes, parsers })` behind optional peer parsers `pdf-parse`/`mammoth` (dynamic-import, fail-closed at creation with an install hint when absent), magic-byte format gating (never extension sniffing), null fall-through to the 0.1.5 text path, refuse-over-truncate for over-page PDFs, byte-safe text truncation, optional `SecretRedactor` at the adapter boundary, no embedded-content execution, no external resource fetch (egress tripwire test), extraction envelope recorded in `scripts/budgets.json`; `createReadTool` gains the additive `documentReader` slot with input/page/text caps re-checked in the read flow. (4) **Recursive delete + brace-expanding glob** (`delete-glob`): `delete` gains the per-call opt-in `recursive: true` (symlink children unlinked never followed, iterative post-order walk, per-call fan-out cap 10,000 default / 100,000 hard, partial deletion reported never silent, `maxEntries` bound); `glob` gains host-selected + per-call `braceExpansion` (`{a,b}` textual expansion, max 128 alternatives / 4096 expanded bytes, unbalanced/nested/empty braces and overflow fail closed, default matcher semantics unchanged). (5) **Checkpoint persistence for loaded-skill bodies** (`checkpoint-bodies`): durable runs may set `includeSkillBodies: true` on BOTH run and resume options (alongside `persistSessionState`) — the exact loaded-skill instructions ride the checkpoint (`{name, instructions}` pairs, ≤64 bodies / ≤256-char names / ≤262144-byte bodies / ≤1 MiB total, validated fail-closed on save and load, redacted at rest) so resume re-renders them registry-independently with no `load_skill` round-trip; names-only stays the default and 0.1.3/0.1.2 checkpoint shapes are byte-identical; `maxStateBytes` refuses oversize bodies with a recorded error, never truncates. Release graph **50** publishable manifests (root + 49 workspace packages — 14 provider adapters, 9 `prism-*` family/profile, 26 capability incl. `@arnilo/prism-document-reader`) at exact **0.1.6**. Exit gate green (core 1,433/1,433 + 190 script gates incl. phase18-freeze done-phase, `sdk:ready`, audit 0 moderate, pack dry-run 50/50 twice byte-identical, plain compat gate at 0.1.6 with 0 breaking deltas then version-literal baseline refresh, evidence in `scripts/phase18-baseline.json`). Store compatibility with 0.1.5: **compatible, no migration** (additive-only; no persisted-shape change). **Publication remains the operator handoff** (`docs/release-and-install.md` `0.1.6 publish handoff` — signed `v0.1.6` tag + npm OIDC).
7
+
8
+ ## [0.1.5] - 2026-08-11
9
+
10
+ ### Changed
11
+ - **Release 0.1.5 (plan 017)** is the **documented breaking cut** on the frozen 0.1.x line — deprecated-option removal with the full removed-symbols list, replacements, before/after examples, dynamic-config refusal behavior, store compatibility, and rollback in the top `docs/migration.md` `0.1.4 → 0.1.5` section (three stale roadmap labels corrected there: `RunOptions.maxToolRounds` not `AgentConfig.maxToolRounds`, `autoResizeImages` removed with `transformImage` retained, `INIT_PROVIDERS` removed with `listInitProviders()` retained). (1) **Provider run-option aliases** (Task 1): `ProviderRequestOptions.timeoutMs`/`maxRetries`/`maxRetryDelayMs` (inert in first-party providers; host-side abort/retry is the replacement — `RunOptions.signal`, `AgentConfig.retry`/`RunOptions.retry`) and `RunOptions.maxToolRounds` (→ `limits.maxToolRounds`, defaults/caps unchanged) removed; untyped legacy run input is refused at the `runInternal` choke point before the agent starts; CLI `--max-tool-rounds` maps to the nested limit. (2) **Observational-memory compatibility surface** (Task 2): the 10 pre-0.0.19 flat settings keys and top-level `workerProvider`/`workerModel` aliases removed — settings resolution is nested-only with `assertNoRemovedFlatKeys` failing closed (settings-provider JSON or untyped overrides throw naming the key + nested replacement before any worker/provider call, compaction, or session append); `fallbackWorker`/`assertWorkerModelCompatibility`/`conflict()` deleted; workers resolve only from `observation`/`reflection`/`dropper` configs plus `sessionModel` fallback. (3) **Read alias + CLI constant** (Task 3): `ReadToolOptions.autoResizeImages` removed (`transformImage` is the only resize path; untyped callers fail closed at `createReadTool` before filesystem access) and `INIT_PROVIDERS` removed (`listInitProviders()` is the single provider-list API). Compat baselines regenerated after the reviewed `--allow-break` break report (root `arnilo__prism.txt` drops the `INIT_PROVIDERS` line; interface-member removals are verified by the phase-17 direct declaration scanner, not baseline text). Exit gate green (core tests + script gates incl. phase17-freeze 20/20, `sdk:ready`, audit 0, pack dry-run 49/49 twice byte-identical, evidence in `scripts/phase17-baseline.json`). Store compatibility with 0.1.4: **compatible, no migration** (removed options were inert aliases; rollback = restore the 0.1.4 manifests/tag). **Publication remains the operator handoff** (`docs/release-and-install.md` `0.1.5 publish handoff` — signed `v0.1.5` tag + npm OIDC).
12
+
3
13
  ## [0.1.4] - 2026-08-10
4
14
 
5
15
  ### Changed
@@ -20,6 +20,8 @@ export interface AgentRunLifecycleRequest {
20
20
  readonly agentId?: string;
21
21
  /** Opt-in (plan 015 Task 4): restore persisted loaded-skill names on resume. */
22
22
  readonly persistSessionState?: boolean;
23
+ /** Opt-in (plan 018 Task 6): restore persisted loaded-skill bodies on resume (requires `persistSessionState` too). */
24
+ readonly includeSkillBodies?: boolean;
23
25
  }
24
26
  /** Bounded live-event options for a durable lifecycle resume. */
25
27
  export interface AgentRunLifecycleStreamRequest extends AgentRunLifecycleRequest, SubscribeOptions {
@@ -28,6 +28,7 @@ export function createAgentRunLifecycle(options) {
28
28
  fencingToken: options.fencingToken,
29
29
  definitionRevision: resolved.definitionRevision,
30
30
  persistSessionState: request.persistSessionState,
31
+ includeSkillBodies: request.includeSkillBodies,
31
32
  });
32
33
  },
33
34
  async *resumeStream(ref, resume, request = {}) {
@@ -45,6 +46,7 @@ export function createAgentRunLifecycle(options) {
45
46
  maxQueuedEvents: request.maxQueuedEvents,
46
47
  overflow: request.overflow,
47
48
  persistSessionState: request.persistSessionState,
49
+ includeSkillBodies: request.includeSkillBodies,
48
50
  });
49
51
  },
50
52
  };
@@ -95,6 +97,11 @@ async function prepareAgentRunResume(agent, ref, resume, options, signal) {
95
97
  if (options.persistSessionState && state.sessionState?.loadedSkillNames) {
96
98
  session.restoreLoadedSkills(state.sessionState.loadedSkillNames);
97
99
  }
100
+ // Plan 018 Task 6 (closeout `checkpoint-bodies`): restore exact instructions so the
101
+ // resumed session renders them registry-independently (no load_skill round-trip).
102
+ if (options.persistSessionState && options.includeSkillBodies && state.sessionState?.loadedSkillBodies) {
103
+ session.restoreLoadedSkillBodies(state.sessionState.loadedSkillBodies);
104
+ }
98
105
  if (resume.decision !== undefined && resume.decisions !== undefined) {
99
106
  throw new AgentDecisionError("ERR_PRISM_DECISION_INVALID", "Resume accepts exactly one of decision or decisions");
100
107
  }
@@ -1,5 +1,6 @@
1
1
  import type { Agent, AgentRunInterruption, AgentRunRef, AgentRunState, AgentRunStateOptions, AgentRunStatusResult, CheckpointRecord, CheckpointStore, JsonValue, Message, ModelConfig, NestedRunRef, OwnershipScope, RunDecision, RunLimitCounters, StickyDecision, ToolCallContent } from "./contracts.js";
2
2
  import type { SecretRedactor } from "./redaction.js";
3
+ import { type LoadedSkillBodiesEntry } from "./skill-load.js";
3
4
  export declare const AGENT_RUN_STATE_NAMESPACE = "prism.agent-run";
4
5
  export declare const AGENT_RUN_STATE_SCHEMA_VERSION: 1;
5
6
  export declare const DEFAULT_MAX_AGENT_RUN_STATE_BYTES: number;
@@ -41,6 +42,7 @@ export interface StoredAgentRunState extends AgentRunState {
41
42
  */
42
43
  readonly sessionState?: {
43
44
  readonly loadedSkillNames?: readonly string[];
45
+ readonly loadedSkillBodies?: readonly LoadedSkillBodiesEntry[];
44
46
  };
45
47
  }
46
48
  /** Session-state caps (plan 015 Task 4): bounded names charged against the run-state byte budget. */
@@ -1,5 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { AgentLoopStateError, AgentRunStateError } from "./contracts.js";
3
+ import { validateLoadedSkillBodies } from "./skill-load.js";
3
4
  export const AGENT_RUN_STATE_NAMESPACE = "prism.agent-run";
4
5
  export const AGENT_RUN_STATE_SCHEMA_VERSION = 1;
5
6
  export const DEFAULT_MAX_AGENT_RUN_STATE_BYTES = 256 * 1024;
@@ -241,6 +242,15 @@ function validateSessionState(sessionState) {
241
242
  if (!sessionState || typeof sessionState !== "object") {
242
243
  throw new AgentRunStateError("Malformed agent run session state");
243
244
  }
245
+ const bodies = sessionState.loadedSkillBodies;
246
+ if (bodies !== undefined) {
247
+ try {
248
+ validateLoadedSkillBodies(bodies);
249
+ }
250
+ catch (error) {
251
+ throw new AgentRunStateError(error instanceof Error ? error.message : String(error));
252
+ }
253
+ }
244
254
  const names = sessionState.loadedSkillNames;
245
255
  if (names === undefined)
246
256
  return;
@@ -1,6 +1,7 @@
1
1
  import { type StoredAgentRunState } from "./agent-run-state.js";
2
2
  import type { Agent, AgentConfig, AgentEvent, AgentRunResult, AgentRunStateOptions, AgentSession, AgentSessionConfig, CompactionOptions, CompactionResult, OwnershipScope, RunDecision, RunOptions, SessionEntry, SteerOptions, SubscribeOptions } from "./contracts.js";
3
3
  import { type AgentInput } from "./input.js";
4
+ import { type LoadedSkillBodiesEntry } from "./skill-load.js";
4
5
  export declare function createAgent(config: AgentConfig): Agent;
5
6
  export declare function createAgentSession(config: AgentSessionConfig & {
6
7
  readonly agent: Agent;
@@ -36,8 +37,14 @@ export declare class RuntimeAgentSession implements AgentSession {
36
37
  private activeGatedRound?;
37
38
  private activeLoopTurn;
38
39
  private readonly loadedSkills;
40
+ /** Plan 018 Task 6 (closeout `checkpoint-bodies`): persisted exact instructions, registry-independent. */
41
+ private restoredSkillBodies;
42
+ /** Skills of the current run (for the bodies snapshot); replaced at each run start. */
43
+ private activeRunSkills;
39
44
  /** Plan 015 Task 4: re-add persisted loaded-skill names (names only; bodies re-resolve on demand). */
40
45
  restoreLoadedSkills(names: readonly string[]): void;
46
+ /** Plan 018 Task 6: restore persisted loaded-skill bodies (already validated fail-closed at load). */
47
+ restoreLoadedSkillBodies(bodies: readonly LoadedSkillBodiesEntry[]): void;
41
48
  private ledgerChain;
42
49
  private ledgerFailure;
43
50
  private snapshotGeneration;
@@ -16,6 +16,7 @@ import { RunLimitError, RunLimitTracker, resolveRunLimits } from "./run-limits.j
16
16
  import { createMemorySessionStore, createSessionEntry, getSessionBranchEntries, rebuildSessionContext, } from "./session-stores.js";
17
17
  import { resolveActiveSkills } from "./skills.js";
18
18
  import { createLoadedSkillSet, resolveSkillsDisclosure } from "./skill-disclosure.js";
19
+ import { applyRestoredSkillBodies, snapshotLoadedSkillBodies, validateLoadedSkillBodies, } from "./skill-load.js";
19
20
  import { resolveToolResultFold } from "./tool-result-fold.js";
20
21
  import { assertStructuredOutputRequestSupported, resolveRunProviderOptions } from "./structured-output.js";
21
22
  import { composeSystemPrompt, mergeSystemPromptConfig } from "./system-prompts.js";
@@ -65,11 +66,22 @@ export class RuntimeAgentSession {
65
66
  activeGatedRound;
66
67
  activeLoopTurn = 1;
67
68
  loadedSkills = createLoadedSkillSet();
69
+ /** Plan 018 Task 6 (closeout `checkpoint-bodies`): persisted exact instructions, registry-independent. */
70
+ restoredSkillBodies = [];
71
+ /** Skills of the current run (for the bodies snapshot); replaced at each run start. */
72
+ activeRunSkills = [];
68
73
  /** Plan 015 Task 4: re-add persisted loaded-skill names (names only; bodies re-resolve on demand). */
69
74
  restoreLoadedSkills(names) {
70
75
  for (const name of names)
71
76
  this.loadedSkills.add(name);
72
77
  }
78
+ /** Plan 018 Task 6: restore persisted loaded-skill bodies (already validated fail-closed at load). */
79
+ restoreLoadedSkillBodies(bodies) {
80
+ validateLoadedSkillBodies(bodies);
81
+ this.restoredSkillBodies = bodies;
82
+ for (const entry of bodies)
83
+ this.loadedSkills.add(entry.name);
84
+ }
73
85
  ledgerChain = Promise.resolve();
74
86
  ledgerFailure;
75
87
  snapshotGeneration = 0;
@@ -156,6 +168,13 @@ export class RuntimeAgentSession {
156
168
  }
157
169
  }
158
170
  async runInternal(input, options, runId, resumed) {
171
+ // 0.1.5 breaking cut: the deprecated RunOptions.maxToolRounds alias was removed. Reject
172
+ // untyped/legacy input fail-closed before any session mutation, provider call, or tool
173
+ // execution; silently honoring it would widen or mis-apply the intended tool-round cap.
174
+ const legacyMaxToolRounds = options.maxToolRounds;
175
+ if (legacyMaxToolRounds !== undefined) {
176
+ throw new TypeError("RunOptions.maxToolRounds was removed in 0.1.5; use RunOptions.limits.maxToolRounds instead");
177
+ }
159
178
  if (this.agent.config.secure &&
160
179
  (options.redactor !== undefined ||
161
180
  options.ownership !== undefined ||
@@ -164,9 +183,7 @@ export class RuntimeAgentSession {
164
183
  options.runState !== undefined)) {
165
184
  throw new AgentRunStateError("Secure agent defaults cannot be replaced per run");
166
185
  }
167
- const requestedLimits = options.maxToolRounds === undefined
168
- ? options.limits
169
- : { ...options.limits, maxToolRounds: Math.min(options.maxToolRounds, options.limits?.maxToolRounds ?? options.maxToolRounds) };
186
+ const requestedLimits = options.limits;
170
187
  const resolvedLimits = resolveRunLimits(this.agent.config.limits, requestedLimits);
171
188
  const durableOptions = options.runState ?? this.agent.config.runState;
172
189
  if (this.agent.config.runState && options.runState && this.agent.config.runState !== options.runState) {
@@ -250,6 +267,7 @@ export class RuntimeAgentSession {
250
267
  await this.rebuildHistory();
251
268
  const { registry, tools } = activeTools(this.agent.config.tools);
252
269
  const activeSkills = this.resolveRunSkills(options, tools);
270
+ this.activeRunSkills = activeSkills; // for the durable bodies snapshot (plan 018 Task 6)
253
271
  if (options.model && JSON.stringify(options.model) !== JSON.stringify(this.agent.config.model)) {
254
272
  await this.appendEntry(createSessionEntry({
255
273
  sessionId: this.id,
@@ -459,7 +477,7 @@ export class RuntimeAgentSession {
459
477
  inputBuilder: this.agent.config.inputBuilder,
460
478
  promptBuilder: this.agent.config.promptBuilder,
461
479
  contextProviders,
462
- skills: activeSkills,
480
+ skills: this.restoredSkillBodies.length ? applyRestoredSkillBodies(activeSkills, this.restoredSkillBodies) : activeSkills,
463
481
  skillsDisclosure: resolveSkillsDisclosure(options.skillsDisclosure, this.agent.config.skillsDisclosure),
464
482
  toolResultFold: resolveToolResultFold(options.toolResultFold, this.agent.config.toolResultFold),
465
483
  loadedSkills: this.loadedSkills,
@@ -1103,7 +1121,17 @@ export class RuntimeAgentSession {
1103
1121
  if (!durable)
1104
1122
  throw new AgentRunStateError("Durable run state is not configured");
1105
1123
  const persisted = durable.options.persistSessionState
1106
- ? { ...state, sessionState: { loadedSkillNames: this.loadedSkills.list() } }
1124
+ ? {
1125
+ ...state,
1126
+ sessionState: {
1127
+ loadedSkillNames: this.loadedSkills.list(),
1128
+ ...(durable.options.includeSkillBodies
1129
+ ? {
1130
+ loadedSkillBodies: snapshotLoadedSkillBodies(this.activeRunSkills, this.loadedSkills, this.restoredSkillBodies.length ? new Map(this.restoredSkillBodies.map((e) => [e.name, e.instructions])) : undefined),
1131
+ }
1132
+ : {}),
1133
+ },
1134
+ }
1107
1135
  : state;
1108
1136
  const saved = await saveAgentRunState({
1109
1137
  checkpoints: durable.options.checkpoints,
@@ -30,8 +30,6 @@ export interface InitResult {
30
30
  }
31
31
  /** Provider ids supported by `prism init --provider`. Loaded from templates data. */
32
32
  export declare function listInitProviders(templatesRoot?: string): readonly string[];
33
- /** @deprecated Prefer listInitProviders(); retained for tests that import the name. */
34
- export declare const INIT_PROVIDERS: readonly string[];
35
33
  export declare function getInitUsage(templatesRoot?: string): string;
36
34
  export declare const initUsage: string;
37
35
  export declare function parseInitArgs(argv: readonly string[], templatesRoot?: string): InitOptions;
package/dist/cli-init.js CHANGED
@@ -34,8 +34,6 @@ export function listInitProviders(templatesRoot) {
34
34
  return cachedProviderIds;
35
35
  return Object.freeze([...loadProvidersCatalog(templatesRoot).keys()]);
36
36
  }
37
- /** @deprecated Prefer listInitProviders(); retained for tests that import the name. */
38
- export const INIT_PROVIDERS = listInitProviders();
39
37
  export function getInitUsage(templatesRoot) {
40
38
  const providers = listInitProviders(templatesRoot).join("|");
41
39
  return `Usage: prism init <dir> [options]
@@ -340,7 +340,7 @@ function defaultCreateSession(options) {
340
340
  }
341
341
  function runOptions(options) {
342
342
  return {
343
- maxToolRounds: options.maxToolRounds,
343
+ ...(options.maxToolRounds !== undefined ? { limits: { maxToolRounds: options.maxToolRounds } } : {}),
344
344
  compaction: options.compact ? { thresholdEntries: options.compact } : undefined,
345
345
  // ponytail: --discover is the explicit opt-in that activates discovered skills.
346
346
  ...(options.discover && !options.noDiscovery && options.discoveredSkills.length > 0
@@ -234,12 +234,6 @@ export interface ProviderRequestOptions {
234
234
  readonly cacheKey?: string;
235
235
  readonly cache?: PromptCacheHints;
236
236
  readonly headers?: Readonly<Record<string, string>>;
237
- /** @deprecated Provider-level timeout is inert in first-party providers; pass an AbortSignal/RunOptions.signal instead. */
238
- readonly timeoutMs?: number;
239
- /** @deprecated Provider-level retry is inert in first-party providers; use AgentConfig.retry/RunOptions.retry instead. */
240
- readonly maxRetries?: number;
241
- /** @deprecated Provider-level retry is inert in first-party providers; use AgentConfig.retry/RunOptions.retry instead. */
242
- readonly maxRetryDelayMs?: number;
243
237
  readonly compat?: JsonObject;
244
238
  readonly extra?: JsonObject;
245
239
  /** Provider-neutral JSON-schema structured output request. Requires model `capabilities.structuredOutput`. */
@@ -62,8 +62,6 @@ export interface RunOptions {
62
62
  readonly signal?: AbortSignal;
63
63
  readonly model?: ModelConfig;
64
64
  readonly providerSource?: ProviderResolver;
65
- /** @deprecated Use `limits.maxToolRounds`. */
66
- readonly maxToolRounds?: number;
67
65
  /** Run-scoped ceilings. When an agent config also sets limits, these can only narrow it. */
68
66
  readonly limits?: RunLimits;
69
67
  readonly providerOptions?: ProviderRequestOptions;
@@ -156,6 +156,17 @@ export interface AgentRunStateOptions {
156
156
  * Default off: checkpoint shape is identical to 0.1.2.
157
157
  */
158
158
  readonly persistSessionState?: boolean;
159
+ /**
160
+ * Opt-in (plan 018 Task 6 closeout `checkpoint-bodies`): alongside
161
+ * `persistSessionState`, persist the exact loaded-skill instructions
162
+ * (`{name, instructions}` pairs, redacted at the checkpoint boundary like all state)
163
+ * so resume re-renders them registry-independently — no `load_skill` round-trip, no
164
+ * drift when the live registry changed or lost the skill. Both the run and the resume
165
+ * options must set it. Bounds: ≤64 bodies, ≤256-char names, ≤262144-byte bodies,
166
+ * ≤1 MiB total; the `maxStateBytes` ceiling refuses oversize with a recorded error
167
+ * (never silently truncates). Default off: checkpoint shape is identical to 0.1.3.
168
+ */
169
+ readonly includeSkillBodies?: boolean;
159
170
  }
160
171
  /** Versioned, redacted checkpoint payload. Treat as opaque except status/version/interruption. */
161
172
  export interface AgentRunState {
@@ -188,6 +199,8 @@ export interface AgentRunResumeOptions {
188
199
  readonly resumeNestedRun?: ResumeNestedRun;
189
200
  /** Opt-in (plan 015 Task 4): restore persisted loaded-skill names into the resumed session catalog. */
190
201
  readonly persistSessionState?: boolean;
202
+ /** Opt-in (plan 018 Task 6): restore persisted loaded-skill bodies (requires `persistSessionState` too). */
203
+ readonly includeSkillBodies?: boolean;
191
204
  }
192
205
  /** Bounded, abortable options for `resumeAgentRunStream()`. */
193
206
  export interface AgentRunResumeStreamOptions extends AgentRunResumeOptions, SubscribeOptions {
package/dist/index.d.ts CHANGED
@@ -86,8 +86,8 @@ export { createMemorySessionStore, createSessionEntry, getSessionBranchEntries,
86
86
  export { createChainedSettingsProvider, createStaticSettingsProvider } from "./settings.js";
87
87
  export type { LoadedSkillSet, SkillRenderContext, SkillsDisclosure } from "./skill-disclosure.js";
88
88
  export { createLoadedSkillSet, DEFAULT_MAX_SKILL_CATALOG_ENTRIES, DEFAULT_MAX_SKILL_DESCRIPTION_BYTES, DEFAULT_MAX_SKILL_INSTRUCTION_BYTES, EMPTY_SKILL_DESCRIPTION, HARD_MAX_SKILL_CATALOG_ENTRIES, HARD_MAX_SKILL_DESCRIPTION_BYTES, HARD_MAX_SKILL_INSTRUCTION_BYTES, isSkillDisclosureError, resolveSkillsDisclosure, SkillDisclosureError, } from "./skill-disclosure.js";
89
- export type { CreateLoadSkillToolOptions, ResolveSkillLoadOptions } from "./skill-load.js";
90
- export { createLoadSkillTool, DEFAULT_LOAD_SKILL_TOOL_NAME, isSkillLoadError, MAX_LOAD_SKILL_RESULT_BYTES, resolveSkillLoad, SKILL_LOAD_ERROR_CODE, SkillLoadError, } from "./skill-load.js";
89
+ export type { CreateLoadSkillToolOptions, LoadedSkillBodiesEntry, ResolveSkillLoadOptions, } from "./skill-load.js";
90
+ export { applyRestoredSkillBodies, createLoadSkillTool, DEFAULT_LOAD_SKILL_TOOL_NAME, HARD_MAX_PERSISTED_SKILL_BODY_TOTAL_BYTES, isSkillLoadError, MAX_LOAD_SKILL_RESULT_BYTES, MAX_PERSISTED_SKILL_BODIES, MAX_PERSISTED_SKILL_BODY_BYTES, MAX_PERSISTED_SKILL_BODY_NAME_CHARS, resolveSkillLoad, SKILL_LOAD_ERROR_CODE, SkillLoadError, snapshotLoadedSkillBodies, validateLoadedSkillBodies, } from "./skill-load.js";
91
91
  export type { ResolveActiveSkillsOptions, SkillRegistryOptions } from "./skills.js";
92
92
  export { createSkillRegistry, resolveActiveSkills } from "./skills.js";
93
93
  export { artifactStructuredOutputRequest, assertStructuredOutputRequestSupported, DEFAULT_MAX_STRUCTURED_OUTPUT_NAME_LENGTH, DEFAULT_MAX_STRUCTURED_OUTPUT_SCHEMA_BYTES, modelSupportsStructuredOutput, resolveRunProviderOptions, StructuredOutputError, validateStructuredOutputOptions, withoutStructuredOutput, } from "./structured-output.js";
@@ -105,5 +105,5 @@ export { createToolParameterValidator, createToolRegistry, dispatchToolCall, fil
105
105
  export type { ResolvedUseCaseModel, ResolveUseCaseModelInput, UseCaseModelBinding, } from "./use-case-model.js";
106
106
  export { resolveUseCaseModel, resolveUseCaseModelBinding, useCaseCredentialProviderId, } from "./use-case-model.js";
107
107
  export declare const name = "prism";
108
- export declare const version = "0.1.4";
108
+ export declare const version = "0.1.6";
109
109
  export declare const description = "Agent harness for AI providers, agents, sessions, and tools.";
package/dist/index.js CHANGED
@@ -46,7 +46,7 @@ export { assertPermission, assertTrusted, checkPermission, createStaticPermissio
46
46
  export { createMemorySessionStore, createSessionEntry, getSessionBranchEntries, listSessionBranches, rebuildSessionContext, } from "./session-stores.js";
47
47
  export { createChainedSettingsProvider, createStaticSettingsProvider } from "./settings.js";
48
48
  export { createLoadedSkillSet, DEFAULT_MAX_SKILL_CATALOG_ENTRIES, DEFAULT_MAX_SKILL_DESCRIPTION_BYTES, DEFAULT_MAX_SKILL_INSTRUCTION_BYTES, EMPTY_SKILL_DESCRIPTION, HARD_MAX_SKILL_CATALOG_ENTRIES, HARD_MAX_SKILL_DESCRIPTION_BYTES, HARD_MAX_SKILL_INSTRUCTION_BYTES, isSkillDisclosureError, resolveSkillsDisclosure, SkillDisclosureError, } from "./skill-disclosure.js";
49
- export { createLoadSkillTool, DEFAULT_LOAD_SKILL_TOOL_NAME, isSkillLoadError, MAX_LOAD_SKILL_RESULT_BYTES, resolveSkillLoad, SKILL_LOAD_ERROR_CODE, SkillLoadError, } from "./skill-load.js";
49
+ export { applyRestoredSkillBodies, createLoadSkillTool, DEFAULT_LOAD_SKILL_TOOL_NAME, HARD_MAX_PERSISTED_SKILL_BODY_TOTAL_BYTES, isSkillLoadError, MAX_LOAD_SKILL_RESULT_BYTES, MAX_PERSISTED_SKILL_BODIES, MAX_PERSISTED_SKILL_BODY_BYTES, MAX_PERSISTED_SKILL_BODY_NAME_CHARS, resolveSkillLoad, SKILL_LOAD_ERROR_CODE, SkillLoadError, snapshotLoadedSkillBodies, validateLoadedSkillBodies, } from "./skill-load.js";
50
50
  export { createSkillRegistry, resolveActiveSkills } from "./skills.js";
51
51
  export { artifactStructuredOutputRequest, assertStructuredOutputRequestSupported, DEFAULT_MAX_STRUCTURED_OUTPUT_NAME_LENGTH, DEFAULT_MAX_STRUCTURED_OUTPUT_SCHEMA_BYTES, modelSupportsStructuredOutput, resolveRunProviderOptions, StructuredOutputError, validateStructuredOutputOptions, withoutStructuredOutput, } from "./structured-output.js";
52
52
  export { composeSystemPrompt, mergeSystemPromptConfig } from "./system-prompts.js";
@@ -57,6 +57,6 @@ export { DEFAULT_TOOL_RESULT_FOLD_MAX_SUMMARY_BYTES, DEFAULT_TOOL_RESULT_FOLD_MI
57
57
  export { createToolParameterValidator, createToolRegistry, dispatchToolCall, filterTools } from "./tools.js";
58
58
  export { resolveUseCaseModel, resolveUseCaseModelBinding, useCaseCredentialProviderId, } from "./use-case-model.js";
59
59
  export const name = "prism";
60
- export const version = "0.1.4";
60
+ export const version = "0.1.6";
61
61
  export const description = "Agent harness for AI providers, agents, sessions, and tools.";
62
62
  //# sourceMappingURL=index.js.map
package/dist/rpc.js CHANGED
@@ -225,9 +225,10 @@ function runOptions(state, params) {
225
225
  const names = stringArrayParam(params, "instructionInjectors");
226
226
  // ponytail: fail-closed — unknown name throws (caller surfaces as RPC error), matching CLI.
227
227
  const injectors = names.length ? resolveInstructionInjectors({ registry: state.instructionInjectors, names }) : undefined;
228
+ const maxToolRounds = numberParam(params, "maxToolRounds");
228
229
  return {
229
230
  model: modelParam(params) ?? state.model,
230
- maxToolRounds: numberParam(params, "maxToolRounds"),
231
+ ...(maxToolRounds !== undefined ? { limits: { maxToolRounds } } : {}),
231
232
  ...(injectors ? { instructionInjectors: injectors } : {}),
232
233
  };
233
234
  }
@@ -3,6 +3,29 @@ import { type LoadedSkillSet } from "./skill-disclosure.js";
3
3
  export declare const DEFAULT_LOAD_SKILL_TOOL_NAME: "load_skill";
4
4
  export declare const SKILL_LOAD_ERROR_CODE: "skill_load_failed";
5
5
  export declare const MAX_LOAD_SKILL_RESULT_BYTES = 512;
6
+ export declare const MAX_PERSISTED_SKILL_BODIES = 64;
7
+ export declare const MAX_PERSISTED_SKILL_BODY_NAME_CHARS = 256;
8
+ export declare const MAX_PERSISTED_SKILL_BODY_BYTES = 262144;
9
+ export declare const HARD_MAX_PERSISTED_SKILL_BODY_TOTAL_BYTES: number;
10
+ /** One persisted loaded-skill body (plan 018 closeout `checkpoint-bodies`). */
11
+ export interface LoadedSkillBodiesEntry {
12
+ readonly name: string;
13
+ readonly instructions: string;
14
+ }
15
+ /** Fail-closed shape/cap validation for a persisted bodies payload (load and save sides). */
16
+ export declare function validateLoadedSkillBodies(value: unknown): asserts value is readonly LoadedSkillBodiesEntry[];
17
+ /**
18
+ * Snapshot the exact instructions of every loaded skill (registry-independent).
19
+ * Loaded names without instructions are skipped (they render no body anyway);
20
+ * a restored body for a loaded name wins over the registry body.
21
+ */
22
+ export declare function snapshotLoadedSkillBodies(skills: readonly Skill[], loaded: LoadedSkillSet, restored?: ReadonlyMap<string, string>): LoadedSkillBodiesEntry[];
23
+ /**
24
+ * Apply persisted bodies to the skills a resumed session will render: replace the
25
+ * instructions of known skills and append synthesized skills for names the live
26
+ * registry no longer serves, so the exact loaded text renders registry-independently.
27
+ */
28
+ export declare function applyRestoredSkillBodies(skills: readonly Skill[], bodies: readonly LoadedSkillBodiesEntry[]): readonly Skill[];
6
29
  export declare class SkillLoadError extends Error {
7
30
  readonly code: "skill_load_failed";
8
31
  constructor(message: string);
@@ -3,6 +3,80 @@ import { HARD_MAX_SKILL_INSTRUCTION_BYTES } from "./skill-disclosure.js";
3
3
  export const DEFAULT_LOAD_SKILL_TOOL_NAME = "load_skill";
4
4
  export const SKILL_LOAD_ERROR_CODE = "skill_load_failed";
5
5
  export const MAX_LOAD_SKILL_RESULT_BYTES = 512;
6
+ // Bodies-mode persistence bounds (plan 018 Task 6 closeout `checkpoint-bodies`): the
7
+ // checkpoint `maxStateBytes` ceiling is the documented outer bound (oversize refuses at
8
+ // save with a recorded error); these caps bound the bodies payload itself on load.
9
+ // ponytail: module constants, not tunable options — hosts tune maxStateBytes for the ceiling.
10
+ export const MAX_PERSISTED_SKILL_BODIES = 64; // names-mode parity (MAX_PERSISTED_SKILL_NAMES)
11
+ export const MAX_PERSISTED_SKILL_BODY_NAME_CHARS = 256; // names-mode parity
12
+ export const MAX_PERSISTED_SKILL_BODY_BYTES = 262_144; // = HARD_MAX_SKILL_INSTRUCTION_BYTES (loader parity)
13
+ export const HARD_MAX_PERSISTED_SKILL_BODY_TOTAL_BYTES = 1024 * 1024; // = HARD_MAX_AGENT_RUN_STATE_BYTES
14
+ /** Fail-closed shape/cap validation for a persisted bodies payload (load and save sides). */
15
+ export function validateLoadedSkillBodies(value) {
16
+ if (!Array.isArray(value) || value.length > MAX_PERSISTED_SKILL_BODIES) {
17
+ throw new SkillLoadError(`Loaded-skill bodies exceed ${MAX_PERSISTED_SKILL_BODIES} entries`);
18
+ }
19
+ let totalBytes = 0;
20
+ for (const entry of value) {
21
+ if (!entry || typeof entry !== "object" || typeof entry.name !== "string") {
22
+ throw new SkillLoadError("Malformed loaded-skill body entry: name must be a string");
23
+ }
24
+ const { name, instructions } = entry;
25
+ if (name.length > MAX_PERSISTED_SKILL_BODY_NAME_CHARS) {
26
+ throw new SkillLoadError(`Loaded-skill body name exceeds ${MAX_PERSISTED_SKILL_BODY_NAME_CHARS} chars`);
27
+ }
28
+ if (typeof instructions !== "string") {
29
+ throw new SkillLoadError("Malformed loaded-skill body entry: instructions must be a string");
30
+ }
31
+ const bodyBytes = Buffer.byteLength(instructions, "utf8");
32
+ if (bodyBytes > MAX_PERSISTED_SKILL_BODY_BYTES) {
33
+ throw new SkillLoadError(`Loaded-skill body exceeds ${MAX_PERSISTED_SKILL_BODY_BYTES} bytes`);
34
+ }
35
+ totalBytes += Buffer.byteLength(name, "utf8") + bodyBytes;
36
+ if (totalBytes > HARD_MAX_PERSISTED_SKILL_BODY_TOTAL_BYTES) {
37
+ throw new SkillLoadError(`Loaded-skill bodies exceed ${HARD_MAX_PERSISTED_SKILL_BODY_TOTAL_BYTES} total bytes`);
38
+ }
39
+ }
40
+ }
41
+ /**
42
+ * Snapshot the exact instructions of every loaded skill (registry-independent).
43
+ * Loaded names without instructions are skipped (they render no body anyway);
44
+ * a restored body for a loaded name wins over the registry body.
45
+ */
46
+ export function snapshotLoadedSkillBodies(skills, loaded, restored) {
47
+ const byName = new Map(skills.map((skill) => [skill.name, skill]));
48
+ const entries = [];
49
+ for (const name of loaded.list()) {
50
+ const instructions = restored?.get(name) ?? byName.get(name)?.instructions;
51
+ if (!instructions)
52
+ continue;
53
+ entries.push({ name, instructions });
54
+ }
55
+ validateLoadedSkillBodies(entries); // refuses oversize with a recorded error (never truncates)
56
+ return entries;
57
+ }
58
+ /**
59
+ * Apply persisted bodies to the skills a resumed session will render: replace the
60
+ * instructions of known skills and append synthesized skills for names the live
61
+ * registry no longer serves, so the exact loaded text renders registry-independently.
62
+ */
63
+ export function applyRestoredSkillBodies(skills, bodies) {
64
+ validateLoadedSkillBodies(bodies);
65
+ if (bodies.length === 0)
66
+ return skills;
67
+ const byName = new Map(skills.map((skill) => [skill.name, skill]));
68
+ const out = skills.map((skill) => {
69
+ const body = bodies.find((entry) => entry.name === skill.name);
70
+ return body ? { ...skill, instructions: body.instructions } : skill;
71
+ });
72
+ const known = new Set(skills.map((skill) => skill.name));
73
+ for (const entry of bodies) {
74
+ if (!known.has(entry.name)) {
75
+ out.push({ name: entry.name, instructions: entry.instructions });
76
+ }
77
+ }
78
+ return out;
79
+ }
6
80
  export class SkillLoadError extends Error {
7
81
  code = SKILL_LOAD_ERROR_CODE;
8
82
  constructor(message) {
package/docs/acp.md CHANGED
@@ -111,7 +111,7 @@ const agent = createPrismAcpAgent({
111
111
 
112
112
  ### Persistence and ownership
113
113
 
114
- - **The agent never persists `modeId`/`configValues`.** Defaults are recomputed per session from the `modes`/`configOptions` seams — a fresh `session/new`, `load`, or `resume` always starts from `defaultModeId` / option `defaultValue`, and the agent's per-session registry is in-memory only. Persisting mode/config across sessions is a **host** decision, and host-side persistence MUST be ownership-scoped.
114
+ - **Without the durability seam the agent never persists `modeId`/`configValues`.** Defaults are recomputed per session from the `modes`/`configOptions` seams — a fresh `session/new`, `load`, or `resume` always starts from `defaultModeId` / option `defaultValue`, and the agent's per-session registry is in-memory only. Persisting mode/config across sessions is a **host** decision, and host-side persistence MUST be ownership-scoped.
115
115
  - **Host persistence MUST key by `sessions.ownership`.** `authorize` binds transport identity to ownership; a host store that persists `modeId`/`configValues` must refuse any restore whose stored ownership differs from the current session's ownership — a `sessionId` alone is never a sufficient key (session ids may collide across tenants). A cross-tenant restore rejects with `ERR_PRISM_ACP_INPUT` and never returns the other tenant's mode/config.
116
116
  - **Ownership-scoped restore (host-owned store).** The store is keyed by `sessionId` and records the owning `userId`; restore refuses on mismatch (this exact pattern is asserted in `packages/ag-ui/src/__tests__/acp-modes-config.test.ts`):
117
117
 
@@ -133,7 +133,7 @@ const agent = createPrismAcpAgent({
133
133
  ```
134
134
 
135
135
  Because the agent recomputes defaults on every `load`/`resume`, a host that restores state re-applies it after load through the same gated seams (`session/set_mode`, `session/set_config_option` — both run the `apply`/`onChange` hooks) and must refuse cross-tenant loads at the `authorize` seam first (falsy `authorize` = `Unauthorized ACP session`, before any mode/config state is reachable).
136
- - **Agent-owned persistence is 0.2.0.** A durable, ownership-scoped ACP session store (agent-side persistence of mode/config and session state) is roadmap 0.2.0 Module E, demand-gated; on the 0.1.x line the agent stays a thin per-session registry. See the [Host security guide](host-security.md) fail-closed checklist for the ACP boundary rows.
136
+ - **Durable registry (0.1.6, plan 018 closeout `acp-session-store`).** Pass `sessionStore` (`AcpSessionStore` from `@arnilo/prism-ag-ui/acp`) to let a restarted agent restore its live-session registry: `save` (on `session/new`, `set_mode`, `set_config_option`), `loadAll` (once per agent instance, lazily on first authorized touch), `evict` (on `close`/`delete`). The stored entry carries `sessionId`, `ownership`, `modeId`, `configValues`, `cwd`, `additionalDirectories`, `updatedAt` — never ephemeral stream state (client/controller/budget) or pending decisions. Restore re-resolves the live `AgentSession` through your `sessionFactory`, re-validates cwd/directories and mode/config values against the seams, enforces the registry cap, and drops corrupt or seam-mismatched entries fail-closed. The seam is additive-only: absent `sessionStore` ⇒ the agent behaves exactly as 0.1.5. Storage topology stays host-owned; the store is the trust boundary for tampering/replay. The full threat model and test mapping live in `docs/_evidence/phase18-primitive-review.md`; enforcement tests in `packages/ag-ui/src/__tests__/acp-session-store.test.ts`. The host-owned mode/config store pattern above stays valid for hosts that persist without the agent seam.
137
137
 
138
138
  ## Security and performance notes
139
139
 
@@ -221,7 +221,7 @@ await session.run(input, { loop: twoShotLoop });
221
221
  - `{ strategy: "single-shot" }` resolves to the exported `singleShotLoop`; `{ strategy: "generate-validate-revise", ... }` is mapped by `resolveLoop()` to `generateValidateReviseLoop(opts)`. An unknown `strategy` throws before the first turn. Passing an `AgentLoopStrategy` instance bypasses the options form entirely (custom-loop escape hatch).
222
222
  - The loop is resolved once per run inside `RuntimeAgentSession.run()`, after the usual setup (provider/skills/tools resolution, history rebuild, model-change entry, input append, auto-compaction). The runtime's outer try/catch/finally, run-exclusivity, abort bridging, and subscriber close remain in place around `loop.run(ctx)`.
223
223
  - `LoopContext.assemble(nextInput, toolResults?)` accepts an optional tool-result accumulator so `singleShotLoop` can pass its loop-local results. Bounded artifact tools append results directly to shared history, then assemble the next turn with empty new input; no second transcript path exists.
224
- - `limits.maxToolRounds` bounds both `singleShotLoop` and opt-in bounded artifact tool rounds across the whole run. Artifact mode always dispatches sequentially, regardless of `toolConcurrency`; all dispatches still use existing registry/filter/permission/validator/middleware/redactor/ledger guards. Deprecated `maxToolRounds` only narrows this limit.
224
+ - `limits.maxToolRounds` bounds both `singleShotLoop` and opt-in bounded artifact tool rounds across the whole run. Artifact mode always dispatches sequentially, regardless of `toolConcurrency`; all dispatches still use existing registry/filter/permission/validator/middleware/redactor/ledger guards.
225
225
  - `maxRevisions` (default 3) counts only failed call-free artifact candidates. Bounded artifact runs make at most `1 + maxRevisions + maxToolRounds` provider turns. A tool-round limit is terminal and returns last usage after `artifact_failed`; it does not throw.
226
226
  - A revision cycle appends one assistant draft and one repair user message per revision to the session store, so store entries reflect every attempted draft. The original user input is stored once by the runtime and pushed into loop history once on the first turn. Repair messages are assembled as the next provider `nextInput` and only pushed into live history after that revision request has been generated, so the model never receives a duplicated repair instruction.
227
227
 
@@ -49,7 +49,7 @@ string | Message | readonly Message[]
49
49
 
50
50
  `AgentConfig.limits` sets run ceilings; `RunOptions.limits` may only narrow configured agent values. Limits cover turns, provider attempts, tool rounds/calls, wall time, request/response bytes, tokens, and optional single-currency cost. A breach emits one `run_limit_exceeded` event and throws `AgentRunError` with `result.limit`; see [Runs and usage ledger](runs-and-usage.md#run-limits).
51
51
 
52
- `RunOptions.model` can override the request model for a run. Model overrides append a `model_change` entry. `AgentConfig.inputLayout` selects the default input assembly layout (`"cache_aware"` by default, or opt-in `"legacy"`); `RunOptions.inputLayout` wins for one run. `AgentConfig.providerOptions`/`RunOptions.providerOptions` supply generic provider request options; `timeoutMs`, `maxRetries`, and `maxRetryDelayMs` are deprecated inert provider-level hints in first-party providers. Use `RunOptions.signal`/host abort controllers for timeouts and `AgentConfig.retry`/`RunOptions.retry` for retry. `AgentConfig.providerRequestPolicies`/`RunOptions.providerRequestPolicies` run before `AIProvider.generate()` and before `provider_request` middleware. `AgentConfig.systemPrompt` and `RunOptions.systemPrompt` add explicit layered system prompt contributions; `RunOptions.systemPrompt: false` disables configured prompt layers for that run while keeping `AgentConfig.instructions` as the base path. `RunOptions.compaction` can enable auto-compaction for that run or use `false` to disable configured auto-compaction. `RunOptions.retry` can enable provider-turn retry for that run or use `false` to disable configured retry. `RunOptions.metadata` is merged with agent/session metadata for assembly, provider requests, and tool contexts. Deprecated `RunOptions.maxToolRounds` narrows `limits.maxToolRounds`. `RunOptions.signal` is bridged into the per-run abort signal passed to assembly, providers, tools, auto-compaction, and retry backoff.
52
+ `RunOptions.model` can override the request model for a run. Model overrides append a `model_change` entry. `AgentConfig.inputLayout` selects the default input assembly layout (`"cache_aware"` by default, or opt-in `"legacy"`); `RunOptions.inputLayout` wins for one run. `AgentConfig.providerOptions`/`RunOptions.providerOptions` supply generic provider request options (session/cache/header/compat/extra hints only — provider-level timeout/retry hints were removed in 0.1.5). Use `RunOptions.signal`/host abort controllers for timeouts and `AgentConfig.retry`/`RunOptions.retry` for retry. `AgentConfig.providerRequestPolicies`/`RunOptions.providerRequestPolicies` run before `AIProvider.generate()` and before `provider_request` middleware. `AgentConfig.systemPrompt` and `RunOptions.systemPrompt` add explicit layered system prompt contributions; `RunOptions.systemPrompt: false` disables configured prompt layers for that run while keeping `AgentConfig.instructions` as the base path. `RunOptions.compaction` can enable auto-compaction for that run or use `false` to disable configured auto-compaction. `RunOptions.retry` can enable provider-turn retry for that run or use `false` to disable configured retry. `RunOptions.metadata` is merged with agent/session metadata for assembly, provider requests, and tool contexts. Run tool-round limits via `RunOptions.limits.maxToolRounds`. `RunOptions.signal` is bridged into the per-run abort signal passed to assembly, providers, tools, auto-compaction, and retry backoff.
53
53
 
54
54
  `RunOptions.activeSkills` selects named skills from a configured `SkillRegistry`; `RunOptions.skills` replaces a plain `Skill[]` config for one run. When `AgentConfig.skills` is a registry and neither is set, **no skills activate** unless `activateAllSkills: true` (run or agent). `skillsDisclosure` (`"progressive"` default, `"eager"` opt-in; run wins) controls catalog vs full instruction bodies; the session-owned `LoadedSkillSet` is populated by `load_skill` when the host registers `createLoadSkillTool`. `toolResultFold` (off unless the host supplies `summarize`) optionally folds aged large tool results in provider input only. See [Context and skills](context-and-skills.md).
55
55
 
@@ -120,7 +120,7 @@ const agent = createAgent({
120
120
  });
121
121
 
122
122
  const session = agent.createSession({ id: "s1" });
123
- const result = await session.run("Hi", { maxToolRounds: 1, compaction: { thresholdEntries: 20, keepRecentEntries: 6 }, retry: { maxAttempts: 3, baseDelayMs: 50 } });
123
+ const result = await session.run("Hi", { limits: { maxToolRounds: 1 }, compaction: { thresholdEntries: 20, keepRecentEntries: 6 }, retry: { maxAttempts: 3, baseDelayMs: 50 } });
124
124
  console.log(result.text, result.usage?.totalTokens);
125
125
 
126
126
  for await (const event of session.stream("Follow up")) console.log(event.type);
@@ -198,7 +198,7 @@ if (result.status === "suspended") {
198
198
  }
199
199
  ```
200
200
 
201
- Resume requires exact checkpoint ownership, version, agent fingerprint, and revision. The fingerprint hashes the agent id/name, `definitionRevision`, model, instructions, system-prompt contributions, skills (name/instructions/tool names), tool definitions (name/parameters/exclusive), guardrail definitions (name/stage/revision), and loop strategy — changing any of them without bumping `definitionRevision` fails resume closed instead of silently continuing with different agent semantics. Prism CAS-claims approval before work, rechecks normal guardrail/permission/validation/limit paths, and marks a pending tool dispatched before its side effect. `createAgentRunLifecycle()` wraps the same core path for server/MCP hosts: adapters pass only authorized ownership, status returns only `{ state, version }`, and `resolveAgent()` supplies current agent/revision. `resumeStream()` uses that same claim path and bounded subscriber, so adapters do not poll or duplicate resume logic. Remote restart requires both checkpoint and session stores to be durable. A crash after that mark is ambiguous and is never replayed automatically; use host tool idempotency keyed by `runId`/`toolCallId` or resolve it manually. Checkpoints contain bounded redacted state plus session/leaf references, never provider objects, callbacks, signals, credentials, or raw secrets. State is bounded at save by `runState.maxStateBytes` (default 256 KB, at most the 1 MB hard cap); load bounds against the 1 MB hard cap only, so state saved with a raised limit stays resumable while oversized records are still rejected. Since 0.1.3 (plan 015 Task 4), durable runs may opt in to session-state persistence with `persistSessionState: true` on both the run and resume options: the loaded-skill **name catalog** (≤64 names, ≤256 chars each) rides the checkpoint and is restored into the resumed session's `LoadedSkillSet`; skill **bodies are never persisted** and re-resolve from the live registry via `load_skill`. Default off keeps the checkpoint shape byte-identical to 0.1.2. Built-in loop options are durable; custom `AgentLoopStrategy` instances are durable when they declare `snapshot`/`restore` hooks (see [Agent loops § Durable runs](agent-loops.md#durable-runs)) and reject before provider work otherwise.
201
+ Resume requires exact checkpoint ownership, version, agent fingerprint, and revision. The fingerprint hashes the agent id/name, `definitionRevision`, model, instructions, system-prompt contributions, skills (name/instructions/tool names), tool definitions (name/parameters/exclusive), guardrail definitions (name/stage/revision), and loop strategy — changing any of them without bumping `definitionRevision` fails resume closed instead of silently continuing with different agent semantics. Prism CAS-claims approval before work, rechecks normal guardrail/permission/validation/limit paths, and marks a pending tool dispatched before its side effect. `createAgentRunLifecycle()` wraps the same core path for server/MCP hosts: adapters pass only authorized ownership, status returns only `{ state, version }`, and `resolveAgent()` supplies current agent/revision. `resumeStream()` uses that same claim path and bounded subscriber, so adapters do not poll or duplicate resume logic. Remote restart requires both checkpoint and session stores to be durable. A crash after that mark is ambiguous and is never replayed automatically; use host tool idempotency keyed by `runId`/`toolCallId` or resolve it manually. Checkpoints contain bounded redacted state plus session/leaf references, never provider objects, callbacks, signals, credentials, or raw secrets. State is bounded at save by `runState.maxStateBytes` (default 256 KB, at most the 1 MB hard cap); load bounds against the 1 MB hard cap only, so state saved with a raised limit stays resumable while oversized records are still rejected. Since 0.1.3 (plan 015 Task 4), durable runs may opt in to session-state persistence with `persistSessionState: true` on both the run and resume options: the loaded-skill **name catalog** (≤64 names, ≤256 chars each) rides the checkpoint and is restored into the resumed session's `LoadedSkillSet`; skill **bodies are never persisted** and re-resolve from the live registry via `load_skill`. Since 0.1.6 (plan 018 closeout `checkpoint-bodies`), `includeSkillBodies: true` on BOTH the run and resume options additionally persists the exact loaded-skill **instructions** (`{name, instructions}` pairs, redacted at the checkpoint boundary like all state, ≤64 bodies / ≤256-char names / ≤262144-byte bodies / ≤1 MiB total) so resume re-renders them registry-independently — no `load_skill` round-trip and no dependence on the registry still serving the same text; `maxStateBytes` (default 256 KB) refuses oversize bodies with a recorded error, never silently truncates. Default off keeps the checkpoint shape byte-identical to 0.1.3. Built-in loop options are durable; custom `AgentLoopStrategy` instances are durable when they declare `snapshot`/`restore` hooks (see [Agent loops § Durable runs](agent-loops.md#durable-runs)) and reject before provider work otherwise.
202
202
 
203
203
  ## Secure composition
204
204