@arnilo/prism 0.0.96 → 0.1.1
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 +290 -2
- package/README.md +17 -3
- package/dist/agent-definitions.js +2 -3
- package/dist/agent-event-source.d.ts +11 -0
- package/dist/agent-event-source.js +512 -0
- package/dist/agent-loops.d.ts +5 -0
- package/dist/agent-loops.js +99 -14
- package/dist/agent-run-lifecycle.d.ts +5 -2
- package/dist/agent-run-lifecycle.js +18 -2
- package/dist/agent-run-state.d.ts +27 -1
- package/dist/agent-run-state.js +113 -7
- package/dist/agents.d.ts +3 -1
- package/dist/agents.js +1255 -129
- package/dist/artifacts.d.ts +132 -0
- package/dist/artifacts.js +44 -0
- package/dist/cache-helpers.js +18 -9
- package/dist/checkpoints.d.ts +4 -0
- package/dist/checkpoints.js +17 -9
- package/dist/cli-init.js +3 -7
- package/dist/cli-runner.d.ts +2 -6
- package/dist/cli-runner.js +71 -33
- package/dist/compaction.js +5 -4
- package/dist/config.js +7 -4
- package/dist/content.js +26 -24
- package/dist/context-budget.d.ts +67 -0
- package/dist/context-budget.js +288 -0
- package/dist/contracts.d.ts +590 -8
- package/dist/contracts.js +142 -1
- package/dist/contribution-parsing.js +6 -2
- package/dist/contributions.d.ts +2 -0
- package/dist/contributions.js +3 -0
- package/dist/conversations.d.ts +50 -0
- package/dist/conversations.js +98 -0
- package/dist/credentials.d.ts +22 -2
- package/dist/credentials.js +18 -3
- package/dist/devices.d.ts +94 -0
- package/dist/devices.js +138 -0
- package/dist/event-multiplexer.js +18 -4
- package/dist/extensions.d.ts +18 -1
- package/dist/extensions.js +79 -6
- package/dist/feedback.js +12 -10
- package/dist/guardrails.d.ts +1 -1
- package/dist/guardrails.js +26 -17
- package/dist/identity.d.ts +92 -0
- package/dist/identity.js +265 -0
- package/dist/index.d.ts +94 -72
- package/dist/index.js +48 -36
- package/dist/input.d.ts +10 -1
- package/dist/input.js +152 -52
- package/dist/instruction-injection.d.ts +1 -1
- package/dist/middleware.js +9 -1
- package/dist/models.d.ts +2 -0
- package/dist/models.js +3 -0
- package/dist/node/agent-definitions.js +16 -8
- package/dist/node/contribution-discovery.d.ts +1 -2
- package/dist/node/contribution-discovery.js +3 -3
- package/dist/node/session-store-jsonl.js +13 -7
- package/dist/node/settings.d.ts +1 -1
- package/dist/node/settings.js +1 -1
- package/dist/node/system-project-prompts.js +2 -4
- package/dist/node/trust.js +1 -1
- package/dist/persistence-lifecycle.d.ts +103 -0
- package/dist/persistence-lifecycle.js +202 -0
- package/dist/provider-events.d.ts +1 -0
- package/dist/provider-events.js +6 -1
- package/dist/provider-request-policy.js +3 -4
- package/dist/providers/media.d.ts +1 -1
- package/dist/providers/openai-compatible.d.ts +46 -1
- package/dist/providers/openai-compatible.js +123 -53
- package/dist/providers/openai-primitives.js +10 -7
- package/dist/providers/transport.d.ts +6 -0
- package/dist/providers/transport.js +21 -0
- package/dist/providers.d.ts +2 -0
- package/dist/providers.js +3 -0
- package/dist/redaction.d.ts +1 -0
- package/dist/redaction.js +26 -9
- package/dist/resources.d.ts +2 -2
- package/dist/resources.js +2 -2
- package/dist/retry.d.ts +5 -0
- package/dist/retry.js +8 -1
- package/dist/rpc.js +55 -11
- package/dist/run-ledger.d.ts +6 -0
- package/dist/run-ledger.js +16 -13
- package/dist/run-limits.js +49 -10
- package/dist/secure-agent.js +8 -2
- package/dist/security.js +7 -2
- package/dist/session-stores.d.ts +7 -2
- package/dist/session-stores.js +195 -21
- package/dist/skill-disclosure.d.ts +35 -0
- package/dist/skill-disclosure.js +101 -0
- package/dist/skill-load.d.ts +25 -0
- package/dist/skill-load.js +112 -0
- package/dist/structured-output.d.ts +5 -1
- package/dist/structured-output.js +20 -2
- package/dist/system-prompts.js +7 -2
- package/dist/testing/agent-event-source-conformance.d.ts +4 -0
- package/dist/testing/agent-event-source-conformance.js +54 -0
- package/dist/testing/compaction-conformance.js +5 -1
- package/dist/testing/extension-conformance.js +15 -3
- package/dist/testing/feedback.d.ts +1 -3
- package/dist/testing/feedback.js +1 -1
- package/dist/testing/persistence-schema.d.ts +2 -2
- package/dist/testing/persistence-schema.js +280 -35
- package/dist/testing/provider-conformance.js +3 -3
- package/dist/testing/run-ledger-conformance.js +1 -1
- package/dist/testing/session-store-conformance.d.ts +6 -0
- package/dist/testing/session-store-conformance.js +37 -2
- package/dist/testing/tool-conformance.js +30 -5
- package/dist/testing/tool-effect-store-conformance.d.ts +9 -0
- package/dist/testing/tool-effect-store-conformance.js +85 -0
- package/dist/thinking.js +4 -1
- package/dist/tool-effects.d.ts +15 -0
- package/dist/tool-effects.js +352 -0
- package/dist/tool-result-fold.d.ts +40 -0
- package/dist/tool-result-fold.js +176 -0
- package/dist/tools.d.ts +8 -3
- package/dist/tools.js +248 -13
- package/docs/0.1.0-readiness.md +215 -0
- package/docs/a2a.md +33 -2
- package/docs/acp.md +152 -0
- package/docs/ag-ui-adoption.md +77 -0
- package/docs/ag-ui.md +225 -0
- package/docs/agent-events.md +34 -3
- package/docs/agent-identity.md +144 -0
- package/docs/agent-loops.md +17 -2
- package/docs/agent-session-runtime.md +21 -4
- package/docs/browser-automation.md +5 -0
- package/docs/caveman.md +129 -0
- package/docs/cli-rpc.md +3 -6
- package/docs/coding-agent-tools.md +229 -25
- package/docs/coding-security.md +77 -11
- package/docs/compaction-and-retry.md +5 -2
- package/docs/compaction-llm.md +20 -1
- package/docs/compaction-observational-memory.md +52 -8
- package/docs/context-and-skills.md +94 -7
- package/docs/contribution-registries.md +1 -0
- package/docs/conversations.md +135 -0
- package/docs/credential-storage.md +34 -1
- package/docs/credentials-and-redaction.md +11 -1
- package/docs/database-persistence.md +27 -7
- package/docs/device-adapters.md +97 -0
- package/docs/enterprise-postgres-state.md +178 -0
- package/docs/evaluations.md +14 -1
- package/docs/extensions.md +4 -1
- package/docs/forge-integration.md +113 -0
- package/docs/guardrails.md +16 -2
- package/docs/host-security.md +35 -4
- package/docs/index.md +69 -37
- package/docs/input-and-prompt-assembly.md +8 -7
- package/docs/language-intelligence.md +162 -0
- package/docs/mcp-tools.md +62 -5
- package/docs/middleware-hooks.md +2 -2
- package/docs/migration.md +427 -2
- package/docs/model-routing.md +111 -0
- package/docs/multimodal-content.md +8 -5
- package/docs/node-jsonl-session-store.md +1 -1
- package/docs/observability.md +2 -0
- package/docs/openapi-tools.md +56 -0
- package/docs/performance.md +282 -0
- package/docs/policy-and-audit.md +171 -0
- package/docs/ponytail.md +127 -0
- package/docs/postgres-persistence.md +8 -4
- package/docs/process-sessions.md +147 -0
- package/docs/provider-caching.md +13 -1
- package/docs/provider-conformance.md +29 -5
- package/docs/provider-packages.md +43 -2
- package/docs/provider-request-policies.md +2 -0
- package/docs/providers/ai-sdk.md +24 -7
- package/docs/providers/alibaba.md +179 -0
- package/docs/providers/anthropic.md +93 -0
- package/docs/providers/azure.md +74 -0
- package/docs/providers/bedrock.md +72 -0
- package/docs/providers/google.md +89 -0
- package/docs/providers/ollama.md +166 -0
- package/docs/providers/openai-compatible.md +31 -2
- package/docs/providers/openai.md +24 -5
- package/docs/providers/openrouter.md +2 -0
- package/docs/providers/vertex.md +71 -0
- package/docs/public-contracts.md +68 -4
- package/docs/rag.md +41 -12
- package/docs/release-and-install.md +362 -208
- package/docs/resource-loading.md +3 -0
- package/docs/runs-and-usage.md +3 -0
- package/docs/server.md +44 -6
- package/docs/session-store-conformance.md +2 -0
- package/docs/session-stores.md +41 -2
- package/docs/sqlite-persistence.md +11 -3
- package/docs/structured-output.md +7 -1
- package/docs/supervisors.md +8 -0
- package/docs/tool-effects.md +95 -0
- package/docs/tools.md +5 -0
- package/docs/work-artifacts-and-review.md +102 -0
- package/docs/work-connectors.md +32 -0
- package/docs/work-tools.md +137 -0
- package/docs/workflows.md +6 -0
- package/docs/working-and-semantic-memory.md +40 -7
- package/package.json +30 -7
- package/templates/init/providers.json +22 -0
- package/docs/review-coverage-2026-07-14.md +0 -260
- package/docs/review-coverage-2026-07-15.md +0 -193
- package/docs/review-coverage-2026-07-17-provider-validation.md +0 -192
- package/docs/review-coverage-2026-07-19-phase-3.md +0 -174
- package/docs/review-coverage-2026-07-20-phase-4.md +0 -175
package/docs/resource-loading.md
CHANGED
|
@@ -87,6 +87,8 @@ console.log(bytes.byteLength, manifest.name, prompt);
|
|
|
87
87
|
- Helpers do not choose a loader by URI scheme. Hosts can use contribution registries or their own routing when they need that.
|
|
88
88
|
- Helpers do not execute loaded text or imported modules. Package activation remains a host decision.
|
|
89
89
|
- `loadManifestResource()` only validates manifest data; it does not register manifest contributions.
|
|
90
|
+
- `@arnilo/prism-rag` `createResourceDocumentLoader({ loader, context? })` is the RAG bridge for an already-authorized artifact. It calls the supplied `ResourceLoader` once for a caller-selected URI, preserves text/binary media type, and adds no URI routing, local-file discovery, or network fallback. Pair it with a bounded RAG `Parser`; `replaceDocument()` then chunks and atomically replaces one exact RAG source.
|
|
91
|
+
- For public web documents, use `createWebFetchDocumentLoader({ fetcher })` with a host-configured `@arnilo/prism-web-tools` fetch adapter instead of adding web I/O to a `ResourceLoader`. It reuses normalized citation/trust data; the web adapter retains DNS/SSRF policy ownership.
|
|
90
92
|
|
|
91
93
|
## Security and performance notes
|
|
92
94
|
|
|
@@ -96,6 +98,7 @@ console.log(bytes.byteLength, manifest.name, prompt);
|
|
|
96
98
|
- Helpers call `loader.load()` once per helper call and do not cache, scan, list, watch, poll, or discover packages.
|
|
97
99
|
- JSON parsing fails closed for invalid JSON or non-object JSON.
|
|
98
100
|
- Do not put resolved credential values, tokens, headers, or executable code in loaded config, manifests, prompts, skills, or metadata.
|
|
101
|
+
- A RAG resource loader is not permission escalation: pass the same host-owned trust/permission context used for any resource load. HTML/PDF parser output and web content are untrusted inert text; compressed/scanned PDFs require a host parser rather than partial fallback.
|
|
99
102
|
|
|
100
103
|
## MCP resources
|
|
101
104
|
|
package/docs/runs-and-usage.md
CHANGED
|
@@ -286,6 +286,7 @@ console.log(cacheUsageReport(aggregate?.usage));
|
|
|
286
286
|
- **Idempotency is host-owned.** The runtime writes the key into `RunRecord.idempotencyKey`; enforcing unique keys and deduplicating retries is the host adapter's responsibility.
|
|
287
287
|
- **Tenant isolation.** `OwnershipScope` fields are copied from the active ownership scope, but the runtime does not enforce tenant isolation for ledger rows. Feedback is stricter: append/query/delete require tenant plus account/user, and first-party stores compare the exact scope to the linked run.
|
|
288
288
|
- **Feedback privacy.** Comments/tags/metadata can contain PII. Configure a feedback redactor, apply retention, and call owned `delete()` for erasure. Never copy comments or tag values into metric labels.
|
|
289
|
+
- **Policy audit is separate.** Enterprise allow/deny/modify/approval rows with evidence refs live in optional `@arnilo/prism-policy`, not `RunLedger`. See [Policy and audit](policy-and-audit.md).
|
|
289
290
|
|
|
290
291
|
## Optional batching and durability
|
|
291
292
|
|
|
@@ -304,6 +305,7 @@ Runtime session snapshots cache one leaf/generation for at most one second. Succ
|
|
|
304
305
|
|
|
305
306
|
## Related APIs
|
|
306
307
|
|
|
308
|
+
- [Policy and audit](policy-and-audit.md): optional enterprise decision ledger (separate from run usage rows).
|
|
307
309
|
- [Performance limits](performance.md): batching, cursor keys, and production sizing assumptions.
|
|
308
310
|
- [Agent/session runtime](agent-session-runtime.md): `session.run()` and runtime event emission.
|
|
309
311
|
- [Agent events](agent-events.md): `AgentEvent` union and `session.subscribe()`.
|
|
@@ -315,3 +317,4 @@ Runtime session snapshots cache one leaf/generation for at most one second. Succ
|
|
|
315
317
|
- [Provider caching](provider-caching.md): cache hints and `cacheUsageReport()` diagnostics.
|
|
316
318
|
- [Observability](observability.md): `provider_turn_*` events, tool duration metadata, OpenTelemetry adapter.
|
|
317
319
|
- [Public contracts](public-contracts.md): full contract inventory.
|
|
320
|
+
- [Frontend interoperability (AG-UI and ACP)](ag-ui.md): `AgentEventRecord` pages become redacted, ownership-scoped, at-least-once replay only through an explicit host adapter.
|
package/docs/server.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
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, and
|
|
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
|
-
No listener starts on import. Empty `agents`/`workflows` maps expose nothing. Authentication, authorization, route selection, durable stores, TLS, rate limiting, and framework/serverless adaptation remain host-owned.
|
|
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
|
|
|
9
9
|
## When to use it
|
|
10
10
|
|
|
@@ -15,15 +15,19 @@ Use `AgentSession` or workflow APIs directly for in-process applications. Do not
|
|
|
15
15
|
## Inputs / request
|
|
16
16
|
|
|
17
17
|
```ts
|
|
18
|
+
const drain = createPrismDrainController({ deadlineMs: 30_000 });
|
|
18
19
|
const handler = createPrismHandler({
|
|
19
|
-
agents?: Record<string, Agent | PrismAgentExposure>,
|
|
20
|
+
agents?: Record<string, Agent | PrismAgentExposure>, // exposure may include events + resolveRun
|
|
20
21
|
agentRuns?: Record<string, PrismAgentRunExposure>, // explicit durable status/resume only
|
|
21
22
|
workflows?: Record<string, PrismWorkflowExposure>,
|
|
22
23
|
schedules?: WorkflowSchedules | ((authorization, signal) => WorkflowSchedules),
|
|
23
24
|
authorize: async ({ request, operation, capabilityId }) => false | {
|
|
24
25
|
ownership: { tenantId?: string; accountId?: string; userId?: string },
|
|
26
|
+
identity?: AgentIdentity, // optional host-verified; must match ownership
|
|
25
27
|
metadata?: Record<string, unknown>,
|
|
26
28
|
},
|
|
29
|
+
drain?, // blocks admit ops with 503 while draining
|
|
30
|
+
rateLimit?, // host adapter after authorize, before session/run create
|
|
27
31
|
basePath?: "/prism",
|
|
28
32
|
allowedHosts?: string[],
|
|
29
33
|
allowedOrigins?: string[],
|
|
@@ -31,6 +35,7 @@ const handler = createPrismHandler({
|
|
|
31
35
|
limits?: PrismServerLimits,
|
|
32
36
|
disconnectAborts?: boolean,
|
|
33
37
|
});
|
|
38
|
+
const health = createPrismHealthHandler({ ready: () => store.ping(), drain });
|
|
34
39
|
```
|
|
35
40
|
|
|
36
41
|
At least one non-empty ownership field must come from `authorize()`. Request JSON never chooses ownership.
|
|
@@ -41,6 +46,7 @@ At least one non-empty ownership field must come from `authorize()`. Request JSO
|
|
|
41
46
|
| `POST /prism/agents/:id/stream` | `agent.stream` | same; SSE response |
|
|
42
47
|
| `GET /prism/agents/:id/runs/:runId` | `agent.status` | none; redacted public state/version only |
|
|
43
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` |
|
|
44
50
|
| `POST /prism/workflows/:id/runs` | `workflow.run` | `{ "input": unknown, "runId"?: string }` |
|
|
45
51
|
| `POST /prism/workflows/:id/stream` | `workflow.stream` | same; SSE response |
|
|
46
52
|
| `POST /prism/workflows/:id/enqueue` | `workflow.enqueue` | `{ "input": unknown, "runId"?: string }`; returns `202` queued handle |
|
|
@@ -59,7 +65,7 @@ POST routes require `Content-Type: application/json`. Capability/run IDs are bou
|
|
|
59
65
|
|
|
60
66
|
## Outputs / response / events
|
|
61
67
|
|
|
62
|
-
Direct routes return bounded JSON.
|
|
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.
|
|
63
69
|
|
|
64
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.
|
|
65
71
|
|
|
@@ -103,6 +109,7 @@ const handler = createPrismHandler({
|
|
|
103
109
|
- Workflow exposure requires its existing `WorkflowCheckpointAdapter`; no server-owned database exists.
|
|
104
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.
|
|
105
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.
|
|
106
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.
|
|
107
114
|
|
|
108
115
|
Default/hard ceilings:
|
|
@@ -117,14 +124,39 @@ Default/hard ceilings:
|
|
|
117
124
|
| concurrent runs | 16 | 256 |
|
|
118
125
|
| subscriber queue | 128 | 4,096 |
|
|
119
126
|
| request/run timeout | 120 s | 30 min |
|
|
127
|
+
| health response | 4 KiB | 64 KiB |
|
|
128
|
+
| drain admit cutoff | 30 s | 5 min |
|
|
129
|
+
| replay page / cursor | 100 / 4 KiB | 500 / 16 KiB |
|
|
130
|
+
|
|
131
|
+
## Deployment seams (optional)
|
|
132
|
+
|
|
133
|
+
Compose beside `createPrismHandler` — Prism starts no listener, container orchestrator, or queue worker.
|
|
134
|
+
|
|
135
|
+
| Helper | Role |
|
|
136
|
+
| --- | --- |
|
|
137
|
+
| `createPrismHealthHandler` | `GET /health`, `/livez`, `/readyz`. Minimal JSON; `?detail=1` requires `authorizeDetail`. No secrets/tenant payloads by default. Ready fails while draining. |
|
|
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. |
|
|
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. |
|
|
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. |
|
|
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). |
|
|
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). |
|
|
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). |
|
|
145
|
+
|
|
146
|
+
**Queues:** Redis/SQS/other adapters are absent. Postgres checkpoint polling via `createWorkflowCoordinator` remains the default background path until a measured polling/load justification is recorded.
|
|
147
|
+
|
|
148
|
+
Network-free demo: [`examples/server-deployment-seams.ts`](../examples/server-deployment-seams.ts).
|
|
120
149
|
|
|
121
150
|
## Security and performance notes
|
|
122
151
|
|
|
123
152
|
- `authorize()` is required and runs for every matched operation before capability lookup or body execution. Return `false` on missing/invalid credentials. Do not trust caller ownership fields.
|
|
153
|
+
- Optional `authorization.identity` must be host-verified (`AgentIdentity.verified`); the handler asserts activity and ownership match, then forwards identity into agent runs. Caller-asserted identity without a host verifier is rejected.
|
|
124
154
|
- Use authorization metadata only for non-secret audit context. Never put credentials in metadata, input, route IDs, run IDs, checkpoints, events, or responses.
|
|
125
155
|
- Configure `SecretRedactor` before runs. Redaction matches known secrets; it is not DLP.
|
|
126
156
|
- Agent tools and workflow tool nodes still need their own `PermissionPolicy`, `ToolValidator`, and `ExecutionPolicy`. HTTP authorization does not replace side-effect policy.
|
|
127
|
-
- Host and origin allow-lists are exact string matches. Configure reverse-proxy normalization, TLS,
|
|
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.
|
|
158
|
+
- Health endpoints reveal process/liveness only by default; detail flags require host authorize and must omit secrets/tenant dumps.
|
|
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`.
|
|
128
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.
|
|
129
161
|
- Source inputs/resource URLs remain host responsibilities and use existing resource/media SSRF policies. Server package does not fetch URLs.
|
|
130
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.
|
|
@@ -134,9 +166,15 @@ A2A routes are not added to `createPrismHandler()`. Install `@arnilo/prism-super
|
|
|
134
166
|
|
|
135
167
|
## Related APIs
|
|
136
168
|
|
|
169
|
+
- [Agent identity](agent-identity.md): optional verified identity on authorize results.
|
|
170
|
+
- [Performance](performance.md): capacity notes for concurrent runs and deployment probes.
|
|
137
171
|
- [Agent/session runtime](agent-session-runtime.md): direct result and event stream semantics.
|
|
138
|
-
- [Workflows](workflows.md): durable checkpoints, status, cancellation,
|
|
172
|
+
- [Workflows](workflows.md): durable checkpoints, status, cancellation, exact-once resume, and `createWorkflowCoordinator` workers.
|
|
139
173
|
- [MCP client and server exposure](mcp-tools.md): selected MCP capabilities and web-standard MCP transport.
|
|
140
174
|
- [Host security guide](host-security.md): remote-boundary checklist.
|
|
141
175
|
- [A2A interoperability](a2a.md): separately mounted A2A 1.0 handler/client.
|
|
176
|
+
- [Conversations](conversations.md): durable user-scoped conversation service, replay, branches, export, deletion.
|
|
177
|
+
- [Work artifacts and review](work-artifacts-and-review.md): durable artifact review service, revisions, approvals, authorized expiring delivery links.
|
|
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.
|
|
142
180
|
- [Release and install](release-and-install.md): optional package installation and profiles.
|
|
@@ -25,6 +25,8 @@ Use this helper when implementing a DB-backed `SessionStore` (for example, the r
|
|
|
25
25
|
- session ids remain isolated (`assertSessionStoreConforms` always probes a secondary session)
|
|
26
26
|
- optional concurrent fork children of the same parent succeed when `exerciseConcurrentParentAppend: true`
|
|
27
27
|
- optional durable reopen/idempotency survival when `runSessionStoreConformance(..., { exerciseReopen: true })`
|
|
28
|
+
- optional `searchSessions` bounds/ownership/empty-page checks when `exerciseSearchSessions: true` (`assertSessionStoreSearchSessions`)
|
|
29
|
+
- optional `searchSessions` bounds/ownership/empty-page checks when `exerciseSearchSessions: true` (`assertSessionStoreSearchSessions`)
|
|
28
30
|
|
|
29
31
|
## Inputs / request
|
|
30
32
|
|
package/docs/session-stores.md
CHANGED
|
@@ -24,13 +24,16 @@ import type { SessionStore, SessionEntry } from "@arnilo/prism";
|
|
|
24
24
|
| `list(sessionId)` | Return all entries for one session in stored order. Development fallback for branch reads. |
|
|
25
25
|
| `get?(id)` | Return one entry by id, if present. Optional. |
|
|
26
26
|
| `readBranchPath?(query)` | Optional DB-friendly branch read. Return one branch's ancestor chain as a `PersistencePage<SessionEntry>` so the runtime can avoid `list(sessionId)`. |
|
|
27
|
+
| `searchSessions?(query)` | Optional bounded session search (`SessionSearchQuery` → `PersistencePage<SessionSearchHit>`). SQLite/Postgres implement FTS + metadata filters; memory default is linear; JSONL throws `SessionSearchUnsupportedError`. |
|
|
27
28
|
|
|
28
29
|
Public helpers:
|
|
29
30
|
|
|
30
31
|
| Helper | Purpose |
|
|
31
32
|
| --- | --- |
|
|
32
33
|
| `createSessionEntry(options)` | Build a `SessionEntry` with generated `id`/`timestamp` when omitted. |
|
|
33
|
-
| `createMemorySessionStore(initialEntries?)` | Built-in in-memory `SessionStore`. |
|
|
34
|
+
| `createMemorySessionStore(initialEntries?, options?)` | Built-in in-memory `SessionStore`. `options.sessionSearchMode`: `"linear"` (default) or `"unsupported"` (throws `SessionSearchUnsupportedError`). |
|
|
35
|
+
| `resolveSessionSearchQuery(query)` | Validate/clamp search limits (page, query bytes, snippet, cursor, linear/FTS caps). |
|
|
36
|
+
| `SessionIndex` | Narrow search seam (`search(query)`); adapters may expose this instead of `SessionStore.searchSessions`. |
|
|
34
37
|
| `getSessionBranchEntries(entries, options)` | Return root-to-leaf entries for a leaf id (sync array path). |
|
|
35
38
|
| `getSessionBranchEntries(reader, query)` | Async overload for a `BranchReader` / `readBranchPath` implementation. |
|
|
36
39
|
| `listSessionBranches(entries)` | List every branch handle as `{ leafId, entries }`. Pair with `sessionId` for a durable `(sessionId, leafId)` branch handle. |
|
|
@@ -97,7 +100,7 @@ await store.append(entry, options);
|
|
|
97
100
|
}
|
|
98
101
|
```
|
|
99
102
|
|
|
100
|
-
Recognize it with `isSessionAppendConflict(error)`, not message text. Built-in stores reject duplicate entry ids, dangling `expectedParentId` values, and exact idempotency retries. They allow two distinct children of the same existing parent because that is a branch/fork, not parent-order corruption. Production stores may add a stricter branch-tip compare-and-swap when a host wants one-writer linear branches.
|
|
103
|
+
Recognize it with `isSessionAppendConflict(error)`, not message text. Built-in stores reject duplicate entry ids, dangling `expectedParentId` values, and `expectedParentId` pointing at another session's entry (the parent must exist in the same session — a cross-session parent would be a write no per-session branch walk could read back), and exact idempotency retries. They allow two distinct children of the same existing parent because that is a branch/fork, not parent-order corruption. Production stores may add a stricter branch-tip compare-and-swap when a host wants one-writer linear branches.
|
|
101
104
|
|
|
102
105
|
## Extension and configuration notes
|
|
103
106
|
|
|
@@ -107,6 +110,42 @@ Recognize it with `isSessionAppendConflict(error)`, not message text. Built-in s
|
|
|
107
110
|
- Branch semantics are parent links plus a leaf id. External UIs should keep branch handles as `(sessionId, leafId)`; RPC exposes an additional `handleId` for active handles.
|
|
108
111
|
- Development stores can omit `readBranchPath`; the runtime falls back to `list(sessionId)` and the pure in-memory branch walk. Database-backed stores should implement `readBranchPath` so `entries()`, `clone()`, and context rebuild read only the selected ancestor chain.
|
|
109
112
|
|
|
113
|
+
## Session search (0.0.11)
|
|
114
|
+
|
|
115
|
+
Bounded `SessionIndex` / `searchSessions` lists sessions by optional `workspaceRoot` (`metadata.workspaceRoot`), provider/model, label/summary, time range, ownership, and optional text `query` (FTS on SQLite/Postgres; case-sensitive substring on memory linear). Hits require `sessionId` and may include `leafId` for `checkout`; never credentials or whole transcripts.
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import { createMemorySessionStore, resolveSessionSearchQuery } from "@arnilo/prism";
|
|
119
|
+
|
|
120
|
+
const store = createMemorySessionStore([], { sessionSearchMode: "linear" });
|
|
121
|
+
const page = await store.searchSessions!({
|
|
122
|
+
workspaceRoot: "/repo",
|
|
123
|
+
query: "flake",
|
|
124
|
+
limit: 20,
|
|
125
|
+
});
|
|
126
|
+
// Opt out: createMemorySessionStore([], { sessionSearchMode: "unsupported" })
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Finite caps (defaults / hard): page 20/100; query string 4 KiB/16 KiB; snippet 512 B/4 KiB; cursor 1 KiB/4 KiB; memory linear sessions 1000/5000, entries 10000/50000, bytes 8 MiB/64 MiB; DB FTS candidates 1000/5000. Overflow fails closed via `resolveSessionSearchQuery`. See [Phase 6 evidence](review-coverage-2026-07-22-phase-6.md).
|
|
130
|
+
|
|
131
|
+
## Session search (0.0.11)
|
|
132
|
+
|
|
133
|
+
Bounded `SessionIndex` / `searchSessions` lists sessions by optional `workspaceRoot` (`metadata.workspaceRoot`), provider/model, label/summary, time range, ownership, and optional text `query` (FTS on SQLite/Postgres; case-sensitive substring on memory linear). Hits require `sessionId` and may include `leafId` for `checkout`; never credentials or whole transcripts.
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
import { createMemorySessionStore, resolveSessionSearchQuery } from "@arnilo/prism";
|
|
137
|
+
|
|
138
|
+
const store = createMemorySessionStore([], { sessionSearchMode: "linear" });
|
|
139
|
+
const page = await store.searchSessions!({
|
|
140
|
+
workspaceRoot: "/repo",
|
|
141
|
+
query: "flake",
|
|
142
|
+
limit: 20,
|
|
143
|
+
});
|
|
144
|
+
// Opt out: createMemorySessionStore([], { sessionSearchMode: "unsupported" })
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Finite caps (defaults / hard): page 20/100; query string 4 KiB/16 KiB; snippet 512 B/4 KiB; cursor 1 KiB/4 KiB; memory linear sessions 1000/5000, entries 10000/50000, bytes 8 MiB/64 MiB; DB FTS candidates 1000/5000. Overflow fails closed via `resolveSessionSearchQuery`. See [Phase 6 evidence](review-coverage-2026-07-22-phase-6.md).
|
|
148
|
+
|
|
110
149
|
## Security and performance notes
|
|
111
150
|
|
|
112
151
|
- Do not store provider credentials, credential resolvers, provider instances, or unredacted secrets in session entries, append options, idempotency keys, or branch records.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
The optional `@arnilo/prism-session-store-sqlite` package ships a production-oriented SQLite adapter that implements:
|
|
6
6
|
|
|
7
|
-
- `SessionStore` — atomic `append` / `list` / `get` / `readBranchPath`
|
|
7
|
+
- `SessionStore` — atomic `append` / `list` / `get` / `readBranchPath` / bounded `searchSessions` / bounded `searchSessions`
|
|
8
8
|
- `RunLedger` — durable run, event, tool-call, and usage rows
|
|
9
9
|
- `ProductionPersistenceStore` — cursor-paginated `query*` reads plus generic `checkpoints` and atomic `leases` capabilities
|
|
10
10
|
|
|
@@ -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-
|
|
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 **
|
|
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.
|
|
@@ -49,8 +49,11 @@ await session.run(input, {
|
|
|
49
49
|
parser, // optional; default treats assistant text as the value
|
|
50
50
|
repairer, // optional; default stringifies validation.errors[].message
|
|
51
51
|
maxRevisions: 3, // optional; default 3
|
|
52
|
+
toolCalls: "bounded", // optional; default "disabled"
|
|
52
53
|
structuredOutput: { name: "answer", schema, strict: true }, // optional native mode
|
|
53
54
|
structuredOutputMode: "native", // or "artifact-loop" to skip provider-native schema
|
|
55
|
+
// Opt-in: schema only on artifact/revision turns (tools free on earlier turns).
|
|
56
|
+
structuredOutputTiming: "final-turn-only", // default "every-turn"
|
|
54
57
|
},
|
|
55
58
|
providerOptions: {
|
|
56
59
|
structuredOutput: { name: "answer", schema, strict: true }, // direct native request
|
|
@@ -58,6 +61,8 @@ await session.run(input, {
|
|
|
58
61
|
});
|
|
59
62
|
```
|
|
60
63
|
|
|
64
|
+
`structuredOutputTiming: "final-turn-only"` (with `toolCalls: "bounded"` and native schema) sends tool-eligible turns **without** `response_format` so models can call tools; once the model returns a call-free candidate (or tool rounds are exhausted), the next turn withdraws tools and attaches schema. Revision turns stay schema-on / tools-off. Default `"every-turn"` keeps legacy behavior (schema on every provider request).
|
|
65
|
+
|
|
61
66
|
## Outputs / response / events
|
|
62
67
|
|
|
63
68
|
`generateValidateReviseLoop.run(ctx)` returns `Promise<Usage | undefined>`. Observable behavior is emitted through `AgentEvent` artifact variants (zero emitted by `singleShotLoop`):
|
|
@@ -230,11 +235,12 @@ Key cross-seam points:
|
|
|
230
235
|
|
|
231
236
|
- `generate-validate-revise` is selected via `AgentConfig.loop` / `RunOptions.loop` (`RunOptions.loop` wins). See [Agent loops](agent-loops.md). `resolveLoop()` maps the options form to the factory; an unknown `strategy` throws before the first turn; a custom `AgentLoopStrategy` instance bypasses the options form.
|
|
232
237
|
- Native structured output uses provider-neutral `StructuredOutputOptions` on `ProviderRequestOptions` / loop options. Capable OpenAI-family providers map to JSON-schema wire fields; unsupported models fail before fetch unless the host sets `structuredOutputMode: "artifact-loop"` and relies on parser/validator/repairer only.
|
|
238
|
+
- `structuredOutputTiming: "final-turn-only"` (opt-in; default `"every-turn"`) with `toolCalls: "bounded"` omits native schema on tool-eligible turns and attaches schema only on artifact/revision turns (tools withdrawn). Call-free tool-phase output promotes to one schema turn before parse/validate.
|
|
233
239
|
- `validateStructuredOutputOptions()` enforces JSON-safe schemas, forbidden prototype-pollution keys, and a 64 KiB schema size cap.
|
|
234
240
|
- The default parser treats non-empty assistant text as the value (`{ ok: true, value: text }`); empty/whitespace-only call-free text is a `parse_error` before the parser. Supply a host parser whenever `T` is not `string`.
|
|
235
241
|
- The default repairer builds a user message from `validation.errors[].message`; supply a host repairer for schema-specific guidance.
|
|
236
242
|
- `maxRevisions` (default 3) bounds revision turns; budget exhaustion ends the loop and emits `artifact_failed`. Session runs then fail with `AgentRunError` unless `artifact_finished` occurred (direct `loop.run` still returns usage without throwing).
|
|
237
|
-
- Tools are inert in artifact turns unless `loop.toolCalls: "bounded"` is explicit. Bounded mode uses run-global `maxToolRounds`, dispatches calls sequentially through normal runtime guards, skips parser/validator for tool-calling responses, and permits at most `1 + maxRevisions + maxToolRounds` provider turns. An extra tool response yields terminal `artifact_failed` with `result.metadata.reason === "tool_round_limit"` and executes nothing.
|
|
243
|
+
- Tools are inert in artifact turns unless `loop.toolCalls: "bounded"` is explicit. Bounded mode uses run-global `maxToolRounds`, dispatches calls sequentially through normal runtime guards, skips parser/validator for tool-calling responses, and permits at most `1 + maxRevisions + maxToolRounds` provider turns (plus one extra schema turn under `final-turn-only` when a tool-phase call-free draft promotes). An extra tool response yields terminal `artifact_failed` with `result.metadata.reason === "tool_round_limit"` and executes nothing.
|
|
238
244
|
|
|
239
245
|
## Security and performance notes
|
|
240
246
|
|
package/docs/supervisors.md
CHANGED
|
@@ -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.
|
|
@@ -61,10 +67,12 @@ Child factories resolve their own providers/credentials and construct context/me
|
|
|
61
67
|
- Tool budget is checked before side effects. Token usage is enforced on terminal aggregate usage and can exceed by at most one provider turn because providers report tokens after generation.
|
|
62
68
|
- Abort and timeout cover hooks, child creation, nested delegation, and the run. Host child code must cooperate with `AbortSignal`.
|
|
63
69
|
- Redaction applies before hook input, run metadata/results, completion hooks, and events. Child credentials are never supplied in delegation context.
|
|
70
|
+
- When forwarding verified identity into children or A2A, use `narrowIdentity` / `assertIdentityPropagation` so scopes and tenant cannot widen across the boundary.
|
|
64
71
|
- Static workflows remain smaller and more reproducible for known graphs.
|
|
65
72
|
|
|
66
73
|
## Related APIs
|
|
67
74
|
|
|
75
|
+
- [Agent identity](agent-identity.md): host-verified identity and narrow delegation.
|
|
68
76
|
- [A2A interoperability](a2a.md): separate remote protocol boundary. `A2ATaskLifecycle` adapts host durable agent/workflow state directly; it does not route A2A execution through local supervisor child planning.
|
|
69
77
|
- [Workflows](workflows.md): preferred deterministic orchestration.
|
|
70
78
|
- [Working and semantic memory](working-and-semantic-memory.md): child scope construction.
|
|
@@ -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
|
|
|
@@ -255,6 +257,7 @@ createJsonSchemaToolArgumentValidator({
|
|
|
255
257
|
|
|
256
258
|
## Related APIs
|
|
257
259
|
|
|
260
|
+
- [OpenAPI tools adapter](openapi-tools.md): optional `@arnilo/prism-openapi-tools` `createOpenApiTools` — compile host-selected OpenAPI 3.1 operationIds into bounded `ToolDefinition`s (allow-list only, pinned origin, resolved/bounded schemas, approval + effect-store idempotency on mutations, bounded body/response/retries/pagination, host credential resolver, untrusted output).
|
|
258
261
|
- [Agent/session runtime](agent-session-runtime.md): dispatches complete provider tool calls through the host-active tool harness and returns tool results on the next provider turn.
|
|
259
262
|
- [Public contracts](public-contracts.md): `ToolDefinition`, `ToolRegistry`, `ToolExecutionContext`, `ToolResult`, and tool `AgentEvent` contracts.
|
|
260
263
|
- [Contribution registries](contribution-registries.md): inert extension/package tool contribution storage.
|
|
@@ -265,6 +268,8 @@ createJsonSchemaToolArgumentValidator({
|
|
|
265
268
|
- [Observational memory compaction package](compaction-observational-memory.md): optional exact-id recall tool factory.
|
|
266
269
|
- [Tool execution primitives](tool-execution-primitives.md): JSON Schema adapter, parallelism, MCP bridge, and execution-policy designs.
|
|
267
270
|
- [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`.
|
|
271
|
+
- [Recoverable tool effects](tool-effects.md): optional `tool.effect` + `effectStore` claim/CAS recovery around dispatch.
|
|
272
|
+
- [Recoverable tool effects](tool-effects.md): optional `tool.effect` + `effectStore` claim/CAS recovery around dispatch.
|
|
268
273
|
- [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
274
|
|
|
270
275
|
`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).
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Work artifacts and review
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
`@arnilo/prism-server` ships a durable artifact co-work review service (Phase 9 / 0.0.14): authorized attach of source/output references with MIME/hash/version, producer-run attribution, citations/data sources, and preview metadata; revision comparison; reviewer approve/reject (request-changes) with last-validated recovery; and authorized, expiring delivery links. Core (`@arnilo/prism`) exports artifact **types only** (`ArtifactRecord`, `ArtifactRevision`, `ArtifactApproval`, `ArtifactDeliveryToken`, approval state `pending | approved | rejected`). Prism persists bounded metadata, revisions, approvals, and delivery references over the existing versioned checkpoint store — **never file bodies**; hosts own blob storage and rendering.
|
|
6
|
+
|
|
7
|
+
## When to use it
|
|
8
|
+
|
|
9
|
+
- Durable human-in-the-loop review of agent-produced outputs (drafts, exports, generated files) where users compare revisions, request changes, and approve/reject.
|
|
10
|
+
- Authorized, time-boxed delivery of a validated artifact revision to a downstream consumer.
|
|
11
|
+
- Recovering the last approved ("validated") revision after a later revision is rejected.
|
|
12
|
+
|
|
13
|
+
Not for: storing file content (use host blob storage), local Office preview/rendering (host-owned), or SaaS connector delivery (see work-connectors).
|
|
14
|
+
|
|
15
|
+
## Inputs / request
|
|
16
|
+
|
|
17
|
+
`createArtifactService(store: CheckpointStore, options)`:
|
|
18
|
+
|
|
19
|
+
| Field | Required | Meaning |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| `store` | yes | Any `CheckpointStore` (sqlite/postgres `persistence.checkpoints`, or `createMemoryCheckpointStore()` for tests) |
|
|
22
|
+
| `options.redactor` | yes | `SecretRedactor`; records are redacted before persist and on every response |
|
|
23
|
+
| `options.linkSecret` | yes | Host HMAC key material for signing/verifying delivery links |
|
|
24
|
+
| `options.limits` | no | Frozen caps (below); each clamped to a hard maximum |
|
|
25
|
+
| `options.onDecision` | no | Audit seam (redacted refs) for attach/revise/approve/reject; bridge to `@arnilo/prism-policy` |
|
|
26
|
+
|
|
27
|
+
Every operation input carries `ownership` (from host `authorize`, never request JSON) plus optional verified `identity`. `attach` requires `threadId`, `uri`, `mime`, `hash`; `revise` requires `uri`, `hash` (mime defaults to the previous revision); `compare` requires two distinct revision numbers; `approve`/`reject` require a `version`; `deliveryLink` accepts optional `version` (defaults to last validated, else latest) and `ttlSeconds`.
|
|
28
|
+
|
|
29
|
+
## Outputs / response / events
|
|
30
|
+
|
|
31
|
+
| API | Result |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| `attach` | `ArtifactRecord` with revision 1, pending state (idempotent get-or-create with explicit `id`) |
|
|
34
|
+
| `list` | Ownership/thread-scoped `PersistencePage<ArtifactRecord>` |
|
|
35
|
+
| `get` | `ArtifactRecord` |
|
|
36
|
+
| `revise` | `ArtifactRecord` with an appended revision (new revision resets state to pending) |
|
|
37
|
+
| `compare` | `{ artifactId, from, to, changed: { hash, mime, uri, citations } }` — hash+metadata only |
|
|
38
|
+
| `approve` / `reject` | `ArtifactRecord`; approve advances `lastValidatedVersion`, reject never clears it |
|
|
39
|
+
| `lastValidated` | The last approved `ArtifactRevision` (fails closed before any approval) |
|
|
40
|
+
| `deliveryLink` | `{ link, token }` — signed expiring `ArtifactDeliveryToken` |
|
|
41
|
+
|
|
42
|
+
No package-owned agent events are emitted; `onDecision` is the audit seam (redacted actor refs only).
|
|
43
|
+
|
|
44
|
+
## Request/response example
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"attach": { "threadId": "thread-1", "uri": "https://blob.example/doc-v1", "mime": "text/markdown", "hash": "sha256:aaa" },
|
|
49
|
+
"compare": { "from": 1, "to": 2, "changed": { "hash": true, "mime": false, "uri": true, "citations": false } },
|
|
50
|
+
"approve": { "version": 2, "lastValidatedVersion": 2, "approvals": [{ "version": 2, "state": "approved", "reviewer": "user:user-1" }] },
|
|
51
|
+
"deliveryLink": { "link": "<base64url payload>.<base64url hmac>", "token": { "artifactId": "art_1", "version": 2, "expiresAt": "2026-07-25T04:10:00.000Z" } }
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Implementation example
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import { createSecretRedactor } from "@arnilo/prism";
|
|
59
|
+
import { createSqlitePersistence } from "@arnilo/prism-session-store-sqlite";
|
|
60
|
+
import { createArtifactService, createArtifactHandler } from "@arnilo/prism-server";
|
|
61
|
+
|
|
62
|
+
const persistence = createSqlitePersistence({ filename: "prism.db" });
|
|
63
|
+
const artifacts = createArtifactService(persistence.checkpoints, {
|
|
64
|
+
redactor: createSecretRedactor([/* host secrets */]),
|
|
65
|
+
linkSecret: process.env.PRISM_ARTIFACT_LINK_SECRET!,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const record = await artifacts.attach({ ownership, identity, threadId: "thread-1", uri: "https://blob.example/doc", mime: "text/markdown", hash: "sha256:aaa" });
|
|
69
|
+
await artifacts.revise({ ownership, threadId: "thread-1", artifactId: record.id, uri: "https://blob.example/doc-v2", hash: "sha256:bbb" });
|
|
70
|
+
await artifacts.approve({ ownership, identity, threadId: "thread-1", artifactId: record.id, version: 2 });
|
|
71
|
+
const { link } = await artifacts.deliveryLink({ ownership, threadId: "thread-1", artifactId: record.id });
|
|
72
|
+
|
|
73
|
+
// Framework-free HTTP adapter (default base /prism/artifacts); ownership only from authorize.
|
|
74
|
+
export const handler = createArtifactHandler({ service: artifacts, authorize: hostAuthorize, linkSecret: process.env.PRISM_ARTIFACT_LINK_SECRET! });
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Extension and configuration notes
|
|
78
|
+
|
|
79
|
+
- Artifact records are versioned checkpoint values (namespace `prism.artifact`, key `threadId:artifactId`). The checkpoint `version` is the CAS counter for concurrent reviewers, distinct from revision numbers. Any `CheckpointStore` works; sqlite/postgres persistence already expose `.checkpoints`, so there is no separate artifact schema or migration.
|
|
80
|
+
- `createArtifactHandler` mounts attach/list/get/revise/compare/approve/reject/last-validated/delivery-link plus `GET /prism/artifacts/download?link=…`. Download verifies the link signature + expiry, then **reauthorizes** against the token's ownership (mismatch fails closed), and returns the authorized revision reference only — the host fetches the body.
|
|
81
|
+
- Delivery links are `base64url(payload).base64url(HMAC-SHA256)` over `{ artifactId, threadId, version, ownership, issuedAt, expiresAt }`; they are reauthorized per download and are not bearer secrets.
|
|
82
|
+
- **Blob storage (0.0.28)**: `createArtifactService` accepts an optional `bodies: ArtifactBodyStore` (core contract in `src/artifacts.ts`: `put`/`get`/`delete`/`presign` by opaque, ownership-scoped `ArtifactBodyRef`). When wired, `deliveryLink` resolves through `bodies.presign` and returns an additional `url` (bounded-TTL, single-object presigned URL) beside the signed link/token; revisions must carry a recorded `size` (optional on attach/revise) or delivery fails closed. The reference adapter is `@arnilo/prism-server/artifact-bodies` `createS3ArtifactBodyStore` (hand-rolled SigV4 over native fetch + WebCrypto, path-style, single-chunk PUT with verified `x-amz-content-sha256`; works with AWS S3, MinIO, Cloudflare R2). Hosts may substitute any store; the contract is storage-free in core.
|
|
83
|
+
- Body stores verify ownership on every operation, verify size/SHA-256/MIME on put and get (fail closed), refuse delete under legal hold (host `isHeld` callback), and are idempotent on delete (retention sweeps delete bodies with metadata). Credentials come only from the host resolver; bucket/path/key never appear in errors, telemetry, or artifact records (the object key is derived from the ref).
|
|
84
|
+
- Review loops driven by an agent consume the shared `RunLimits` at the host's agent layer; the artifact service itself is a passive, bounded record store.
|
|
85
|
+
|
|
86
|
+
## Security and performance notes
|
|
87
|
+
|
|
88
|
+
- Every operation requires authenticated identity + thread ownership derived from host `authorize`; cross-ownership access fails closed as `not_found` (never leaks existence).
|
|
89
|
+
- Concurrent reviewer conflicts resolve via checkpoint CAS (`expectedVersion`); the loser gets a retryable `conflict` and no approval is lost or duplicated. A throw before commit persists nothing, so failed updates roll back.
|
|
90
|
+
- Local filesystem paths are rejected in `uri`/citations (`file:`, absolute, or drive paths); records are redacted before persist and on response, so paths/secrets/document-private data never enter records, events, or exports.
|
|
91
|
+
- Frozen caps (default / hard): artifacts per thread 64/256; revisions per artifact 32/128; record 8/64 KiB; preview 16/64 KiB; citations 32/128 and 2/8 KiB each; MIME 128/512 B; hash 256/1 KiB; compare exactly 2 revisions; delivery TTL 5 min/24 h; delivery token 4/16 KiB. Raising the revision cap may require raising `recordBytes` (aggregate backstop).
|
|
92
|
+
- Compare is hash+metadata-bounded (hosts render content); no file bodies are persisted or transferred. With a wired body store, bodies live in the host's object store and are streamed through the adapter (bounded by `maxBodyBytes` 64 MiB/512 MiB, concurrent transfers 4/16, presign TTL 10 min/24 h); object-store outages surface typed `ERR_PRISM_S3_*` / `ERR_PRISM_ARTIFACT_BODY_*` errors, never silent success.
|
|
93
|
+
|
|
94
|
+
## Related APIs
|
|
95
|
+
|
|
96
|
+
- [Server](server.md): `createArtifactService` / `createArtifactHandler` mount alongside the Prism handler; ownership only from `authorize`.
|
|
97
|
+
- [Conversations](conversations.md): artifact threads reuse conversation thread ownership scoping.
|
|
98
|
+
- [Database persistence](database-persistence.md): artifact records persist as versioned checkpoint values (sqlite/postgres `.checkpoints`).
|
|
99
|
+
- [Workflows](workflows.md): durable suspend/approve seam; hosts may gate revisions behind `tool_approval`.
|
|
100
|
+
- [Policy and audit](policy-and-audit.md): `onDecision` events bridge here for an auditable review ledger.
|
|
101
|
+
- [Host security](host-security.md): identity/ownership, redaction, and expiring-link boundaries.
|
|
102
|
+
- [Frontend interoperability (AG-UI and ACP)](ag-ui.md): projects artifact progress/approval/download-link as redacted co-work events over the durable-resume stream.
|