@arnilo/prism 0.0.3 → 0.0.5

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 (131) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +62 -26
  3. package/dist/agent-loops.d.ts +8 -1
  4. package/dist/agent-loops.js +57 -11
  5. package/dist/agents.js +212 -32
  6. package/dist/checkpoints.d.ts +11 -0
  7. package/dist/checkpoints.js +144 -0
  8. package/dist/cli-init.d.ts +41 -0
  9. package/dist/cli-init.js +390 -0
  10. package/dist/cli-runner.d.ts +7 -1
  11. package/dist/cli-runner.js +13 -1
  12. package/dist/compaction.js +9 -1
  13. package/dist/content.d.ts +121 -0
  14. package/dist/content.js +538 -0
  15. package/dist/contracts.d.ts +236 -11
  16. package/dist/contracts.js +8 -0
  17. package/dist/event-multiplexer.d.ts +23 -0
  18. package/dist/event-multiplexer.js +136 -0
  19. package/dist/execution-policy.d.ts +28 -0
  20. package/dist/execution-policy.js +24 -0
  21. package/dist/feedback.d.ts +48 -0
  22. package/dist/feedback.js +230 -0
  23. package/dist/index.d.ts +20 -6
  24. package/dist/index.js +13 -5
  25. package/dist/input.js +11 -1
  26. package/dist/leases.d.ts +8 -0
  27. package/dist/leases.js +111 -0
  28. package/dist/node/agent-definitions.js +3 -5
  29. package/dist/node/config.d.ts +1 -0
  30. package/dist/node/config.js +5 -3
  31. package/dist/node/contribution-discovery.js +5 -8
  32. package/dist/node/session-store-jsonl.js +8 -5
  33. package/dist/node/settings.js +2 -2
  34. package/dist/node/trust.js +2 -4
  35. package/dist/observability.d.ts +3 -0
  36. package/dist/observability.js +18 -0
  37. package/dist/providers/media.d.ts +44 -0
  38. package/dist/providers/media.js +126 -0
  39. package/dist/providers/openai-compatible.js +18 -119
  40. package/dist/providers/openai-primitives.d.ts +9 -0
  41. package/dist/providers/openai-primitives.js +129 -0
  42. package/dist/providers/transport.d.ts +40 -0
  43. package/dist/providers/transport.js +221 -0
  44. package/dist/redaction.js +40 -13
  45. package/dist/resources.d.ts +5 -0
  46. package/dist/resources.js +4 -0
  47. package/dist/structured-output.d.ts +11 -0
  48. package/dist/structured-output.js +59 -0
  49. package/dist/testing/feedback.d.ts +6 -0
  50. package/dist/testing/feedback.js +37 -0
  51. package/dist/testing/persistence-schema.d.ts +102 -0
  52. package/dist/testing/persistence-schema.js +487 -0
  53. package/dist/testing/provider-conformance.js +10 -1
  54. package/dist/testing/run-ledger-conformance.d.ts +33 -0
  55. package/dist/testing/run-ledger-conformance.js +178 -0
  56. package/dist/testing/session-store-conformance.d.ts +16 -0
  57. package/dist/testing/session-store-conformance.js +73 -0
  58. package/dist/tools.d.ts +17 -0
  59. package/dist/tools.js +29 -2
  60. package/docs/a2a.md +73 -0
  61. package/docs/agent-events.md +17 -10
  62. package/docs/agent-loops.md +11 -5
  63. package/docs/agent-session-runtime.md +15 -16
  64. package/docs/cli-rpc.md +36 -5
  65. package/docs/coding-agent-tools.md +43 -9
  66. package/docs/coding-security.md +88 -0
  67. package/docs/compaction-observational-memory.md +2 -0
  68. package/docs/context-and-skills.md +1 -0
  69. package/docs/credential-storage.md +177 -0
  70. package/docs/credentials-and-redaction.md +4 -3
  71. package/docs/database-persistence.md +52 -7
  72. package/docs/evaluations.md +122 -0
  73. package/docs/extensions.md +2 -2
  74. package/docs/host-security.md +33 -2
  75. package/docs/index.md +46 -18
  76. package/docs/input-and-prompt-assembly.md +6 -5
  77. package/docs/mcp-tools.md +184 -0
  78. package/docs/middleware-hooks.md +2 -0
  79. package/docs/migration.md +51 -28
  80. package/docs/model-registry.md +5 -3
  81. package/docs/multimodal-content.md +156 -0
  82. package/docs/observability.md +171 -0
  83. package/docs/performance.md +249 -1
  84. package/docs/persistence-credentials-multimodality-primitives.md +303 -0
  85. package/docs/postgres-persistence.md +143 -0
  86. package/docs/provider-conformance.md +18 -0
  87. package/docs/provider-layer.md +1 -1
  88. package/docs/provider-packages.md +2 -0
  89. package/docs/provider-primitives.md +281 -0
  90. package/docs/providers/ai-sdk.md +113 -0
  91. package/docs/providers/kimi.md +1 -0
  92. package/docs/providers/neuralwatt.md +1 -0
  93. package/docs/providers/openai-compatible.md +2 -1
  94. package/docs/providers/openai.md +8 -1
  95. package/docs/providers/opencode-go.md +1 -0
  96. package/docs/providers/openrouter.md +1 -0
  97. package/docs/providers/zai.md +1 -0
  98. package/docs/public-contracts.md +13 -5
  99. package/docs/rag.md +113 -0
  100. package/docs/release-and-install.md +237 -30
  101. package/docs/resource-loading.md +14 -4
  102. package/docs/review-coverage-2026-07-14.md +260 -0
  103. package/docs/review-coverage-2026-07-15.md +193 -0
  104. package/docs/run-ledger-conformance.md +96 -0
  105. package/docs/runs-and-usage.md +43 -4
  106. package/docs/server.md +139 -0
  107. package/docs/session-store-conformance.md +16 -0
  108. package/docs/session-stores-and-branching.md +1 -0
  109. package/docs/settings-auth-trust-security.md +6 -5
  110. package/docs/sqlite-persistence.md +123 -0
  111. package/docs/structured-output.md +9 -0
  112. package/docs/supervisors.md +71 -0
  113. package/docs/tool-conformance.md +1 -0
  114. package/docs/tool-execution-primitives.md +374 -0
  115. package/docs/tools.md +39 -1
  116. package/docs/workflow-orchestration-primitives.md +581 -0
  117. package/docs/workflow-tui-primitives.md +5 -0
  118. package/docs/workflows.md +293 -0
  119. package/docs/working-and-semantic-memory.md +169 -0
  120. package/package.json +43 -5
  121. package/templates/init/README.md.tmpl +28 -0
  122. package/templates/init/env.example.tmpl +1 -0
  123. package/templates/init/gitignore.tmpl +11 -0
  124. package/templates/init/optional/evals-example.ts.tmpl +17 -0
  125. package/templates/init/optional/workflows-example.ts.tmpl +27 -0
  126. package/templates/init/package.json.tmpl +22 -0
  127. package/templates/init/providers.json +76 -0
  128. package/templates/init/src/agent.ts.tmpl +10 -0
  129. package/templates/init/src/index.ts.tmpl +12 -0
  130. package/templates/init/src/tests/agent.test.ts.tmpl +24 -0
  131. package/templates/init/tsconfig.json.tmpl +15 -0
@@ -0,0 +1,184 @@
1
+ # MCP client bridge and server exposure
2
+
3
+ ## What it does
4
+
5
+ `@arnilo/prism-mcp` has two explicit directions. Its client bridge connects hosts to remote [Model Context Protocol](https://modelcontextprotocol.io) servers and maps discovered tools to ordinary `ToolDefinition`s. Its server API registers selected Prism `ToolDefinition` and `CommandDefinition` values on the official SDK `McpServer`, with required authorization and a bounded optional Web-standard Streamable HTTP handler. The package wraps `@modelcontextprotocol/sdk` v1.29+ and adds no MCP branch to core Prism.
6
+
7
+ Primary API:
8
+
9
+ ```ts
10
+ import { connectMcpTools } from "@arnilo/prism-mcp";
11
+
12
+ const bridge = await connectMcpTools({
13
+ serverId: "fs",
14
+ transport: { type: "stdio", command: "node", args: ["server.js"] },
15
+ });
16
+
17
+ // bridge.tools are ToolDefinition[] — register with createToolRegistry / createAgent
18
+ await bridge.refresh(); // re-list after notifications or TTL expiry
19
+ await bridge.close(); // close client + transport
20
+ ```
21
+
22
+ Advanced hosts that manage their own `Client` + `Transport` can call `attachMcpToolBridge(client, transport, options)` after `client.connect(transport)`.
23
+
24
+ Server direction:
25
+
26
+ ```ts
27
+ import { createPrismMcpServer, createPrismMcpWebHandler } from "@arnilo/prism-mcp";
28
+
29
+ const server = createPrismMcpServer({
30
+ tools: [approvedTool],
31
+ commands: [approvedWorkflowStatusCommand],
32
+ authorize: async ({ authInfo, kind, name }) => hostPolicy(authInfo, kind, name)
33
+ ? { allowed: true, ownership: { tenantId: "tenant-1" } }
34
+ : false,
35
+ validate,
36
+ permission,
37
+ redactor,
38
+ });
39
+
40
+ const handleMcp = await createPrismMcpWebHandler(server, {
41
+ resolveAuthInfo: authenticateRequest,
42
+ allowedHosts: ["api.example.test"],
43
+ allowedOrigins: ["https://app.example.test"],
44
+ });
45
+ ```
46
+
47
+ `McpServer.connect(transport)` remains available for SDK stdio or in-memory transports. The helper uses SDK `WebStandardStreamableHTTPServerTransport` in bounded stateless JSON-response mode; it does not start a listener.
48
+
49
+ ## When to use it
50
+
51
+ - **Integrate external MCP tool servers** (filesystem, databases, SaaS adapters) without reimplementing JSON-RPC transports in your app.
52
+ - **Keep core dispatch gates** — register returned tools and let `dispatchToolCall` enforce permission, JSON Schema validation (`ToolValidator`), middleware, abort, and parallel execution (Plan 055 Tasks 1–2).
53
+ - **Explicit lifecycle** — connect, refresh on `notifications/tools/list_changed`, and `close()` when the session ends.
54
+ - **Expose selected capabilities** — register a reviewed tool/command allow-list for MCP clients without a custom JSON-RPC server.
55
+
56
+ Do **not** use this package as a sandbox, permission engine, or auto-discovery loader. Hosts must trust configured commands/URLs and gate registration.
57
+
58
+ ## Inputs / request
59
+
60
+ `connectMcpTools()` requires a stable `serverId` plus an explicit stdio or Streamable HTTP transport. Optional bounds control list caching, call timeout, result bytes, name prefix, and abort behavior; defaults are listed below.
61
+
62
+ ## Outputs / response / events
63
+
64
+ The resolved `McpToolBridge` exposes `tools`, `refresh()`, and `close()`. Each discovered MCP tool becomes a normal Prism `ToolDefinition`; calls return `ToolResult`, with remote `isError` mapped to `ToolResult.error`. List-change notifications invalidate the cache but register nothing automatically.
65
+
66
+ `createPrismMcpServer()` returns the SDK `McpServer`. It lists only passed tools/commands; JSON Schema parameters are converted through installed Zod v4 for SDK validation, then Prism tool calls still pass through `dispatchToolCall` permission/validator/redactor gates. Command definitions support explicitly selected direct/background/replay workflow operations and optional ownership-scoped schedule operations from `createWorkflowCommands()`; none are registered unless the host passes those command definitions. Calls return bounded MCP text content and `isError` on denial/failure. `createPrismMcpWebHandler()` returns `(Request) => Promise<Response>`.
67
+
68
+ ## Request/response example
69
+
70
+ ```json
71
+ {
72
+ "request": { "serverId": "docs", "transport": { "type": "stdio", "command": "node", "args": ["server.js"] } },
73
+ "mappedTool": { "name": "mcp:docs:search", "parameters": { "type": "object" } }
74
+ }
75
+ ```
76
+
77
+ ## Implementation example
78
+
79
+ ```ts
80
+ import { createToolRegistry } from "@arnilo/prism";
81
+ import { connectMcpTools } from "@arnilo/prism-mcp";
82
+
83
+ const bridge = await connectMcpTools({
84
+ serverId: "docs",
85
+ transport: { type: "stdio", command: "node", args: ["server.js"] },
86
+ callTimeoutMs: 30_000,
87
+ });
88
+ const registry = createToolRegistry({ duplicate: "error" });
89
+ for (const tool of bridge.tools) registry.register(tool);
90
+ ```
91
+
92
+ ## Tool naming and mapping
93
+
94
+ | MCP | Prism |
95
+ | --- | --- |
96
+ | `tools/list` `inputSchema` | `ToolDefinition.parameters` |
97
+ | `tools/call` arguments | Parsed `ToolCallContent.arguments` |
98
+ | `tools/call` content blocks | `ToolResult.content` (`text`, `image`; resource/audio/link → descriptive `text`) |
99
+ | Tool `name` | Prefixed `mcp:<serverId>:<name>` (override with `namePrefix`) |
100
+ | `isError` results | `ToolResult.error` with summarized text |
101
+ | `structuredContent` | `ToolResult.value` / metadata |
102
+
103
+ Duplicate prefixed names throw `McpToolNameCollisionError` at refresh time.
104
+
105
+ ## Extension and configuration notes
106
+
107
+ | Option | Default | Purpose |
108
+ | --- | --- | --- |
109
+ | `serverId` | required | Stable identifier used in default name prefix |
110
+ | `transport` | required | `stdio` or `streamable-http` config |
111
+ | `namePrefix` | `mcp:<serverId>:` | Registry namespace for remote tools |
112
+ | `listCacheTtlMs` | `30000` | Skip re-listing until TTL expires (invalidated on list-changed) |
113
+ | `callTimeoutMs` | `60000` | Per-call MCP request timeout |
114
+ | `maxResultBytes` | `10000000` | Bound mapped result content |
115
+ | `signal` | none | Abort connect and trigger close on abort |
116
+
117
+ ### Stdio transport
118
+
119
+ ```ts
120
+ {
121
+ type: "stdio",
122
+ command: "node",
123
+ args: ["path/to/server.js"],
124
+ env?: Record<string, string>,
125
+ cwd?: string,
126
+ stderr?: "inherit" | "pipe" | "ignore" | "overlapped",
127
+ }
128
+ ```
129
+
130
+ The host explicitly chooses the executable, arguments, environment, and working directory. Prism does not search `PATH` for unknown servers or inject credentials.
131
+
132
+ ### Streamable HTTP transport
133
+
134
+ ```ts
135
+ {
136
+ type: "streamable-http",
137
+ url: "https://mcp.example.com/mcp",
138
+ requestInit?: RequestInit,
139
+ sessionId?: string,
140
+ }
141
+ ```
142
+
143
+ Only `http:` and `https:` URLs are accepted. Authentication (Bearer tokens, cookies) is supplied through `requestInit.headers` by the host.
144
+
145
+ ### MCP server options
146
+
147
+ | Option | Default | Purpose |
148
+ | --- | --- | --- |
149
+ | `tools` / `commands` | empty | Explicit allow-list; zero default exposure |
150
+ | `authorize` | required | Per-call host authz using SDK auth/session metadata |
151
+ | `permission` / `validate` / `redactor` | none | Core tool-dispatch gates and known-secret redaction |
152
+ | `maxResultBytes` | 1 MiB (8 MiB hard) | Bound mapped MCP call output |
153
+ | `maxConcurrentCalls` | 16 (256 hard) | Bound active tool/command execution |
154
+ | `callTimeoutMs` | 60 s (30 min hard) | Abort and return timed-out calls |
155
+
156
+ Web handler defaults: 1 MiB request (8 MiB hard), 2 MiB response (16 MiB hard), 32 concurrent requests (512 hard), and 60 s timeout (30 min hard). It parses bounded JSON before passing `parsedBody` to the SDK transport. `allowedHosts`/`allowedOrigins` activate SDK DNS-rebinding checks only when explicitly configured. Authentication data comes only from host `resolveAuthInfo()`.
157
+
158
+ ## Security and performance notes
159
+
160
+ | Risk | Mitigation |
161
+ | --- | --- |
162
+ | Untrusted subprocess (stdio) | Explicit `command` / `args` / `env` / `cwd`; review before deploy |
163
+ | SSRF / open redirects (HTTP) | Host URL allow-lists, network policy, no implicit discovery |
164
+ | Tool-name shadowing | Prefixed names + `createToolRegistry({ duplicate: "error" })` |
165
+ | Oversized server output | `maxResultBytes` on content mapping |
166
+ | Unvalidated arguments | Register tools with `createJsonSchemaToolArgumentValidator()` at dispatch |
167
+ | Missing permission gate | Client direction: `PermissionPolicy` on `tool:mcp:<serverId>:<name>:execute`; server direction: required MCP `authorize` plus optional core `PermissionPolicy` |
168
+ | Accidental server exposure | Empty default arrays, duplicate-name rejection, explicit tools/commands only |
169
+ | Unbounded MCP HTTP | Bounded pre-parsed JSON, response bytes, concurrent requests, call timeout, SDK web-standard transport |
170
+ | Cross-tenant operation | Authorizer derives ownership from validated auth and passes it to tool dispatch/selected workflow commands; never trust arguments as identity |
171
+
172
+ MCP output is untrusted. Apply `SecretRedactor` and host logging policy to `ToolResult` before persisting or displaying. MCP server authorization does not replace tool `PermissionPolicy`, argument validation, coding `ExecutionPolicy`, workflow ownership checks, TLS, rate limiting, or sandboxing. A timed-out tool must cooperate with `AbortSignal` to stop side effects; the response is bounded even when untrusted code ignores abort.
173
+
174
+ ## Related APIs
175
+
176
+ - [Tools](tools.md): registry, dispatch, validation
177
+ - [Tool execution primitives](tool-execution-primitives.md): Plan 055 design and conformance matrix
178
+ - [Host security guide](host-security.md): permission, trust, validation checklist
179
+ - [Web-standard server handler](server.md): agent/workflow HTTP routes and shared remote-boundary rules
180
+ - Package README: [`@arnilo/prism-mcp`](../packages/mcp/README.md)
181
+
182
+ ## Testing
183
+
184
+ Package tests use in-memory MCP transports (no network). Hosts should integration-test their configured stdio commands and HTTP endpoints in staging before production registration.
@@ -101,6 +101,7 @@ export const extension: Extension = {
101
101
  - `retry` middleware may stop retrying or adjust delay, but runtime still owns retry event emission, abort-aware waiting, and provider-turn boundaries.
102
102
  - The registry does not discover packages, read manifests, load config, call providers, execute tools, read resources, or start sessions.
103
103
  - Hosts may pass a middleware registry into `createExtensionKernel({ middleware })` to share it with direct host code.
104
+ - For OpenTelemetry export, prefer `session.subscribe()` + `@arnilo/prism-observability-opentelemetry` (see [Observability](observability.md)) rather than adding a parallel event bus. Middleware hooks remain for transforming payloads at named boundaries.
104
105
 
105
106
  ## Security and performance notes
106
107
 
@@ -118,6 +119,7 @@ export const extension: Extension = {
118
119
  - [Input and prompt assembly](input-and-prompt-assembly.md): `input_assembly` and `prompt_build` helper call sites.
119
120
  - [Compaction and retry policies](compaction-and-retry.md): compaction/retry middleware payloads and runtime timing.
120
121
  - [Context and skills](context-and-skills.md): `context` helper call site.
122
+ - [Observability](observability.md): optional OpenTelemetry adapter over `AgentEvent` streams.
121
123
  - [Public contracts](public-contracts.md): provider, tool, context, session, and extension contracts that runtimes can pass through hooks.
122
124
 
123
125
  Permission checks for tools, extensions, and resources are hard guards; middleware can transform payloads but cannot bypass a denied `PermissionPolicy`.
package/docs/migration.md CHANGED
@@ -2,12 +2,33 @@
2
2
 
3
3
  ## What it does
4
4
 
5
- This page is the single navigation entry for the two cross-cutting migrations external apps hit when moving from Prism's development defaults to its production persistence and explicit-capability surfaces:
5
+ Prism 0.0.5 preserves documented 0.0.3 agent construction except for two intentional Phase 3 public-API cleanups:
6
6
 
7
- 1. **In-memory / JSONL → database-backed persistence** — swap the single-process development `SessionStore` for a host-implemented `ProductionPersistenceStore` / `SessionStore` adapter, and optionally attach a durable `RunLedger`.
8
- 2. **Permissive capability defaults → explicit capability activation** — move from "omitted tool/skill lists activate everything in scope" (pre-Phase 38 behavior) to named, fail-closed tool/skill activation.
7
+ 1. **`session.run()` / `session.prompt()` return `AgentRunResult`** and `session.stream()` starts one owned run after subscribing. Callers that ignored the previous `Promise<void>` keep working; failed/aborted runs reject with `AgentRunError` (`.result` attached).
8
+ 2. **`AgentConfig.extensions` / `settings` / `credentials` are removed.** Wire extensions through `createExtensionKernel()`, read settings in the host, and pass credential resolvers to the provider edge.
9
9
 
10
- It is a thin, link-first guide: it states before/after shapes and points at the detailed pages for schema, indexes, redaction, branch handles, capability semantics, and security.
10
+ Phase 4 adds optional `@arnilo/prism-evals` for deterministic scorers/datasets/experiments. It is not a core dependency; install it directly or through `@arnilo/prism-all`.
11
+
12
+ Phase 5 adds `prism init <dir>` to the existing CLI. It scaffolds a tiny TypeScript project with one selected provider and an offline mock test. Optional `--with-workflows` / `--with-evals` flags add only those packages; storage and telemetry stay opt-in elsewhere.
13
+
14
+ Phase 6 adds optional `@arnilo/prism-provider-ai-sdk` for AI SDK `LanguageModelV4` interoperability. Install it with `@ai-sdk/provider@^4`, through `@arnilo/prism-providers`, or through `@arnilo/prism-all`; it is not a core dependency.
15
+
16
+ Phase 7 adds optional `@arnilo/prism-memory` for schema/template-backed working memory and embedding-based semantic recall. Install it directly or through `@arnilo/prism-all`; in-memory adapters are default, and PostgreSQL/pgvector is opt-in. It is not a core dependency.
17
+
18
+ Phase 8 extends `@arnilo/prism-workflows` compatibly. Nodes may return `suspend()`, and opted-in tool nodes may declare `approval`. Resuming a suspended run requires `{ decision, input?, expectedVersion }`; ordinary failed/aborted recovery resume remains unchanged. `WorkflowRunStatus` adds `suspended` and terminal `denied`. Suspension/resume records remain bounded checkpoint JSON, so SQLite/PostgreSQL require no migration.
19
+
20
+ Phase 9 adds optional `@arnilo/prism-rag` for bounded plain-text/Markdown chunking, Phase 7 vector indexing/retrieval, stable citations, and explicit context injection. Existing agents and memory stores are unchanged; install and attach its context provider explicitly. No database migration is required.
21
+
22
+ Phase 10 adds optional `@arnilo/prism-server` and extends `@arnilo/prism-mcp` with server-direction APIs. Existing agent/workflow/MCP client behavior is unchanged. Install the server package explicitly, pass selected capability maps plus required host authorization, and adapt its Web handler in the deployment host. MCP servers likewise register only passed tools/commands and require authorization. No listener, route, credential source, profile package, or database migration activates automatically.
23
+
24
+ Phase 11 compatibly extends `@arnilo/prism-workflows` with `workflowNode`, shared state fields/context updates, replay lineage, explicit background enqueue, and ownership-scoped schedules. Existing workflow definitions and direct runs remain valid; `WorkflowRunResult` now always includes `state`. State schemas require a host `validateState` callback. Schedules reuse generic checkpoint/lease stores, so SQLite/PostgreSQL need no migration and no scheduler starts automatically.
25
+
26
+ This page also covers two optional adoption paths:
27
+
28
+ 1. **In-memory / JSONL → database-backed persistence** — replace the single-process development `SessionStore` with `@arnilo/prism-session-store-sqlite`, `@arnilo/prism-session-store-postgres`, or a host implementation, and optionally attach its durable `RunLedger`.
29
+ 2. **Legacy permissive capability configuration → explicit activation** — name tools/skills and keep omitted capabilities fail-closed.
30
+
31
+ It states before/after shapes and links detailed schema, redaction, branch, capability, and security guidance.
11
32
 
12
33
  ## When to use it
13
34
 
@@ -15,7 +36,7 @@ Read this page when:
15
36
 
16
37
  - you are taking an app from the `createMemorySessionStore()` / `createJsonlSessionStore()` path to a multi-process, multi-tenant, or durable database backend;
17
38
  - you are hardening an agent that previously relied on "every scoped tool/skill is active" and need to name capabilities explicitly;
18
- - you are adopting the Phase 34–40 production surfaces (atomic append, branch handles, run/event/tool/usage ledger, security boundary hardening) for the first time.
39
+ - you are adopting 0.0.5 persistence, checkpoints/leases, workflows, structured output, multimodality, or explicit tool safety for the first time.
19
40
 
20
41
  If you are new to Prism, start at [Session stores](session-stores.md) and [Agent/session runtime](agent-session-runtime.md) instead.
21
42
 
@@ -26,6 +47,8 @@ There is no runtime import for this page. The migrations below use these surface
26
47
  | Surface | Where | Migration role |
27
48
  | --- | --- | --- |
28
49
  | `SessionStore` | `@arnilo/prism` | Runtime seam swapped from memory/JSONL to DB. |
50
+ | `createSqlitePersistence` | `@arnilo/prism-session-store-sqlite` | Local durable session, ledger, query, checkpoint, and lease adapter. |
51
+ | `createPostgresPersistence` | `@arnilo/prism-session-store-postgres` | Multi-process pooled persistence with advisory-lock migrations. |
29
52
  | `ProductionPersistenceStore` | `@arnilo/prism` | Adapter-facing contract for paginated, multi-tenant reads (`query*`, optional `readBranchPath`). |
30
53
  | `RunLedger` / `RunLedgerRecord` | `@arnilo/prism` | Durable run/event/tool-call/usage ledger attached via `AgentConfig.runLedger` / `RunOptions.runLedger`. |
31
54
  | `SessionAppendOptions` / `SessionAppendConflictError` / `SessionBranchHandle` | `@arnilo/prism` | Atomic append, retry dedup, durable branch handles. |
@@ -77,34 +100,23 @@ Capability migration (before/after):
77
100
 
78
101
  ### Migration 1 — in-memory / JSONL → database-backed persistence
79
102
 
80
- A complete, network-free reference adapter that implements these contracts against in-memory tables (and wires a `RunLedger`, branch-handle checkout, fork, and prior-run timeline resume) lives at [`examples/external-app-db-backed.ts`](../examples/external-app-db-backed.ts). The steps below mirror its structure.
103
+ Runnable references: [`examples/workflow-sqlite-resume.ts`](../examples/workflow-sqlite-resume.ts), credential-gated [`examples/workflow-postgres-resume.ts`](../examples/workflow-postgres-resume.ts), and the network-free custom-adapter example [`examples/external-app-db-backed.ts`](../examples/external-app-db-backed.ts).
81
104
 
82
- Step 1: implement a `SessionStore` (or the richer `ProductionPersistenceStore`) against your database. The runtime only requires `append(entry, options?)`, `list(sessionId)`, and optional `get(id)` / `readBranchPath(query)`.
105
+ Step 1: replace the development store with a first-party adapter. Use PostgreSQL instead when multiple processes or sustained concurrent writers matter.
83
106
 
84
107
  ```ts
85
108
  // Before: development store, single process.
86
109
  import { createJsonlSessionStore } from "@arnilo/prism/node/session-store-jsonl";
87
- const store = createJsonlSessionStore("./sessions.jsonl");
88
-
89
- // After: host-implemented database adapter implementing the documented contract, no real DB needed to satisfy the contract.
90
- import type { SessionStore, SessionEntry, SessionAppendOptions, PersistencePage, SessionBranchRead } from "@arnilo/prism";
91
-
92
- const store: SessionStore = {
93
- async append(entry: SessionEntry, options?: SessionAppendOptions) {
94
- // 1. idempotency dedup: insert (session_id, expected_parent_id, idempotency_key, entry_id)
95
- // into prism_session_append_idempotency; unique hit => SessionAppendConflictError { idempotencyDuplicate: true }
96
- // 2. expectedParentId existence check => SessionAppendConflictError { expectedParentId } if missing
97
- // 3. insert prism_session_entries row; duplicate id fails the transaction
98
- // 4. optionally compare-and-swap prism_branches.leaf_entry_id
99
- },
100
- async list(sessionId: string) { /* O(n) development fallback only */ return []; },
101
- async readBranchPath(query: SessionBranchRead): Promise<PersistencePage<SessionEntry>> {
102
- // one recursive CTE / ancestor query — do NOT list(sessionId)+in-memory walk for long sessions
103
- return { items: [] };
104
- },
105
- };
110
+ const oldStore = createJsonlSessionStore("./sessions.jsonl");
111
+
112
+ // After: local durable adapter. The same object implements SessionStore,
113
+ // RunLedger, ProductionPersistenceStore, checkpoints, and leases.
114
+ import { createSqlitePersistence } from "@arnilo/prism-session-store-sqlite";
115
+ const store = createSqlitePersistence({ filename: "./prism.db" });
106
116
  ```
107
117
 
118
+ Custom adapters remain supported through `SessionStore` / `ProductionPersistenceStore`; implement indexed `readBranchPath()` rather than full-session scans.
119
+
108
120
  Step 2: optionally attach a durable run/event/tool/usage ledger and ownership scope so a process exit leaves enough to resume and bill:
109
121
 
110
122
  ```ts
@@ -134,6 +146,8 @@ What you leave behind and why:
134
146
  - `createMemorySessionStore()` — process-local maps; lost on restart, no cross-process locking. Keep for tests.
135
147
  - `createJsonlSessionStore()` — single-process file adapter; reads are linear in file size, no cross-process lock, no durable idempotency table, two writers to the same file can race. Keep for local/dev only.
136
148
 
149
+ Prism 0.0.5 persistence adapters automatically apply additive schema step `002_usage_scope`, then `003_run_feedback`. Migration 003 creates immutable owned run/trace feedback with a run FK, cascade deletion, JSON tag/scorer/evaluation ID lists, and owner/run/trace cursor indexes. Existing rows are unchanged. Custom adapters may omit optional `ProductionPersistenceStore.feedback`; adopters implement `RunFeedbackStore` append/query/delete semantics and must verify exact linked-run ownership before insert.
150
+
137
151
  See [Database persistence](database-persistence.md) for the full reference schema, indexes, conditional-append transaction pattern, retention, and NoSQL mapping; [Session stores](session-stores.md) for the `SessionStore` contract and branch helpers; [Session stores and branching](session-stores-and-branching.md) for branch semantics; [Runs and usage ledger](runs-and-usage.md) for the `RunLedger` record shapes and ordering rules.
138
152
 
139
153
  ### Migration 2 — permissive capability defaults → explicit capability activation
@@ -174,7 +188,7 @@ Runtime skill activation remains explicit: `RunOptions.activeSkills` narrows per
174
188
 
175
189
  ## Extension and configuration notes
176
190
 
177
- - **Persistence is host-owned.** Prism ships no database adapter, no DDL, no migration runner. Hosts own connection pools, transactions, cursor encoding, retention jobs, and tenant isolation. The runtime only talks to `SessionStore` (+ optional `readBranchPath`) and `RunLedger`.
191
+ - **Persistence remains host-configured.** Optional SQLite/PostgreSQL packages ship adapters and versioned setup, but hosts choose connection paths/pools, TLS, credentials, retention, tenant policy, and lifecycle. Core only consumes `SessionStore`, `RunLedger`, feedback, checkpoint, and lease contracts.
178
192
  - **`RunLedger` is not a `SessionStore` replacement.** Messages, branches, and session entries still flow through `SessionStore.append()`; the ledger records run/event/tool/usage facts. See [Runs and usage ledger](runs-and-usage.md).
179
193
  - **Capability activation is config over code.** Every seam lives on `AgentDefinition` / `AgentDefinitionResolutionContext` / `RunOptions`; no auto-activation, no privilege grant. A declaration cannot grant permissions or bypass `toolNames`.
180
194
  - **Migration order is decoupled.** You can adopt database persistence without changing capability activation, and vice versa. Both migrations are independent config swaps.
@@ -190,7 +204,16 @@ Runtime skill activation remains explicit: `RunOptions.activeSkills` narrows per
190
204
 
191
205
  ## Related APIs
192
206
 
193
- - [Database persistence](database-persistence.md): production persistence contracts, reference schema, indexes, conditional append, retention, migrations, NoSQL mapping.
207
+ - [Evaluations](evaluations.md): optional `@arnilo/prism-evals` scorers/datasets/experiments over `AgentRunResult`.
208
+ - [AI SDK provider adapter](providers/ai-sdk.md): optional `@arnilo/prism-provider-ai-sdk` `LanguageModelV4` bridge.
209
+ - [Working and semantic memory](working-and-semantic-memory.md): optional `@arnilo/prism-memory` working/semantic recall primitives.
210
+ - [Retrieval-augmented generation](rag.md): optional text/Markdown chunk, index, retrieval, and citation helpers.
211
+ - [Web-standard server handler](server.md): optional authorized agent/workflow HTTP routes.
212
+ - [Supervisor delegation](supervisors.md) and [A2A interoperability](a2a.md): optional install only; core agent/workflow behavior is unchanged. Child factories now receive package-derived memory IDs and narrowing permission, while remote endpoints require exact HTTPS origin allow-lists.
213
+ - [MCP client/server exposure](mcp-tools.md): selected MCP tools/commands and bounded Web transport.
214
+ - [Database persistence](database-persistence.md): production contracts, reference schema, indexes, conditional append, retention, migrations, and custom adapters.
215
+ - [SQLite persistence](sqlite-persistence.md): local durable first-party adapter and writer ceiling.
216
+ - [PostgreSQL persistence](postgres-persistence.md): pooled multi-process adapter, TLS/pool ownership, and live gate.
194
217
  - [Session stores](session-stores.md): `SessionStore` contract, `SessionAppendOptions`, `SessionAppendConflictError`, branch handles, `readBranchPath`.
195
218
  - [Session stores and branching](session-stores-and-branching.md): detailed branch semantics and helper reference.
196
219
  - [Runs and usage ledger](runs-and-usage.md): `RunLedger` record shapes, redaction, and event/usage ordering.
@@ -36,7 +36,7 @@ import { createModelRegistry, type ModelConfig } from "@arnilo/prism";
36
36
  | --- | --- |
37
37
  | `provider` / `model` | Required registry key. |
38
38
  | `displayName` | Human-readable label. |
39
- | `capabilities` | Input/output modes plus reasoning/tools/streaming booleans. |
39
+ | `capabilities` | Input/output modes (`text`, `image`, `audio`, `file`, `document`) plus reasoning/tools/streaming booleans and optional `structuredOutput` (`true` or `"json_schema"`) for native JSON-schema requests. |
40
40
  | `limits` | Context and output-token limits. |
41
41
  | `cost` | Input/output/cache read/cache write pricing. |
42
42
  | `cache` | Generic `ModelCacheCapabilities`. |
@@ -74,7 +74,7 @@ The registry emits no events and performs no I/O.
74
74
  "model": {
75
75
  "provider": "demo",
76
76
  "model": "demo-large",
77
- "capabilities": { "input": ["text"], "tools": true, "streaming": true },
77
+ "capabilities": { "input": ["text", "image", "audio", "file", "document"], "tools": true, "streaming": true },
78
78
  "limits": { "contextWindow": 128000, "maxOutputTokens": 8192 },
79
79
  "cost": { "input": 10, "output": 30, "cacheRead": 2, "currency": "USD", "unit": "1M tokens" },
80
80
  "cache": { "kind": "cache_control", "maxBreakpoints": 4, "longRetention": true }
@@ -91,7 +91,7 @@ const model: ModelConfig = {
91
91
  provider: "demo",
92
92
  model: "demo-large",
93
93
  displayName: "Demo Large",
94
- capabilities: { input: ["text"], output: ["text"], tools: true, streaming: true },
94
+ capabilities: { input: ["text", "document"], output: ["text"], tools: true, streaming: true },
95
95
  limits: { contextWindow: 128_000, maxOutputTokens: 8_192 },
96
96
  cost: { input: 10, output: 30, cacheRead: 2, cacheWrite: 12, currency: "USD", unit: "1M tokens" },
97
97
  cache: { kind: "cache_control", maxBreakpoints: 4, minCacheableTokens: 1024, longRetention: true },
@@ -110,12 +110,14 @@ Provider packages register models through `ProviderPackageAPI.registerModel(mode
110
110
  ## Security and performance notes
111
111
 
112
112
  - Model metadata must not contain credentials or secrets.
113
+ - Declare truthful `capabilities.input` tags. Prism core rejects undeclared modalities in `assembleProviderInput()` when the list is present.
113
114
  - Registration is in-memory and O(1) by provider/model key.
114
115
  - `ModelConfig.cache` is declarative capability info only; it does not grant permissions, select tools, or bypass auth.
115
116
  - Provider-specific behavior belongs in provider packages, not Prism core.
116
117
 
117
118
  ## Related APIs
118
119
 
120
+ - [Multimodal content](multimodal-content.md): `audio`/`file`/`document` blocks and `MODEL_INPUT_CAPABILITIES`.
119
121
  - [Provider layer](provider-layer.md): provider/model registry overview.
120
122
  - [Provider caching](provider-caching.md): `ModelCacheCapabilities` and cache helpers.
121
123
  - [Provider packages](provider-packages.md): package registration of model metadata.
@@ -0,0 +1,156 @@
1
+ # Multimodal content
2
+
3
+ ## What it does
4
+
5
+ Prism core ships generic `audio`, `file`, and `document` `ContentBlock` types plus bounded media resolution helpers. Blocks carry MIME type, optional name, and exactly one source: inline base64 `data`, remote `url`, or host `resourceUri`. Optional `transcript` metadata can accompany audio/document blocks.
6
+
7
+ `assembleProviderInput()` calls `assertMessagesSupportModelCapabilities()` so declared `ModelCapabilities.input` tags are enforced before provider calls. First-party provider packages map supported blocks locally; unsupported combinations fail closed with `UnsupportedModalityError` or an explicit provider error.
8
+
9
+ ## When to use it
10
+
11
+ - **Host apps** attaching PDFs, audio clips, or generic files to user messages before a provider turn.
12
+ - **Resource loaders** returning binary payloads for `resourceUri` references under trust/permission policy.
13
+ - **Provider authors** reading truthful `ModelCapabilities.input` tags (`text`, `image`, `audio`, `file`, `document`) before mapping wire formats.
14
+
15
+ Do not embed provider upload IDs, tenant-scoped remote file IDs, or API-specific handles in core content blocks.
16
+
17
+ ## Inputs / request
18
+
19
+ ```ts
20
+ import {
21
+ resolveMediaContentBlock,
22
+ resolveMediaContentBlocks,
23
+ assertSsrfAllowedUrl,
24
+ type AudioContent,
25
+ type FileContent,
26
+ type DocumentContent,
27
+ } from "@arnilo/prism";
28
+
29
+ const file: FileContent = {
30
+ type: "file",
31
+ mediaType: "application/pdf",
32
+ name: "report.pdf",
33
+ data: base64Pdf,
34
+ };
35
+
36
+ const audio: AudioContent = {
37
+ type: "audio",
38
+ mediaType: "audio/wav",
39
+ resourceUri: "package://demo/sample.wav",
40
+ durationMs: 12_000,
41
+ };
42
+
43
+ await resolveMediaContentBlock(file, { bounds: { maxItemBytes: 10_000_000 } });
44
+ ```
45
+
46
+ `ResolveMediaContentOptions` also exposes `ssrf`, `fetch`, `resolveHostname`, `requestUrl`, `loader`, `loadContext`, and `signal`. `MediaHostnameResolver`, `MediaHostAddress`, `MediaUrlRequester`, and `MediaUrlRequest` are exported for typed custom/test transports; ordinary callers need none of them.
47
+
48
+ Known `ModelCapabilities.input` tags are exported as `MODEL_INPUT_CAPABILITIES`:
49
+
50
+ | Tag | Block type | First-party mapping (declared capability required) |
51
+ | --- | --- | --- |
52
+ | `text` | `text` (default) | All providers |
53
+ | `image` | `image` | OpenAI Responses, OpenRouter, OpenCode Go Anthropic route, Kimi, NeuralWatt |
54
+ | `audio` | `audio` | OpenAI Responses (`input_audio`) |
55
+ | `file` | `file` | OpenAI Responses (`input_file`); Anthropic routes map PDF only |
56
+ | `document` | `document` | OpenAI Responses (`input_file`); OpenCode Go Anthropic route; Kimi |
57
+
58
+ ## Outputs / response / events
59
+
60
+ - `resolveMediaContentBlock()` resolves one item and returns `{ mediaType, bytes, name?, durationMs?, transcript?, metadata? }`.
61
+ - `resolveMediaContentBlocks()` is the complete-request path: it rejects item count/inline estimates before I/O, resolves each item within its bound, then enforces exact aggregate decoded bytes.
62
+ - `assertModelSupportsContentBlocks()` / `assertMessagesSupportModelCapabilities()` throw `UnsupportedModalityError` when a declared capability list omits the block modality.
63
+ - `assertMediaBlocksWithinBounds()` enforces per-item bytes, total request bytes, item count, and audio duration ceilings.
64
+ - `assertSsrfAllowedUrl()` rejects loopback, private, link-local, unspecified, multicast, IPv4-mapped private, and metadata literals/hosts unless explicitly allow-listed.
65
+ - Default URL resolution performs one bounded `dns.lookup(..., { all: true })`, rejects the whole hostname if any answer is non-public, then pins the selected public address into `http.request()`/`https.request()` so a second DNS lookup cannot rebind the connection.
66
+ - `sniffMediaMimeType()` / `assertDeclaredMediaTypeMatches()` compare declared MIME types to magic bytes.
67
+ - No events are emitted and no provider calls occur in these helpers.
68
+
69
+ Default ceilings:
70
+
71
+ | Constant | Value |
72
+ | --- | --- |
73
+ | `DEFAULT_MAX_MEDIA_ITEM_BYTES` | 10 MB |
74
+ | `DEFAULT_MAX_MEDIA_REQUEST_BYTES` | 32 MiB |
75
+ | `DEFAULT_MAX_AUDIO_DURATION_MS` | 5 minutes |
76
+ | `DEFAULT_MEDIA_FETCH_TIMEOUT_MS` | 30 seconds |
77
+ | `DEFAULT_MAX_MEDIA_ITEMS_PER_REQUEST` | 32 items |
78
+
79
+ ## Request/response example
80
+
81
+ ```json
82
+ {
83
+ "block": {
84
+ "type": "file",
85
+ "mediaType": "application/pdf",
86
+ "name": "report.pdf",
87
+ "resourceUri": "package://demo/report.pdf"
88
+ },
89
+ "resolved": {
90
+ "mediaType": "application/pdf",
91
+ "bytes": "<Uint8Array>",
92
+ "name": "report.pdf"
93
+ }
94
+ }
95
+ ```
96
+
97
+ ## Implementation example
98
+
99
+ ```ts
100
+ import {
101
+ assembleProviderInput,
102
+ loadBinaryResource,
103
+ resolveMediaContentBlock,
104
+ UnsupportedModalityError,
105
+ } from "@arnilo/prism";
106
+
107
+ const loader = {
108
+ async load(uri) {
109
+ return { uri, mediaType: "application/pdf", data: pdfBytes };
110
+ },
111
+ };
112
+
113
+ const bytes = await loadBinaryResource(loader, "package://demo/report.pdf");
114
+ const resolved = await resolveMediaContentBlock(
115
+ { type: "document", mediaType: "application/pdf", resourceUri: "package://demo/report.pdf" },
116
+ { loader },
117
+ );
118
+
119
+ try {
120
+ await assembleProviderInput({
121
+ model: { provider: "demo", model: "text-only", capabilities: { input: ["text"] } },
122
+ input: [{ role: "user", content: [{ type: "file", mediaType: "application/pdf", data: "..." }] }],
123
+ });
124
+ } catch (error) {
125
+ if (error instanceof UnsupportedModalityError) {
126
+ // Host-visible reject before provider HTTP.
127
+ }
128
+ }
129
+ ```
130
+
131
+ ## Extension and configuration notes
132
+
133
+ - URL fetches use the DNS-classifying, address-pinned Node transport by default. `resolveHostname` and `requestUrl` are paired test/custom seams; `requestUrl` must connect to the supplied validated address while preserving the original URL hostname for HTTP Host/TLS verification.
134
+ - Supplying `fetch` is a trusted compatibility/custom-transport escape hatch: Prism still checks URL literals and host allow-lists, but the host-provided fetch owns DNS resolution, rebinding protection, redirects, proxies, TLS, auth, and logging.
135
+ - `resourceUri` resolution requires a caller-provided `ResourceLoader` and optional `ResourceLoadContext.permission` check.
136
+ - Local filesystem paths should use trust policies such as `createPathTrustPolicy()` before exposing URIs to loaders.
137
+ - Provider upload/create/delete lifecycles are provider-package-local. `@arnilo/prism-provider-openai` inlines files under 4 MiB as `data:<mediaType>;base64,...` `file_data`, otherwise uses a bounded per-run upload cache and best-effort `DELETE /v1/files` cleanup after each stream.
138
+ - Shared wire helpers live in `@arnilo/prism/providers/media` (`resolveProviderMediaMessages`, `serializeOpenAIResponsesInputFile`, `serializePdfDocumentWireBlock`, `createBoundedUploadCache`). OpenAI Responses, Kimi, and OpenCode Go Anthropic routes resolve their complete media collection once before serialization or upload.
139
+
140
+ ## Security and performance notes
141
+
142
+ - SSRF deny-by-default blocks IPv4/IPv6 loopback, private/unique-local, link-local, unspecified, multicast, IPv4-mapped private, and cloud metadata targets. DNS answers are all classified before one public address is pinned; mixed public/private answers fail closed.
143
+ - `allowedHostnames` is an explicit trust override and may permit a private destination. `denyPrivateHosts: false` is broader and should be reserved for hosts that intentionally own private-network access.
144
+ - DNS lookup, connection, and body streaming share `fetchTimeoutMs` and caller abort; more than 32 resolved addresses, redirects, and oversized response bodies are rejected.
145
+ - MIME validation rejects common magic-byte spoofing; extensions alone are never trusted.
146
+ - Byte budgets use base64 size estimates before decode and re-check decoded bytes after every read/fetch. Complete-request resolution keeps at most the configured request budget plus one bounded item in memory and performs no provider upload/request until validation succeeds.
147
+ - Media errors omit raw bytes/base64 payloads from messages.
148
+ - Fetch readers are cancelled promptly after bound violations or abort signals.
149
+
150
+ ## Related APIs
151
+
152
+ - [Input and prompt assembly](input-and-prompt-assembly.md): attachments and `assembleProviderInput()` capability checks.
153
+ - [Resource loading](resource-loading.md): `loadBinaryResource()` and text/JSON helpers.
154
+ - [Model registry](model-registry.md): `ModelCapabilities.input` metadata.
155
+ - [Provider conformance](provider-conformance.md): serialized request coverage for content blocks.
156
+ - [Persistence, credentials, and multimodality primitives](persistence-credentials-multimodality-primitives.md): Plan 056 inventory and threat model.