@arnilo/prism 0.11.0 → 0.12.0
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 +32 -0
- package/README.md +28 -27
- package/dist/cli-dev.js +1 -1
- package/dist/cli-init.js +4 -4
- package/dist/cli-provider-add.js +2 -2
- package/dist/field-evidence.d.ts +53 -0
- package/dist/field-evidence.js +143 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/run-bundle.js +2 -2
- package/docs/acp-agent.md +2 -2
- package/docs/ag-ui.md +1 -1
- package/docs/attention-compiler.md +1 -1
- package/docs/cli-rpc.md +2 -2
- package/docs/coding-tools.md +3 -3
- package/docs/compaction-observational-memory.md +13 -3
- package/docs/computer-use-linux.md +2 -2
- package/docs/core.md +9 -10
- package/docs/dev-inspector.md +1 -1
- package/docs/disaster-recovery.md +1 -1
- package/docs/document-extraction.md +140 -0
- package/docs/evaluations.md +1 -1
- package/docs/execution-timeline.md +30 -0
- package/docs/guardrails.md +53 -0
- package/docs/history/130-cyclic-workflows-primitive-review.md +293 -0
- package/docs/history/132-document-ingestion-primitive-review.md +124 -0
- package/docs/history/README.md +2 -0
- package/docs/history/migrate-to-0.12.0.md +57 -0
- package/docs/history/retire-node-runtime.md +59 -0
- package/docs/hooks.md +1 -1
- package/docs/host-compositions.md +2 -2
- package/docs/host-security.md +1 -1
- package/docs/index.md +30 -25
- package/docs/live-testing.md +1 -1
- package/docs/mcp-tools.md +2 -2
- package/docs/messaging-channels.md +1 -1
- package/docs/migrate-to-0.7.md +1 -1
- package/docs/migrate-to-0.8.md +2 -2
- package/docs/migrate-to-0.9.md +1 -1
- package/docs/migration.md +2 -2
- package/docs/model-registry.md +1 -1
- package/docs/model-routing.md +21 -0
- package/docs/multi-agent-patterns.md +77 -4
- package/docs/obscura.md +1 -1
- package/docs/openapi-tools.md +1 -1
- package/docs/operations.md +2 -2
- package/docs/peer-dependencies.md +18 -17
- package/docs/performance.md +32 -32
- package/docs/prompt-registry.md +1 -1
- package/docs/provider-packages.md +23 -21
- package/docs/providers/decisions.md +134 -0
- package/docs/providers/laya.md +116 -0
- package/docs/providers/typesafe.md +148 -0
- package/docs/rag.md +17 -5
- package/docs/release-and-install.md +119 -113
- package/docs/run-bundle.md +4 -0
- package/docs/runs-and-usage.md +1 -1
- package/docs/signal-channel.md +1 -1
- package/docs/sqlite-persistence.md +5 -5
- package/docs/synapta-integration-review.md +238 -0
- package/docs/telegram-channel.md +1 -1
- package/docs/testing.md +33 -14
- package/docs/wiki.md +12 -11
- package/docs/work-sandbox.md +2 -0
- package/docs/work-tools.md +3 -3
- package/docs/workflows.md +213 -11
- package/package.json +22 -21
- package/templates/business-worker/README.md.tmpl +3 -3
- package/templates/business-worker/package.json.tmpl +4 -5
- package/templates/business-worker/src/tests/agent.test.ts.tmpl +1 -1
- package/templates/business-worker/tsconfig.json.tmpl +2 -1
- package/templates/deep-research/README.md.tmpl +4 -4
- package/templates/deep-research/package.json.tmpl +4 -5
- package/templates/deep-research/src/tests/research.test.ts.tmpl +1 -1
- package/templates/deep-research/tsconfig.json.tmpl +2 -1
- package/templates/init/README.md.tmpl +3 -3
- package/templates/init/optional/evals-example.ts.tmpl +1 -1
- package/templates/init/optional/workflows-example.ts.tmpl +1 -1
- package/templates/init/package.json.tmpl +4 -5
- package/templates/init/src/tests/agent.test.ts.tmpl +1 -1
- package/templates/init/tsconfig.json.tmpl +2 -1
- package/templates/personal-assistant/README.md.tmpl +3 -3
- package/templates/personal-assistant/package.json.tmpl +4 -5
- package/templates/personal-assistant/src/tests/agent.test.ts.tmpl +1 -1
- package/templates/personal-assistant/tsconfig.json.tmpl +2 -1
- package/templates/provider/README.md.tmpl +2 -2
- package/templates/provider/package.json.tmpl +3 -4
- package/templates/provider/src/tests/provider.test.ts.tmpl +1 -1
- package/templates/provider/tsconfig.json.tmpl +1 -1
package/docs/core.md
CHANGED
|
@@ -5,20 +5,19 @@ The `@arnilo/prism-core` family package unifies Prism's privileged runtime, sess
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
|
|
8
|
+
bun add @arnilo/prism @arnilo/prism-core
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
SQLite persistence uses the Bun runtime's built-in `bun:sqlite`. No package to install.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
# SQLite sessions & prompt storage
|
|
15
|
-
npm install better-sqlite3
|
|
13
|
+
For PostgreSQL and distributed event streams, install the optional peer:
|
|
16
14
|
|
|
15
|
+
```bash
|
|
17
16
|
# PostgreSQL sessions, enterprise persistence & prompt storage
|
|
18
|
-
|
|
17
|
+
bun add pg
|
|
19
18
|
|
|
20
19
|
# NATS JetStream distributed event source
|
|
21
|
-
|
|
20
|
+
bun add @nats-io/jetstream @nats-io/transport-node
|
|
22
21
|
```
|
|
23
22
|
|
|
24
23
|
Every peer below is optional and fails closed at first use; the [optional peer dependencies](peer-dependencies.md) matrix lists the exact ranges, pins, and which of them reach the network.
|
|
@@ -31,12 +30,12 @@ Every peer below is optional and fails closed at first use; the [optional peer d
|
|
|
31
30
|
| `@arnilo/prism-core/runtime/supervisor` | Agent-to-Agent (A2A) protocol server, client, event source, and multi-agent supervisor | — |
|
|
32
31
|
| `@arnilo/prism-core/runtime/workflows` | Multi-step DAG workflow coordinator, saga recovery, checkpoints, and loop nodes | — |
|
|
33
32
|
| `@arnilo/prism-core/sessions/codecs` | Checkpoint, cursor, feedback, and search serialization codecs | — |
|
|
34
|
-
| `@arnilo/prism-core/sessions/sqlite` | SQLite session store, leases, lifecycle, and schema migrations | `
|
|
33
|
+
| `@arnilo/prism-core/sessions/sqlite` | SQLite session store, leases, lifecycle, and schema migrations | — (`bun:sqlite`, built in) |
|
|
35
34
|
| `@arnilo/prism-core/sessions/postgres` | PostgreSQL session store, event source, and migrations | `pg` |
|
|
36
35
|
| `@arnilo/prism-core/sessions/nats` | NATS JetStream distributed event source | `@nats-io/jetstream`, `@nats-io/transport-node` |
|
|
37
36
|
| `@arnilo/prism-core/governance/policy` | Capability admission, tool execution approvals, audit log exporter, and OPA evaluator | — |
|
|
38
37
|
| `@arnilo/prism-core/governance/evals` | Offline evaluation runs, scorers, judges, threshold assertions, and trace curation | — |
|
|
39
|
-
| `@arnilo/prism-core/governance/prompts` | Versioned prompt registry, promotion gating, rollback, and storage | `
|
|
38
|
+
| `@arnilo/prism-core/governance/prompts` | Versioned prompt registry, promotion gating, rollback, and storage | `pg` (SQLite is `bun:sqlite`) |
|
|
40
39
|
| `@arnilo/prism-core/governance/model-router` | Cost- and latency-aware model routing, token reservations, and failover | — |
|
|
41
40
|
| `@arnilo/prism-core/governance/observability` | OpenTelemetry instrumentation and event tracing | `@opentelemetry/api` |
|
|
42
41
|
| `@arnilo/prism-core/credentials/node` | Keyring-backed encrypted credential store, scrypt envelope encryption, OAuth2 PKCE providers, and OIDC identity verification | `@napi-rs/keyring` (bundled) |
|
|
@@ -81,7 +80,7 @@ const validator = createJsonSchemaToolArgumentValidator();
|
|
|
81
80
|
|
|
82
81
|
## Security & Import Isolation
|
|
83
82
|
|
|
84
|
-
- Subpaths never load
|
|
83
|
+
- Subpaths never load the `pg` driver unless the specific database subpath is imported. SQLite uses the runtime's `bun:sqlite`, not a package.
|
|
85
84
|
- All database and network drivers fail closed with clear actionable error messages when peers are omitted.
|
|
86
85
|
- Root `@arnilo/prism` remains dependency-free contracts and CLI runner.
|
|
87
86
|
- Messaging channels are `@arnilo/prism-channels` (`/telegram`, `/signal`), not a `@arnilo/prism-core` subpath.
|
package/docs/dev-inspector.md
CHANGED
|
@@ -17,7 +17,7 @@ Use it when iterating on prompts in a local Prism host and you want a inspectabl
|
|
|
17
17
|
### Quickstart — `prism dev` (plan 040 Task 4)
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
|
|
20
|
+
bun add --dev @arnilo/prism-coding-tools/dev
|
|
21
21
|
cd my-agent && npm run dev # → prism dev → http://127.0.0.1:4311
|
|
22
22
|
```
|
|
23
23
|
|
|
@@ -29,7 +29,7 @@ This page is the operator runbook for backup, restore, migration rollback, point
|
|
|
29
29
|
```sh
|
|
30
30
|
# Protected drill (standard tools only, orchestrated by the script):
|
|
31
31
|
PRISM_PITR_URL=postgresql://user:***@localhost:55436/postgres \
|
|
32
|
-
|
|
32
|
+
bun scripts/phase27-dr.test.mjs \
|
|
33
33
|
--source "$PRISM_TEST_POSTGRES_URL" \
|
|
34
34
|
--target postgresql://user:***@localhost:55432/prism_dr_target \
|
|
35
35
|
--confirm-target prism_dr_restore
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Document extraction (`@arnilo/prism-work/document-extraction`)
|
|
2
|
+
|
|
3
|
+
> **Optional peer install:** `bun add @arnilo/prism-work @firecrawl/anydoc@0.2.4`. Default hosts skip it. See [Optional peer dependencies](peer-dependencies.md).
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
Converts in-memory Office, OpenDocument, RTF, EPUB, CSV, and text-PDF bytes to Markdown with a host-selected optional peer. Scanned PDFs throw `NeedsOcrError` unless the host injects a Docling runner. This subpath does not call Firecrawl Parse and does not read an API key.
|
|
8
|
+
|
|
9
|
+
## When to use it
|
|
10
|
+
|
|
11
|
+
Use when a host needs structured Markdown from a document byte buffer and can install `@firecrawl/anydoc`. Do not use it for the coding read tool (`document-reader`), for editable OOXML (`documents`), or for decimal-safe sheet ingest (`sheets`). Image OCR runs only when the host passes a runner and sets `ocr: true` on that call.
|
|
12
|
+
|
|
13
|
+
## Inputs / request
|
|
14
|
+
|
|
15
|
+
`createDocumentExtractor(options?)` loads the peer once. `extract({ bytes, filename?, signal? })` converts one buffer.
|
|
16
|
+
|
|
17
|
+
| Option | Meaning | Default | Ceiling |
|
|
18
|
+
| --- | --- | --- | --- |
|
|
19
|
+
| `maxBytes` | Input cap. Oversize buffers refuse before the native call | 32 MiB | 512 MiB |
|
|
20
|
+
| `maxTextBytes` | Markdown cap. Oversize results refuse; they are not truncated | 2 MiB | 64 MiB |
|
|
21
|
+
| `maxPages` | OCR page cap. Checked before the runner when anydoc reported a count, and again on the worker result | 32 | 256 |
|
|
22
|
+
| `ocrTimeoutMs` | Wall time the host runner must enforce. This module cannot kill a process | 120 s | 600 s |
|
|
23
|
+
| `maxStderrBytes` | Worker stderr cap. Oversize or truncated stderr fails with no Markdown | 1 MiB | 4 MiB |
|
|
24
|
+
| `ocr` | Host runner. Absent: scanned PDFs throw `NeedsOcrError` | none | |
|
|
25
|
+
|
|
26
|
+
Format comes from content. `filename` is used only when content has no signature and its basename ends with `.csv`. A path in `filename` is not opened. `extract({ ocr: true })` selects image OCR only; a text PDF that anydoc converts still skips the runner.
|
|
27
|
+
|
|
28
|
+
## Outputs / response / events
|
|
29
|
+
|
|
30
|
+
Success: `{ markdown, format, ocrUsed }`. `pages` is set only when Docling reported it. `ocrUsed` is false for anydoc. Markdown is untrusted data; label it at the wiki or RAG boundary that stores it.
|
|
31
|
+
|
|
32
|
+
Errors: `DocumentExtractionError` (`code: "ERR_PRISM_DOCUMENT_EXTRACTION"`) with `reason`:
|
|
33
|
+
|
|
34
|
+
| `reason` | When |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| `needsOcr` | `NeedsOcrError`. `pages` (1-indexed) and `pageCount`. No Markdown |
|
|
37
|
+
| `unsupported` | No signature, and the name is not CSV |
|
|
38
|
+
| `encrypted` / `malformed` / `resourceLimit` / `missingPart` / `io` | Peer refused. No fallback |
|
|
39
|
+
| `hosted` | Peer reported a hosted-OCR failure. This API never requests hosted OCR |
|
|
40
|
+
| `missingPeer` | `@firecrawl/anydoc` is not installed, at creation |
|
|
41
|
+
| `inputLimit` / `outputLimit` | Cap exceeded. No text returned |
|
|
42
|
+
| `busy` | One conversion is in flight and another is already waiting |
|
|
43
|
+
|
|
44
|
+
Invalid caps throw `RangeError` at creation. An aborted `signal` throws `AbortError`.
|
|
45
|
+
|
|
46
|
+
## Request/response example
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"format": "docx",
|
|
51
|
+
"ocrUsed": false,
|
|
52
|
+
"markdown": "Hello Prism DOCX\n"
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Implementation example
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { createDocumentExtractor, NeedsOcrError } from "@arnilo/prism-work/document-extraction";
|
|
60
|
+
|
|
61
|
+
const extractor = await createDocumentExtractor();
|
|
62
|
+
try {
|
|
63
|
+
const { markdown, format } = await extractor.extract({ bytes, filename, signal });
|
|
64
|
+
// markdown is untrusted. Do not grant it tool authority.
|
|
65
|
+
} catch (error) {
|
|
66
|
+
if (error instanceof NeedsOcrError) {
|
|
67
|
+
// pages and pageCount only. No partial Markdown.
|
|
68
|
+
}
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Host OCR runner. Prism does not spawn this. Kill the child on abort; do not pass the process env through.
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { spawn } from "node:child_process";
|
|
77
|
+
import { createDocumentExtractor, doclingOcrArgs, type OcrWorkerResult } from "@arnilo/prism-work/document-extraction";
|
|
78
|
+
|
|
79
|
+
const extractor = await createDocumentExtractor({
|
|
80
|
+
ocr: (request) =>
|
|
81
|
+
new Promise<OcrWorkerResult>((resolve, reject) => {
|
|
82
|
+
const child = spawn(python, doclingOcrArgs(request, artifacts), {
|
|
83
|
+
env: { PATH: process.env.PATH ?? "", HF_HUB_OFFLINE: "1", TRANSFORMERS_OFFLINE: "1", HF_HUB_DISABLE_TELEMETRY: "1" },
|
|
84
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
85
|
+
});
|
|
86
|
+
const stdout: Buffer[] = [];
|
|
87
|
+
const stderr: Buffer[] = [];
|
|
88
|
+
const kill = () => child.kill("SIGKILL");
|
|
89
|
+
request.signal?.addEventListener("abort", kill, { once: true });
|
|
90
|
+
child.stdout.on("data", (chunk: Buffer) => stdout.push(chunk));
|
|
91
|
+
child.stderr.on("data", (chunk: Buffer) => stderr.push(chunk));
|
|
92
|
+
child.on("error", reject);
|
|
93
|
+
child.on("close", (exitCode) => {
|
|
94
|
+
resolve({ exitCode, stdout: Buffer.concat(stdout), stderr: Buffer.concat(stderr), aborted: request.signal?.aborted });
|
|
95
|
+
});
|
|
96
|
+
child.stdin.end(request.bytes);
|
|
97
|
+
}),
|
|
98
|
+
});
|
|
99
|
+
const scanned = await extractor.extract({ bytes: pdfBytes });
|
|
100
|
+
const image = await extractor.extract({ bytes: pngBytes, ocr: true });
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Wiki and RAG stay in the memory package. This adapter does not import it. Set `ocrImages` on both sides or images keep the wiki stub.
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import { createDocumentExtractor, createDocumentIngest } from "@arnilo/prism-work/document-extraction";
|
|
107
|
+
import { createWikiExtension } from "@arnilo/prism-memory/wiki";
|
|
108
|
+
import { replaceDocument } from "@arnilo/prism-memory/rag";
|
|
109
|
+
|
|
110
|
+
const extractor = await createDocumentExtractor({ ocr });
|
|
111
|
+
const ingest = createDocumentIngest(extractor, { ocrImages: true });
|
|
112
|
+
const wiki = createWikiExtension({ extractDocument: ingest.extractDocument, ocrImages: ingest.ocrImages });
|
|
113
|
+
await replaceDocument({ uri, loader, parser: ingest.parser, store, scope, sourceId });
|
|
114
|
+
// ingest.stats() is counts and milliseconds only. Markdown is untrusted at the wiki and RAG boundary.
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Extension and configuration notes
|
|
118
|
+
|
|
119
|
+
- Peer version is pinned to `0.2.4`. The published wrapper accepts a third `options` argument that can upload the whole file. This module never passes it.
|
|
120
|
+
- Docling is not an npm dependency. The package ships `docling/ocr.py`. The host pins `docling==2.130.0` and CPU torch (`https://download.pytorch.org/whl/cpu`; default PyPI torch pulls CUDA wheels), prefetches models (~1.4 GiB, about 150 s), and injects a runner. `doclingOcrArgs(request, artifactsPath)` builds the helper argv. Bytes go on stdin. A URL artifact path is rejected.
|
|
121
|
+
- The runner must use a scrubbed env, close stdin after the bytes, kill the worker on abort and on stdout/stderr caps, and not leave the process running. `WorkSandbox.execFile` has no stdin, so it is not this runner. One OCR job in flight per extractor, same slot as anydoc. A third call fails with `busy`.
|
|
122
|
+
- The helper accepts only `ConversionStatus.SUCCESS`. Partial, timeout, missing models, and over-page results are errors. Empty Markdown with a page count is valid. PDF and PNG Markdown are not byte-identical.
|
|
123
|
+
- Native packages: darwin x64/arm64, linux x64/arm64 gnu and musl, win32 x64. No FreeBSD, Android, riscv, or Windows ARM build.
|
|
124
|
+
- Importing `/documents`, `/sheets`, `/connectors`, or `/document-reader` does not load this peer.
|
|
125
|
+
|
|
126
|
+
## Security and performance notes
|
|
127
|
+
|
|
128
|
+
- Local conversion only. No `process.env` read, no hosted OCR, no process spawn, no macro execution, no filename filesystem read.
|
|
129
|
+
- Upstream error text is not copied into `Error.message`. A peer message can contain document bytes.
|
|
130
|
+
- One conversion in flight per extractor, plus one waiter. A third call fails with `busy`. Two extractors can run at once; share one if memory matters.
|
|
131
|
+
- `AbortSignal` is checked before the native call and after it returns. It does not preempt the native call. The slot stays held until that call returns, then the result is discarded if the signal aborted.
|
|
132
|
+
- Caps are checked on the input length before native code runs, and on UTF-8 output length after. Defaults match the document-reader defaults. Raising them toward the hard ceiling is host-owned: native work on a large file cannot be cancelled.
|
|
133
|
+
- Small anydoc fixtures (under 10 KiB) on Bun 1.4.2 finished in a few milliseconds with process VmHWM under 64 MiB. The regression gate is 500 ms and 128 MiB for that class of input.
|
|
134
|
+
- After model prefetch, one scanned page was about 5 s cold and 2 s warm, with VmHWM about 1.8–2 GiB. Two converters in one process reached about 3 GiB. Keep one worker. Budget for inputs under 100 KiB: under 30 s and 4 GiB VmHWM. Prefetch itself is host-owned and is not part of that budget.
|
|
135
|
+
|
|
136
|
+
## Related APIs
|
|
137
|
+
|
|
138
|
+
- [Document reader](document-reader.md): literal text for the coding read tool, including an opt-in Mistral OCR parser. Separate from this subpath.
|
|
139
|
+
- [Documents, spreadsheets, and presentations](documents.md): editable OOXML model. Not a Markdown converter.
|
|
140
|
+
- [Optional peer dependencies](peer-dependencies.md): install line for `@firecrawl/anydoc`.
|
package/docs/evaluations.md
CHANGED
|
@@ -323,7 +323,7 @@ if (records.some((record) => record.status !== "scored" || record.score !== 1))
|
|
|
323
323
|
|
|
324
324
|
```sh
|
|
325
325
|
# Protected run (requires a disposable PostgreSQL instance):
|
|
326
|
-
PRISM_TEST_POSTGRES_URL=postgresql://...
|
|
326
|
+
PRISM_TEST_POSTGRES_URL=postgresql://... bun test scripts/phase27-erp-journey.test.mjs
|
|
327
327
|
```
|
|
328
328
|
|
|
329
329
|
The journey reuses the two-replica failover worker (`scripts/phase27-ha-worker.mjs`) and asserts the comprehensive DR drill evidence (`docs/_evidence/phase27-dr-evidence.json`) is present and not stale. Local substitutes are labelled in the journey evidence and never converted into production claims: an in-memory WORM/SIEM sink (host owns the immutable store in production), in-memory saga checkpoint/lease stores (saga durability is proven in its own suite), and a logical pg-client backup/restore of the ERP tables (comprehensive PITR is in the DR drill evidence). Passing this protected journey **does not** satisfy the 0.3.0 live-service matrix.
|
|
@@ -211,6 +211,36 @@ const timeline = projectWorkflowTimeline(workflowEvents, {
|
|
|
211
211
|
|
|
212
212
|
See runnable host demo in `examples/execution-timeline.ts` for offline workflow timeline projection, cockpit summary, and Mermaid diagram export.
|
|
213
213
|
|
|
214
|
+
### Host step loops and ledger persistence
|
|
215
|
+
|
|
216
|
+
When the host owns the step loop (one business step per `session.run()`), correlate its own
|
|
217
|
+
`stepId` / `actionId` / `attemptId` with the Prism `sessionId` / `runId` in the host ledger envelope:
|
|
218
|
+
Prism's events already carry those ids, so the timeline needs no extra correlation field. Project one
|
|
219
|
+
incremental folder per step:
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
const subscription = session.subscribe(); // before run — see ordering note below
|
|
223
|
+
const folder = createTimelineFolder({ content: "metadata" });
|
|
224
|
+
const consume = (async () => {
|
|
225
|
+
for await (const event of subscription) folder.push(event);
|
|
226
|
+
})();
|
|
227
|
+
const result = await session.run(prompt); // result.sessionId / result.runId
|
|
228
|
+
await consume;
|
|
229
|
+
const timeline = folder.snapshot(); // per-step frozen projection
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
`subscribe()` registers synchronously, while `session.run()` emits `agent_started` before it returns —
|
|
233
|
+
so start the subscription *before* the run, or the folder misses the run identity and `timeline.runId`
|
|
234
|
+
stays empty. Persist the frozen timeline (default `metadata` policy) into the host ledger as
|
|
235
|
+
newline-delimited JSON. Keep any legacy prose trace as display-only data beside the structural record:
|
|
236
|
+
budgets, turns and stop reasons come from the typed fields, never from parsing prose. External host
|
|
237
|
+
evidence (commits, verifications) is host authority, not a Prism effect — attach it to the ledger
|
|
238
|
+
envelope with an explicit host marker instead of adding a timeline step.
|
|
239
|
+
|
|
240
|
+
Runnable: [`examples/host-step-loop-timeline.ts`](../examples/host-step-loop-timeline.ts) — eight steps,
|
|
241
|
+
one tool round persisting a `sha256:` argument hash instead of raw args, and external commit evidence
|
|
242
|
+
on the last step.
|
|
243
|
+
|
|
214
244
|
### Stop reasons
|
|
215
245
|
|
|
216
246
|
Run-level `stopReason` mirrors `agent_finished.finishReason` when the loop stopped on a ceiling or a host turn policy (`"host_policy"`); `status` reads `finished:<stopReason>` for those runs and `succeeded` for a natural end. `stopDetail` carries the host's `turnPolicy.stop` reason, bounded to 256 bytes and redacted at the runtime boundary. See [Runs and usage ledger § Clean stops and stop reasons](runs-and-usage.md#clean-stops-and-stop-reasons).
|
package/docs/guardrails.md
CHANGED
|
@@ -149,6 +149,59 @@ An explicit citation is `[evidence:<ref>]`, immediately after its claim (within
|
|
|
149
149
|
|
|
150
150
|
With `onViolation: "block"`, the standard `GuardrailError` has `reason: "claim_ungrounded"` and bounded metadata `{ claim, contentIndex, start, end }` — never a full response body. `"flag"` returns `action: "allow"` plus that same metadata and `violation: true`, so the response stays visible while the normal `guardrail_decision` event and run ledger preserve the flag. Strict mode treats every standalone number (including dates, percentages, and versions) as a claim; use the option only where that law is wanted.
|
|
151
151
|
|
|
152
|
+
## Field-evidence provenance
|
|
153
|
+
|
|
154
|
+
`createFieldEvidenceGuardrail(options)` is a deterministic `tool_input` guardrail for one host-selected
|
|
155
|
+
tool. It verifies that each required field carries a typed source reference and that the claimed
|
|
156
|
+
value's revision and normalized value match the host's evidence set. It performs no retrieval, calls
|
|
157
|
+
no model, and grants no authority: retrieval, grants, digest freeze, and commit-time validation stay
|
|
158
|
+
with the host.
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
import { createFieldEvidenceGuardrail } from "@arnilo/prism";
|
|
162
|
+
|
|
163
|
+
const provenance = createFieldEvidenceGuardrail({
|
|
164
|
+
toolName: "synapta:proposal",
|
|
165
|
+
required: ["amount", "vendor.id"],
|
|
166
|
+
evidence: ({ toolCallId }) => hostLedger.evidenceFor(toolCallId), // current rows only
|
|
167
|
+
normalize: (value) => (typeof value === "string" ? Number(value.replace(/[$,]/g, "")) : value),
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const agent = createAgent({ model, provider, guardrails: { toolInput: [provenance] } });
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
A required field must hold a provenance envelope:
|
|
174
|
+
|
|
175
|
+
```json
|
|
176
|
+
{
|
|
177
|
+
"amount": { "value": 1250, "source": "tool:invoice_fetch", "path": "invoice.total", "revision": 7 }
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Evidence rows are `{ source, path, value, revision? }` — the source's current value. `source` and
|
|
182
|
+
`path` are matched literally; `revision` must match exactly (a claim without a revision cannot match
|
|
183
|
+
an evidence row that has one, and vice versa). After `normalize`, values compare exactly
|
|
184
|
+
(`Object.is`), so a normalizer that returns objects never matches. `required` supports dot paths
|
|
185
|
+
(`vendor.id`), bounded to 256 entries and 256 chars per path; evidence is capped at 4096 rows per call
|
|
186
|
+
and a larger set fails closed instead of truncating.
|
|
187
|
+
|
|
188
|
+
| Violation | Meaning |
|
|
189
|
+
| --- | --- |
|
|
190
|
+
| `missing_field` | The required path is absent from the call arguments. |
|
|
191
|
+
| `malformed_claim` | The path holds a plain value or an incomplete envelope (missing `value`, `source`, or `path`; invalid `revision`). |
|
|
192
|
+
| `missing_evidence` | The host evidence source returned no valid rows for this call. |
|
|
193
|
+
| `unknown_source` | No evidence row matches the claimed `source` + `path` (invented field or wrong object). |
|
|
194
|
+
| `stale_revision` | Matching rows exist but none carries the claimed revision. |
|
|
195
|
+
| `value_mismatch` | A fresh row exists but its normalized value differs. |
|
|
196
|
+
| `evidence_over_limit` | More than 4096 valid evidence rows; fails closed instead of truncating. |
|
|
197
|
+
|
|
198
|
+
A violation blocks the call before lookup, permission, validation, or execution: the decision is
|
|
199
|
+
`{ action: "block", reason: "field_evidence", metadata: { field, violation } }`, the blocked
|
|
200
|
+
`ToolResult` stays neutral, and the `guardrail_decision` event carries only the field path and code —
|
|
201
|
+
never the claimed value, evidence value, or any commit/approval semantics. Calls to other tools are
|
|
202
|
+
allowed untouched. The host decides which fields require evidence and supplies it per call; the
|
|
203
|
+
guardrail never widens, rewrites, or commits anything.
|
|
204
|
+
|
|
152
205
|
## Extension and configuration notes
|
|
153
206
|
|
|
154
207
|
Guardrails are callbacks supplied by the host. Prism does not discover, load, retry, or persist callback code. `createSecureAgent()` keeps configured guardrails and only appends run-level checks; it never lets a run remove secure defaults. Custom loops receive guarded `LoopContext.generate()` and `LoopContext.dispatchToolCall()`; host code that directly calls a provider or `ToolDefinition.execute()` is outside the runtime boundary. Guardrail packs follow the same rule: they are host-supplied config, compiled in memory per session, never discovered from disk. Their compiled identity and pack-owned state persist only inside an opt-in durable checkpoint (`persistSessionState`, see above) and nowhere else.
|