@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/server.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## What it does
4
4
 
5
- `@arnilo/prism-server` exposes explicitly selected agents and workflows through one framework-free `(Request) => Promise<Response>` handler. It supports direct agent results, bounded agent/workflow SSE, opt-in durable agent status/resume, durable workflow start/enqueue/status/cancel/resume/replay, ownership-scoped schedules, host authorization, ownership propagation, redaction, resource ceilings, and optional deployment seams (health/readiness, drain, host rate-limit adapter, ownership-scoped event replay, worker/coordinator lease election).
5
+ `@arnilo/prism-server` exposes explicitly selected agents and workflows through one framework-free `(Request) => Promise<Response>` handler. It supports direct agent results, bounded agent/workflow SSE, cross-replica durable agent-event reconnect, opt-in durable agent status/resume, durable workflow start/enqueue/status/cancel/resume/replay, ownership-scoped schedules, host authorization, ownership propagation, redaction, resource ceilings, and optional deployment seams (health/readiness, drain, host rate-limit adapter, ownership-scoped event replay, worker/coordinator lease election).
6
6
 
7
7
  No listener starts on import. Empty `agents`/`workflows` maps expose nothing. Authentication, authorization, route selection, durable stores, TLS, distributed rate limiting, queues, and framework/serverless adaptation remain host-owned.
8
8
 
@@ -17,7 +17,7 @@ Use `AgentSession` or workflow APIs directly for in-process applications. Do not
17
17
  ```ts
18
18
  const drain = createPrismDrainController({ deadlineMs: 30_000 });
19
19
  const handler = createPrismHandler({
20
- agents?: Record<string, Agent | PrismAgentExposure>,
20
+ agents?: Record<string, Agent | PrismAgentExposure>, // exposure may include events + resolveRun
21
21
  agentRuns?: Record<string, PrismAgentRunExposure>, // explicit durable status/resume only
22
22
  workflows?: Record<string, PrismWorkflowExposure>,
23
23
  schedules?: WorkflowSchedules | ((authorization, signal) => WorkflowSchedules),
@@ -46,6 +46,7 @@ At least one non-empty ownership field must come from `authorize()`. Request JSO
46
46
  | `POST /prism/agents/:id/stream` | `agent.stream` | same; SSE response |
47
47
  | `GET /prism/agents/:id/runs/:runId` | `agent.status` | none; redacted public state/version only |
48
48
  | `POST /prism/agents/:id/runs/:runId/resume` | `agent.resume` | `{ "decision": "approve" | "deny", "expectedVersion": number }` |
49
+ | `GET /prism/agents/:id/runs/:runId/events?cursor=` | `agent.events` | none; durable SSE, also accepts `Last-Event-ID` |
49
50
  | `POST /prism/workflows/:id/runs` | `workflow.run` | `{ "input": unknown, "runId"?: string }` |
50
51
  | `POST /prism/workflows/:id/stream` | `workflow.stream` | same; SSE response |
51
52
  | `POST /prism/workflows/:id/enqueue` | `workflow.enqueue` | `{ "input": unknown, "runId"?: string }`; returns `202` queued handle |
@@ -64,7 +65,7 @@ POST routes require `Content-Type: application/json`. Capability/run IDs are bou
64
65
 
65
66
  ## Outputs / response / events
66
67
 
67
- Direct routes return bounded JSON. Stream routes return `text/event-stream`; every event is one `data: <AgentEvent|WorkflowEvent>` frame. Status returns the ownership-scoped durable checkpoint record. Resume uses Phase 8 expected-version CAS. Cancel aborts active work or marks eligible durable checkpoints aborted.
68
+ Direct routes return bounded JSON. New-run stream routes return `text/event-stream`; every event is one `data: <AgentEvent|WorkflowEvent>` frame. Durable event reconnect frames add `id: <opaque source cursor>` before `data: <AgentEvent>` and resume strictly after either matching `?cursor=` or `Last-Event-ID`. Conflicting header/query cursors fail before source access. Status returns the ownership-scoped durable checkpoint record. Resume uses Phase 8 expected-version CAS. Cancel aborts active work or marks eligible durable checkpoints aborted.
68
69
 
69
70
  Errors use `{ "error": { "code", "message" } }`. Unknown routes/capabilities are `404`, authorization/policy denial `403`, malformed input `400`, unsupported content type `415`, body overflow `413`, concurrency overflow `429`, and result overflow `507`. Unexpected errors are generic and never include stacks.
70
71
 
@@ -135,7 +136,8 @@ Compose beside `createPrismHandler` — Prism starts no listener, container orch
135
136
  | `createPrismHealthHandler` | `GET /health`, `/livez`, `/readyz`. Minimal JSON; `?detail=1` requires `authorizeDetail`. No secrets/tenant payloads by default. Ready fails while draining. |
136
137
  | `createPrismDrainController` | `beginDrain()` rejects admit ops (`agent.run`/`stream`/`resume`, workflow run/stream/enqueue/resume/replay, schedule create/trigger) with `503 ERR_PRISM_SERVER_DRAINING`. Status/cancel/list stay open. |
137
138
  | `rateLimit` on handler | Host adapter after authorize, before session create. Return denial `{ retryAfterMs, code, message }` → `429` + optional `Retry-After`. `createMemoryRateLimiter` is single-process only. |
138
- | `createPrismEventReplay` / `createPrismReplayHandler` | Ownership-scoped `queryEvents` pages (`redacted: true`). Does not re-run work. Unauthorized replay denies. |
139
+ | `createPrismAgentEventReplay` | Shared `AgentEventSource` page/follow semantics for exact-owned runs. |
140
+ | `createPrismEventReplay` / `createPrismReplayHandler` | Compatible ownership-scoped legacy `queryEvents` pages (`redacted: true`). Does not re-run work. Unauthorized replay denies. |
139
141
  | `createPrismDeploymentLease` | Lease election under `prism.server.deployment`. Coordinator replica holds `key: "coordinator"` before schedule ticks; workers run `@arnilo/prism-workflows` `createWorkflowCoordinator` for queued runs (fencing tokens). |
140
142
  | `createConversationService` / `createConversationHandler` | Durable user-scoped conversation threads (create/list/continue/branch/archive/export/delete) over session + event-ledger seams, with thread-bound reconnectable replay. Mounts beside the handler; see [Conversations](conversations.md). |
141
143
  | `createArtifactService` / `createArtifactHandler` | Durable artifact co-work review (attach/revise/compare/approve/reject/last-validated/delivery-link + authorized download) over the versioned checkpoint store; records persist metadata/revisions/approvals only, never file bodies. Mounts beside the handler; see [Work artifacts and review](work-artifacts-and-review.md). |
@@ -153,7 +155,7 @@ Network-free demo: [`examples/server-deployment-seams.ts`](../examples/server-de
153
155
  - Agent tools and workflow tool nodes still need their own `PermissionPolicy`, `ToolValidator`, and `ExecutionPolicy`. HTTP authorization does not replace side-effect policy.
154
156
  - Host and origin allow-lists are exact string matches. Configure reverse-proxy normalization, TLS, IP policy, CSRF/cookie policy, and authentication outside Prism. Optional `rateLimit` is an attributable short-circuit only — not a WAF.
155
157
  - Health endpoints reveal process/liveness only by default; detail flags require host authorize and must omit secrets/tenant dumps.
156
- - Drain and event replay require the same ownership/authorize boundary as other routes; replay never invokes providers or tools.
158
+ - Drain and event replay require the same ownership/authorize boundary as other routes; replay never invokes providers or tools. Durable event routes exist only on object `PrismAgentExposure` entries with both `events` and `resolveRun`; every reconnect authorizes again, resolves public run ID to exact internal session/run IDs, and opens the shared source without `sessionFactory`.
157
159
  - SSE uses bounded upstream subscriber queues. Consumer cancellation aborts owned work by default and releases concurrency; set `disconnectAborts: false` only when the host deliberately owns background completion.
158
160
  - Source inputs/resource URLs remain host responsibilities and use existing resource/media SSRF policies. Server package does not fetch URLs.
159
161
  - Schedule routes never accept ownership from JSON. Services carry mandatory ownership and explicit workflow/calculator registries; route authorization cannot broaden either. Replay applies workflow ownership/hash/approval checks.
@@ -172,5 +174,6 @@ A2A routes are not added to `createPrismHandler()`. Install `@arnilo/prism-super
172
174
  - [A2A interoperability](a2a.md): separately mounted A2A 1.0 handler/client.
173
175
  - [Conversations](conversations.md): durable user-scoped conversation service, replay, branches, export, deletion.
174
176
  - [Work artifacts and review](work-artifacts-and-review.md): durable artifact review service, revisions, approvals, authorized expiring delivery links.
175
- - [Frontend interoperability (AG-UI and ACP)](ag-ui.md): separately installed authorized AG-UI Web handler; it is not a `@arnilo/prism-server` route.
177
+ - [Frontend interoperability (AG-UI and ACP)](ag-ui.md): separately installed authorized AG-UI Web handler.
178
+ - [AG-UI adoption evaluation](ag-ui-adoption.md): official 0.0.57 support matrix and MCP/A2A follow-up scope.
176
179
  - [Release and install](release-and-install.md): optional package installation and profiles.
@@ -56,7 +56,7 @@ import { createSqlitePersistence } from "@arnilo/prism-session-store-sqlite";
56
56
  | `leases` | Atomic `LeaseStore` backed by `prism_leases`; database-clock expiry, opaque renew/release token, monotonic takeover fence. |
57
57
  | `close()` | Closes the underlying database when the adapter opened it. |
58
58
 
59
- Migrations run automatically on open and are idempotent across reopen. Under the SQLite migration transaction, startup checks ordered contract name/version/SHA-256 rows plus full schema-v4 PRAGMA/catalog shape (all required tables, columns/types/nullability/defaults, PK/unique/FK keys, and named indexes) before any runtime write. A complete legacy 0.0.5 history with all `checksum` values `NULL` is shape-verified then backfilled transactionally once. Unknown, duplicate, out-of-order, partial-legacy, checksum, or shape drift rejects open; restore or apply reviewed DDL rather than editing migration rows.
59
+ Migrations run automatically on open and are idempotent across reopen. Under the SQLite migration transaction, startup checks ordered contract name/version/SHA-256 rows plus full schema-v6 PRAGMA/catalog shape (all required tables, columns/types/nullability/defaults, PK/unique/FK keys, and named indexes) before any runtime write. A complete legacy 0.0.5 history with all `checksum` values `NULL` is shape-verified then backfilled transactionally once. Unknown, duplicate, out-of-order, partial-legacy, checksum, or shape drift rejects open; restore or apply reviewed DDL rather than editing migration rows.
60
60
 
61
61
  ## Request/response example
62
62
 
@@ -99,9 +99,17 @@ For resume/timeline flows, use `queryRuns`, `queryEvents`, `queryToolCalls`, and
99
99
  - The package is optional and workspace-local; `@arnilo/prism` core has no SQLite dependency.
100
100
  - Hosts choose the database path and own backup, retention enforcement, and filesystem permissions.
101
101
  - `SessionAppendOptions` idempotency rows are durable in `prism_session_append_idempotency` and survive reopen.
102
- - Schema version **5** applies `001_init`, `002_usage_scope`, `003_run_feedback`, `004_session_search`, and `005_lifecycle_hold_quota`. Migration 003 adds immutable `prism_run_feedback` rows with run FK/cascade deletion and owner/run/trace cursor indexes. Migration 004 adds session search FTS (FTS5 virtual table `prism_session_search_fts` dual-written on append) plus `prism_sessions(updated_at, id)` cursor index; existing entries are backfilled once. `persistence.feedback` validates exact run ownership, bounds/redacts through optional `feedbackRedactor`, queries bounded pages, and deletes only exact-owned IDs. Search hits never include credentials; ownership filters apply when present. PostgreSQL shares the same model with dialect-local DDL.
102
+ - Schema version **6** applies `001_init`, `002_usage_scope`, `003_run_feedback`, `004_session_search`, `005_lifecycle_hold_quota`, and `006_agent_event_source`. Migration 006 backfills `prism_agent_event_streams` and uses it to allocate unique per-run event sequences inside the SQLite append transaction. It is sequence-compatible with PostgreSQL but remains local/file-backed; it does not expose distributed subscriptions. Migration 003 adds immutable `prism_run_feedback` rows with run FK/cascade deletion and owner/run/trace cursor indexes. Migration 004 adds session search FTS (FTS5 virtual table `prism_session_search_fts` dual-written on append) plus `prism_sessions(updated_at, id)` cursor index; existing entries are backfilled once. `persistence.feedback` validates exact run ownership, bounds/redacts through optional `feedbackRedactor`, queries bounded pages, and deletes only exact-owned IDs. Search hits never include credentials; ownership filters apply when present. PostgreSQL shares the same model with dialect-local DDL.
103
103
  - Pass an existing `better-sqlite3` `Database` via `database` when your host already manages connections.
104
104
 
105
+ ## Durable events
106
+
107
+ SQLite applies migrations **006**/**007** for per-run event sequence compatibility and the owner retention index. It does **not** provide cross-process subscribe/LISTEN; use PostgreSQL for distributed reconnect.
108
+
109
+ ## Durable events
110
+
111
+ SQLite applies migrations **006**/**007** for per-run event sequence compatibility and the owner retention index. It does **not** provide cross-process subscribe/LISTEN; use PostgreSQL for distributed reconnect.
112
+
105
113
  ## Security and performance notes
106
114
 
107
115
  - **Parameterized SQL only.** Session ids, idempotency keys, tenant ids, and JSON payloads are bound parameters.
@@ -54,6 +54,8 @@ const result = await supervisor.delegate({ childId: "research", input: "Check so
54
54
 
55
55
  Child factories resolve their own providers/credentials and construct context/memory using the supplied IDs. Parent, child, returned-agent, budget, and hook permission policies are AND-composed. Child/request/hook limits can only lower inherited limits. A nested factory can call the supplied `delegate()`; immutable path state rejects cycles and depth overflow.
56
56
 
57
+ Supervisors propagate parent `identity` and `effectStore` to every child agent/run so delegated tool effects stay under the same ownership scope.
58
+
57
59
  ## Security and performance notes
58
60
 
59
61
  - Child IDs are explicit; no package/provider discovery occurs.
@@ -0,0 +1,95 @@
1
+ # Recoverable tool effects
2
+
3
+ ## What it does
4
+
5
+ Optional tool-effect contracts record whether a tool call may mutate state and how Prism recovers after crash or duplicate delivery. When a host supplies `effectStore` and a tool declares `effect`, dispatch claims before side effects, marks dispatched before execute, and completes or marks unknown after. Ambiguous outcomes never auto-replay. This is at-least-once claim coordination, not exactly-once delivery.
6
+
7
+ APIs:
8
+
9
+ - `ToolEffectDeclaration` / `ToolEffectClassifier` on `ToolDefinition.effect`
10
+ - `ToolEffectStore` (`get` / `begin` / `markDispatched` / `complete` / `fail` / `markUnknown` / `resolveUnknown` / `cleanup`)
11
+ - `createMemoryToolEffectStore()` (in-process reference)
12
+ - `deriveToolEffectKey()` / `toolEffectArgumentsHash()` / `canonicalToolEffectJson()`
13
+ - Enterprise: `createPostgresEnterpriseState().toolEffects`
14
+
15
+ ## When to use it
16
+
17
+ Use when tools perform local or external mutations and a host needs durable claim/CAS recovery across process restart. Skip for pure observation tools (`kind: "none"`). Keep `session.subscribe()` for live UI; pair durable events with [Agent events](agent-events.md) when reconnecting replicas.
18
+
19
+ ## Inputs / request
20
+
21
+ | Field | Values | Meaning |
22
+ | --- | --- | --- |
23
+ | `kind` | `none` / `local_mutation` / `external_mutation` | Observation vs local vs external side effect |
24
+ | `idempotency` | `none` / `optional` / `required` / `tool_managed` / `unsupported` | Whether core claims a key |
25
+ | `effectStore` | on `AgentConfig` / `RunOptions` | Opt-in store; required when idempotency is `required` |
26
+ | `context.idempotencyKey` | core-derived only | Model-supplied keys are ignored |
27
+
28
+ Statuses: `pending` → `dispatched` → `completed` | `failed_retryable` | `failed_terminal` | `unknown`. Expired pending becomes retryable; expired dispatched becomes unknown. Unknown needs operator `resolveUnknown`.
29
+
30
+ ## Outputs / response / events
31
+
32
+ | Outcome | Behavior |
33
+ | --- | --- |
34
+ | First claim | `begin` acquires; dispatch marks dispatched then executes |
35
+ | Duplicate completed | returns bounded stored result; tool body does not rerun |
36
+ | Ambiguous crash | `unknown` + `ERR_PRISM_TOOL_EFFECT_UNKNOWN`; never silent replay |
37
+ | Unsupported | no store/identity required; host owns recovery |
38
+
39
+ ## Request/response example
40
+
41
+ ```json
42
+ {
43
+ "kind": "external_mutation",
44
+ "idempotency": "required"
45
+ }
46
+ ```
47
+
48
+ ## Implementation example
49
+
50
+ ```ts
51
+ import { createAgent, createMemoryToolEffectStore, dispatchToolCall } from "@arnilo/prism";
52
+
53
+ const effectStore = createMemoryToolEffectStore();
54
+ const tool = {
55
+ name: "mail.send",
56
+ description: "Send mail",
57
+ parameters: { type: "object", properties: {} },
58
+ effect: { kind: "external_mutation", idempotency: "required" },
59
+ execute: async (_args, context) => ({ toolCallId: context.toolCallId, name: "mail.send", value: { sent: true } }),
60
+ };
61
+
62
+ const agent = createAgent({ model, provider, tools: [tool], effectStore });
63
+ ```
64
+
65
+ Runnable demo: `examples/distributed-events-and-tool-effects.ts`.
66
+
67
+ ## Extension and configuration notes
68
+
69
+ | Surface | Classification |
70
+ | --- | --- |
71
+ | Coding read/list/search/glob | `none` / `none` |
72
+ | Coding write/edit/delete/move/git commit | `local_mutation` / `optional` |
73
+ | Coding shell / check | `external_mutation` / `unsupported` |
74
+ | Browser observation | `none` / `none` |
75
+ | Browser mutation | `external_mutation` / `unsupported` |
76
+ | Work connector reads | `none` / `none` |
77
+ | Work connector mutations | `external_mutation` / `tool_managed` (core key + store) |
78
+ | MCP remote tools | host `effect` policy; default unsupported |
79
+ | Supervisor children | inherit parent `identity` + `effectStore` |
80
+
81
+ ## Security and performance notes
82
+
83
+ - Ownership and verified identity bind every claim key; cursors/keys never select tenants.
84
+ - Results/references are byte-bounded and redacted; oversized writes fail closed.
85
+ - Claim TTL default 15 min (hard 60); attempts default 3 (hard 10); unknown has no auto-expiry.
86
+ - PostgreSQL store uses parameterized CAS; request roles stay `SELECT`/`INSERT`/`UPDATE`/`DELETE` only.
87
+ - Recorded p95 claim/transition ≈ 3.1 ms under Task 0 ceilings — see [performance](performance.md).
88
+
89
+ ## Related APIs
90
+
91
+ - [Tools](tools.md): dispatch harness that hosts effect claim/recovery.
92
+ - [Agent events](agent-events.md): durable `AgentEventSource` for replica reconnect (separate from effect claims).
93
+ - [Work tools](work-tools.md): connector `tool_managed` reconciliation.
94
+ - [Enterprise PostgreSQL state](enterprise-postgres-state.md): durable `toolEffects` store.
95
+ - [Host security](host-security.md): unknown-outcome fail-closed guidance.
package/docs/tools.md CHANGED
@@ -237,6 +237,8 @@ await session.run(input, {
237
237
  - Prism does not sandbox host tools and does not include built-in app tools.
238
238
  - Contribution registration and registry/filter calls do not perform provider calls, credential resolution, resource loading, network, filesystem discovery, or tool execution.
239
239
  - Dispatch performs explicit in-memory checks and executes only the selected host-active tool; it adds no retries, queues, timers, or new dependencies.
240
+ - When `effectStore` is configured, dispatch claims before side effects and never auto-replays `unknown` outcomes (see [tool effects](tool-effects.md)).
241
+ - When `effectStore` is configured, dispatch claims before side effects and never auto-replays `unknown` outcomes (see [tool effects](tool-effects.md)).
240
242
 
241
243
  ## JSON Schema validator limits
242
244
 
@@ -265,6 +267,8 @@ createJsonSchemaToolArgumentValidator({
265
267
  - [Observational memory compaction package](compaction-observational-memory.md): optional exact-id recall tool factory.
266
268
  - [Tool execution primitives](tool-execution-primitives.md): JSON Schema adapter, parallelism, MCP bridge, and execution-policy designs.
267
269
  - [MCP client bridge](mcp-tools.md): optional remote tool mapping plus separate bounded resource/prompt facades; non-tool MCP capabilities never bypass tool dispatch by masquerading as `ToolDefinition`.
270
+ - [Recoverable tool effects](tool-effects.md): optional `tool.effect` + `effectStore` claim/CAS recovery around dispatch.
271
+ - [Recoverable tool effects](tool-effects.md): optional `tool.effect` + `effectStore` claim/CAS recovery around dispatch.
268
272
  - [Coding agent tools](coding-agent-tools.md): optional first-party `@arnilo/prism-coding-agent` `shell`/`read`/`write`/`edit` tools a host registers into this harness.
269
273
 
270
274
  `DispatchToolCallOptions.trust` and `.permission` run before validation or `execute()`; denial emits `tool_execution_blocked`. Middleware cannot bypass either guard. `AgentConfig.validator`/`RunOptions.validate` run after these guards; their output is redacted through the active `SecretRedactor`. `createSecureAgent()` requires all three seams plus non-empty schemas and durable pre-tool approval. Prism does not sandbox tools. See [Security/auth/trust](settings-auth-trust-security.md).
@@ -89,7 +89,22 @@ Startup: M365 `version --output json`; GWS `--version`. Forbidden: `login`, `set
89
89
 
90
90
  ### Draft → approve → execute
91
91
 
92
- Mutation tools (`*_mail_draft_send`, `*_draft_*`) create an in-adapter draft and return `{ status: "pending_approval", draftId }` until `approval.isApproved` is true. Retries with the same `idempotencyKey` return `{ status: "duplicate" }` after first successful execute.
92
+ Mutation tools (`*_mail_draft_send`, `*_draft_*`) create an in-adapter draft and return `{ status: "pending_approval", draftId }` until `approval.isApproved` is true.
93
+
94
+ ### Durable idempotency (0.0.23)
95
+
96
+ `createMemoryIdempotencyStore()` remains for tests and a single process. For production replicas, use `createPostgresEnterpriseState({ pool }).workIdempotency`. It changes the old `get`/`put` replay abstraction to explicit async state transitions:
97
+
98
+ | Observable state | Meaning / host action |
99
+ | --- | --- |
100
+ | absent | `begin()` atomically acquires the first claim. |
101
+ | `in_progress` | Another worker owns the claim; do not dispatch a second connector effect. |
102
+ | `completed` | Return the bounded stored `{ draftId, resourceId? }` duplicate summary. |
103
+ | `failed_retryable` | A later `begin()` may reclaim it within the capped attempt policy. |
104
+ | `failed_terminal` | Do not retry; surface the bounded failure. |
105
+ | `unknown` | External result is ambiguous; reconcile with the connector/operator through `resolveUnknown()`. Never auto-replay. |
106
+
107
+ Call `begin({ identity, key, op })` **before** the external effect. After it succeeds, call `complete`, `fail`, or `markUnknown` with the returned claim token and version. The connector effect stays outside the database transaction, so this is claim-before-effect/deduplication—not exactly-once delivery. Claims default to 15 minutes (hard 60 minutes); expired claims transition to `unknown`; attempts default to 3 (hard 5). Stored rows contain no request body, token, raw provider response, or unrestricted payload.
93
108
 
94
109
  ## Limits
95
110
 
@@ -101,6 +116,10 @@ Mutation tools (`*_mail_draft_send`, `*_draft_*`) create an in-adapter draft and
101
116
  | Process wall time | 60 s / 10 min |
102
117
  | Concurrent CLI / identity | 2 / 8 |
103
118
 
119
+ ## Tool effects
120
+
121
+ Approved mutations require core-derived `context.idempotencyKey` and a configured store (`effect: external_mutation/tool_managed`). Model-supplied idempotency keys are ignored. Ambiguous connector outcomes stay `unknown` — never auto-replayed (not exactly-once). See [tool effects](tool-effects.md).
122
+
104
123
  ## Security
105
124
 
106
125
  - Require host-verified `AgentIdentity`; no cross-identity configDir reuse.
@@ -111,6 +130,7 @@ Mutation tools (`*_mail_draft_send`, `*_draft_*`) create an in-adapter draft and
111
130
 
112
131
  ## Related
113
132
 
133
+ - [Enterprise PostgreSQL state](enterprise-postgres-state.md): durable claim/CAS store, cleanup, and operator reconciliation.
114
134
  - [Work connectors](work-connectors.md)
115
135
  - [Agent identity](agent-identity.md)
116
136
  - [Host security](host-security.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arnilo/prism",
3
- "version": "0.0.22",
3
+ "version": "0.0.24",
4
4
  "description": "Agent harness for AI providers, agents, sessions, and tools.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -30,6 +30,10 @@
30
30
  "types": "./dist/testing/provider-conformance.d.ts",
31
31
  "default": "./dist/testing/provider-conformance.js"
32
32
  },
33
+ "./testing/agent-event-source-conformance": {
34
+ "types": "./dist/testing/agent-event-source-conformance.d.ts",
35
+ "default": "./dist/testing/agent-event-source-conformance.js"
36
+ },
33
37
  "./testing/session-store-conformance": {
34
38
  "types": "./dist/testing/session-store-conformance.d.ts",
35
39
  "default": "./dist/testing/session-store-conformance.js"
@@ -42,6 +46,10 @@
42
46
  "types": "./dist/testing/tool-conformance.d.ts",
43
47
  "default": "./dist/testing/tool-conformance.js"
44
48
  },
49
+ "./testing/tool-effect-store-conformance": {
50
+ "types": "./dist/testing/tool-effect-store-conformance.d.ts",
51
+ "default": "./dist/testing/tool-effect-store-conformance.js"
52
+ },
45
53
  "./testing/extension-conformance": {
46
54
  "types": "./dist/testing/extension-conformance.d.ts",
47
55
  "default": "./dist/testing/extension-conformance.js"
@@ -123,6 +131,7 @@
123
131
  "packages/work-tools",
124
132
  "packages/policy",
125
133
  "packages/model-router",
134
+ "packages/enterprise-postgres",
126
135
  "packages/browser",
127
136
  "packages/ag-ui",
128
137
  "packages/prism-*"
@@ -138,7 +147,7 @@
138
147
  "format": "biome format --write .",
139
148
  "format:check": "biome format .",
140
149
  "pack:dry-run": "npm pack --dry-run && npm run pack:dry-run --workspaces --if-present",
141
- "test:postgres": "npm run test:postgres --workspace @arnilo/prism-session-store-postgres && npm run test:postgres --workspace @arnilo/prism-memory",
150
+ "test:postgres": "node scripts/require-postgres-url.mjs && npm run test:postgres --workspace @arnilo/prism-session-store-postgres && npm run test:postgres --workspace @arnilo/prism-memory && npm run test:postgres --workspace @arnilo/prism-enterprise-postgres && node --test scripts/phase7-conformance.test.mjs",
142
151
  "release:dry-run": "npm run sdk:ready",
143
152
  "release:check": "node scripts/release.mjs check",
144
153
  "release:publish": "node scripts/release.mjs publish",