@arnilo/prism 0.0.22 → 0.0.24

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +36 -2
  2. package/dist/agent-event-source.d.ts +11 -0
  3. package/dist/agent-event-source.js +512 -0
  4. package/dist/agent-run-state.js +6 -1
  5. package/dist/agents.js +7 -2
  6. package/dist/contracts.d.ts +157 -0
  7. package/dist/index.d.ts +7 -2
  8. package/dist/index.js +4 -1
  9. package/dist/testing/agent-event-source-conformance.d.ts +4 -0
  10. package/dist/testing/agent-event-source-conformance.js +54 -0
  11. package/dist/testing/persistence-schema.d.ts +2 -2
  12. package/dist/testing/persistence-schema.js +58 -21
  13. package/dist/testing/tool-effect-store-conformance.d.ts +9 -0
  14. package/dist/testing/tool-effect-store-conformance.js +85 -0
  15. package/dist/tool-effects.d.ts +15 -0
  16. package/dist/tool-effects.js +338 -0
  17. package/dist/tools.d.ts +3 -1
  18. package/dist/tools.js +204 -9
  19. package/docs/0.1.0-readiness.md +10 -9
  20. package/docs/a2a.md +6 -2
  21. package/docs/ag-ui-adoption.md +77 -0
  22. package/docs/ag-ui.md +39 -42
  23. package/docs/agent-events.md +5 -1
  24. package/docs/browser-automation.md +2 -0
  25. package/docs/coding-agent-tools.md +2 -0
  26. package/docs/database-persistence.md +5 -0
  27. package/docs/enterprise-postgres-state.md +177 -0
  28. package/docs/evaluations.md +13 -0
  29. package/docs/host-security.md +11 -1
  30. package/docs/index.md +17 -14
  31. package/docs/mcp-tools.md +17 -2
  32. package/docs/migration.md +41 -0
  33. package/docs/model-routing.md +17 -8
  34. package/docs/performance.md +38 -0
  35. package/docs/policy-and-audit.md +12 -0
  36. package/docs/postgres-persistence.md +7 -3
  37. package/docs/public-contracts.md +2 -0
  38. package/docs/release-and-install.md +67 -680
  39. package/docs/server.md +9 -6
  40. package/docs/sqlite-persistence.md +10 -2
  41. package/docs/supervisors.md +2 -0
  42. package/docs/tool-effects.md +95 -0
  43. package/docs/tools.md +4 -0
  44. package/docs/work-tools.md +21 -1
  45. package/package.json +11 -2
package/docs/a2a.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## What it does
4
4
 
5
- `@arnilo/prism-supervisor` implements bounded A2A 1.0 over the JSON-RPC/HTTPS binding. Supported operations: `SendMessage`, `SendStreamingMessage`, `GetTask`, `ListTasks`, `CancelTask`, `SubscribeToTask`, push-notification-config create/get/list/delete, and `GetExtendedAgentCard`. Agent Cards retain explicit ES256 verification. gRPC, HTTP+JSON, discovery registries, automatic JWK/OAuth fetching, and an internal task worker/store are absent.
5
+ `@arnilo/prism-supervisor` implements bounded A2A 1.0 over the JSON-RPC/HTTPS binding. Supported operations: `SendMessage`, `SendStreamingMessage`, `GetTask`, `ListTasks`, `CancelTask`, `SubscribeToTask`, push-notification-config create/get/list/delete, and `GetExtendedAgentCard`. `client.streamMessage()` additionally exposes verified rich task/message events for frontend adapters while legacy `stream()` remains text-compatible. Agent Cards retain explicit ES256 verification. gRPC, HTTP+JSON, discovery registries, automatic JWK/OAuth fetching, and an internal task worker/store are absent.
6
6
 
7
7
  ## When to use it
8
8
 
@@ -59,12 +59,15 @@ Streams use ordered SSE frames with `id:` and JSON-RPC `result` containing one `
59
59
  Client APIs:
60
60
 
61
61
  - `send()` / `stream()` preserve text-to-`AgentRunResult` compatibility.
62
+ - `streamMessage(message)` exposes bounded verified `A2AStreamEvent` task/message records without discarding artifact/data parts.
62
63
  - `sendMessage()` returns rich/durable `A2ATask`.
63
64
  - `getTask()`, `listTasks()`, `cancelTask()`, `subscribeToTask()` operate on durable tasks.
64
65
  - `createPushConfig()`, `getPushConfig()`, `listPushConfigs()`, `deletePushConfig()` expose declared push config operations.
65
66
 
66
67
  Every protocol request sends/negotiates `A2A-Version: 1.0`. Client endpoint/card URLs require exact allow-listed HTTPS and `redirect: "error"`. Cards are parsed then optionally verified against host-pinned keys; no key URL is fetched.
67
68
 
69
+ `createA2AAgentEventSource({ source, resolveTask, map })` supplies only the durable `subscribe` seam for a host-owned `A2ATaskLifecycle`. It resolves task→exact Prism run under authorization, consumes `AgentEventSource.subscribe()`, and uses each opaque source cursor as stable A2A `eventId`. With no cursor, the first mapped update must be a full Task, matching A2A streaming rules. It creates no task store or worker. Standard `SubscribeToTask` has no `afterEventId`; Prism retains that bounded field as an explicitly documented reconnect extension.
70
+
68
71
  ## Request/response example
69
72
 
70
73
  ```json
@@ -73,7 +76,7 @@ Every protocol request sends/negotiates `A2A-Version: 1.0`. Client endpoint/card
73
76
 
74
77
  ## Extension and configuration notes
75
78
 
76
- Handler requires `card.capabilities.pushNotifications` to exactly match supplied `push`; mismatch fails construction, preserving signed-card integrity and preventing false capability claims. Streaming remains available for direct text invocation. Push adapter owns exact-owner persistence, signing/auth credentials, and network transport. Host explicitly calls `deliverA2APushEvent()` from its durable update path; helper bounds event, timeout (10s default/60s hard), attempts (1 default/3 hard), and passes stable event ID as idempotency key to host `A2APushDelivery`. It starts no hidden sender and performs no network itself. Config handling validates IDs/count/bytes and requires same explicit URL policy used for URL parts. Returned push configs omit token and authentication credentials.
79
+ Handler requires `card.capabilities.pushNotifications` to exactly match supplied `push`; mismatch fails construction, preserving signed-card integrity and preventing false capability claims. `createAgUiA2AAdapter({ client, select, correlate, projectPart })` in `@arnilo/prism-ag-ui` fronts one host-selected verified client: host selects new/follow task mode, persists exact run/thread/task correlation before output, and may project non-text/tool/A2UI parts. It never discovers agents, opens a local session, or replaces this direct A2A API. Streaming remains available for direct text invocation. Push adapter owns exact-owner persistence, signing/auth credentials, and network transport. Host explicitly calls `deliverA2APushEvent()` from its durable update path; helper bounds event, timeout (10s default/60s hard), attempts (1 default/3 hard), and passes stable event ID as idempotency key to host `A2APushDelivery`. It starts no hidden sender and performs no network itself. Config handling validates IDs/count/bytes and requires same explicit URL policy used for URL parts. Returned push configs omit token and authentication credentials.
77
80
 
78
81
  Defaults/hard caps include: request 64 KiB/1 MiB; response 1/8 MiB; event 64 KiB/1 MiB; stream 10/64 MiB and 10k/100k events; replay 1k/10k events; concurrency 16/256; timeout 120s/30m; IDs 256/4096 B; parts 32/256; part/raw 1/8 MiB; data 256 KiB/4 MiB; artifacts 32/256; history/page 100/1000; cursor 4/16 KiB; push configs 10/100. Hosts may narrow limits.
79
82
 
@@ -95,3 +98,4 @@ Defaults/hard caps include: request 64 KiB/1 MiB; response 1/8 MiB; event 64 KiB
95
98
  - [Workflows](workflows.md)
96
99
  - [Host security](host-security.md)
97
100
  - [Frontend interoperability (AG-UI and ACP)](ag-ui.md): browser/editor protocol adapters over a Prism session; not an A2A card, task lifecycle, or remote-agent transport.
101
+ - [AG-UI adoption evaluation](ag-ui-adoption.md): official AG-UI A2A fronting assessment and shipped explicit adapter.
@@ -0,0 +1,77 @@
1
+ # AG-UI adoption evaluation
2
+
3
+ ## What it does
4
+
5
+ This page records Prism's compatibility review against official AG-UI `@ag-ui/core` **0.0.57** and the official repository at commit [`a40b5c0`](https://github.com/ag-ui-protocol/ag-ui/commit/a40b5c0824564eb2f9ab9edf2be43f355f42a3b8). It separates shipped transport/replay support from remaining work needed to claim full AG-UI support, including AG-UI fronting MCP and A2A agents.
6
+
7
+ Official material reviewed:
8
+
9
+ - [Events](https://docs.ag-ui.com/concepts/events), [messages](https://docs.ag-ui.com/concepts/messages), [tools](https://docs.ag-ui.com/concepts/tools), [state](https://docs.ag-ui.com/concepts/state), [reasoning](https://docs.ag-ui.com/concepts/reasoning), [interrupts](https://docs.ag-ui.com/concepts/interrupts), [capabilities](https://docs.ag-ui.com/concepts/capabilities), [serialization](https://docs.ag-ui.com/concepts/serialization), [server quickstart](https://docs.ag-ui.com/quickstart/server), and [protocol architecture](https://docs.ag-ui.com/concepts/architecture).
10
+ - Official [MCP/A2A/AG-UI relationship](https://docs.ag-ui.com/agentic-protocols), [integrations](https://docs.ag-ui.com/integrations), [`@ag-ui/mcp-middleware`](https://github.com/ag-ui-protocol/ag-ui/tree/main/middlewares/mcp-middleware), [`@ag-ui/mcp-apps-middleware`](https://github.com/ag-ui-protocol/ag-ui/tree/main/middlewares/mcp-apps-middleware), [`@ag-ui/a2a`](https://github.com/ag-ui-protocol/ag-ui/tree/main/integrations/a2a/typescript), and [`@ag-ui/a2a-middleware`](https://github.com/ag-ui-protocol/ag-ui/tree/main/middlewares/a2a-middleware).
11
+ - A2A [current specification](https://a2a-protocol.org/latest/specification/) and [streaming rules](https://a2a-protocol.org/latest/topics/streaming-and-async/).
12
+ - MCP Apps [SEP-1865](https://modelcontextprotocol.io/seps/1865-mcp-apps-interactive-user-interfaces-for-mcp) and the [`io.modelcontextprotocol/ui` draft specification](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx).
13
+
14
+ ## When to use it
15
+
16
+ Use this matrix when selecting Prism for an AG-UI client or planning protocol work. Tasks 3A and 3B complete full AG-UI 0.0.57 request/event compatibility plus explicit hardened MCP, MCP Apps, and remote A2A fronting. These are opt-in adapters over existing Prism clients, not an alternate runtime or discovery path.
17
+
18
+ ## Inputs / request
19
+
20
+ Official `RunAgentInput` fields—lineage, all message roles/history, state, tools, context, props, media, and resume—are schema/bound checked then have no authority until `input.project` returns host-selected messages. Client tools remain client handoffs; state/props/media never grant identity, ownership, or server tools. Resume is exact `${runId}:${version}` CAS; edited arguments deny.
21
+
22
+ ## Outputs / response / events
23
+
24
+ Prism emits current standard lifecycle, step, text, tool, state, messages, activity, reasoning, raw, and custom families when its mapper or an explicit projector can prove them. Deprecated `THINKING_*` and convenience chunk output are intentionally absent. SSE is baseline; capabilities truthfully narrow to configured replay/projectors/lifecycle, and source cursors remain bounded `prismCursor` metadata for reconnect.
25
+
26
+ ## Request/response example
27
+
28
+ ```json
29
+ {
30
+ "type": "TEXT_MESSAGE_CONTENT",
31
+ "messageId": "message-1",
32
+ "delta": "hello",
33
+ "prismEventId": "event-42",
34
+ "prismCursor": "opaque-owner-run-bound-cursor"
35
+ }
36
+ ```
37
+
38
+ ## Implementation example
39
+
40
+ ```ts
41
+ import { createAgentEventSourceAgUiReplay, createAgUiHandler } from "@arnilo/prism-ag-ui";
42
+
43
+ const replay = createAgentEventSourceAgUiReplay(persistence.events, {
44
+ resolveRun: hostResolveProtocolRun,
45
+ ownership: (authorization) => authorization.ownership,
46
+ });
47
+
48
+ const handle = createAgUiHandler({ authorize, sessionFactory, lifecycle, resolveRun, replay });
49
+ ```
50
+
51
+ ## Extension and configuration notes
52
+
53
+ ### A2A adoption
54
+
55
+ A2A stays separately mounted. `createA2AAgentEventSource()` maps durable runs to task events; `afterEventId` remains Prism-only.
56
+
57
+ `createAgUiA2AAdapter({ client, select, correlate, projectPart })` fronts one verified host-selected client: task text/status becomes AG-UI text/activity, correlation persists before output, and non-text/tool/A2UI needs a schema-validated projector. It uses streaming when declared; fallback accepts only a terminal task. Client origin/card/auth/bounds/abort checks remain active.
58
+
59
+ ### MCP adoption through AG-UI
60
+
61
+ Prism adapts its hardened MCP bridge; no official middleware or second runtime. `connectMcpTools({ mcpApps: true })` requires `io.modelcontextprotocol/ui` acknowledgement, retains nested UI metadata over deprecated flat metadata, hides app-only tools, and bounds linked `ui://` HTML through `bridge.apps`. `createAgUiMcpAdapter()` selects model-visible tools for normal core dispatch; `createAgUiMcpAppHandler()` reauthorizes one-bridge initialize/ping/logging/tool/resource calls with approval and visibility; sandbox helper returns fixed iframe/CSP constraints. No generic proxy, cross-server call, raw HTML rendering, or automatic mutation retry.
62
+
63
+ ## Security and performance notes
64
+
65
+ - Every replay/reconnect reauthorizes, then source access uses exact host ownership and host-resolved internal run IDs. Cursor content never selects ownership.
66
+ - Durable streams are at-least-once. Clients deduplicate `prismEventId`; source cursors resume strictly after a durable record.
67
+ - Client tools, state, context, forwarded properties, media URLs/data, remote A2A parts, MCP metadata, HTML, iframe messages, and reasoning blobs are untrusted. Projectors must use existing Prism media URL/SSRF/MIME policy before any resolution.
68
+ - `input.project` and all output projectors are allow-lists; all generic JSON has byte/depth/property/array caps and prototype-pollution keys fail before host callbacks. Tool handoffs are client-only and cannot widen identity, ownership, permissions, or active server tools.
69
+ - MCP Apps requires sandbox-origin separation, restrictive CSP, declared-domain ceilings, audited JSON-RPC, app/tool visibility checks, and user approval for UI-initiated mutations. The shipped proxy does not retry those mutations; Task 4 adds generic durable effect recovery.
70
+
71
+ ## Related APIs
72
+
73
+ - [Frontend interoperability](ag-ui.md): shipped Prism AG-UI/ACP API.
74
+ - [Agent events](agent-events.md): source events and durable delivery.
75
+ - [Web-standard server](server.md): SSE `Last-Event-ID` reconnect route.
76
+ - [A2A interoperability](a2a.md): separately mounted A2A lifecycle and durable source adapter.
77
+ - [MCP bridge/server](mcp-tools.md): hardened MCP transport and capability boundary reused by future AG-UI MCP support.
package/docs/ag-ui.md CHANGED
@@ -4,12 +4,10 @@
4
4
 
5
5
  `@arnilo/prism-ag-ui` is an optional, framework-free protocol adapter over Prism's existing redacted `AgentEvent`, session, durable-run, and persistence seams.
6
6
 
7
- - Root export maps Prism events to AG-UI `@ag-ui/core` **0.0.57** events and offers `createAgUiHandler()` (`Request` → SSE `Response`) plus `createPersistenceAgUiReplay()`.
7
+ - Root export maps Prism events to AG-UI `@ag-ui/core` **0.0.57** events and offers `createAgUiHandler()` (`Request` → SSE `Response`), compatible `createPersistenceAgUiReplay()` pages, distributed `createAgentEventSourceAgUiReplay()` follow, and explicit `createAgUiMcpAdapter()` / `createAgUiMcpAppHandler()` / `createAgUiA2AAdapter()` protocol handshakes.
8
8
  - `@arnilo/prism-ag-ui/acp` uses stable `@agentclientprotocol/sdk` **1.3.0** root exports for `createAcpEventMapper()` and `createPrismAcpAgent()`.
9
9
  - Core remains protocol-free. `resumeAgentRunStream()` / `AgentRunLifecycle.resumeStream()` are generic durable-resume streams shared by adapters.
10
10
 
11
- This is not an app TUI, desktop shell, conversation database, terminal/filesystem bridge, A2A implementation, or frontend tool registry.
12
-
13
11
  ## When to use it
14
12
 
15
13
  Use AG-UI when a host already authenticates users, owns sessions and durable run correlation, and needs a bounded Web endpoint for a browser/TUI/desktop client. Use ACP when an editor client already supplies an ACP transport and needs text, safe tool status, usage, and approval updates from a Prism session.
@@ -29,70 +27,68 @@ npm install @arnilo/prism @arnilo/prism-ag-ui
29
27
  | Input | Purpose |
30
28
  | --- | --- |
31
29
  | `authorize` | Rebinds untrusted AG-UI thread/run selectors to host ownership on every request. `false` returns 403. |
32
- | `sessionFactory` | Returns an authorized Prism `AgentSession`; client input never selects tools or capabilities. |
30
+ | `sessionFactory` | Returns an authorized Prism `AgentSession`; it receives only host-approved `AgUiPreparedInput`, never raw client tools/state. |
31
+ | `input.project` | Opts into full `RunAgentInput`; turns bounded, still-untrusted history, state, context, forwarded props, media, and lineage into host-selected Prism `Message` values. Omit for legacy final-text mode. |
32
+ | `input.frontendTools` | Explicitly selects client-side handoffs. Returned names must be request-tool subset; adapter never turns JSON tool declarations into Prism `ToolDefinition`s. |
33
+ | `mcp` | Optional `createAgUiMcpAdapter({ bridge, select })`; host selects reviewed `bridge.tools`, then `sessionFactory` receives them as `input.serverTools`. Normal Prism dispatch/loop remains sole executor. |
34
+ | `a2a` | Optional `createAgUiA2AAdapter({ client, select, correlate })`; verified remote A2A task stream replaces this handler's local session only. Host selection/correlation binds each remote task to ownership. |
33
35
  | `lifecycle` + `resolveRun` | Optional durable status/resume path. Required only for a resumed interruption. |
34
- | `replay` | Optional `createPersistenceAgUiReplay(store, options)` adapter for ownership-scoped durable pages. |
35
- | `projection` | Explicit safe tool args/results, paths, or state projection. Omit it for default deny. |
36
+ | `interrupts.resume` | Optional host aggregate-policy callback for multiple AG-UI interrupts; it returns one current-version core approve/deny decision. |
37
+ | `replay` | Optional page adapter or `createAgentEventSourceAgUiReplay(source, options)` for gap-free distributed replay/live follow. |
38
+ | `projection` | Explicit safe tool/state/messages/activity/reasoning/raw/custom/interrupt projection. Omit each callback for default deny. |
39
+ | `capabilities` | Optional host declaration narrowed to implemented SSE/projector/lifecycle features; read `handler.capabilities`. |
36
40
  | `redactor`, `limits` | Host redaction and narrowing-only finite caps. |
37
41
 
38
- The handler accepts only `POST` JSON validated with AG-UI `RunAgentInputSchema`. IDs are bounded URL-safe values; it uses only the last text user message. Frontend tools and non-empty frontend state are rejected before authorization or session lookup. Start a run with no `resume` and no `?cursor=`; resume has exactly one entry; replay supplies `?cursor=`.
42
+ The handler accepts only `POST` JSON validated with official AG-UI `RunAgentInputSchema`. Every aggregate is bounded before a callback runs. With no `input.project`, it preserves compatibility: final text user message only; non-empty state or frontend tools fail before authorization/session lookup. With a projector, all current roles/history, context, state, forwarded props, multimodal parts, parent lineage, and tool-result continuations are available as untrusted input. The projector must apply Prism media URL/SSRF/MIME policy before forwarding media. Start a run with no `resume` and no `?cursor=`; replay supplies `?cursor=`.
39
43
 
40
44
  ## Outputs / response / events
41
45
 
42
- The handler returns `text/event-stream`, one `data: <AG-UI event>\n\n` frame per output. Mapper lifecycle is ordered: Prism `agent_started`/assistant text/tool events map to `RUN_STARTED`, `TEXT_MESSAGE_*`, and `TOOL_CALL_*`; terminal success maps to `RUN_FINISHED`; runtime errors map to `RUN_ERROR`. Active AG-UI message/tool sequences close before an error, interruption, or finish.
46
+ The handler returns `text/event-stream`, one `data: <AG-UI event>\n\n` frame per output. Mapper lifecycle is ordered: `RUN_*`, `STEP_*`, `TEXT_MESSAGE_*`, and `TOOL_CALL_*` are deterministic Prism mappings. Host projectors may additionally prove and emit `STATE_SNAPSHOT`/`STATE_DELTA`, `MESSAGES_SNAPSHOT`, `ACTIVITY_*`, current `REASONING_*`, `RAW`, and named `CUSTOM` values. All values revalidate against official `EventSchemas`; deprecated `THINKING_*` and convenience chunk events are not produced. Active message/tool/reasoning/step sequences close before error, interruption, or finish.
43
47
 
44
- A Prism durable `agent_suspended` returns `RUN_FINISHED` with interrupt id `${runId}:${version}` and a strict `{ decision: "approve" | "deny" }` schema. A client must address that exact current id. `cancelled` means deny; a resolved resume payload must contain only that decision. The adapter checks host authorization, selected run, suspended status, and checkpoint version, then calls `AgentRunLifecycle.resumeStream()` once. Claimed/dispatched tools are never replayed.
48
+ A Prism durable `agent_suspended` returns `RUN_FINISHED` with core interrupt id `${runId}:${version}` and a strict `{ decision: "approve" | "deny" }` schema. `projection.interrupt` may attach bounded expiry/metadata or additional host policy interrupts but must retain that core id. Without `interrupts.resume`, one exact entry is required; `cancelled` means deny. An aggregate policy may validate bounded multiple entries, then returns one current-version core decision. Payloads containing `editedArgs`/`args` always deny: Prism does not mutate persisted tool calls. The adapter checks host authorization, selected run, suspended status, and checkpoint version, then calls `AgentRunLifecycle.resumeStream()` once. Claimed/dispatched tools are never replayed.
45
49
 
46
- `createPersistenceAgUiReplay()` queries only the host-resolved run with ownership and ascending bounded pagination. Every record must already be redacted. Events carry `prismEventId` for at-least-once page-boundary de-duplication; a nonterminal final page may attach a filtered live subscriber. Terminal pages never create a session or rerun a provider/tool.
50
+ `createPersistenceAgUiReplay()` remains a compatible page adapter. `createAgentEventSourceAgUiReplay()` resolves exact ownership/run once per open, then consumes the shared durable source through terminal or live follow; it never attaches replica-local `session.subscribe()`. Every record must already be redacted. Mapped events carry stable `prismEventId` and bounded opaque `prismCursor`; records with no standard mapping emit `CUSTOM prism.replay_cursor`, so clients can persist progress. Terminal replay never creates a session or reruns a provider/tool.
47
51
 
48
52
  ACP maps assistant text to `agent_message_chunk`, safe tool lifecycle to `tool_call`/`tool_call_update`, provider usage to `usage_update`, and durable suspension to `session/request_permission`. Only `allow_once` approves; reject, cancellation, unknown outcomes, and request failure deny. It advertises only close-session capability—no terminal, filesystem, MCP, editor state, location, diff, or raw input/output capability.
49
53
 
50
- Co-work review (0.0.14) projects durable collaboration state over the same stream as named `CUSTOM` events, one per kind: `prism.cowork.artifact.progress`, `prism.cowork.artifact.approval.requested`, `prism.cowork.draft.connector.pending`, `prism.cowork.browser.snapshot`, and `prism.cowork.artifact.download.link`. `AgUiEventMapper.mapCoWork()` (and ACP `mapCoWork()` parity) validate, host-project (`AgUiProjection.coWork`), redact, and byte-cap each event; malformed or oversized events fail closed to nothing rather than leak. The handler accepts optional `coWorkContext` (thread/artifact/identity) and a durable `coWork` source (`createCoWorkReplay()`); one bounded, redacted page is appended after the run stream. Because projection is a pure read + map, disconnect/resume from a cursor replays co-work state without duplicate side effects. Download-link events carry an authorized, expiring token only — hosts fetch the body and never receive local paths, raw credentials, injected browser secrets, or tool-argument dumps.
54
+ Selected MCP tools use normal `TOOL_CALL_*` core dispatch. Linked Apps add safe `mcp-apps` activity; app-only tools stay model-hidden. The separate reauthorizing Apps proxy allow-lists initialize/ping/logging/tool/resource calls for one bridge; its sandbox helper returns CSP/iframe config and never executes HTML.
51
55
 
52
- ## Request/response example
56
+ `createAgUiA2AAdapter()` maps verified task text/activity. Non-text/tool/A2UI parts need host `projectPart`; non-streaming fallback accepts only a terminal task, otherwise host follows saved correlation.
53
57
 
54
- ```json
55
- {
56
- "threadId": "thread-1",
57
- "runId": "run-1",
58
- "messages": [{ "id": "message-1", "role": "user", "content": "Summarize this" }],
59
- "tools": [],
60
- "state": {}
61
- }
62
- ```
58
+ Co-work uses bounded, redacted `CUSTOM prism.cowork.*` events through `mapCoWork()` / `createCoWorkReplay()`; see [Work artifacts and review](work-artifacts-and-review.md).
63
59
 
64
- A suspended response includes this resumable interrupt shape:
65
-
66
- ```json
67
- {
68
- "type": "RUN_FINISHED",
69
- "threadId": "thread-1",
70
- "runId": "run-1",
71
- "outcome": {
72
- "type": "interrupt",
73
- "interrupts": [{ "id": "run-1:4", "responseSchema": { "required": ["decision"] } }]
74
- }
75
- }
76
- ```
60
+ ## Request/response example
77
61
 
78
- Resume the same host thread/run with `resume: [{ "interruptId": "run-1:4", "status": "resolved", "payload": { "decision": "approve" } }]`. Do not send a copied session transcript, tool definitions, or mutable application state.
62
+ Resume a default single interrupt with `resume: [{ "interruptId": "run-1:4", "status": "resolved", "payload": { "decision": "approve" } }]`. Full history, client tool results, and mutable state need authorized `input.project` selection. This adapter is not a conversation database.
79
63
 
80
64
  ## Implementation example
81
65
 
82
66
  ```ts
83
67
  import { createAgent, createMockProvider, providerDone, providerTextDelta } from "@arnilo/prism";
84
- import { createAgUiHandler } from "@arnilo/prism-ag-ui";
68
+ import { createAgentEventSourceAgUiReplay, createAgUiHandler } from "@arnilo/prism-ag-ui";
85
69
 
86
70
  const agent = createAgent({
87
71
  model: { provider: "mock", model: "offline" },
88
72
  provider: createMockProvider([providerTextDelta("ready"), providerDone()]),
89
73
  });
90
74
 
75
+ const replay = createAgentEventSourceAgUiReplay(persistence.events, {
76
+ resolveRun,
77
+ ownership: (authorization) => authorization.ownership,
78
+ });
79
+
91
80
  const handle = createAgUiHandler({
92
81
  authorize: ({ request }) => request.headers.get("authorization") === "Bearer host-checked"
93
82
  ? { ownership: { userId: "user-1" } }
94
83
  : false,
84
+ // Create a run-scoped agent/session using input.serverTools when mcp is enabled.
95
85
  sessionFactory: () => agent.createSession({ id: "host-owned-thread" }),
86
+ replay,
87
+ input: {
88
+ project: () => ({
89
+ messages: [{ id: "user", role: "user", content: [{ type: "text", text: "host-selected input" }] }],
90
+ }),
91
+ },
96
92
  projection: { toolArguments: () => undefined, toolResult: () => undefined },
97
93
  });
98
94
 
@@ -103,19 +99,17 @@ See runnable network-free [`examples/ag-ui-server.ts`](../examples/ag-ui-server.
103
99
 
104
100
  ## Extension and configuration notes
105
101
 
106
- All identity, authorization, session/thread mapping, durable checkpoint lookup, persistence selection, replay cursor persistence, transport adaptation, and optional projection are host-owned. The adapter owns no listener, database, background reconnect loop, credential resolver, or UI state.
102
+ All identity, authorization, session/thread mapping, durable checkpoint lookup, persistence selection, replay cursor persistence, transport adaptation, MCP bridge/card configuration, app sandbox DOM, remote A2A task correlation, and optional projection are host-owned. The adapter owns no listener, database, background reconnect loop, credential resolver, or UI state.
107
103
 
108
- `AgUiProjection` is an allow-list. Without a callback, raw tool arguments/results/progress, paths, arbitrary state, raw Prism events, ACP locations/diffs/terminals/raw I/O, and frontend-supplied tools remain absent. Use a projector that returns a redacted display value, not a host filesystem path or tool payload.
104
+ `AgUiProjection` is an allow-list. Without a callback, raw tool arguments/results/progress, arbitrary state/patches/transcripts/activity/reasoning/raw events, paths, ACP locations/diffs/terminals/raw I/O, and frontend-supplied tools remain absent. Reasoning signatures do not become AG-UI encrypted values automatically: a host must explicitly provide an already client-encrypted opaque value. `input.project` is also an allow-list: do not merge client state/forwarded props into ownership, identity, tools, permissions, provider options, or media fetch policy.
109
105
 
110
106
  Co-work projection reuses the same allow-list: `AgUiProjection.coWork(event)` may return a curated, JSON-serializable payload for a co-work event; absent it, the redacted event fields are exposed. Wire `coWorkContext` to derive thread/artifact/identity from the authorized request (never client JSON) and `coWork` to a `createCoWorkReplay()` over your durable artifact/draft/snapshot stores. The handler projects one bounded page after the run; mount a dedicated cursor-paged co-work endpoint when full pagination is needed.
111
107
 
112
108
  ## Security and performance notes
113
109
 
114
- Authorize every start, replay, resume, ACP new/prompt/cancel/close request. Treat thread IDs, run IDs, cursors, client messages, resume payloads, and protocol output as untrusted. Persist run ↔ protocol correlation before exposing an interrupt. Keep `SecretRedactor` active for streaming and ledger writes.
115
-
116
- Defaults / hard caps: request 64 KiB / 1 MiB; input 128 / 1024 messages and 64 KiB / 1 MiB text; projected event 64 KiB / 1 MiB; error 8 KiB / 64 KiB; cursor 4 / 16 KiB; replay page 100 / 500 records; queue 128 / 4096 events; stream 10,000 / 100,000 events and 10 / 64 MiB; wall time 120 seconds / 30 minutes. Overflow yields a bounded error/closed stream, not an unbounded queue. Reconnect is at-least-once, so clients de-duplicate stable event/message/tool IDs.
110
+ Authorize every start/replay/resume/proxy/follow. Treat protocol fields, MCP metadata/HTML, and A2A cards/parts as untrusted; persist run/task correlation before output and redact streams. MCP Apps requires extension acknowledgement, exact proxy origin, same-bridge visibility, approval, `ui://` HTML/MIME bounds, and sandbox CSP. It never retries UI mutations; Task 4 adds recovery.
117
111
 
118
- Benchmark command/result placeholder: Task 8 adds `node scripts/benchmark-0.0.12.mjs` for mapper throughput, replay/handler latency, queue/heap, bytes, and coding-compaction preparation. No 0.0.12 timing result is claimed before that gate.
112
+ Defaults / hard caps: request 64 KiB / 1 MiB; input 128 / 1024 messages, 32 / 256 tools/contexts, 8 / 64 interrupts, 16 / 64 media parts, and 64 KiB / 1 MiB text/state/media; frontend tool/context payloads 16 KiB / 256 KiB; projected event/state/activity/reasoning/raw values 64 KiB / 1 MiB; patches 128 / 4096 operations; JSON depth 16 / 64, properties 128 / 4096, arrays 512 / 8192; cursor 4 / 16 KiB; replay page 100 / 500 records; queue 128 / 4096 events; stream 10,000 / 100,000 events and 10 / 64 MiB; wall time 120 seconds / 30 minutes. Overflow yields a bounded error/closed stream, not an unbounded queue. SSE is declared; WebSocket/protobuf/push are not. Reconnect is at-least-once, so clients de-duplicate stable event/message/tool IDs.
119
113
 
120
114
  ## Related APIs
121
115
 
@@ -124,5 +118,8 @@ Benchmark command/result placeholder: Task 8 adds `node scripts/benchmark-0.0.12
124
118
  - [Runs and usage ledger](runs-and-usage.md): durable `AgentEventRecord` query source.
125
119
  - [Web-standard server handler](server.md): generic Prism HTTP API, separate from AG-UI.
126
120
  - [A2A interoperability](a2a.md): remote agent-to-agent tasks, not frontend protocol mapping.
121
+ - [AG-UI adoption evaluation](ag-ui-adoption.md): official 0.0.57 event/input matrix and shipped explicit MCP/MCP Apps/A2A handshakes.
122
+ - [MCP bridge/server](mcp-tools.md): `mcpApps` negotiation, bounded resources, and remote tool trust.
123
+ - [A2A interoperability](a2a.md): verified rich task client and remote task lifecycle.
127
124
  - [Host security guide](host-security.md): authorization, ownership, redaction, and credential boundaries.
128
125
  - [Work artifacts and review](work-artifacts-and-review.md): durable artifact service that produces the co-work approval/progress/download-link events projected here.
@@ -10,7 +10,7 @@ Events are emitted by the runtime and by loops through `LoopContext.emit`, both
10
10
 
11
11
  Subscribe via `session.stream()` for a single owned run, or `session.subscribe()` when a host needs a long-lived observer across runs: render streamed assistant text in a UI, react to tool execution, drive observability/telemetry, or audit artifact validation outcomes. Do not parse provider stream events directly for these — `AgentEvent` is the stable, normalized surface across providers and loops.
12
12
 
13
- Do not use `AgentEvent` for durable replay (use a `SessionStore`) or for cross-session coordination (the broadcaster is per-session and live-only).
13
+ Do not use live `session.subscribe()` for cross-replica reconnect — use durable `AgentEventSource` below. Live subscribe remains process-local.
14
14
 
15
15
  ## Durable event ledger
16
16
 
@@ -18,6 +18,10 @@ When `AgentConfig.runLedger` or `RunOptions.runLedger` is configured, every emit
18
18
 
19
19
  Event records preserve emission order within a run because the runtime drains pending event appends before writing the final `RunRecord`. Subscribers still see the live, in-memory stream; the ledger is the durable copy.
20
20
 
21
+ ## Durable AgentEventSource
22
+
23
+ `AgentEventSource` (`createMemoryAgentEventSource` / `persistence.events` on PostgreSQL) appends, pages, and subscribes with opaque ownership-bound cursors. `subscribe` registers wake interest before replaying history so replay-to-live handoff has no gap. Delivery is at-least-once; consumers dedupe `record.id`. PostgreSQL uses transactional sequence allocation plus `LISTEN`/`NOTIFY` wakeups with polling fallback. Transport adapters (server SSE `Last-Event-ID`, AG-UI, A2A `afterEventId`) map source envelopes only — they do not invent private replay loops. This is not exactly-once.
24
+
21
25
  ## Inputs / request
22
26
 
23
27
  ```ts
@@ -108,6 +108,8 @@ await browser.close();
108
108
  - Raw CSS is absent from production defaults. Ref resolution uses Playwright’s built-in `aria-ref=` selector with a package-owned snapshot ref table for staleness checks.
109
109
  - Verified-state checkpoints (0.0.14): `createBrowserCheckpointLedger()` records navigation state — URL, a domain-state hash, and host-owned data refs — never serialized browser internals (cookies/storage/contexts), which are fragile and secret-bearing. Frozen caps: URL 8 KiB/16 KiB, domain-state hash 256 B/1 KiB, host-data ref 2 KiB/8 KiB (refs only, never bodies), 16/64 checkpoints per run (oldest evicted). After any resume/interruption `markResumed(runId)` marks state stale; `assertVerifiedBeforeSideEffect(runId)` fails closed until the host reloads + `verify()`s, so side effects never replay on stale state. Checkpoints are run-scoped: a conversation thread composes through the run it owns, reusing the manager's sandbox/egress/approval/limit policy above.
110
110
 
111
+ Observation tools declare `kind: none`; mutations are `external_mutation`/`unsupported` and fail closed on stale checkpoint state. See [tool effects](tool-effects.md).
112
+
111
113
  ## Security and performance notes
112
114
 
113
115
  Import is inert. Construction fails clearly when neither `browser` nor `manager` is supplied. Browser installation, launch, version, and control endpoint are host-owned. Prism never exposes `page.evaluate`, init scripts, CDP, extensions, persistent profiles, or model-supplied Playwright launch options. Secrets and storage state must not appear in snapshots, tool results, logs, or checkpoints. Finite caps charge before context/page/action/queue/snapshot/network/artifact retention; snapshots retain no unbounded DOM, console, request, response, or trace history. Unreleased downloads are deleted on context close.
@@ -491,6 +491,8 @@ Packed capability demo: `examples/coding-tools-capability-gaps.ts` (search modes
491
491
  - **`ToolsOptions`** and the per-tool option types are exported from the package barrel for host configuration.
492
492
  - No auto-discovery or manifest registration: import and register explicitly. This package registers no extensions and owns no globals (the mutation queue is a process-wide per-path map — see `ponytail:` note in the source).
493
493
 
494
+ Read/list/search/glob are observation effects; write/edit/delete/move are optional local mutations; shell/check are unsupported external mutations. `reconcileCodingToolEffect` proves local postconditions or returns `unknown`. See [tool effects](tool-effects.md).
495
+
494
496
  ## Security and performance notes
495
497
 
496
498
  - **Host shell/filesystem access.** These tools run real commands and read/write/list/search/glob/delete/move real files. They provide **no sandbox**. Gate them with Prism `PermissionPolicy` / `ToolValidator` / trust policies before registering them for any provider turn. Shared `executionPolicy` applies to both full and read-only aggregators before filesystem/process side effects. See [Host security guide](host-security.md) and [Security/auth/trust](settings-auth-trust-security.md).
@@ -8,6 +8,8 @@ Prism itself does not ship a production database adapter. The built-in `SessionS
8
8
 
9
9
  Plan 056 Task 1 adds dialect-neutral shared primitives under `@arnilo/prism/testing/persistence-schema`, `@arnilo/prism/testing/session-store-conformance`, and `@arnilo/prism/testing/run-ledger-conformance`. Task 2 ships `@arnilo/prism-session-store-sqlite` (see [SQLite persistence](sqlite-persistence.md)); Task 3 ships `@arnilo/prism-session-store-postgres` (see [PostgreSQL persistence](postgres-persistence.md)). Both implement dialect-local SQL against the shared model; Prism core still ships no ORM, driver, or migration runner.
10
10
 
11
+ Release 0.0.23 additionally ships [`@arnilo/prism-enterprise-postgres`](enterprise-postgres-state.md), a separate PostgreSQL composition for policy decisions, evaluations, work-mutation claims, and model-router state. It is not a `ProductionPersistenceStore` replacement and does not store sessions/runs. Its fixed `prism_enterprise_migrations` history is independent of `prism_migrations`; hosts may use both compositions against the same validated schema.
12
+
11
13
  ## When to use it
12
14
 
13
15
  Use these contracts when you write a database-backed `SessionStore` or a separate persistence adapter that needs:
@@ -449,6 +451,8 @@ const dbStore: ProductionPersistenceStore = {
449
451
  - Cursor values and idempotency keys are host-defined and opaque to Prism.
450
452
  - First-party SQLite/PostgreSQL adapters expose `persistence.checkpoints` and `persistence.leases`, backed by package-owned `prism_checkpoints` / `prism_leases` tables. `@arnilo/prism-workflows` consumes them for durable resume, human suspension, multi-process coordination, Phase 11 schedule records/fire leases, shared state, and replay lineage; workflow code owns no SQL table. `suspended`/`denied`, schedules, state history, and replay lineage remain namespaces/categories plus bounded checkpoint JSON values, so Phases 8 and 11 need no database migration.
451
453
 
454
+ Schema version **7** adds the exact-owner durable event retention index (`prism_agent_events_owner_timestamp_sequence_idx`). Distributed subscribe/LISTEN remains PostgreSQL-only via `persistence.events`.
455
+
452
456
  ## Security and performance notes
453
457
 
454
458
  - **No credentials in storage.** The contracts never include `CredentialResolver`, `AIProvider`, `ProviderResolver`, provider API keys, or credential values.
@@ -461,6 +465,7 @@ const dbStore: ProductionPersistenceStore = {
461
465
  ## Related APIs
462
466
 
463
467
  - [Session store conformance](session-store-conformance.md): executable adapter baseline for append/idempotency/conflict/branch invariants.
468
+ - [Enterprise PostgreSQL state](enterprise-postgres-state.md): durable governance and connector/router state outside the session/run contract.
464
469
  - [Migration guide](migration.md): before/after shapes for moving from in-memory/JSONL to this contract.
465
470
  - [Performance limits](performance.md): production sizing, subscriber queues, branch-read limits, and database adapter guidance.
466
471
  - [Session stores and branching](session-stores-and-branching.md): `SessionStore`, `SessionEntry`, branch helpers, and runtime branch semantics.
@@ -0,0 +1,177 @@
1
+ # Enterprise PostgreSQL state
2
+
3
+ ## What it does
4
+
5
+ `@arnilo/prism-enterprise-postgres` is one optional PostgreSQL composition for the four existing enterprise state seams:
6
+
7
+ | State | Composition property | Durable behavior |
8
+ | --- | --- | --- |
9
+ | Policy decisions | `policy` | Append-only `PolicyDecisionStore` with owner-bound cursor pages. |
10
+ | Evaluations | `evaluations` | `EvaluationStore` append/query records with exact owner pages. |
11
+ | Work mutations | `workIdempotency` | Atomic claim/CAS lifecycle for connector effects. |
12
+ | Model routing | `modelRouter` | Shared rate, budget, and circuit state for router replicas. |
13
+ | Tool effects | `toolEffects` | Durable `ToolEffectStore` claim/CAS for recoverable tool side effects (migration 002). |
14
+ | Tool effects | `toolEffects` | Durable `ToolEffectStore` claim/CAS for recoverable tool side effects (migration 002). |
15
+
16
+ `createPostgresEnterpriseState()` opens a host-supplied or adapter-owned `pg` pool, verifies/applies checksum-protected enterprise migrations (`001_enterprise_state`, `002_tool_effects`), and returns those stores plus explicit cleanup and close operations. Importing it performs no I/O. It is separate from session/run persistence in [`@arnilo/prism-session-store-postgres`](postgres-persistence.md).
17
+
18
+ ## When to use it
19
+
20
+ Use it for a multi-process production host that needs policy/evaluation audit state, connector mutation reconciliation, or model-router limits to survive restart and coordinate across replicas.
21
+
22
+ Use memory/file stores only for tests, demos, or a deliberately single-process host. They do not provide PostgreSQL cross-replica coordination. This package is optional; it adds no database driver to `@arnilo/prism` core.
23
+
24
+ ## Inputs / request
25
+
26
+ ```ts
27
+ import { createPostgresEnterpriseState } from "@arnilo/prism-enterprise-postgres";
28
+ import { Pool } from "pg";
29
+
30
+ const pool = new Pool({
31
+ connectionString: process.env.DATABASE_URL,
32
+ max: 10,
33
+ ssl: { rejectUnauthorized: true },
34
+ });
35
+
36
+ const state = await createPostgresEnterpriseState({ pool, schema: "prism" });
37
+ ```
38
+
39
+ | Input | Meaning |
40
+ | --- | --- |
41
+ | `pool` | Existing `pg` pool. Exactly one of `pool` and `connectionString` is required; caller retains pool lifecycle. |
42
+ | `connectionString` | Creates an adapter-owned pool when `pool` is omitted. |
43
+ | `schema` | Validated identifier; defaults to `"prism"`. It is never interpolated as an unchecked SQL identifier. |
44
+ | `poolMax` / `poolConfig` | Adapter-owned pool settings; maximum defaults to 10 and is capped at 100. Put TLS in `poolConfig.ssl`. |
45
+ | `skipMigrations` | Isolated-test escape hatch only. Production opens verify/apply the fixed migration before request traffic. |
46
+ | `cleanup({ tenantId, accountId?, userId?, principalId, limit?, signal? })` | Explicit exact-owner cleanup; default 100 and hard maximum 500 rows. No worker starts automatically. |
47
+
48
+ Every durable policy/work/router action starts from an active host-verified `AgentIdentity`. Evaluation records/queries must be projected by the host from that verified ownership. PostgreSQL rejects missing tenant scope; optional account/user values are normalized and matched exactly, including absence.
49
+
50
+ ## Outputs / response / events
51
+
52
+ `PostgresEnterpriseState` has this public shape:
53
+
54
+ ```ts
55
+ interface PostgresEnterpriseState {
56
+ readonly policy: PolicyDecisionStore;
57
+ readonly evaluations: EvaluationStore;
58
+ readonly workIdempotency: IdempotencyStore;
59
+ readonly modelRouter: ModelRouterStateStore;
60
+ readonly toolEffects: ToolEffectStore;
61
+ readonly toolEffects: ToolEffectStore;
62
+ cleanup(input: EnterpriseStateCleanupInput): Promise<EnterpriseStateCleanupResult>;
63
+ close(): Promise<void>;
64
+ }
65
+ ```
66
+
67
+ `close()` ends only a pool created from `connectionString`; it leaves a caller-owned pool open. `cleanup()` returns `{ removed, transitioned }`. It transitions expired work claims to `unknown` and abandoned circuit probes back to cooldown before deleting expired/idle retained state.
68
+
69
+ Work mutations expose six observable states: **absent**, `in_progress`, `completed`, `failed_retryable`, `failed_terminal`, and `unknown`. `begin()` atomically returns `acquired` or the existing record; `complete`/`fail`/`markUnknown` use claim-token plus version compare-and-swap. `unknown` requires an operator/connector-specific `resolveUnknown` decision. It is not automatically replayed and it does **not** claim exactly-once external effects.
70
+
71
+ Model-router state is asynchronous and owner/principal/provider/model scoped. Supplying it to `createModelRouter({ stateStore })` requires awaited `resolve`, `recordUsage`, and `recordOutcome` calls with verified identity. The legacy synchronous `providerSource` facade throws `ERR_PRISM_MODEL_ROUTER_ASYNC_STATE` when durable state is configured.
72
+
73
+ ## Request/response example
74
+
75
+ ```json
76
+ {
77
+ "schema": "prism",
78
+ "cleanup": {
79
+ "tenantId": "tenant-1",
80
+ "userId": "user-7",
81
+ "principalId": "agent-9",
82
+ "limit": 100
83
+ },
84
+ "result": { "removed": 12, "transitioned": 1 }
85
+ }
86
+ ```
87
+
88
+ A migration creates `prism_policy_decisions`, `prism_evaluations`, `prism_work_idempotency`, three `prism_model_router_*` tables, and its separate `prism_enterprise_migrations` history. Startup serializes per-schema setup with an advisory transaction lock and rejects checksum or catalog drift rather than silently repairing it.
89
+
90
+ ## Implementation example
91
+
92
+ ```ts
93
+ import type { AgentIdentity } from "@arnilo/prism";
94
+ import { createPostgresEnterpriseState, type PostgresEnterpriseState } from "@arnilo/prism-enterprise-postgres";
95
+
96
+ const identity: AgentIdentity = {
97
+ tenantId: "tenant-1",
98
+ userId: "user-7",
99
+ principal: { kind: "agent", id: "agent-9" },
100
+ scopes: ["enterprise:write"],
101
+ verified: true,
102
+ issuedAt: "2026-08-03T00:00:00.000Z",
103
+ };
104
+
105
+ export async function recordEnterpriseState(state: PostgresEnterpriseState) {
106
+ const now = new Date().toISOString();
107
+ await state.policy.append({
108
+ id: "policy-1",
109
+ policyId: "mail",
110
+ policyVersion: "2026-08-03",
111
+ outcome: "approval",
112
+ identity,
113
+ target: { kind: "draft", id: "draft-1" },
114
+ evidenceRefs: ["rule:external-recipient"],
115
+ createdAt: now,
116
+ });
117
+ await state.evaluations.append({
118
+ id: "eval-1",
119
+ scorerId: "quality",
120
+ status: "scored",
121
+ score: 1,
122
+ sampled: true,
123
+ tenantId: identity.tenantId,
124
+ userId: identity.userId,
125
+ createdAt: now,
126
+ });
127
+
128
+ const claim = await state.workIdempotency.begin({ identity, key: "mail-send-1", op: "mail.send" });
129
+ if (claim.outcome === "acquired") {
130
+ // Run approved connector effect outside PostgreSQL transaction.
131
+ await state.workIdempotency.complete({
132
+ identity,
133
+ key: "mail-send-1",
134
+ op: "mail.send",
135
+ claimToken: claim.record.claimToken!,
136
+ expectedVersion: claim.record.version,
137
+ result: { draftId: "draft-1", resourceId: "message-1" },
138
+ });
139
+ }
140
+
141
+ await state.modelRouter.addUsage({
142
+ key: { tenantId: "tenant-1", userId: "user-7", principalId: "agent-9", provider: "openai", model: "gpt-4.1-mini" },
143
+ tokens: 100,
144
+ windowMs: 86_400_000,
145
+ now: Date.now(),
146
+ });
147
+ return state.cleanup({ tenantId: "tenant-1", userId: "user-7", principalId: "agent-9" });
148
+ }
149
+
150
+ // `state` comes from `await createPostgresEnterpriseState({ pool, schema: "prism" })`.
151
+ ```
152
+
153
+ ## Extension and configuration notes
154
+
155
+ - `createModelRouter({ resolver, stateStore: state.modelRouter })` keeps allow-list, residency, fallback, and diagnostics behavior in `@arnilo/prism-model-router`; this package only supplies durable state.
156
+ - Policy/evaluation/query public contracts stay in their owning packages. This package exports only `createPostgresEnterpriseState`, its options/result types, and `EnterprisePostgresError`; it has no SQL, DDL, codec, queryable, or migration subpath.
157
+ - The fixed schema has no generic key/value table and no background cleanup scheduler. Schedule `state.cleanup()` from an authorized host job, size its bounded batch for the deployment, and monitor unknown work rows for reconciliation. Run protected integration checks with `PRISM_TEST_POSTGRES_URL="$DATABASE_URL" npm run test:postgres`; the command rejects an absent URL instead of silently skipping database coverage.
158
+ - Request-path state SQL uses `SELECT`, `INSERT`, `UPDATE`, and `DELETE` on the six state tables. The open/migration lifecycle additionally needs schema/catalog/advisory-lock and DDL permissions. Use a deployment migration principal for that lifecycle and a least-privilege request role for request traffic; this release intentionally does not ship a migration CLI or worker.
159
+
160
+ ## Security and performance notes
161
+
162
+ - Configure TLS, database credentials, pool timeouts, backups, restore drills, retention schedule, and database role grants in the host. Never put connection strings, tokens, prompts, raw connector responses, unrestricted payloads, or provider credentials in records.
163
+ - Every value is a bound parameter. Schema identifiers are validated; table/index names are fixed. Cursors embed and recheck ownership, so a foreign tenant cannot reuse a page cursor.
164
+ - Policy records cap at 64 KiB; evaluations at 64 KiB; work rows at 8 KiB; router material at 512 bytes. JSON rejects prototype-pollution keys, non-finite values, excess depth/properties, and over-size material.
165
+ - PostgreSQL transaction SQLSTATE `40001`/`40P01` retries whole safe transactions up to three times. Connector effects remain outside those transactions and ambiguous errors become `unknown` rather than being retried.
166
+ - Recorded `postgres:16-alpine` evidence (Node 24.18.0/Linux x64, 10 tenants × 10 principals × 1,000 policy/evaluation rows, 10,000 router keys, 16 clients) stayed below 50 ms p95 for point operations and 100 ms for cursor/cleanup pages. The highest recorded p95 was router-circuit contention at 28.410 ms. Fourteen representative `EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON)` shapes used named indexes with no sequential scans. These are recorded comparison evidence, not hardware-independent guarantees.
167
+
168
+ ## Related APIs
169
+
170
+ - [Policy and audit](policy-and-audit.md): policy record and WORM export semantics.
171
+ - [Evaluations](evaluations.md): scorer/evaluation record lifecycle.
172
+ - [Work tools](work-tools.md): draft approval, unknown outcomes, and connector boundaries.
173
+ - [Model routing](model-routing.md): durable asynchronous router migration.
174
+ - [PostgreSQL persistence](postgres-persistence.md): sessions/runs/checkpoints/leases adapter.
175
+ - [Database persistence](database-persistence.md): host retention and persistence guidance.
176
+ - [Host security](host-security.md): database ownership, TLS, secrets, and role boundaries.
177
+ - [Migration guide](migration.md): 0.0.22 → 0.0.23 upgrade steps.
@@ -146,6 +146,18 @@ Release 0.0.9 ships curated network-free adversarial fixtures in package tests:
146
146
 
147
147
  Fixtures reuse `@arnilo/prism-evals` (`defineDataset` / `defineScorer` / `scoreRun` / `assertEvaluationThreshold` / `serializeEvaluationReport`). Optional SWE-bench-compatible or live-browser harnesses remain host adapters — they are not default dependencies or quality claims. Protected real Docker/Playwright gates stay env-gated (`PRISM_TEST_DOCKER_SANDBOX`, `PRISM_LIVE_PLAYWRIGHT`) and never enter `sdk:ready`.
148
148
 
149
+ ## PostgreSQL enterprise state (0.0.23)
150
+
151
+ `createPostgresEnterpriseState({ pool, schema }).evaluations` implements this package's existing `EvaluationStore`. The host creates an `EvaluationRecord` from verified ownership before append; every PostgreSQL query requires tenant scope, uses exact normalized account/user matching, and returns owner-bound opaque cursor pages. It is durable across reopen and supports the existing id/scorer/session/run/trace/dataset/item/experiment/status filters.
152
+
153
+ ```ts
154
+ const state = await createPostgresEnterpriseState({ pool });
155
+ await state.evaluations.append(record); // record is already host-owned and redacted
156
+ const page = await state.evaluations.query({ tenantId: "t1", userId: "u1", status: "scored", limit: 100 });
157
+ ```
158
+
159
+ Memory evaluation storage remains suitable for development and deterministic tests; it is not cross-replica production storage. PostgreSQL bounds each evaluation row to 64 KiB (reason/error 8 KiB each and metadata 32 KiB).
160
+
149
161
  ## Related APIs
150
162
 
151
163
  - [Agent/session runtime](agent-session-runtime.md): `AgentRunResult` and `session.run()`
@@ -153,4 +165,5 @@ Fixtures reuse `@arnilo/prism-evals` (`defineDataset` / `defineScorer` / `scoreR
153
165
  - [Observability](observability.md): use `onTraceReference` or bounded `traceId(runId)` to supply `ScoreRunOptions.traceId`; evaluation telemetry emits no reason/explanation content
154
166
  - [Coding agent tools](coding-agent-tools.md) / [Browser automation](browser-automation.md) / [Workflows](workflows.md): network-free coding-task composition at `examples/durable-coding-workflow.ts`; adversarial coding/browser eval example at `examples/coding-browser-evaluation.ts`
155
167
  - [Performance limits](performance.md): `scripts/benchmark-0.0.11.mjs` search/budget evidence, `scripts/benchmark-0.0.10.mjs` workspace-mode evidence, and `scripts/benchmark-0.0.9.mjs` coding/browser evidence fields
168
+ - [Enterprise PostgreSQL state](enterprise-postgres-state.md): durable owner-scoped evaluation storage.
156
169
  - [Release and install](release-and-install.md): optional package install and protected sandbox-browser workflow