@arnilo/prism 0.0.3 → 0.0.4
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 +22 -0
- package/README.md +32 -20
- package/dist/agent-loops.d.ts +8 -1
- package/dist/agent-loops.js +57 -11
- package/dist/agents.js +70 -17
- package/dist/checkpoints.d.ts +11 -0
- package/dist/checkpoints.js +144 -0
- package/dist/compaction.js +9 -1
- package/dist/content.d.ts +102 -0
- package/dist/content.js +410 -0
- package/dist/contracts.d.ts +142 -2
- package/dist/event-multiplexer.d.ts +23 -0
- package/dist/event-multiplexer.js +136 -0
- package/dist/execution-policy.d.ts +28 -0
- package/dist/execution-policy.js +24 -0
- package/dist/index.d.ts +17 -5
- package/dist/index.js +11 -4
- package/dist/input.js +11 -1
- package/dist/leases.d.ts +8 -0
- package/dist/leases.js +111 -0
- package/dist/node/agent-definitions.js +3 -5
- package/dist/node/config.d.ts +1 -0
- package/dist/node/config.js +5 -3
- package/dist/node/contribution-discovery.js +5 -8
- package/dist/node/session-store-jsonl.js +8 -5
- package/dist/node/settings.js +2 -2
- package/dist/node/trust.js +2 -4
- package/dist/observability.d.ts +3 -0
- package/dist/observability.js +18 -0
- package/dist/providers/media.d.ts +42 -0
- package/dist/providers/media.js +116 -0
- package/dist/providers/openai-compatible.js +18 -119
- package/dist/providers/openai-primitives.d.ts +9 -0
- package/dist/providers/openai-primitives.js +129 -0
- package/dist/providers/transport.d.ts +40 -0
- package/dist/providers/transport.js +221 -0
- package/dist/redaction.js +40 -13
- package/dist/resources.d.ts +5 -0
- package/dist/resources.js +4 -0
- package/dist/structured-output.d.ts +11 -0
- package/dist/structured-output.js +59 -0
- package/dist/testing/persistence-schema.d.ts +102 -0
- package/dist/testing/persistence-schema.js +457 -0
- package/dist/testing/provider-conformance.js +10 -1
- package/dist/testing/run-ledger-conformance.d.ts +33 -0
- package/dist/testing/run-ledger-conformance.js +172 -0
- package/dist/testing/session-store-conformance.d.ts +16 -0
- package/dist/testing/session-store-conformance.js +73 -0
- package/dist/tools.d.ts +17 -0
- package/dist/tools.js +29 -2
- package/docs/agent-events.md +13 -4
- package/docs/agent-loops.md +10 -4
- package/docs/agent-session-runtime.md +1 -0
- package/docs/cli-rpc.md +3 -0
- package/docs/coding-agent-tools.md +41 -7
- package/docs/coding-security.md +84 -0
- package/docs/credential-storage.md +177 -0
- package/docs/credentials-and-redaction.md +2 -1
- package/docs/database-persistence.md +44 -2
- package/docs/host-security.md +15 -1
- package/docs/index.md +28 -12
- package/docs/input-and-prompt-assembly.md +6 -5
- package/docs/mcp-tools.md +139 -0
- package/docs/middleware-hooks.md +2 -0
- package/docs/migration.md +21 -28
- package/docs/model-registry.md +5 -3
- package/docs/multimodal-content.md +148 -0
- package/docs/observability.md +163 -0
- package/docs/performance.md +40 -1
- package/docs/persistence-credentials-multimodality-primitives.md +303 -0
- package/docs/postgres-persistence.md +141 -0
- package/docs/provider-conformance.md +17 -0
- package/docs/provider-layer.md +1 -1
- package/docs/provider-primitives.md +281 -0
- package/docs/providers/kimi.md +1 -0
- package/docs/providers/neuralwatt.md +1 -0
- package/docs/providers/openai-compatible.md +2 -1
- package/docs/providers/openai.md +8 -1
- package/docs/providers/opencode-go.md +1 -0
- package/docs/providers/openrouter.md +1 -0
- package/docs/providers/zai.md +1 -0
- package/docs/public-contracts.md +9 -2
- package/docs/release-and-install.md +209 -25
- package/docs/resource-loading.md +14 -4
- package/docs/review-coverage-2026-07-14.md +260 -0
- package/docs/run-ledger-conformance.md +96 -0
- package/docs/runs-and-usage.md +2 -0
- package/docs/session-store-conformance.md +16 -0
- package/docs/session-stores-and-branching.md +1 -0
- package/docs/settings-auth-trust-security.md +2 -1
- package/docs/sqlite-persistence.md +122 -0
- package/docs/structured-output.md +9 -0
- package/docs/tool-conformance.md +1 -0
- package/docs/tool-execution-primitives.md +374 -0
- package/docs/tools.md +39 -1
- package/docs/workflow-orchestration-primitives.md +565 -0
- package/docs/workflow-tui-primitives.md +5 -0
- package/docs/workflows.md +219 -0
- package/package.json +33 -5
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# MCP client bridge
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
`@arnilo/prism-mcp` connects Prism hosts to remote [Model Context Protocol](https://modelcontextprotocol.io) servers and maps discovered tools to ordinary `ToolDefinition`s. Transports are stdio subprocesses and Streamable HTTP. The package wraps the official MCP TypeScript SDK (`@modelcontextprotocol/sdk` v1.29+) and does **not** add MCP-specific branches 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
|
+
## When to use it
|
|
25
|
+
|
|
26
|
+
- **Integrate external MCP tool servers** (filesystem, databases, SaaS adapters) without reimplementing JSON-RPC transports in your app.
|
|
27
|
+
- **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).
|
|
28
|
+
- **Explicit lifecycle** — connect, refresh on `notifications/tools/list_changed`, and `close()` when the session ends.
|
|
29
|
+
|
|
30
|
+
Do **not** use this package as a sandbox, permission engine, or auto-discovery loader. Hosts must trust configured commands/URLs and gate registration.
|
|
31
|
+
|
|
32
|
+
## Inputs / request
|
|
33
|
+
|
|
34
|
+
`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.
|
|
35
|
+
|
|
36
|
+
## Outputs / response / events
|
|
37
|
+
|
|
38
|
+
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.
|
|
39
|
+
|
|
40
|
+
## Request/response example
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"request": { "serverId": "docs", "transport": { "type": "stdio", "command": "node", "args": ["server.js"] } },
|
|
45
|
+
"mappedTool": { "name": "mcp:docs:search", "parameters": { "type": "object" } }
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Implementation example
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import { createToolRegistry } from "@arnilo/prism";
|
|
53
|
+
import { connectMcpTools } from "@arnilo/prism-mcp";
|
|
54
|
+
|
|
55
|
+
const bridge = await connectMcpTools({
|
|
56
|
+
serverId: "docs",
|
|
57
|
+
transport: { type: "stdio", command: "node", args: ["server.js"] },
|
|
58
|
+
callTimeoutMs: 30_000,
|
|
59
|
+
});
|
|
60
|
+
const registry = createToolRegistry({ duplicate: "error" });
|
|
61
|
+
for (const tool of bridge.tools) registry.register(tool);
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Tool naming and mapping
|
|
65
|
+
|
|
66
|
+
| MCP | Prism |
|
|
67
|
+
| --- | --- |
|
|
68
|
+
| `tools/list` `inputSchema` | `ToolDefinition.parameters` |
|
|
69
|
+
| `tools/call` arguments | Parsed `ToolCallContent.arguments` |
|
|
70
|
+
| `tools/call` content blocks | `ToolResult.content` (`text`, `image`; resource/audio/link → descriptive `text`) |
|
|
71
|
+
| Tool `name` | Prefixed `mcp:<serverId>:<name>` (override with `namePrefix`) |
|
|
72
|
+
| `isError` results | `ToolResult.error` with summarized text |
|
|
73
|
+
| `structuredContent` | `ToolResult.value` / metadata |
|
|
74
|
+
|
|
75
|
+
Duplicate prefixed names throw `McpToolNameCollisionError` at refresh time.
|
|
76
|
+
|
|
77
|
+
## Extension and configuration notes
|
|
78
|
+
|
|
79
|
+
| Option | Default | Purpose |
|
|
80
|
+
| --- | --- | --- |
|
|
81
|
+
| `serverId` | required | Stable identifier used in default name prefix |
|
|
82
|
+
| `transport` | required | `stdio` or `streamable-http` config |
|
|
83
|
+
| `namePrefix` | `mcp:<serverId>:` | Registry namespace for remote tools |
|
|
84
|
+
| `listCacheTtlMs` | `30000` | Skip re-listing until TTL expires (invalidated on list-changed) |
|
|
85
|
+
| `callTimeoutMs` | `60000` | Per-call MCP request timeout |
|
|
86
|
+
| `maxResultBytes` | `10000000` | Bound mapped result content |
|
|
87
|
+
| `signal` | none | Abort connect and trigger close on abort |
|
|
88
|
+
|
|
89
|
+
### Stdio transport
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
{
|
|
93
|
+
type: "stdio",
|
|
94
|
+
command: "node",
|
|
95
|
+
args: ["path/to/server.js"],
|
|
96
|
+
env?: Record<string, string>,
|
|
97
|
+
cwd?: string,
|
|
98
|
+
stderr?: "inherit" | "pipe" | "ignore" | "overlapped",
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The host explicitly chooses the executable, arguments, environment, and working directory. Prism does not search `PATH` for unknown servers or inject credentials.
|
|
103
|
+
|
|
104
|
+
### Streamable HTTP transport
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
{
|
|
108
|
+
type: "streamable-http",
|
|
109
|
+
url: "https://mcp.example.com/mcp",
|
|
110
|
+
requestInit?: RequestInit,
|
|
111
|
+
sessionId?: string,
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Only `http:` and `https:` URLs are accepted. Authentication (Bearer tokens, cookies) is supplied through `requestInit.headers` by the host.
|
|
116
|
+
|
|
117
|
+
## Security and performance notes
|
|
118
|
+
|
|
119
|
+
| Risk | Mitigation |
|
|
120
|
+
| --- | --- |
|
|
121
|
+
| Untrusted subprocess (stdio) | Explicit `command` / `args` / `env` / `cwd`; review before deploy |
|
|
122
|
+
| SSRF / open redirects (HTTP) | Host URL allow-lists, network policy, no implicit discovery |
|
|
123
|
+
| Tool-name shadowing | Prefixed names + `createToolRegistry({ duplicate: "error" })` |
|
|
124
|
+
| Oversized server output | `maxResultBytes` on content mapping |
|
|
125
|
+
| Unvalidated arguments | Register tools with `createJsonSchemaToolArgumentValidator()` at dispatch |
|
|
126
|
+
| Missing permission gate | `PermissionPolicy` on `tool:mcp:<serverId>:<name>:execute` (or broader deny rules) |
|
|
127
|
+
|
|
128
|
+
MCP output is untrusted. Apply `SecretRedactor` and host logging policy to `ToolResult` before persisting or displaying.
|
|
129
|
+
|
|
130
|
+
## Related APIs
|
|
131
|
+
|
|
132
|
+
- [Tools](tools.md): registry, dispatch, validation
|
|
133
|
+
- [Tool execution primitives](tool-execution-primitives.md): Plan 055 design and conformance matrix
|
|
134
|
+
- [Host security guide](host-security.md): permission, trust, validation checklist
|
|
135
|
+
- Package README: [`@arnilo/prism-mcp`](../packages/mcp/README.md)
|
|
136
|
+
|
|
137
|
+
## Testing
|
|
138
|
+
|
|
139
|
+
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.
|
package/docs/middleware-hooks.md
CHANGED
|
@@ -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,12 @@
|
|
|
2
2
|
|
|
3
3
|
## What it does
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Prism 0.0.4 is source-compatible with documented 0.0.3 agent construction; no mandatory code migration is required. New capabilities are additive and inactive until configured. This page covers two optional adoption paths:
|
|
6
6
|
|
|
7
|
-
1. **In-memory / JSONL → database-backed persistence** —
|
|
8
|
-
2. **
|
|
7
|
+
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`.
|
|
8
|
+
2. **Legacy permissive capability configuration → explicit activation** — name tools/skills and keep omitted capabilities fail-closed.
|
|
9
9
|
|
|
10
|
-
It
|
|
10
|
+
It states before/after shapes and links detailed schema, redaction, branch, capability, and security guidance.
|
|
11
11
|
|
|
12
12
|
## When to use it
|
|
13
13
|
|
|
@@ -15,7 +15,7 @@ Read this page when:
|
|
|
15
15
|
|
|
16
16
|
- you are taking an app from the `createMemorySessionStore()` / `createJsonlSessionStore()` path to a multi-process, multi-tenant, or durable database backend;
|
|
17
17
|
- 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
|
|
18
|
+
- you are adopting 0.0.4 persistence, checkpoints/leases, workflows, structured output, multimodality, or explicit tool safety for the first time.
|
|
19
19
|
|
|
20
20
|
If you are new to Prism, start at [Session stores](session-stores.md) and [Agent/session runtime](agent-session-runtime.md) instead.
|
|
21
21
|
|
|
@@ -26,6 +26,8 @@ There is no runtime import for this page. The migrations below use these surface
|
|
|
26
26
|
| Surface | Where | Migration role |
|
|
27
27
|
| --- | --- | --- |
|
|
28
28
|
| `SessionStore` | `@arnilo/prism` | Runtime seam swapped from memory/JSONL to DB. |
|
|
29
|
+
| `createSqlitePersistence` | `@arnilo/prism-session-store-sqlite` | Local durable session, ledger, query, checkpoint, and lease adapter. |
|
|
30
|
+
| `createPostgresPersistence` | `@arnilo/prism-session-store-postgres` | Multi-process pooled persistence with advisory-lock migrations. |
|
|
29
31
|
| `ProductionPersistenceStore` | `@arnilo/prism` | Adapter-facing contract for paginated, multi-tenant reads (`query*`, optional `readBranchPath`). |
|
|
30
32
|
| `RunLedger` / `RunLedgerRecord` | `@arnilo/prism` | Durable run/event/tool-call/usage ledger attached via `AgentConfig.runLedger` / `RunOptions.runLedger`. |
|
|
31
33
|
| `SessionAppendOptions` / `SessionAppendConflictError` / `SessionBranchHandle` | `@arnilo/prism` | Atomic append, retry dedup, durable branch handles. |
|
|
@@ -77,34 +79,23 @@ Capability migration (before/after):
|
|
|
77
79
|
|
|
78
80
|
### Migration 1 — in-memory / JSONL → database-backed persistence
|
|
79
81
|
|
|
80
|
-
|
|
82
|
+
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
83
|
|
|
82
|
-
Step 1:
|
|
84
|
+
Step 1: replace the development store with a first-party adapter. Use PostgreSQL instead when multiple processes or sustained concurrent writers matter.
|
|
83
85
|
|
|
84
86
|
```ts
|
|
85
87
|
// Before: development store, single process.
|
|
86
88
|
import { createJsonlSessionStore } from "@arnilo/prism/node/session-store-jsonl";
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
// After:
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const store
|
|
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
|
-
};
|
|
89
|
+
const oldStore = createJsonlSessionStore("./sessions.jsonl");
|
|
90
|
+
|
|
91
|
+
// After: local durable adapter. The same object implements SessionStore,
|
|
92
|
+
// RunLedger, ProductionPersistenceStore, checkpoints, and leases.
|
|
93
|
+
import { createSqlitePersistence } from "@arnilo/prism-session-store-sqlite";
|
|
94
|
+
const store = createSqlitePersistence({ filename: "./prism.db" });
|
|
106
95
|
```
|
|
107
96
|
|
|
97
|
+
Custom adapters remain supported through `SessionStore` / `ProductionPersistenceStore`; implement indexed `readBranchPath()` rather than full-session scans.
|
|
98
|
+
|
|
108
99
|
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
100
|
|
|
110
101
|
```ts
|
|
@@ -174,7 +165,7 @@ Runtime skill activation remains explicit: `RunOptions.activeSkills` narrows per
|
|
|
174
165
|
|
|
175
166
|
## Extension and configuration notes
|
|
176
167
|
|
|
177
|
-
- **Persistence
|
|
168
|
+
- **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`, checkpoint, and lease contracts.
|
|
178
169
|
- **`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
170
|
- **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
171
|
- **Migration order is decoupled.** You can adopt database persistence without changing capability activation, and vice versa. Both migrations are independent config swaps.
|
|
@@ -190,7 +181,9 @@ Runtime skill activation remains explicit: `RunOptions.activeSkills` narrows per
|
|
|
190
181
|
|
|
191
182
|
## Related APIs
|
|
192
183
|
|
|
193
|
-
- [Database persistence](database-persistence.md): production
|
|
184
|
+
- [Database persistence](database-persistence.md): production contracts, reference schema, indexes, conditional append, retention, migrations, and custom adapters.
|
|
185
|
+
- [SQLite persistence](sqlite-persistence.md): local durable first-party adapter and writer ceiling.
|
|
186
|
+
- [PostgreSQL persistence](postgres-persistence.md): pooled multi-process adapter, TLS/pool ownership, and live gate.
|
|
194
187
|
- [Session stores](session-stores.md): `SessionStore` contract, `SessionAppendOptions`, `SessionAppendConflictError`, branch handles, `readBranchPath`.
|
|
195
188
|
- [Session stores and branching](session-stores-and-branching.md): detailed branch semantics and helper reference.
|
|
196
189
|
- [Runs and usage ledger](runs-and-usage.md): `RunLedger` record shapes, redaction, and event/usage ordering.
|
package/docs/model-registry.md
CHANGED
|
@@ -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,148 @@
|
|
|
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
|
+
assertSsrfAllowedUrl,
|
|
23
|
+
type AudioContent,
|
|
24
|
+
type FileContent,
|
|
25
|
+
type DocumentContent,
|
|
26
|
+
} from "@arnilo/prism";
|
|
27
|
+
|
|
28
|
+
const file: FileContent = {
|
|
29
|
+
type: "file",
|
|
30
|
+
mediaType: "application/pdf",
|
|
31
|
+
name: "report.pdf",
|
|
32
|
+
data: base64Pdf,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const audio: AudioContent = {
|
|
36
|
+
type: "audio",
|
|
37
|
+
mediaType: "audio/wav",
|
|
38
|
+
resourceUri: "package://demo/sample.wav",
|
|
39
|
+
durationMs: 12_000,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
await resolveMediaContentBlock(file, { bounds: { maxItemBytes: 10_000_000 } });
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Known `ModelCapabilities.input` tags are exported as `MODEL_INPUT_CAPABILITIES`:
|
|
46
|
+
|
|
47
|
+
| Tag | Block type | First-party mapping (declared capability required) |
|
|
48
|
+
| --- | --- | --- |
|
|
49
|
+
| `text` | `text` (default) | All providers |
|
|
50
|
+
| `image` | `image` | OpenAI Responses, OpenRouter, OpenCode Go Anthropic route, Kimi, NeuralWatt |
|
|
51
|
+
| `audio` | `audio` | OpenAI Responses (`input_audio`) |
|
|
52
|
+
| `file` | `file` | OpenAI Responses (`input_file`); Anthropic routes map PDF only |
|
|
53
|
+
| `document` | `document` | OpenAI Responses (`input_file`); OpenCode Go Anthropic route; Kimi |
|
|
54
|
+
|
|
55
|
+
## Outputs / response / events
|
|
56
|
+
|
|
57
|
+
- `resolveMediaContentBlock()` returns `{ mediaType, bytes, name?, durationMs?, transcript?, metadata? }`.
|
|
58
|
+
- `assertModelSupportsContentBlocks()` / `assertMessagesSupportModelCapabilities()` throw `UnsupportedModalityError` when a declared capability list omits the block modality.
|
|
59
|
+
- `assertMediaBlocksWithinBounds()` enforces per-item bytes, total request bytes, item count, and audio duration ceilings.
|
|
60
|
+
- `assertSsrfAllowedUrl()` rejects private/link-local/metadata hosts unless explicitly allow-listed.
|
|
61
|
+
- `sniffMediaMimeType()` / `assertDeclaredMediaTypeMatches()` compare declared MIME types to magic bytes.
|
|
62
|
+
- No events are emitted and no provider calls occur in these helpers.
|
|
63
|
+
|
|
64
|
+
Default ceilings:
|
|
65
|
+
|
|
66
|
+
| Constant | Value |
|
|
67
|
+
| --- | --- |
|
|
68
|
+
| `DEFAULT_MAX_MEDIA_ITEM_BYTES` | 10 MB |
|
|
69
|
+
| `DEFAULT_MAX_MEDIA_REQUEST_BYTES` | 32 MiB |
|
|
70
|
+
| `DEFAULT_MAX_AUDIO_DURATION_MS` | 5 minutes |
|
|
71
|
+
| `DEFAULT_MEDIA_FETCH_TIMEOUT_MS` | 30 seconds |
|
|
72
|
+
| `DEFAULT_MAX_MEDIA_ITEMS_PER_REQUEST` | 32 items |
|
|
73
|
+
|
|
74
|
+
## Request/response example
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"block": {
|
|
79
|
+
"type": "file",
|
|
80
|
+
"mediaType": "application/pdf",
|
|
81
|
+
"name": "report.pdf",
|
|
82
|
+
"resourceUri": "package://demo/report.pdf"
|
|
83
|
+
},
|
|
84
|
+
"resolved": {
|
|
85
|
+
"mediaType": "application/pdf",
|
|
86
|
+
"bytes": "<Uint8Array>",
|
|
87
|
+
"name": "report.pdf"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Implementation example
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import {
|
|
96
|
+
assembleProviderInput,
|
|
97
|
+
loadBinaryResource,
|
|
98
|
+
resolveMediaContentBlock,
|
|
99
|
+
UnsupportedModalityError,
|
|
100
|
+
} from "@arnilo/prism";
|
|
101
|
+
|
|
102
|
+
const loader = {
|
|
103
|
+
async load(uri) {
|
|
104
|
+
return { uri, mediaType: "application/pdf", data: pdfBytes };
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const bytes = await loadBinaryResource(loader, "package://demo/report.pdf");
|
|
109
|
+
const resolved = await resolveMediaContentBlock(
|
|
110
|
+
{ type: "document", mediaType: "application/pdf", resourceUri: "package://demo/report.pdf" },
|
|
111
|
+
{ loader },
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
await assembleProviderInput({
|
|
116
|
+
model: { provider: "demo", model: "text-only", capabilities: { input: ["text"] } },
|
|
117
|
+
input: [{ role: "user", content: [{ type: "file", mediaType: "application/pdf", data: "..." }] }],
|
|
118
|
+
});
|
|
119
|
+
} catch (error) {
|
|
120
|
+
if (error instanceof UnsupportedModalityError) {
|
|
121
|
+
// Host-visible reject before provider HTTP.
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Extension and configuration notes
|
|
127
|
+
|
|
128
|
+
- URL fetches use injectable `fetch` for tests and custom transports; production hosts should supply TLS, auth, and logging policy outside Prism core.
|
|
129
|
+
- `resourceUri` resolution requires a caller-provided `ResourceLoader` and optional `ResourceLoadContext.permission` check.
|
|
130
|
+
- Local filesystem paths should use trust policies such as `createPathTrustPolicy()` before exposing URIs to loaders.
|
|
131
|
+
- 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.
|
|
132
|
+
- Shared wire helpers live in `@arnilo/prism/providers/media` (`serializeOpenAIResponsesInputFile`, `serializePdfDocumentWireBlock`, `createBoundedUploadCache`).
|
|
133
|
+
|
|
134
|
+
## Security and performance notes
|
|
135
|
+
|
|
136
|
+
- SSRF deny-by-default blocks loopback, RFC1918, link-local, and cloud metadata hostnames unless `SsrfPolicy.allowedHostnames` is set.
|
|
137
|
+
- MIME validation rejects common magic-byte spoofing; extensions alone are never trusted.
|
|
138
|
+
- Byte budgets use base64 size estimates before decode and re-check decoded `buffer.length` after read/fetch.
|
|
139
|
+
- Media errors omit raw bytes/base64 payloads from messages.
|
|
140
|
+
- Fetch readers are cancelled promptly after bound violations or abort signals.
|
|
141
|
+
|
|
142
|
+
## Related APIs
|
|
143
|
+
|
|
144
|
+
- [Input and prompt assembly](input-and-prompt-assembly.md): attachments and `assembleProviderInput()` capability checks.
|
|
145
|
+
- [Resource loading](resource-loading.md): `loadBinaryResource()` and text/JSON helpers.
|
|
146
|
+
- [Model registry](model-registry.md): `ModelCapabilities.input` metadata.
|
|
147
|
+
- [Provider conformance](provider-conformance.md): serialized request coverage for content blocks.
|
|
148
|
+
- [Persistence, credentials, and multimodality primitives](persistence-credentials-multimodality-primitives.md): Plan 056 inventory and threat model.
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Observability
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
Prism exposes provider and tool timing through stable, metadata-only `AgentEvent` variants. Hosts subscribe via `session.subscribe()` or persist events through `RunLedger`. Core helpers build `ProviderTurnMetadata` and classify HTTP failures without echoing prompts, tool arguments, or credentials.
|
|
6
|
+
|
|
7
|
+
Optional package `@arnilo/prism-observability-opentelemetry` maps those events to OpenTelemetry spans and low-cardinality metrics. OpenTelemetry is **not** a dependency of `@arnilo/prism`.
|
|
8
|
+
|
|
9
|
+
APIs:
|
|
10
|
+
|
|
11
|
+
- `ProviderTurnMetadata`, `ToolExecutionMetadata` on `AgentEvent`
|
|
12
|
+
- `createProviderTurnMetadata()`, `readProviderHttpStatus()` in `@arnilo/prism`
|
|
13
|
+
- `createOpenTelemetryInstrumentation()`, `wrapOpenTelemetryApi()`, `createInMemoryTelemetry()` in `@arnilo/prism-observability-opentelemetry`
|
|
14
|
+
|
|
15
|
+
## When to use it
|
|
16
|
+
|
|
17
|
+
Use agent events when you need run-scoped latency, retry attempt numbers, token/cache usage, tool duration, or error classification in-process or through your own exporter.
|
|
18
|
+
|
|
19
|
+
Use the OpenTelemetry adapter when you already run the OpenTelemetry SDK and want spans/metrics without forking the runtime.
|
|
20
|
+
|
|
21
|
+
Do not parse raw provider SSE for timing — provider packages normalize stream events; the session emits `provider_turn_*` once per `generate()` attempt.
|
|
22
|
+
|
|
23
|
+
## Inputs / request
|
|
24
|
+
|
|
25
|
+
Core metadata helpers:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { createProviderTurnMetadata, readProviderHttpStatus } from "@arnilo/prism";
|
|
29
|
+
|
|
30
|
+
const metadata = createProviderTurnMetadata(request, providerId, { attempt: 2, latencyMs: 120 });
|
|
31
|
+
const httpStatus = readProviderHttpStatus(errorInfo);
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
New agent event variants (metadata only):
|
|
35
|
+
|
|
36
|
+
| Variant | When | Key fields |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| `provider_turn_started` | Before each provider `generate()` attempt | `turn`, `metadata: ProviderTurnMetadata` |
|
|
39
|
+
| `provider_turn_finished` | After success or failure of that attempt | `metadata` (includes `latencyMs`, optional `httpStatus`), `usage?`, `error?` |
|
|
40
|
+
|
|
41
|
+
`ToolExecutionMetadata` on terminal tool events:
|
|
42
|
+
|
|
43
|
+
| Field | Meaning |
|
|
44
|
+
| --- | --- |
|
|
45
|
+
| `durationMs` | Wall time from dispatch start to finish/block/error |
|
|
46
|
+
| `status` | `finished` \| `error` \| `blocked` |
|
|
47
|
+
|
|
48
|
+
OpenTelemetry adapter:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { trace, metrics } from "@opentelemetry/api";
|
|
52
|
+
import { createOpenTelemetryInstrumentation, wrapOpenTelemetryApi } from "@arnilo/prism-observability-opentelemetry";
|
|
53
|
+
|
|
54
|
+
const { tracer, meter } = wrapOpenTelemetryApi(trace.getTracer("app"), metrics.getMeter("app"));
|
|
55
|
+
const telemetry = createOpenTelemetryInstrumentation({ tracer, meter, onExporterError: console.error });
|
|
56
|
+
|
|
57
|
+
const detach = telemetry.attachSession(session);
|
|
58
|
+
// or: for await (const event of session.subscribe()) telemetry.handleAgentEvent(event);
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Set `enabled: false` or omit `tracer`/`meter` for a no-op adapter.
|
|
62
|
+
|
|
63
|
+
## Outputs / response / events
|
|
64
|
+
|
|
65
|
+
Provider turn metadata fields:
|
|
66
|
+
|
|
67
|
+
| Field | Source |
|
|
68
|
+
| --- | --- |
|
|
69
|
+
| `providerId` | Active provider id |
|
|
70
|
+
| `model` | `ProviderRequest.model` |
|
|
71
|
+
| `requestId` | `request.metadata.requestId` or `request.options.sessionId` |
|
|
72
|
+
| `attempt` | Retry attempt (1-based) |
|
|
73
|
+
| `latencyMs` | Set on `provider_turn_finished` |
|
|
74
|
+
| `httpStatus` | Numeric `ErrorInfo.code` when present |
|
|
75
|
+
| `rateLimitRemaining` / `rateLimitResetMs` | Reserved for provider adapters (optional) |
|
|
76
|
+
|
|
77
|
+
OpenTelemetry mapping (when enabled):
|
|
78
|
+
|
|
79
|
+
| Agent event | Span | Metric labels |
|
|
80
|
+
| --- | --- | --- |
|
|
81
|
+
| `agent_started` / `agent_finished` | `prism.agent.run` | — |
|
|
82
|
+
| `provider_turn_*` | `prism.provider.turn` | `provider_id`, `outcome` on duration histogram |
|
|
83
|
+
| `tool_execution_*` (terminal) | `prism.tool.execute` when started | `status` on duration histogram |
|
|
84
|
+
| `provider_turn_finished` / `agent_finished` usage | span attributes | `prism.provider.tokens` counter (`kind`: input/output/cache_*) |
|
|
85
|
+
|
|
86
|
+
High-cardinality identifiers (`sessionId`, `runId`, `requestId`, `toolCallId`) are **span attributes only**, never metric labels.
|
|
87
|
+
|
|
88
|
+
## Request/response example
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"type": "provider_turn_finished",
|
|
93
|
+
"sessionId": "sess_01J...",
|
|
94
|
+
"runId": "run_01J...",
|
|
95
|
+
"turn": 1,
|
|
96
|
+
"metadata": {
|
|
97
|
+
"providerId": "openai",
|
|
98
|
+
"model": { "provider": "openai", "model": "gpt-4.1" },
|
|
99
|
+
"requestId": "sess_01J...",
|
|
100
|
+
"attempt": 2,
|
|
101
|
+
"latencyMs": 842,
|
|
102
|
+
"httpStatus": 503
|
|
103
|
+
},
|
|
104
|
+
"error": { "message": "upstream unavailable", "code": 503 }
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"type": "tool_execution_finished",
|
|
111
|
+
"sessionId": "sess_01J...",
|
|
112
|
+
"runId": "run_01J...",
|
|
113
|
+
"result": { "toolCallId": "call_1", "name": "echo" },
|
|
114
|
+
"metadata": { "durationMs": 12, "status": "finished" }
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Implementation example
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
import { createAgent, createMockProvider, providerDone, providerTextDelta } from "@arnilo/prism";
|
|
122
|
+
import { createInMemoryTelemetry, createOpenTelemetryInstrumentation } from "@arnilo/prism-observability-opentelemetry";
|
|
123
|
+
|
|
124
|
+
const memory = createInMemoryTelemetry();
|
|
125
|
+
const telemetry = createOpenTelemetryInstrumentation({ tracer: memory.tracer, meter: memory.meter });
|
|
126
|
+
|
|
127
|
+
const session = createAgent({
|
|
128
|
+
model: { provider: "mock", model: "demo" },
|
|
129
|
+
provider: createMockProvider([providerTextDelta("hi"), providerDone()]),
|
|
130
|
+
}).createSession();
|
|
131
|
+
|
|
132
|
+
const detach = telemetry.attachSession(session);
|
|
133
|
+
await session.run("hello");
|
|
134
|
+
detach();
|
|
135
|
+
|
|
136
|
+
console.log(memory.spans.map((span) => span.name));
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Extension and configuration notes
|
|
140
|
+
|
|
141
|
+
- Events flow through `redactAgentEvent` before subscribers and ledger writes — configure `createSecretRedactor` on the agent/run.
|
|
142
|
+
- `retry_scheduled` still signals backoff; each retry attempt emits its own `provider_turn_*` pair with `metadata.attempt`.
|
|
143
|
+
- NeuralWatt `neuralwatt:telemetry` provider events remain package-local; hosts may forward numeric cost/energy into custom metrics.
|
|
144
|
+
- `@arnilo/prism-observability-opentelemetry` is optional and included through `@arnilo/prism-sdk` and `@arnilo/prism-all`; instrumentation remains disabled until a host configures it.
|
|
145
|
+
- Exporter failures are isolated: instrumentation catches tracer/meter errors and invokes `onExporterError` without affecting the run.
|
|
146
|
+
- Disabled instrumentation performs no per-delta span work (`enabled: false` or missing tracer/meter).
|
|
147
|
+
|
|
148
|
+
## Security and performance notes
|
|
149
|
+
|
|
150
|
+
- Default events are metadata-only — no prompts, streamed deltas, tool arguments, or credentials.
|
|
151
|
+
- Opt-in content in other event types (`message_delta`, tool `result`) is still subject to `redactAgentEvent`.
|
|
152
|
+
- Metric labels stay low-cardinality (`provider_id`, `outcome`, `status`, token `kind`); never use `sessionId`/`runId` as labels.
|
|
153
|
+
- Target overhead when enabled is under 5% excluding exporter I/O; disabled hooks allocate no spans.
|
|
154
|
+
- Provider transport limits and redaction order are documented in [Provider primitives](provider-primitives.md).
|
|
155
|
+
|
|
156
|
+
## Related APIs
|
|
157
|
+
|
|
158
|
+
- [Agent events](agent-events.md): full `AgentEvent` union and subscriber semantics.
|
|
159
|
+
- [Runs and usage ledger](runs-and-usage.md): durable `AgentEventRecord` persistence.
|
|
160
|
+
- [Middleware hooks](middleware-hooks.md): transform boundaries alongside event subscribers.
|
|
161
|
+
- [Provider primitives](provider-primitives.md): frozen observability contract for Plan 054.
|
|
162
|
+
- [Credentials and redaction](credentials-and-redaction.md): secret redaction before events and ledger rows.
|
|
163
|
+
- [Workflows](workflows.md): package-local `WorkflowEvent` stream that can wrap redacted `AgentEvent`s from agent nodes.
|