@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.
- package/CHANGELOG.md +36 -2
- package/dist/agent-event-source.d.ts +11 -0
- package/dist/agent-event-source.js +512 -0
- package/dist/agent-run-state.js +6 -1
- package/dist/agents.js +7 -2
- package/dist/contracts.d.ts +157 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +4 -1
- package/dist/testing/agent-event-source-conformance.d.ts +4 -0
- package/dist/testing/agent-event-source-conformance.js +54 -0
- package/dist/testing/persistence-schema.d.ts +2 -2
- package/dist/testing/persistence-schema.js +58 -21
- package/dist/testing/tool-effect-store-conformance.d.ts +9 -0
- package/dist/testing/tool-effect-store-conformance.js +85 -0
- package/dist/tool-effects.d.ts +15 -0
- package/dist/tool-effects.js +338 -0
- package/dist/tools.d.ts +3 -1
- package/dist/tools.js +204 -9
- package/docs/0.1.0-readiness.md +10 -9
- package/docs/a2a.md +6 -2
- package/docs/ag-ui-adoption.md +77 -0
- package/docs/ag-ui.md +39 -42
- package/docs/agent-events.md +5 -1
- package/docs/browser-automation.md +2 -0
- package/docs/coding-agent-tools.md +2 -0
- package/docs/database-persistence.md +5 -0
- package/docs/enterprise-postgres-state.md +177 -0
- package/docs/evaluations.md +13 -0
- package/docs/host-security.md +11 -1
- package/docs/index.md +17 -14
- package/docs/mcp-tools.md +17 -2
- package/docs/migration.md +41 -0
- package/docs/model-routing.md +17 -8
- package/docs/performance.md +38 -0
- package/docs/policy-and-audit.md +12 -0
- package/docs/postgres-persistence.md +7 -3
- package/docs/public-contracts.md +2 -0
- package/docs/release-and-install.md +67 -680
- package/docs/server.md +9 -6
- package/docs/sqlite-persistence.md +10 -2
- package/docs/supervisors.md +2 -0
- package/docs/tool-effects.md +95 -0
- package/docs/tools.md +4 -0
- package/docs/work-tools.md +21 -1
- 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`)
|
|
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`;
|
|
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
|
-
| `
|
|
35
|
-
| `
|
|
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`.
|
|
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:
|
|
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.
|
|
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()`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
|
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
|
-
|
|
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.
|
package/docs/agent-events.md
CHANGED
|
@@ -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 `
|
|
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.
|
package/docs/evaluations.md
CHANGED
|
@@ -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
|