@arnilo/prism 0.0.23 → 0.0.25

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 (49) hide show
  1. package/CHANGELOG.md +39 -3
  2. package/dist/agent-event-source.d.ts +11 -0
  3. package/dist/agent-event-source.js +512 -0
  4. package/dist/agent-loops.js +37 -5
  5. package/dist/agent-run-state.d.ts +27 -1
  6. package/dist/agent-run-state.js +86 -5
  7. package/dist/agents.js +890 -78
  8. package/dist/contracts.d.ts +338 -4
  9. package/dist/contracts.js +53 -0
  10. package/dist/index.d.ts +9 -4
  11. package/dist/index.js +5 -2
  12. package/dist/testing/agent-event-source-conformance.d.ts +4 -0
  13. package/dist/testing/agent-event-source-conformance.js +54 -0
  14. package/dist/testing/persistence-schema.d.ts +2 -2
  15. package/dist/testing/persistence-schema.js +58 -21
  16. package/dist/testing/tool-effect-store-conformance.d.ts +9 -0
  17. package/dist/testing/tool-effect-store-conformance.js +85 -0
  18. package/dist/tool-effects.d.ts +15 -0
  19. package/dist/tool-effects.js +338 -0
  20. package/dist/tools.d.ts +4 -1
  21. package/dist/tools.js +219 -9
  22. package/docs/0.1.0-readiness.md +10 -9
  23. package/docs/a2a.md +6 -2
  24. package/docs/ag-ui-adoption.md +77 -0
  25. package/docs/ag-ui.md +77 -42
  26. package/docs/agent-events.md +5 -1
  27. package/docs/agent-loops.md +9 -1
  28. package/docs/agent-session-runtime.md +9 -2
  29. package/docs/browser-automation.md +2 -0
  30. package/docs/coding-agent-tools.md +2 -0
  31. package/docs/coding-security.md +1 -1
  32. package/docs/database-persistence.md +2 -0
  33. package/docs/enterprise-postgres-state.md +5 -1
  34. package/docs/host-security.md +8 -1
  35. package/docs/index.md +13 -11
  36. package/docs/mcp-tools.md +19 -2
  37. package/docs/migration.md +46 -0
  38. package/docs/performance.md +25 -0
  39. package/docs/postgres-persistence.md +5 -2
  40. package/docs/public-contracts.md +2 -0
  41. package/docs/release-and-install.md +70 -690
  42. package/docs/server.md +10 -6
  43. package/docs/sqlite-persistence.md +10 -2
  44. package/docs/supervisors.md +6 -0
  45. package/docs/tool-effects.md +95 -0
  46. package/docs/tools.md +4 -0
  47. package/docs/work-tools.md +4 -0
  48. package/docs/workflows.md +1 -1
  49. package/package.json +11 -3
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
 
@@ -108,6 +109,7 @@ const handler = createPrismHandler({
108
109
  - Workflow exposure requires its existing `WorkflowCheckpointAdapter`; no server-owned database exists.
109
110
  - Schedule exposure is optional and may be one service or an authorization-selected resolver. Returned service ownership must exactly match authorized tenant/account/user scope; otherwise request is forbidden.
110
111
  - `PrismWorkflowExposure.runOptions` can supply agent/tool/policy/resume-validator wiring. Server-owned ownership, signal, checkpoint, redactor, run ID, and event bus fields cannot be overridden.
112
+ - The agent resume endpoint (`/prism/agents/{id}/runs/{runId}/resume`) accepts `{ decision: "approve" | "deny" }` or `{ decisions: [{ approvalId, outcome, reason?, modifiedArguments?, elicitation? }] }` next to `expectedVersion` — exactly one of `decision`/`decisions`. Entries are validated at the boundary (count ≤ 128, four outcomes, bounded reason/payloads) and core applies them atomically under the run's CAS; unknown ids, stale versions, and malformed batches fail closed without touching the run.
111
113
  - Host/origin checks and CORS headers activate only when their allow-lists are configured. Hosts still own reverse-proxy trust and canonical host handling.
112
114
 
113
115
  Default/hard ceilings:
@@ -135,7 +137,8 @@ Compose beside `createPrismHandler` — Prism starts no listener, container orch
135
137
  | `createPrismHealthHandler` | `GET /health`, `/livez`, `/readyz`. Minimal JSON; `?detail=1` requires `authorizeDetail`. No secrets/tenant payloads by default. Ready fails while draining. |
136
138
  | `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
139
  | `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. |
140
+ | `createPrismAgentEventReplay` | Shared `AgentEventSource` page/follow semantics for exact-owned runs. |
141
+ | `createPrismEventReplay` / `createPrismReplayHandler` | Compatible ownership-scoped legacy `queryEvents` pages (`redacted: true`). Does not re-run work. Unauthorized replay denies. |
139
142
  | `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
143
  | `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
144
  | `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 +156,7 @@ Network-free demo: [`examples/server-deployment-seams.ts`](../examples/server-de
153
156
  - Agent tools and workflow tool nodes still need their own `PermissionPolicy`, `ToolValidator`, and `ExecutionPolicy`. HTTP authorization does not replace side-effect policy.
154
157
  - 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
158
  - 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.
159
+ - 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
160
  - 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
161
  - Source inputs/resource URLs remain host responsibilities and use existing resource/media SSRF policies. Server package does not fetch URLs.
159
162
  - 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 +175,6 @@ A2A routes are not added to `createPrismHandler()`. Install `@arnilo/prism-super
172
175
  - [A2A interoperability](a2a.md): separately mounted A2A 1.0 handler/client.
173
176
  - [Conversations](conversations.md): durable user-scoped conversation service, replay, branches, export, deletion.
174
177
  - [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.
178
+ - [Frontend interoperability (AG-UI and ACP)](ag-ui.md): separately installed authorized AG-UI Web handler.
179
+ - [AG-UI adoption evaluation](ag-ui-adoption.md): official 0.0.57 support matrix and MCP/A2A follow-up scope.
176
180
  - [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.
@@ -50,10 +50,16 @@ const supervisor = createSupervisor({
50
50
  const result = await supervisor.delegate({ childId: "research", input: "Check sources" });
51
51
  ```
52
52
 
53
+ ## Durable child approvals
54
+
55
+ With `checkpoints` + `definitionRevision`, every child run is durable with `interruptBeforeTool: true`. A child that suspends on pending decisions throws `AgentDelegationSuspendedError` out of `delegate()`; when the delegation runs inside a root agent's tool, core converts it into a root suspension whose `interruption.pendingDecisions` carry hashed root-visible approval ids (`sub_<sha256(runId:childApprovalId)>`) and `attribution.path` (redacted child ids, root first, at most 8 deep). Root decisions route back through the same CAS rules: pass `supervisor.resumeNestedRun` as `resumeNestedRun` in the root run's `runState` and in every `resumeAgentRun` options object. The supervisor rebuilds the child from a bounded delegation mapping stored in the same checkpoint store (child id, delegation/thread ids, redacted input, version), re-runs the `before` hook so its narrowing applies to the resumed run (hooks must be idempotent), and re-attributes re-suspensions recursively, so grandchild decisions surface with the full path. A delegating child's own `interruptBeforeTool` also gates its delegate tool, so hosts approve delegation and the child's own side effects as separate stages. Root `*_for_run` stickies record the attribution path and only match the same delegation path; child stickies live on the child run and expire with it. A root approval never widens the child: the child's narrowed permission re-runs at dispatch. Unknown or foreign nested run ids fail closed with one non-enumerating error. Child factories must return stable configs and a durable (or rebuild-stable) session store for resume to work.
56
+
53
57
  ## Extension and configuration notes
54
58
 
55
59
  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
60
 
61
+ Supervisors propagate parent `identity` and `effectStore` to every child agent/run so delegated tool effects stay under the same ownership scope.
62
+
57
63
  ## Security and performance notes
58
64
 
59
65
  - 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).
@@ -116,6 +116,10 @@ Call `begin({ identity, key, op })` **before** the external effect. After it suc
116
116
  | Process wall time | 60 s / 10 min |
117
117
  | Concurrent CLI / identity | 2 / 8 |
118
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
+
119
123
  ## Security
120
124
 
121
125
  - Require host-verified `AgentIdentity`; no cross-identity configDir reuse.
package/docs/workflows.md CHANGED
@@ -73,7 +73,7 @@ All workflow limits and runtime `concurrency` reject non-safe integers, zero, ne
73
73
 
74
74
  A function node returns `suspend({ reason, data?, resumeSchema? })` to persist `status: "suspended"`. Its next invocation receives `ctx.resume` only after an approved resume. `resumeWorkflow(workflow, { runId }, options)` validates schema/version/ownership/`definitionHash`, claims the checkpoint before node execution, and continues the suspended node. Denial persists terminal `denied` status without invoking it. Existing failed/aborted checkpoint resume remains available without a human decision.
75
75
 
76
- Coding-agent ask-user glue (opt-in, no Goal DB): `suspendAskUserDecision(request)` wraps `suspend` with durable question/options/`selectionMode`/`allowCustom` data + resume schema; resume with `createAskUserDecisionResumeValidator()` or `validateAskUserDecisionResume`. Goal→verify: `runCodingGoalVerify` / `createCodingGoalVerifyWorkflow` compose plan Markdown → named checks → approve suspend → bounded handoff over the same primitives (`examples/coding-goal-verify.ts`).
76
+ Coding-agent ask-user glue (opt-in, no Goal DB): `suspendAskUserDecision(request)` wraps `suspend` with durable question/options/`selectionMode`/`allowCustom` data + resume schema; resume with `createAskUserDecisionResumeValidator()` or `validateAskUserDecisionResume`. Goal→verify: `runCodingGoalVerify` / `createCodingGoalVerifyWorkflow` compose plan Markdown → named checks → approve suspend → bounded handoff over the same primitives (`examples/coding-goal-verify.ts`). When a workflow node wraps a durable agent run, that run's shared pending-decision batch (Task 2) is the approval authority — workflow `suspend`/`resume` stay workflow-scoped and do not mint a parallel decision store.
77
77
 
78
78
  Every node receives bounded `ctx.state`, `ctx.stateVersion`, and async `ctx.updateState(patch, { mode: "merge" | "replace" })`. Updates serialize, validate, redact, and snapshot before checkpoint save. `workflowNode({ workflow })` runs its child with the same ownership, agent/tool registries, execution policy, redactor, signal, checkpoints, and event bus; child state replaces parent state after success.
79
79
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arnilo/prism",
3
- "version": "0.0.23",
3
+ "version": "0.0.25",
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"
@@ -133,13 +141,13 @@
133
141
  "clean": "rm -rf dist packages/*/dist",
134
142
  "build": "npm run clean && npm run build:core && npm run build --workspaces --if-present",
135
143
  "typecheck": "npm run build && npm run typecheck --workspaces --if-present && tsc -p examples --noEmit",
136
- "test": "npm run build && node --test dist/__tests__/*.test.js && node --test scripts/release-gate.test.mjs scripts/tooling-gate.test.mjs scripts/budget-gate.test.mjs && npm run test --workspaces --if-present",
144
+ "test": "npm run build && node --test dist/__tests__/*.test.js && node --test scripts/release-gate.test.mjs scripts/tooling-gate.test.mjs scripts/budget-gate.test.mjs scripts/phase8-conformance.test.mjs && npm run test --workspaces --if-present",
137
145
  "test:coverage": "node --test --experimental-test-coverage --test-coverage-lines=60 --test-coverage-functions=70 --test-coverage-branches=75 --test-coverage-exclude='**/__tests__/**' --test-coverage-exclude='**/node_modules/**' --test-coverage-exclude='**/scripts/**' dist/__tests__/*.test.js",
138
146
  "lint": "biome lint .",
139
147
  "format": "biome format --write .",
140
148
  "format:check": "biome format .",
141
149
  "pack:dry-run": "npm pack --dry-run && npm run pack:dry-run --workspaces --if-present",
142
- "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",
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",
143
151
  "release:dry-run": "npm run sdk:ready",
144
152
  "release:check": "node scripts/release.mjs check",
145
153
  "release:publish": "node scripts/release.mjs publish",