# Migration guide

## 0.9.0 → 0.10.0 (hook lifecycle completion, scoped agent memory)

**Prism 0.10.0 is a lockstep minor for all twelve publishable packages** — `@arnilo/prism-hooks` is new. Node `>=22` stays the floor. Nothing was removed: no import path moved and no export was dropped (compat baseline: +47 names, zero removals, zero renames). Scoped memory is a new opt-in subpath that stays inert until a host constructs a policy.

What a 0.9.0 host must check before upgrading:

- **One type-level addition: `AgentSession.close(): Promise<void>`.** Hosts that implement or proxy the interface (not only consume it) must add a `close()`; `async close() {}` satisfies the type, and a proxy should forward to the wrapped session so `session_shutdown` fires once. The in-repo precedent is the observational-memory proxy in `@arnilo/prism-memory`, which forwards it.
- **`session_start` and `session_shutdown` are now emitted.** Both names were declared in 0.9.0 but had no call site, so extension handlers registered against them never ran. `session_start` fires once per session at the first turn of the first run (durable resumes included); `session_shutdown` fires from `session.close()` and is idempotent — a host that never calls `close()` never sees it.
- **`hook_limit` is a new `AgentFinishReason`** (and a `StoredAgentRunState.stopReason`). Exhaustive switches over finish reasons must handle it; a `hook_limit` stop stays resumable only under `checkpointPolicy: "every-turn"`.
- **Stop hooks are bounded by default.** `RunLimits.maxStopContinuations` defaults to 3 (`0` disables continuation, `null` uncaps), and only a run that registers stop hooks can reach the limit.
- **`compaction_request` runs before the compaction strategy** when a handler is registered (entries and budget are rewritable); with no handler the compaction path is unchanged.
- **Additive, inert by default:** `AgentConfig.stopHooks` / `RunOptions.stopHooks` and `ExtensionAPI.registerStopHook()`, `forwardAgentEvents()`, the `@arnilo/prism-hooks` adapter, and the whole `@arnilo/prism-memory/scoped` surface (reviewer, promotion ladder, GC proposals, bounded facts block, approval gate, audit mirror, eval harness).

## 0.8.0 → 0.9.0 (attention budget axes, turn traces, tool narrowing, guardrail packs, background agents, session search, deterministic turns, shared scopes)

**Prism 0.9.0 is a lockstep minor for all eleven publishable packages.** Node `>=22` stays the floor. Nothing was removed: no import path moved, no export was dropped, and every new surface defaults to 0.8 behavior. The full guide — the four deltas inside existing surfaces, every new option with its sizing line, upgrade steps, and rollback — is [migrate-to-0.9.md](migrate-to-0.9.md).

What a 0.8.0 host must check before upgrading:

- **A limit death no longer ends a stream early.** `run_limit_exceeded` and `budget_exhausted` are not terminal; keep reading until `error` (or `isTerminalAgentEventType(type)` is true) to see the breach, its attribution, and the run's outcome in order.
- **`provider_turn_finished` metadata grew** (`stopReason`, `budgets`, `tools`, `cache`), and `agent_finished` now carries `finishReason` / `stopDetail`; consumers that deep-equal `metadata` must allow the new fields.
- **`AgentConfig.usageEstimation` defaults to `"fallback"`**: a provider that reports no usage is charged one labeled estimate (`estimated: true` + `confidence`) instead of zero. Set `"off"` for the old behavior; billing code must read the label.
- **Progressive disclosure is cache-stable**: skill bodies and deferred schemas append at the tail instead of rewriting the prefix. Defaults keep 0.8 bytes for hosts that never load late context.
- **One type-level change**: the `recordUsage` callback of `generateProviderTurn` / `generateWithRetry` returns `Promise<Usage | undefined>` instead of `Promise<void>`.
- **Additive, inert by default**: attention `trigger` axes and `durable` folding, per-turn `toolNarrowing`, `guardrailPacks`, session-lifetime child agents with child-event passthrough, `checkpointMetadata` / `restoreHooks`, `searchSessions`, `beforeProviderTurn` deterministic turns, observability shared work scopes, deletion propagation, and the local reranker. One additive migration (004) adds the session-search index; no existing table or column changes.

## 0.7.0 → 0.8.0 (messaging channels, connected apps, work family, durable runs)

**Prism 0.8.0 is a lockstep minor for all eleven publishable packages.** Node `>=22` stays the floor. The only import-map break is `@arnilo/prism-office` → `@arnilo/prism-work` (plus the work/document-reader subpath moves). A host that never imported those paths upgrades by moving every `@arnilo/*` dependency and peer to `^0.8.0`. The full guide — per-item actions, opt-in activation, and rollback — is [migrate-to-0.8.md](migrate-to-0.8.md).

What a 0.7.0 host must check before upgrading:

- **One import-map break.** Replace `@arnilo/prism-office/*`, `@arnilo/prism-core/integrations/work*`, and `@arnilo/prism-coding-tools/document-reader` with `@arnilo/prism-work` subpaths. Catch work-idempotency by `error.code`, not class. No pre-1.0 shim.
- **Eleventh package.** `@arnilo/prism-channels` is new and optional; omit it if the host has no messaging ingress.
- **Additive, inert by default:** connected-app MCP sessions, work HTTP adapters, turn-boundary checkpoints / `decision: "continue"`, turn-stop policy, run-bundle snapshots, claim-grounding guardrail, work sandbox/skills.
- **Behavioral pins inside existing surfaces:** observational-memory workers stay tool-only (text-only turns are successful no-ops); channel lease release clears in-memory state only after the store acknowledges; AG-UI `inputPolicy.clientState: "ignore"` is opt-in (default honor matches 0.7.0); a foreign checkpoint/run-status load is a miss, not an existence leak.

## Next lockstep cut — work family package move

`@arnilo/prism-office` and the work/document-reader subpaths are removed with no pre-1.0 compatibility shim. Install `@arnilo/prism-work` with `@arnilo/prism` and update imports:

| Old import | Replacement |
| --- | --- |
| `@arnilo/prism-office/documents` | `@arnilo/prism-work/documents` |
| `@arnilo/prism-office/sheets` | `@arnilo/prism-work/sheets` |
| `@arnilo/prism-office/diagrams` | `@arnilo/prism-work/diagrams` |
| `@arnilo/prism-core/integrations/work` | `@arnilo/prism-work/connectors` |
| `@arnilo/prism-core/integrations/work/microsoft365` | `@arnilo/prism-work/connectors/microsoft365` |
| `@arnilo/prism-core/integrations/work/google-workspace` | `@arnilo/prism-work/connectors/google-workspace` |
| `@arnilo/prism-core/integrations/work/drafts` | `@arnilo/prism-work/connectors/drafts` |
| `@arnilo/prism-coding-tools/document-reader` | `@arnilo/prism-work/document-reader` |

The coding `createReadTool({ documentReader })` injection seam is unchanged; pass the reader created by the new subpath. Core keeps its durable adapter behind `createPostgresEnterpriseState({ pool }).workIdempotency` at `@arnilo/prism-core/enterprise/postgres` with type-only structural coupling.

**Match work-idempotency conflicts by `code`, not by error class.** The portable codes are unchanged across the move — `ERR_PRISM_WORK_IDEMPOTENCY` for a rejected claim/transition and `ERR_PRISM_WORK_IDEMPOTENCY_CONFLICT` for a lost race or stale claim token — and a host that catches them by `error.code` (the pattern `docs/work-tools.md` documents) needs no change. The *class* is adapter-specific: `createMemoryIdempotencyStore()` throws `WorkToolError` (an upstream `Error` subclass) while the PostgreSQL adapter throws `EnterprisePostgresError`, since `@arnilo/prism-core` cannot depend on `@arnilo/prism-work` at runtime. A pre-existing adapter that caught the old import path's error by `instanceof` must switch to `code` matching; `packages/prism-core/src/enterprise/postgres/__tests__/work-idempotency.integration.test.ts` runs both adapters through the same conflict scenarios and asserts the two agree.

## 0.6.0 → 0.7.0 (host completeness, evidence, and capability boundaries)

**Prism 0.7.0 is a lockstep minor for all ten publishable packages.** Node `>=22` stays the floor; no import path was removed and no store schema changed, so a host that does not touch the ACP agent or the model-router facade upgrades by moving every `@arnilo/*` dependency and peer to `^0.7.0`. The full guide — per-item migration actions, opt-in activation, and rollback — is [migrate-to-0.7.md](migrate-to-0.7.md).

What a 0.6.0 host must check before upgrading:

- **Two hard refusals inside existing surfaces (the only host-breaking changes).** `@arnilo/prism-acp-agent` now matches `mcp.allow` destinations by WHATWG origin plus path-segment subtree (prefix lookalikes such as `https://mcp.example.com.attacker.invalid` no longer match, and allow entries with userinfo/query/fragment/ambiguous encodings fail `ConfigError` at parse time); `router.providerSource(model)` throws `ERR_PRISM_MODEL_ROUTER_ASYNC_REQUIRED` / `ERR_PRISM_MODEL_ROUTER_ASYNC_STATE` instead of silently bypassing budgets, rate limits, circuits, fallbacks, selection policies, or durable state — move those call sites to `await router.resolve(...)`. An ACP deployment without an explicit provider no longer silently runs `createMockProvider()`; mock mode is explicit.
- **Behavioral tightenings inside existing surfaces** (agent tool narrowing is monotonic per run, RAG queries are authorized in both legs, memory corrections/revocations propagate, import-fidelity reports replace silent drops, model-router/worker accounting is aggregated): these need a host read-through, not a code change.
- **Additive declarations only.** No export was removed in 0.7.0; the compat baselines were regenerated because existing declaration groups gained members (new fields on options/results and new subpath exports for the attention compiler, memory fabric, work scopes, and supervisor spawn tools).
- **Opt-in additions are inert by default**: attention compiler (`AgentConfig`/`RunOptions`), `@arnilo/prism-memory/fabric`, the observational-memory work-scope index, and the supervisor `spawn_agent`/`wait_agent`/`cancel_agent` tools. Omitted, request bytes, stores, and tool lists are unchanged.
- **Channel adapters (Telegram/Signal) are not in 0.7.0.**

## 0.5.6 → 0.6.0 (Node 22 floor; folds the never-published 0.5.7)

**Prism 0.6.0 requires Node `>=22`.** Every publishable package declares `"engines": { "node": ">=22" }`; a Node 20 host gets an `EBADENGINE` warning from npm (a hard failure under `engine-strict`) and an unsupported runtime. Node 20 reached upstream end-of-life on 2026-04-30, so the 0.6.0 line moves to Node 22 (maintenance LTS to 2027-04-30) while Node 24 stays the CI default (active LTS to 2028-04-30). The full 0.5.6 → 0.6.0 guide — third-party floors, the removed office peer, the additive host knobs, and upgrade/rollback steps — lives in [migrate-to-0.6.md](migrate-to-0.6.md).

What a 0.5.x host must check before upgrading:

- **Runtime.** Move the host process and any container image to Node 22.6+ (the docs/test harness strips TypeScript natively from 22.6; Node 22 LTS or later is the supported answer). `engines.node` is now `>=22`, so `npm install` fails closed on older runtimes with `engine-strict` enabled.
- **CI legs.** The release workflow's compatibility leg is renamed `node20-compat` → `node22-compat` and runs on `node-version: "22"`; branch-protection required-check lists that name the old job id must be updated.
- **Development types.** `@types/node` (dev) moves `^20.19.0` → `^22.20.0` in the root and `@arnilo/prism-coding-tools`, tracking the declared floor. Hosts building Prism from source should not pin their own `@types/node` below 22 while the floor is `>=22`.
- **No migration step for Prism itself.** No import path, store schema, event shape, or public signature changed for the floor; it is one of the two host-visible deltas in this cut (`scripts/phase12-freeze-manifest.json` deviation `dev-006`), the other being the third-party peer floors and the removed `@arnilo/prism-office` `playwright-core` peer listed in [migrate-to-0.6.md](migrate-to-0.6.md#3-arniloprim-office-is-peer-free).

### Folded 0.5.7 content (no import, store, or event-shape break)

The 0.5.7 cut was never published, so its content ships in 0.6.0. Third-party ranges moved; hosts that pin these themselves must move with them:

- `pg` **`^8.22.0` → `^8.23.0`** — driver dependency of `@arnilo/prism-core/sessions/postgres` and `@arnilo/prism-memory`, and its optional peer in core. Hosts on 8.22 see a peer warning until they upgrade.
- `playwright-core` optional exact peer **`1.61.0` → `1.63.0`** in `@arnilo/prism-web-tools` (the exact pin is deliberate — browser control is version-sensitive, and the host still owns the browser binary/image). `@arnilo/prism-office` **drops** its optional `playwright-core` peer: no office subpath ever imported it at runtime (the diagrams embed takes a host-supplied iframe), so it becomes a devDependency behind the gated live draw.io test. Hosts that installed it for office can remove it.
- `@ai-sdk/provider` exact peer **`4.0.10` → `4.0.13`** in `@arnilo/prism-providers/ai-sdk`. The supported-version matrix gained a `4.0.13` row; `4.0.3`, `4.0.4`, and `4.0.10` stay listed. Unlisted versions still fail closed with `AiSdkProviderError { code: "unsupported_version" }`.
- `@nanonets/graft` optional peer **`^0.16.0` → `^0.16.0 || ^0.18.0`** in `@arnilo/prism-memory/graft` (upstream published no 0.17; both listed floors pass the offline peer-contract smoke).
- `@agentclientprotocol/sdk` exact pin **`1.3.0` → `1.4.0`** in `@arnilo/prism-ag-ui/acp` and `@arnilo/prism-acp-agent`. Wire protocol stays v1 (`PROTOCOL_VERSION === 1`); 1.4.0 stabilizes elicitation (the SDK's `unstable_createElicitation`/`unstable_completeElicitation` helpers become `createElicitation`/`completeElicitation`, wire method names unchanged — Prism never called the unstable helpers) and adds `compaction` session-update kinds, which Prism does not advertise or map.
- `@office-open/*` **`0.13.1` → `0.14.5`** in `@arnilo/prism-office`. Upstream made `parseDocument`/`parsePresentation`/`parseWorkbook` async; Prism's synchronous document adapters now call the new `parse*Sync` variants, so no Prism signature changed — but the office package requires the 0.14.5 line.
- `zod` **`^4.4.3` → `^4.6.2`** in `@arnilo/prism-mcp` (AG-UI's `^3.25.0 || ^4.0.0` peer range is unchanged and still admits it).
- `@biomejs/biome` dev **`2.5.11` → `2.5.13`** (lint/format only; 0 findings on the repo).

Dev-tooling and release-gate changes in the same cut (no host action required):

- **`@types/node` dev `^26.1.1` → `^20.19.0` at the 0.5.7 cut, then `^22.20.0` here.** Development types track the declared runtime floor, so a Node-22+-only API fails the build instead of compiling clean against a newer type surface. `docs/release-and-install.md` records the policy: the types package tracks the floor, and raising the floor is a support-matrix change (freeze manifest + CI legs), not a dependency bump. The `^20.19.0` pin immediately caught four runnable examples using `import.meta.main` (Node ≥22.18/≥24.2) on a Node-20 floor — they now use the house `import.meta.url === \`file://${process.argv[1]}\`` guard, so they no longer silently no-op below Node 22.18.
- **Node 20 floor removed in 0.6.0.** The never-published 0.5.7 deliberately kept `engines.node >=20` because dropping a supported line is a host-breaking support-matrix change that does not belong in a patch release; the 0.6.0 minor is the right vehicle (Node 22 is maintenance LTS to 2027-04-30, Node 24 active LTS to 2028-04-30).
- **Internal first-party ranges are gated at the cut version exactly.** `release.mjs validateRelease` (lockstep mode, which `release.mjs gate --lockstep --version` and the publish path both use) requires every `@arnilo/*` range to be the cut version (exact `0.6.0` or caret `^0.6.0`). A range that merely *satisfies* it — `^0.5.5` alongside `^0.5.6`, which is what the pre-cut tree carried — now fails the gate closed, because it lets two installs of the same release line resolve different first-party minors.

## 0.5.3 → 0.5.4 (export-shape break in `@arnilo/prism`)


Run-limit process ceilings split from host policy (plan 067). `HARD_RUN_LIMITS` shrinks to the two process-safety axes (`maxRequestBytes`/`maxResponseBytes`, 64 MiB) and `HARD_MAX_RUN_COST` is removed — delete imports; no replacement exists because product axes have no hard cap. `RunLimits` policy axes (turns, attempts, tool rounds/calls, wall time, tokens) now accept `number | null`, where `null` explicitly disables the axis and omitted keys keep the `DEFAULT_RUN_LIMITS` fence; byte axes reject `null`. Resolution stays narrowing-only (`null` acts as +Infinity), an omitted `maxProviderAttempts` lifts to at least a raised/disabled `maxTurns`, and the former `$10k` `maxCost` ceiling is gone (any finite non-negative amount is valid). `resolveRunLimits` returns the new `ResolvedRunLimits` type (policy axes `number | null`). Durable run state without a wall limit omits `deadlineAt`; older checkpoints carrying one still resume with it. `DEFAULT_RUN_LIMITS` values and all breach semantics are unchanged.

## 0.5.2 → 0.5.3 (additive)


Content-only tool results fold onto `tool_result.result` at construction. Serializers join sibling `type:text` blocks when `result` is missing, so coding tools that return `content` (not `value`) no longer reach the model as JSON `"null"`. No import, store, or peer-range break; bump `@arnilo/prism*` to `^0.5.3`.

## 0.5.1 → 0.5.2 (additive)


Stream tokens coalesce on persist (adjacent `text`/`thinking` deltas merge). Replay serializers join those parts with an empty string instead of a newline. No import, store, or peer-range break; bump `@arnilo/prism*` to `^0.5.2`.

## 0.5.0 → 0.5.1 (additive)


Kernel constructs valid provider requests: session correlation, default cache breakpoints, and `thinkingLevel` on `AgentConfig` / `RunOptions`. Hosts may drop a host-side `createSessionCachePolicy` overlay. OpenCode Go raw `generate` without `sessionId` throws `ProviderRequirementError` (`ERR_PRISM_PROVIDER_REQUIREMENT`) before fetch instead of an upstream 400. Observational memory uses derived `om:{session.id}`; LLM compaction uses the agent session id. See [migrate-to-0.5.md](migrate-to-0.5.md#8-provider-request-construction--additive-plan-066--051).

## What it does


Prism 0.0.6 preserves documented 0.0.3 agent construction except for two intentional Phase 3 public-API cleanups:

1. **`session.run()` / `session.prompt()` return `AgentRunResult`** and `session.stream()` starts one owned run after subscribing. Callers that ignored the previous `Promise<void>` keep working; failed/aborted runs reject with `AgentRunError` (`.result` attached).
2. **`AgentConfig.extensions` / `settings` / `credentials` are removed.** Wire extensions through `createExtensionKernel()`, read settings in the host, and pass credential resolvers to the provider edge.

## When to use it


Read this page when:

- you are taking an app from the `createMemorySessionStore()` / `createJsonlSessionStore()` path to a multi-process, multi-tenant, or durable database backend;
- you are hardening an agent that previously relied on "every scoped tool/skill is active" and need to name capabilities explicitly;
- you are adopting 0.0.6 persistence, checkpoints/leases, workflows, structured output, multimodality, or explicit tool safety for the first time.

If you are new to Prism, start at [Session stores](session-stores.md) and [Agent/session runtime](agent-session-runtime.md) instead.

## Inputs / request


There is no runtime import for this page. The migrations below use these surfaces:

| Surface | Where | Migration role |
| --- | --- | --- |
| `SessionStore` | `@arnilo/prism` | Runtime seam swapped from memory/JSONL to DB. |
| `createSqlitePersistence` | `@arnilo/prism-session-store-sqlite` | Local durable session, ledger, query, checkpoint, and lease adapter. |
| `createPostgresPersistence` | `@arnilo/prism-session-store-postgres` | Multi-process pooled persistence with advisory-lock migrations. |
| `ProductionPersistenceStore` | `@arnilo/prism` | Adapter-facing contract for paginated, multi-tenant reads (`query*`, optional `readBranchPath`). |
| `RunLedger` / `RunLedgerRecord` | `@arnilo/prism` | Durable run/event/tool-call/usage ledger attached via `AgentConfig.runLedger` / `RunOptions.runLedger`. |
| `SessionAppendOptions` / `SessionAppendConflictError` / `SessionBranchHandle` | `@arnilo/prism` | Atomic append, retry dedup, durable branch handles. |
| `AgentDefinition.tools` / `skills` | `@arnilo/prism` | Named, fail-closed capability activation (Phase 38). |
| `activateAllCapabilities` | `@arnilo/prism` | Temporary all-tools/all-skills compatibility opt-in while migrating. |

## Outputs / response / events


These migrations are configuration swaps: they do not add `AgentEvent` variants or change runtime event order. The observable differences are:

- reads come from a database instead of an in-memory map / JSONL file;
- branches are addressable by a storable `(sessionId, leafId)` handle;
- a run leaves durable `RunRecord` / `AgentEventRecord` / `ToolCallRecord` / `UsageRecord` rows;
- an agent with omitted `tools`/`skills` activates **no** capabilities instead of every in-scope one.

## Request/response example


Persistence migration (before/after):

```json
// Before — development SessionStore, single process, no ledger.
{
  "store": "createMemorySessionStore() | createJsonlSessionStore(path)",
  "runLedger": null,
  "ownership": null
}
```

```json
// After — host-implemented database-backed adapter + durable ledger.
{
  "store": "createDbSessionStore({ pool })",
  "runLedger": "createDbRunLedger({ pool })",
  "ownership": { "tenantId": "t1", "accountId": "a1", "userId": "u1" }
}
```

Capability migration (before/after):

```json
// Before (pre-Phase 38) — omitted tools/skills could receive every scoped capability.
{ "name": "doc", "model": "openai/gpt-4o" }

// After — explicit names; omitted means none.
{ "name": "doc", "model": "openai/gpt-4o", "tools": ["read"], "skills": ["brief"] }
```

## Implementation example


### Migration 1 — in-memory / JSONL → database-backed persistence

Runnable references: [`examples/workflow-sqlite-resume.ts`](../examples/workflow-sqlite-resume.ts), credential-gated [`examples/workflow-postgres-resume.ts`](../examples/workflow-postgres-resume.ts), and the network-free custom-adapter example [`examples/external-app-db-backed.ts`](../examples/external-app-db-backed.ts).

Step 1: replace the development store with a first-party adapter. Use PostgreSQL instead when multiple processes or sustained concurrent writers matter.

```ts
// Before: development store, single process.
import { createJsonlSessionStore } from "@arnilo/prism/node/session-store-jsonl";
const oldStore = createJsonlSessionStore("./sessions.jsonl");

// After: local durable adapter. The same object implements SessionStore,
// RunLedger, ProductionPersistenceStore, checkpoints, and leases.
import { createSqlitePersistence } from "@arnilo/prism-session-store-sqlite";
const store = createSqlitePersistence({ filename: "./prism.db" });
```

Custom adapters remain supported through `SessionStore` / `ProductionPersistenceStore`; implement indexed `readBranchPath()` rather than full-session scans.

Step 2: optionally attach a durable run/event/tool/usage ledger and ownership scope so a process exit leaves enough to resume and bill:

```ts
import { createAgent, type RunLedger } from "@arnilo/prism";

const runLedger: RunLedger = {
  // appendRun / appendEvent / appendToolCall / appendUsage — redact before storage, preserve per-run order
  async appendRun(record) { /* insert prism_runs */ },
  async appendEvent(record) { /* insert prism_agent_events with monotonic sequence per run_id */ },
  async appendToolCall(record) { /* insert prism_tool_calls */ },
  async appendUsage(record) { /* insert prism_usage */ },
};

const agent = createAgent({
  model,
  provider,
  store,
  runLedger,
  ownership: { tenantId: "t1", accountId: "a1", userId: "u1" },
});
```

Step 3: store branch handles `(sessionId, leafId)` in your app state and use checkout to move an existing session to a previous or sibling leaf. The runtime's branch helpers (`getSessionBranchEntries`, `rebuildSessionContext`) consume `readBranchPath` so large sessions never require a full `list(sessionId)` load.

What you leave behind and why:

- `createMemorySessionStore()` — process-local maps; lost on restart, no cross-process locking. Keep for tests.
- `createJsonlSessionStore()` — single-process file adapter; reads are linear in file size, no cross-process lock, no durable idempotency table, two writers to the same file can race. Keep for local/dev only.

Prism 0.0.5 persistence adapters automatically apply additive schema step `002_usage_scope`, then `003_run_feedback`. Migration 003 creates immutable owned run/trace feedback with a run FK, cascade deletion, JSON tag/scorer/evaluation ID lists, and owner/run/trace cursor indexes. Existing rows are unchanged. Custom adapters may omit optional `ProductionPersistenceStore.feedback`; adopters implement `RunFeedbackStore` append/query/delete semantics and must verify exact linked-run ownership before insert.

See [Database persistence](database-persistence.md) for the full reference schema, indexes, conditional-append transaction pattern, retention, and NoSQL mapping; [Session stores](session-stores.md) for the `SessionStore` contract and branch helpers; [Session stores and branching](session-stores-and-branching.md) for branch semantics; [Runs and usage ledger](runs-and-usage.md) for the `RunLedger` record shapes and ordering rules.

### Migration 2 — permissive capability defaults → explicit capability activation

Pre-Phase 38 behavior could treat an omitted `tools` list as "every scoped tool"; some hosts also expected all scoped skills to be available. Phase 38 changes the safe default: omitted `tools` and omitted `skills` mean no active capabilities.

```ts
import { resolveAgentDefinition } from "@arnilo/prism";

// Before: omitted tools could receive every scoped tool.
resolveAgentDefinition({ name: "doc", model: "openai/gpt-4o" }, context);

// After: list the capabilities this agent may use.
resolveAgentDefinition(
  { name: "doc", model: "openai/gpt-4o", tools: ["read"], skills: ["brief"] },
  context,
);
```

Temporary compatibility shim (use only while migrating old configs):

```ts
resolveAgentDefinition(
  { name: "legacy", model: "openai/gpt-4o" },
  { ...context, activateAllCapabilities: true },
);
```

`activateAllCapabilities: true` intentionally scans/list-activates every in-scope tool/skill. New configs should list names and use strict contribution registries so a third-party package cannot silently shadow a capability name:

```ts
import { createContributionRegistries } from "@arnilo/prism";

const registries = createContributionRegistries({ duplicate: "error" });
```

Runtime skill activation remains explicit: `RunOptions.activeSkills` narrows per run after an agent has a skill registry configured, and `Skill.toolNames` is enforced fail-closed before the first provider turn. See [Agent definitions](agent-definitions.md), [Context and skills](context-and-skills.md), and [Contribution registries](contribution-registries.md) for the full capability semantics.

## Extension and configuration notes


- **Persistence remains host-configured.** Optional SQLite/PostgreSQL packages ship adapters and versioned setup, but hosts choose connection paths/pools, TLS, credentials, retention, tenant policy, and lifecycle. Core only consumes `SessionStore`, `RunLedger`, feedback, checkpoint, and lease contracts.
- **`RunLedger` is not a `SessionStore` replacement.** Messages, branches, and session entries still flow through `SessionStore.append()`; the ledger records run/event/tool/usage facts. See [Runs and usage ledger](runs-and-usage.md).
- **Capability activation is config over code.** Every seam lives on `AgentDefinition` / `AgentDefinitionResolutionContext` / `RunOptions`; no auto-activation, no privilege grant. A declaration cannot grant permissions or bypass `toolNames`.
- **Migration order is decoupled.** You can adopt database persistence without changing capability activation, and vice versa. Both migrations are independent config swaps.
- **Strict duplicate mode for new registries.** `createContributionRegistries({ duplicate: "error" })` makes a third-party package fail loud instead of silently shadowing a capability name during migration.

## Security and performance notes


- **Never store provider credentials or secrets in the persistence contract.** `ProductionPersistenceStore`, `RunLedger`, `AgentEventRecord`, `ToolCallRecord`, `UsageRecord`, and `AgentDefinitionRecord` never require API keys, resolvers, or provider instances. Redact `SessionEntry` / event / tool-call / usage payloads before storage; the runtime redacts `AgentEvent`s via `redactAgentEvent` and ledger records via `redactRunLedgerRecord` before calling the adapter.
- **JSONL is a development-only adapter.** No cross-process lock, no durable idempotency table, no tenant isolation, no retention enforcement, no migrations. Do not use it as a production multi-writer store.
- **Avoid full-session scans in production.** Implement `readBranchPath(query)` with a recursive CTE / ancestor query and cursor-paginate `query*` from indexed columns. `list(sessionId)` + in-memory parent walk is the development fallback only.
- **`activateAllCapabilities` widens blast radius.** It activates every in-scope tool/skill, so prefer named lists. Strict duplicate mode catches capability-name collisions early.
- **`toolNames` enforcement is fail-closed.** A skill demanding an inactive tool throws at activation, before any provider turn — for both the old and new migration paths.

## Related APIs


- [Evaluations](evaluations.md): optional `@arnilo/prism-evals` scorers/datasets/experiments over `AgentRunResult`.
- [AI SDK provider adapter](providers/ai-sdk.md): optional `@arnilo/prism-provider-ai-sdk` `LanguageModelV4` bridge.
- [Working and semantic memory](working-and-semantic-memory.md): optional `@arnilo/prism-memory` working/semantic recall primitives.
- [Retrieval-augmented generation](rag.md): optional text/Markdown chunk, index, retrieval, and citation helpers.
- [Web-standard server handler](server.md): optional authorized agent/workflow HTTP routes.
- [Supervisor delegation](supervisors.md) and [A2A interoperability](a2a.md): optional install only; core agent/workflow behavior is unchanged. Child factories now receive package-derived memory IDs and narrowing permission, while remote endpoints require exact HTTPS origin allow-lists.
- [MCP client/server exposure](mcp-tools.md): selected MCP tools/commands and bounded Web transport.
- [Database persistence](database-persistence.md): production contracts, reference schema, indexes, conditional append, retention, migrations, and custom adapters.
- [SQLite persistence](sqlite-persistence.md): local durable first-party adapter and writer ceiling.
- [PostgreSQL persistence](postgres-persistence.md): pooled multi-process adapter, TLS/pool ownership, and live gate.
- [Session stores](session-stores.md): `SessionStore` contract, `SessionAppendOptions`, `SessionAppendConflictError`, branch handles, `readBranchPath`.
- [Session stores and branching](session-stores-and-branching.md): detailed branch semantics and helper reference.
- [Runs and usage ledger](runs-and-usage.md): `RunLedger` record shapes, redaction, and event/usage ordering.
- [Node JSONL session store](node-jsonl-session-store.md): development-only JSONL adapter and its limits.
- [Agent definitions](agent-definitions.md): declarative `AgentDefinition`, `resolveAgentDefinition`, and the explicit-capability-activation migration.
- [Context and skills](context-and-skills.md): `RunOptions.activeSkills`, `Skill.context`, `toolNames` enforcement.
- [Contribution registries](contribution-registries.md): strict `duplicate: "error"` mode for capability shadowing prevention.
- [Release and install](release-and-install.md): packaged surfaces and the offline test budget that gate these migrations.

Historical release migrations live in [history/](history/README.md).
