@arnilo/prism 0.11.1 → 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 +27 -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 +29 -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 +22 -22
- package/docs/providers/decisions.md +134 -0
- package/docs/providers/laya.md +3 -0
- package/docs/providers/typesafe.md +5 -2
- package/docs/rag.md +17 -5
- package/docs/release-and-install.md +118 -114
- 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/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
## [0.12.0] - 2026-09-26
|
|
2
|
+
|
|
3
|
+
> **Twelve publishable packages.** Lockstep minor from 0.11.1. Runtime is Bun `>=1.4.2`. Optional local document extraction is not a default install. See [docs/history/migrate-to-0.12.0.md](docs/history/migrate-to-0.12.0.md).
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **`@arnilo/prism-providers/decisions` typed decision call.** `askSystemOneDecisions(body, { timeoutMs?, maxStateBytes? }, signal?)` makes one `/v1/systemone` round trip and returns `{ model, answers, usage, timingMs }` with raw choice/score probabilities and confidence preserved; pre-fetch bounds (1–256 questions, 2–255 choice options, 2–10 score levels, 256 KiB state), typed auth/invalid-request/retry-exhausted/aborted errors, and deadline/abort composition into the existing retry loop. The `/typesafe` and `/laya` provider adapters still render schema text only. See [docs/providers/decisions.md](docs/providers/decisions.md).
|
|
7
|
+
- **Per-iteration replay forking with state and input injection.** `replayWorkflow` accepts `iteration?: number`, `injectState?: JsonObject`, and `injectInput?: unknown` in `ReplayWorkflowInput`. Targeted replay verifies the iteration exists in node execution history, restores checkpoint state at `stateVersionBefore` in $O(1)$ from `stateHistory`, merges injected state overrides, and seeds subsequent supersteps with deterministic lineage metadata. See [docs/workflows.md](docs/workflows.md).
|
|
8
|
+
- **Workflow state scoping & subgraph isolation (`withNodeScope`, `createScopedState`, `scopedSubgraphNode`).** Higher-order node wrapper `withNodeScope(scopeKey, node)` isolates node reads and updates to a sub-namespace within workflow state (`ctx.state = rootState[scopeKey]`), while exposing `ctx.rootState` for explicit cross-scope reads and validating scope keys against identifier traversal. `scopedSubgraphNode` executes nested workflows in isolated state namespaces with bidirectional input/output mapping. See [docs/workflows.md](docs/workflows.md).
|
|
9
|
+
- **Declarative event-driven swarm topology (`swarmRouterNode`, `defineSwarmWorkflow`).** Topic-based pub/sub multi-agent routing over cyclic supersteps. Agents publish events via `publishSwarmEvent(ctx, event)` and query triggering events via `getActiveSwarmEvents(ctx)`. `swarmRouterNode` matches topics (including wildcard patterns `topic:*` and `*`) against declared subscriptions and dynamically fans out to subscriber agents, draining cleanly to completion when all topic queues are empty. See [docs/workflows.md](docs/workflows.md) and [docs/multi-agent-patterns.md](docs/multi-agent-patterns.md).
|
|
10
|
+
- **Cyclic workflow graphs and dynamic routing (`routeNode`).** Wave-based superstep execution engine for cyclic workflows (`limits.maxSupersteps`, hard cap 256), node activation semantics (`activation: "all" | "any"`), dynamic branch selection via `routeNode({ select })` with fail-closed target validation against declared edges (`WorkflowRouteTargetError`, `ERR_PRISM_WORKFLOW_ROUTE_TARGET`), idle-drain completion, fail-closed host-unforgeable budget enforcement (`WorkflowSuperstepLimitError`, `ERR_PRISM_WORKFLOW_SUPERSTEP_LIMIT`), checkpoint schemaVersion 2 (`execution.superstep`, `execution.pending`, `execution.maxSupersteps`) with crash-consistent wave resumes, and cyclic replay resolution to latest succeeded iteration evidence. See [docs/workflows.md](docs/workflows.md).
|
|
11
|
+
- **Field-evidence guardrail (`createFieldEvidenceGuardrail`).** Opt-in `tool_input` guardrail beside claim grounding: each host-selected argument dot-path must resolve to a claim envelope (`{ value, source, path, revision? }`) that synchronous host-supplied evidence confirms for the same source and revision. Failures block with reason `field_evidence` and a violation token, never echo claimed or evidence values, and cap required fields (256) and evidence records (4,096). See [docs/guardrails.md](docs/guardrails.md).
|
|
12
|
+
- **Optional local document extraction.** `@arnilo/prism-work/document-extraction` converts supported bytes to Markdown with optional `@firecrawl/anydoc@0.2.4`. Scanned PDFs and explicit image OCR need a host-provisioned Docling worker (`docling/ocr.py`). Default hosts install neither. One OCR job. No upload by default.
|
|
13
|
+
- **`advertiseRecall` for observational-memory compaction.** `renderObservationalMemory` / `buildObservationalMemoryContextBlocks` can withhold the recall guidance sentence when the host exposes no recall tool; default `true` keeps existing output. See [docs/compaction-observational-memory.md](docs/compaction-observational-memory.md).
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- **Lockstep `0.11.1` → `0.12.0`.** All twelve manifests and internal caret ranges move together. The `src/index.ts` version constant is `"0.12.0"`.
|
|
17
|
+
- **SQLite is `bun:sqlite`.** The `better-sqlite3` peer is removed. Blob reads are `Uint8Array`. `lastInsertRowid` is a number unless `safeIntegers`. An `undefined` bind stores NULL. A `get()` miss is `null`. A Node import fails with the Bun runtime message.
|
|
18
|
+
- **Scaffolds emit Bun.** `prism init` writes `packageManager` `bun@1.4.2` and Bun install commands.
|
|
19
|
+
- **Coverage-safe observational-memory compaction.** The strategy keeps every eligible message entry after `latestObservationCoverageId`, not only the recent-message window, and leaves the fold boundary behind an unscanned prefix so a covered prefix can still compact. Automatic compaction defers when the observer skipped and no coverage cursor exists yet (`run_active`, `passive`, `missing_model`, `missing_credentials`, `in_flight`, `error`) instead of appending a per-turn compaction entry that folds nothing; `ObservationalMemoryFlushResult.skipped` is now the exported `ObservationalMemoryFlushSkipReason` union. See [docs/compaction-observational-memory.md](docs/compaction-observational-memory.md).
|
|
20
|
+
|
|
21
|
+
### Security
|
|
22
|
+
- The field-evidence verifier fails closed and keeps claimed and evidence values out of guardrail decisions. The typed decision call keeps timeout/error distinguishable from measured uncertainty and grants no effect authority; calibration stays host configuration.
|
|
23
|
+
- Route targets in `routeNode` are strictly constrained fail-closed to declared workflow edges (`targets ⊆ declaredSuccessors`), preventing arbitrary node injection or traversal of undeclared paths. Superstep execution budgets (`limits.maxSupersteps`) are host-unforgeable across crash restarts and resumes.
|
|
24
|
+
|
|
25
|
+
### Removed
|
|
26
|
+
- **Node runtime support (retired in 0.12.0).** All twelve publishable manifests declare `engines.bun >=1.4.2` and no longer declare `engines.node`; the two measured Node CI legs (the Node 24 public-import smoke and the `node22-compat` job) retired with them, and every install page moved to `bun add`. `engines.bun` is advisory metadata on Bun 1.4.2 — neither npm nor Bun enforces it — so the compensating control is the fail-closed import message plan 126 adds when the durable storage layer moves to `bun:sqlite`; the registry keeps the 0.11.x line for Node hosts, and `npm pack`/`npm publish`/`npm sbom` stay on the release host (plan 125 Task 5). See [docs/history/retire-node-runtime.md](docs/history/retire-node-runtime.md).
|
|
27
|
+
|
|
1
28
|
## [0.11.1] - 2026-09-24 (System One decision models)
|
|
2
29
|
|
|
3
30
|
### Added
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# prism
|
|
2
2
|
|
|
3
|
-
`prism` is a TypeScript
|
|
3
|
+
`prism` is a TypeScript agent harness for the Bun runtime. Host apps and extension packages
|
|
4
4
|
bring their tools, providers, credentials, storage, and UI; Prism supplies the
|
|
5
5
|
common contracts, registries, agent/session runtime, replaceable input/prompt
|
|
6
6
|
and compaction strategies, CLI/RPC adapters, and first-party provider/compaction
|
|
@@ -46,7 +46,7 @@ packages. The current 0.4 line publishes the generated package inventory (see
|
|
|
46
46
|
## Install
|
|
47
47
|
|
|
48
48
|
```bash
|
|
49
|
-
|
|
49
|
+
bun add @arnilo/prism
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
First-party code packages are separate imports and require `@arnilo/prism` as
|
|
@@ -54,14 +54,14 @@ a non-optional peer. Install atomic packages directly or choose a manifest-only
|
|
|
54
54
|
family/profile; profiles install packages but expose no alias exports and activate nothing:
|
|
55
55
|
|
|
56
56
|
```bash
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
57
|
+
bun add @arnilo/prism @arnilo/prism-providers # core + all provider adapters
|
|
58
|
+
bun add @arnilo/prism @arnilo/prism-core @arnilo/prism-memory # replaces prism-base
|
|
59
|
+
bun add @arnilo/prism @arnilo/prism-coding-tools @arnilo/prism-mcp @arnilo/prism-providers # replaces prism-code
|
|
60
|
+
bun add @arnilo/prism @arnilo/prism-core @arnilo/prism-mcp @arnilo/prism-providers # replaces prism-sdk
|
|
61
|
+
bun add @arnilo/prism @arnilo/prism-core @arnilo/prism-providers # pick families explicitly (no umbrella)
|
|
62
|
+
bun add @arnilo/prism-core/runtime/server @arnilo/prism-core/runtime/workflows # optional Web API boundary
|
|
63
|
+
bun add @arnilo/prism-core/runtime/supervisor # optional local delegation + A2A 1.0
|
|
64
|
+
bun add @arnilo/prism-web-tools # unified web tools family (root search + /browser + /obscura subpaths)
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
See [docs/release-and-install.md](docs/release-and-install.md) for install
|
|
@@ -72,12 +72,12 @@ specifiers, tarball contents, and the offline test budget.
|
|
|
72
72
|
Scaffold a project (offline mock test included):
|
|
73
73
|
|
|
74
74
|
```bash
|
|
75
|
-
|
|
75
|
+
bunx --package @arnilo/prism prism init my-agent
|
|
76
76
|
# or, scaffold with a real provider package selected:
|
|
77
|
-
|
|
77
|
+
bunx --package @arnilo/prism prism init my-agent --provider openai
|
|
78
78
|
# or, scaffold a full deep research agent from the template gallery:
|
|
79
|
-
|
|
80
|
-
cd my-agent &&
|
|
79
|
+
bunx --package @arnilo/prism prism init my-research --template deep-research
|
|
80
|
+
cd my-agent && bun install && bun test
|
|
81
81
|
```
|
|
82
82
|
|
|
83
83
|
List available template gallery starters:
|
|
@@ -158,22 +158,22 @@ printf '{"id":"1","command":"prompt","params":{"input":"Hi"}}\n' \
|
|
|
158
158
|
## Packages
|
|
159
159
|
|
|
160
160
|
<!-- generated:package-truth:inventory begin -->
|
|
161
|
-
**12 publishable manifests** — root `@arnilo/prism` plus 11 workspace packages (4 `prism-*` family packages, 7 capability packages). Generated by `
|
|
161
|
+
**12 publishable manifests** — root `@arnilo/prism` plus 11 workspace packages (4 `prism-*` family packages, 7 capability packages). Generated by `bun scripts/package-truth.mjs --emit-docs` — do not hand-edit.
|
|
162
162
|
|
|
163
163
|
| package | version | notes |
|
|
164
164
|
| --- | --- | --- |
|
|
165
|
-
| `@arnilo/prism` | 0.
|
|
166
|
-
| `@arnilo/prism-channels` | 0.
|
|
167
|
-
| `@arnilo/prism-coding-tools` | 0.
|
|
168
|
-
| `@arnilo/prism-core` | 0.
|
|
169
|
-
| `@arnilo/prism-providers` | 0.
|
|
170
|
-
| `@arnilo/prism-acp-agent` | 0.
|
|
171
|
-
| `@arnilo/prism-ag-ui` | 0.
|
|
172
|
-
| `@arnilo/prism-hooks` | 0.
|
|
173
|
-
| `@arnilo/prism-mcp` | 0.
|
|
174
|
-
| `@arnilo/prism-memory` | 0.
|
|
175
|
-
| `@arnilo/prism-web-tools` | 0.
|
|
176
|
-
| `@arnilo/prism-work` | 0.
|
|
165
|
+
| `@arnilo/prism` | 0.12.0 | core — runtime, CLI/RPC, templates, docs |
|
|
166
|
+
| `@arnilo/prism-channels` | 0.12.0 | family — transport-neutral messaging runtime, durable journal, pairing and one-use approvals; official /telegram (private DMs, opt-in granted groups/topics) and experimental pinned signal-cli /signal |
|
|
167
|
+
| `@arnilo/prism-coding-tools` | 0.12.0 | family — /agent, /security, /openapi, /computer-use-linux, /dev, /impeccable subpaths |
|
|
168
|
+
| `@arnilo/prism-core` | 0.12.0 | family — /runtime, /sessions, /governance, /credentials, /enterprise, /validation subpaths |
|
|
169
|
+
| `@arnilo/prism-providers` | 0.12.0 | family — all provider adapters as `/<adapter>` subpaths |
|
|
170
|
+
| `@arnilo/prism-acp-agent` | 0.12.0 | capability — ACP adapter |
|
|
171
|
+
| `@arnilo/prism-ag-ui` | 0.12.0 | capability — AG-UI/A2A/A2UI adapter |
|
|
172
|
+
| `@arnilo/prism-hooks` | 0.12.0 | capability — Claude/Codex-compatible hooks.json adapter compiled onto middleware, guardrail, injector, and stop-hook seams |
|
|
173
|
+
| `@arnilo/prism-mcp` | 0.12.0 | capability — MCP client/server/OAuth interop |
|
|
174
|
+
| `@arnilo/prism-memory` | 0.12.0 | capability — memory plus /rag, /compaction/*, /fabric, /wiki subpaths |
|
|
175
|
+
| `@arnilo/prism-web-tools` | 0.12.0 | capability — Brave/Exa/Firecrawl plus peer-gated /browser and /obscura subpaths |
|
|
176
|
+
| `@arnilo/prism-work` | 0.12.0 | capability — /connectors, /documents, /sheets, /diagrams, /document-extraction, /document-reader, /sandbox, /skills, /tools subpaths |
|
|
177
177
|
<!-- generated:package-truth:inventory end -->
|
|
178
178
|
|
|
179
179
|
## Scripts
|
|
@@ -185,6 +185,7 @@ printf '{"id":"1","command":"prompt","params":{"input":"Hi"}}\n' \
|
|
|
185
185
|
| `bun run typecheck` | Type-check without emitting |
|
|
186
186
|
| `bun run test` | Build + run network-free tests (`npm test` still works) |
|
|
187
187
|
| `bun run test:coverage` | Coverage gate: `bun test --coverage` over the core suite and every gated workspace, with Bun-measured floors (`scripts/coverage-thresholds.json`) |
|
|
188
|
+
| `bun run test:coverage` | Coverage gate: `bun test --coverage` over the core suite and every gated workspace, with Bun-measured floors (`scripts/coverage-thresholds.json`) |
|
|
188
189
|
| `bun run test:live` | Run live suites whose credentials are present (skip the rest) |
|
|
189
190
|
| `prism --help` | CLI help |
|
|
190
191
|
|
package/dist/cli-dev.js
CHANGED
|
@@ -10,7 +10,7 @@ import { join } from "node:path";
|
|
|
10
10
|
import process from "node:process";
|
|
11
11
|
import { pathToFileURL } from "node:url";
|
|
12
12
|
const installHint = "prism dev requires the dev inspector package in this project.\n" +
|
|
13
|
-
"
|
|
13
|
+
" bun add --dev @arnilo/prism-coding-tools\n" +
|
|
14
14
|
"(Scaffolded projects from newer `prism init` templates already include it.)\n";
|
|
15
15
|
/**
|
|
16
16
|
* Default loader: resolve `@arnilo/prism-coding-tools/dev/cli` (or `@arnilo/prism-dev/cli`)
|
package/dist/cli-init.js
CHANGED
|
@@ -235,8 +235,8 @@ export async function runInitCommand(argv, runtime) {
|
|
|
235
235
|
"",
|
|
236
236
|
"Next:",
|
|
237
237
|
` cd ${shellQuote(displayPath(result.targetDir, runtime.cwd ?? process.cwd()))}`,
|
|
238
|
-
"
|
|
239
|
-
"
|
|
238
|
+
" bun install",
|
|
239
|
+
" bun test",
|
|
240
240
|
"",
|
|
241
241
|
].join("\n"));
|
|
242
242
|
return 0;
|
|
@@ -482,8 +482,8 @@ function buildTokens(input) {
|
|
|
482
482
|
? `# Placeholder only — never commit real secrets.\n${input.provider.envKey}=${input.provider.envPlaceholder ?? ""}\n`
|
|
483
483
|
: `# No API key required for the mock provider.\n# Switch providers with: prism init <dir> --provider openai --force\n`;
|
|
484
484
|
const nextStepsLive = input.provider.envKey
|
|
485
|
-
? `2. Copy \`.env.example\` to \`.env\` and set \`${input.provider.envKey}\`.\n3. Run \`
|
|
486
|
-
: `2. Run \`
|
|
485
|
+
? `2. Copy \`.env.example\` to \`.env\` and set \`${input.provider.envKey}\`.\n3. Run \`bun run start\` for a live provider call.`
|
|
486
|
+
: `2. Run \`bun run start\` (mock provider; no network or credentials).`;
|
|
487
487
|
const optionalDocs = [
|
|
488
488
|
...(input.withWorkflows ? ["- `src/workflows-example.ts` — tiny DAG example using `@arnilo/prism-workflows`."] : []),
|
|
489
489
|
...(input.withEvals ? ["- `src/evals-example.ts` — deterministic scorer example using `@arnilo/prism-evals`."] : []),
|
package/dist/cli-provider-add.js
CHANGED
|
@@ -112,8 +112,8 @@ export async function runProviderAddCommand(argv, runtime) {
|
|
|
112
112
|
"",
|
|
113
113
|
"Next:",
|
|
114
114
|
` cd ${result.name}`,
|
|
115
|
-
"
|
|
116
|
-
"
|
|
115
|
+
" bun install",
|
|
116
|
+
" bun test",
|
|
117
117
|
" Replace the starter model metadata and docs stub with docs-verified values before publishing.",
|
|
118
118
|
"",
|
|
119
119
|
].join("\n"));
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Guardrail, JsonObject } from "./contracts.js";
|
|
2
|
+
/** One authoritative host evidence row: the current value of `path` inside `source` at `revision`. */
|
|
3
|
+
export interface FieldEvidenceRecord {
|
|
4
|
+
/** Host ledger / tool-result identity the value lives in. */
|
|
5
|
+
readonly source: string;
|
|
6
|
+
/** Field path inside that source object; matched literally against the claim's `path`. */
|
|
7
|
+
readonly path: string;
|
|
8
|
+
readonly value: unknown;
|
|
9
|
+
/** Source revision/freshness marker; a claim must name the same revision when the host sets one. */
|
|
10
|
+
readonly revision?: string | number;
|
|
11
|
+
}
|
|
12
|
+
export interface FieldEvidenceContext {
|
|
13
|
+
readonly sessionId: string;
|
|
14
|
+
readonly runId: string;
|
|
15
|
+
readonly toolName: string;
|
|
16
|
+
readonly toolCallId?: string;
|
|
17
|
+
/** The tool call's normalized arguments, so the host can resolve evidence per call. */
|
|
18
|
+
readonly arguments: JsonObject;
|
|
19
|
+
readonly metadata: Readonly<Record<string, unknown>>;
|
|
20
|
+
readonly signal: AbortSignal;
|
|
21
|
+
}
|
|
22
|
+
/** Supplies per-call host evidence without coupling this primitive to a host ledger or store. */
|
|
23
|
+
export type FieldEvidenceSource = (context: FieldEvidenceContext) => readonly FieldEvidenceRecord[];
|
|
24
|
+
/** The provenance envelope a proposal-shaped call must place at a required field path. */
|
|
25
|
+
export interface FieldProvenanceClaim {
|
|
26
|
+
readonly value: unknown;
|
|
27
|
+
readonly source: string;
|
|
28
|
+
/** Field path inside `source`; matched literally against {@link FieldEvidenceRecord.path}. */
|
|
29
|
+
readonly path: string;
|
|
30
|
+
readonly revision?: string | number;
|
|
31
|
+
}
|
|
32
|
+
/** Canonicalizes claimed and evidence values before exact comparison (units, currency, case). */
|
|
33
|
+
export type FieldEvidenceNormalizer = (value: unknown, field: string) => unknown;
|
|
34
|
+
/** Closed rejection reasons; the decision metadata carries the field and this code. */
|
|
35
|
+
export type FieldEvidenceViolation = "missing_field" | "malformed_claim" | "missing_evidence" | "unknown_source" | "stale_revision" | "value_mismatch" | "evidence_over_limit";
|
|
36
|
+
export interface FieldEvidenceGuardrailOptions {
|
|
37
|
+
/** Only calls to this tool are verified; every other tool call is allowed. */
|
|
38
|
+
readonly toolName: string;
|
|
39
|
+
/** Argument paths that must carry a {@link FieldProvenanceClaim}; 1–256 entries. */
|
|
40
|
+
readonly required: readonly string[];
|
|
41
|
+
/** Host-supplied authoritative evidence for the call. Must be synchronous — no retrieval here. */
|
|
42
|
+
readonly evidence: FieldEvidenceSource;
|
|
43
|
+
/** Defaults to identity; comparison after normalization is exact (`Object.is`). */
|
|
44
|
+
readonly normalize?: FieldEvidenceNormalizer;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Deterministic tool-input guardrail verifying claimed field provenance on one host-selected tool.
|
|
48
|
+
* It performs no retrieval and grants no authority: it only checks that each required field carries
|
|
49
|
+
* a typed source reference whose revision and normalized value match the host's evidence set.
|
|
50
|
+
* Fail-closed: an absent field, absent evidence, unknown source, revision mismatch, or value
|
|
51
|
+
* mismatch blocks the tool call before dispatch.
|
|
52
|
+
*/
|
|
53
|
+
export declare function createFieldEvidenceGuardrail(options: FieldEvidenceGuardrailOptions): Guardrail<"tool_input">;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
const MAX_REQUIRED_FIELDS = 256;
|
|
2
|
+
const MAX_FIELD_PATH_CHARS = 256;
|
|
3
|
+
const MAX_EVIDENCE_RECORDS = 4096;
|
|
4
|
+
const MAX_EVIDENCE_KEY_CHARS = 512;
|
|
5
|
+
/**
|
|
6
|
+
* Deterministic tool-input guardrail verifying claimed field provenance on one host-selected tool.
|
|
7
|
+
* It performs no retrieval and grants no authority: it only checks that each required field carries
|
|
8
|
+
* a typed source reference whose revision and normalized value match the host's evidence set.
|
|
9
|
+
* Fail-closed: an absent field, absent evidence, unknown source, revision mismatch, or value
|
|
10
|
+
* mismatch blocks the tool call before dispatch.
|
|
11
|
+
*/
|
|
12
|
+
export function createFieldEvidenceGuardrail(options) {
|
|
13
|
+
const resolved = resolveOptions(options);
|
|
14
|
+
return {
|
|
15
|
+
name: "field-evidence",
|
|
16
|
+
stage: "tool_input",
|
|
17
|
+
revision: "1",
|
|
18
|
+
evaluate(context) {
|
|
19
|
+
if (context.toolName !== resolved.toolName)
|
|
20
|
+
return { action: "allow" };
|
|
21
|
+
const args = context.value.arguments;
|
|
22
|
+
const { records, truncated } = resolveEvidence(resolved.evidence, resolved.toolName, context);
|
|
23
|
+
if (truncated)
|
|
24
|
+
return { action: "block", reason: "field_evidence", metadata: { violation: "evidence_over_limit" } };
|
|
25
|
+
for (const field of resolved.required) {
|
|
26
|
+
const violation = verifyField(field, args, records, resolved.normalize);
|
|
27
|
+
if (violation !== undefined) {
|
|
28
|
+
return { action: "block", reason: "field_evidence", metadata: { field, violation } };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return { action: "allow" };
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function resolveOptions(options) {
|
|
36
|
+
if (!options || typeof options.toolName !== "string" || options.toolName.trim().length === 0) {
|
|
37
|
+
throw new TypeError("Field evidence toolName must be a non-empty string");
|
|
38
|
+
}
|
|
39
|
+
if (!Array.isArray(options.required) || options.required.length === 0 || options.required.length > MAX_REQUIRED_FIELDS) {
|
|
40
|
+
throw new TypeError(`Field evidence required must be 1–${MAX_REQUIRED_FIELDS} field paths`);
|
|
41
|
+
}
|
|
42
|
+
for (const field of options.required) {
|
|
43
|
+
if (typeof field !== "string" || field.trim().length === 0 || field.length > MAX_FIELD_PATH_CHARS) {
|
|
44
|
+
throw new TypeError(`Field evidence required paths must be non-empty strings of at most ${MAX_FIELD_PATH_CHARS} chars`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (typeof options.evidence !== "function")
|
|
48
|
+
throw new TypeError("Field evidence requires an evidence source function");
|
|
49
|
+
if (options.normalize !== undefined && typeof options.normalize !== "function") {
|
|
50
|
+
throw new TypeError("Field evidence normalize must be a function");
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
toolName: options.toolName,
|
|
54
|
+
required: [...options.required],
|
|
55
|
+
evidence: options.evidence,
|
|
56
|
+
normalize: options.normalize ?? ((value) => value),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function resolveEvidence(source, toolName, context) {
|
|
60
|
+
const value = context.value;
|
|
61
|
+
const raw = source({
|
|
62
|
+
sessionId: context.sessionId,
|
|
63
|
+
runId: context.runId,
|
|
64
|
+
toolName,
|
|
65
|
+
...(context.toolCallId === undefined ? {} : { toolCallId: context.toolCallId }),
|
|
66
|
+
arguments: value.arguments,
|
|
67
|
+
metadata: context.metadata,
|
|
68
|
+
signal: context.signal,
|
|
69
|
+
});
|
|
70
|
+
if (!Array.isArray(raw))
|
|
71
|
+
return { records: [], truncated: false };
|
|
72
|
+
const valid = raw.filter(validRecord);
|
|
73
|
+
return { records: valid.slice(0, MAX_EVIDENCE_RECORDS), truncated: valid.length > MAX_EVIDENCE_RECORDS };
|
|
74
|
+
}
|
|
75
|
+
function validRecord(record) {
|
|
76
|
+
if (!record || typeof record !== "object")
|
|
77
|
+
return false;
|
|
78
|
+
if (typeof record.source !== "string" || record.source.length === 0 || record.source.length > MAX_EVIDENCE_KEY_CHARS)
|
|
79
|
+
return false;
|
|
80
|
+
if (typeof record.path !== "string" || record.path.length === 0 || record.path.length > MAX_EVIDENCE_KEY_CHARS)
|
|
81
|
+
return false;
|
|
82
|
+
if (!Object.hasOwn(record, "value"))
|
|
83
|
+
return false;
|
|
84
|
+
const revision = record.revision;
|
|
85
|
+
return (revision === undefined ||
|
|
86
|
+
(typeof revision === "string" && revision.length > 0) ||
|
|
87
|
+
(typeof revision === "number" && Number.isFinite(revision)));
|
|
88
|
+
}
|
|
89
|
+
function verifyField(field, args, records, normalize) {
|
|
90
|
+
const raw = readPath(args, field);
|
|
91
|
+
if (raw === undefined)
|
|
92
|
+
return "missing_field";
|
|
93
|
+
const claim = asClaim(raw);
|
|
94
|
+
if (claim === undefined)
|
|
95
|
+
return "malformed_claim";
|
|
96
|
+
if (records.length === 0)
|
|
97
|
+
return "missing_evidence";
|
|
98
|
+
const candidates = records.filter((record) => record.source === claim.source && record.path === claim.path);
|
|
99
|
+
if (candidates.length === 0)
|
|
100
|
+
return "unknown_source";
|
|
101
|
+
const fresh = candidates.filter((record) => record.revision === claim.revision);
|
|
102
|
+
if (fresh.length === 0)
|
|
103
|
+
return "stale_revision";
|
|
104
|
+
const claimed = normalize(claim.value, field);
|
|
105
|
+
if (fresh.some((record) => Object.is(normalize(record.value, field), claimed)))
|
|
106
|
+
return undefined;
|
|
107
|
+
return "value_mismatch";
|
|
108
|
+
}
|
|
109
|
+
function asClaim(raw) {
|
|
110
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
111
|
+
return undefined;
|
|
112
|
+
const record = raw;
|
|
113
|
+
if (!Object.hasOwn(record, "value"))
|
|
114
|
+
return undefined;
|
|
115
|
+
if (typeof record.source !== "string" || record.source.length === 0)
|
|
116
|
+
return undefined;
|
|
117
|
+
if (typeof record.path !== "string" || record.path.length === 0)
|
|
118
|
+
return undefined;
|
|
119
|
+
const revision = record.revision;
|
|
120
|
+
if (revision !== undefined &&
|
|
121
|
+
!(typeof revision === "string" && revision.length > 0) &&
|
|
122
|
+
!(typeof revision === "number" && Number.isFinite(revision))) {
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
return { value: record.value, source: record.source, path: record.path, ...(revision === undefined ? {} : { revision }) };
|
|
126
|
+
}
|
|
127
|
+
function readPath(value, path) {
|
|
128
|
+
let current = value;
|
|
129
|
+
for (const segment of path.split(".")) {
|
|
130
|
+
if (Array.isArray(current)) {
|
|
131
|
+
const index = Number(segment);
|
|
132
|
+
if (!Number.isInteger(index) || index < 0)
|
|
133
|
+
return undefined;
|
|
134
|
+
current = current[index];
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (!current || typeof current !== "object")
|
|
138
|
+
return undefined;
|
|
139
|
+
current = current[segment];
|
|
140
|
+
}
|
|
141
|
+
return current;
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=field-evidence.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -53,6 +53,8 @@ export type { ActivatedKernelConfig, AgentEventBridgeOptions, ExtensionErrorPoli
|
|
|
53
53
|
export { activateKernel, createExtensionEventBus, createExtensionKernel, forwardAgentEvents } from "./extensions.js";
|
|
54
54
|
export type { MemoryRunFeedbackStoreOptions, PrepareRunFeedbackOptions, RunFeedbackLimits, RunFeedbackRun, RunFeedbackRunResolver, } from "./feedback.js";
|
|
55
55
|
export { createMemoryRunFeedbackStore, prepareRunFeedback, RunFeedbackError, requireRunFeedbackOwnership, runFeedbackPageLimit, } from "./feedback.js";
|
|
56
|
+
export type { FieldEvidenceContext, FieldEvidenceGuardrailOptions, FieldEvidenceNormalizer, FieldEvidenceRecord, FieldEvidenceSource, FieldEvidenceViolation, FieldProvenanceClaim, } from "./field-evidence.js";
|
|
57
|
+
export { createFieldEvidenceGuardrail } from "./field-evidence.js";
|
|
56
58
|
export type { ApplyFieldPolicyOptions, AuditFieldRedaction, AuditFieldRedactorLike, AuditFieldRedactorOptions, FieldPolicy, FieldPolicyAction, FieldPolicyDecision, FieldPolicyInput, ProtectedFieldPolicyOptions, } from "./field-policy.js";
|
|
57
59
|
export { ALLOW_FIELD_POLICY, applyFieldPolicy, createAuditFieldRedactor, createProtectedFieldPolicy, FIELD_POLICY_LIMITS, FieldPolicyError, } from "./field-policy.js";
|
|
58
60
|
export { BUILT_IN_GUARDRAIL_PACK_IDS } from "./guardrail-packs/index.js";
|
|
@@ -131,5 +133,5 @@ export { MODEL_FAMILY_TOKENS, resolveModelFamily } from "./usage-estimation.js";
|
|
|
131
133
|
export type { ResolvedUseCaseModel, ResolveUseCaseModelInput, UseCaseModelBinding, } from "./use-case-model.js";
|
|
132
134
|
export { resolveUseCaseModel, resolveUseCaseModelBinding, useCaseCredentialProviderId, } from "./use-case-model.js";
|
|
133
135
|
export declare const name = "prism";
|
|
134
|
-
export declare const version = "0.
|
|
136
|
+
export declare const version = "0.12.0";
|
|
135
137
|
export declare const description = "Agent harness for AI providers, agents, sessions, and tools.";
|
package/dist/index.js
CHANGED
|
@@ -27,6 +27,7 @@ export { createClaimGroundingGuardrail } from "./evidence-grounding.js";
|
|
|
27
27
|
export { applyExecutionDecision, assertExecutionAllowed, checkExecution, ExecutionDeniedError } from "./execution-policy.js";
|
|
28
28
|
export { activateKernel, createExtensionEventBus, createExtensionKernel, forwardAgentEvents } from "./extensions.js";
|
|
29
29
|
export { createMemoryRunFeedbackStore, prepareRunFeedback, RunFeedbackError, requireRunFeedbackOwnership, runFeedbackPageLimit, } from "./feedback.js";
|
|
30
|
+
export { createFieldEvidenceGuardrail } from "./field-evidence.js";
|
|
30
31
|
export { ALLOW_FIELD_POLICY, applyFieldPolicy, createAuditFieldRedactor, createProtectedFieldPolicy, FIELD_POLICY_LIMITS, FieldPolicyError, } from "./field-policy.js";
|
|
31
32
|
export { BUILT_IN_GUARDRAIL_PACK_IDS } from "./guardrail-packs/index.js";
|
|
32
33
|
export { assertGuardrailsAllowed, compileGuardrailPacks, describeGuardrailPacks, GuardrailError, GuardrailPackError, MAX_GUARDRAIL_CONCURRENCY, MAX_GUARDRAIL_PACK_RULES, MAX_GUARDRAIL_PACKS, runGuardrails, } from "./guardrails.js";
|
|
@@ -72,6 +73,6 @@ export { trimTrailingSlashes } from "./trim-trailing-slashes.js";
|
|
|
72
73
|
export { MODEL_FAMILY_TOKENS, resolveModelFamily } from "./usage-estimation.js";
|
|
73
74
|
export { resolveUseCaseModel, resolveUseCaseModelBinding, useCaseCredentialProviderId, } from "./use-case-model.js";
|
|
74
75
|
export const name = "prism";
|
|
75
|
-
export const version = "0.
|
|
76
|
+
export const version = "0.12.0";
|
|
76
77
|
export const description = "Agent harness for AI providers, agents, sessions, and tools.";
|
|
77
78
|
//# sourceMappingURL=index.js.map
|
package/dist/run-bundle.js
CHANGED
|
@@ -23,7 +23,7 @@ export function snapshotRunBundle(input) {
|
|
|
23
23
|
const model = run?.model ?? config.model;
|
|
24
24
|
const effectiveLoop = run?.loop ?? config.loop;
|
|
25
25
|
const systemPrompt = run?.systemPrompt ?? config.systemPrompt;
|
|
26
|
-
const policies = run?.providerRequestPolicies ??
|
|
26
|
+
const policies = [config.providerRequestPolicies, run?.providerRequestPolicies].flatMap((policy) => policy ?? []);
|
|
27
27
|
const snapshot = {
|
|
28
28
|
schemaVersion: RUN_BUNDLE_SCHEMA_VERSION,
|
|
29
29
|
fingerprint: agentFingerprint(input.agent, definitionRevision ?? ""),
|
|
@@ -64,7 +64,7 @@ export function snapshotRunBundle(input) {
|
|
|
64
64
|
provider: typeof model === "string" ? (config.provider?.id ?? null) : (model?.provider ?? null),
|
|
65
65
|
model: typeof model === "string" ? model : (model?.model ?? null),
|
|
66
66
|
},
|
|
67
|
-
requestPolicies: policies
|
|
67
|
+
requestPolicies: policies.map((policy) => policy.name),
|
|
68
68
|
storage: {
|
|
69
69
|
sessionStore: kindOf(describeStorage(input.config?.store ?? config.store, undefined)),
|
|
70
70
|
checkpoints: kindOf(describeStorage(undefined, (run?.runState ?? config.runState)?.checkpoints)),
|
package/docs/acp-agent.md
CHANGED
|
@@ -5,7 +5,7 @@ New in 0.2.8 (plan 028 Task 10 / adoption F3). A thin binary that serves [`creat
|
|
|
5
5
|
## Running
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
|
-
|
|
8
|
+
bunx prism-acp-agent [--config prism-acp-agent.json]
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
The agent speaks ACP v1 as newline-delimited JSON on `stdin`/`stdout` (SDK `ndJsonStream` adapter over `Readable.toWeb(process.stdin)` / `Writable.toWeb(process.stdout)`). It serves until the client closes stdin; an `EPIPE` on stdout (client disconnected) is a normal shutdown.
|
|
@@ -13,7 +13,7 @@ The agent speaks ACP v1 as newline-delimited JSON on `stdin`/`stdout` (SDK `ndJs
|
|
|
13
13
|
```sh
|
|
14
14
|
# a config file must exist; missing/invalid config fails closed with a clear error and exit 1
|
|
15
15
|
printf '%s\n' '{"userId":"local","cwd":"/workspace"}' > prism-acp-agent.json
|
|
16
|
-
|
|
16
|
+
bunx prism-acp-agent
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
## Config reference
|
package/docs/ag-ui.md
CHANGED
|
@@ -21,7 +21,7 @@ Use [A2A interoperability](a2a.md) for remote agent-to-agent JSON-RPC/HTTPS task
|
|
|
21
21
|
Install the optional package beside the core runtime (it becomes publishable with the 0.0.12 release graph):
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
|
|
24
|
+
bun add @arnilo/prism @arnilo/prism-ag-ui
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
`createAgUiHandler()` takes host-owned callbacks:
|
|
@@ -338,7 +338,7 @@ await resumeAgentRun(agent, { runId, sessionId }, { decision: "continue", expect
|
|
|
338
338
|
- Compaction stays a task boundary: the compiler never writes the store and never triggers compaction mid-run (`session.compact()` still refuses while a run is active), and it never rewrites observational memory.
|
|
339
339
|
- Telemetry stays payload-free: the `attention_compiled` event carries `used`, `usedAfter`, `inputCap`, `triggerRatio`, `droppedThinkingTurns`, `stubbedToolResults`, `stubbedBytes`, and `truncated` only.
|
|
340
340
|
- `applyContextBudget` is not the compiler's last resort — overflowing after all eligible stages throws `AttentionBudgetError` so the frozen prefix (system instructions, `AGENTS.md`, skill catalog, tool declarations) cannot be silently evicted.
|
|
341
|
-
- Measured on the hermetic fixture in [`docs/_evidence/phase74-attention-measurements.md`](_evidence/phase74-attention-measurements.md): 63.7 % fewer input tokens, one cache bust on the turn the gate trips (compiler-off is append-only), and a volatile provider block that re-sends everything behind it every turn — which is why pinning a block stays a host recipe (`resolve` once per session) rather than compiler behavior. Regenerate with `
|
|
341
|
+
- Measured on the hermetic fixture in [`docs/_evidence/phase74-attention-measurements.md`](_evidence/phase74-attention-measurements.md): 63.7 % fewer input tokens, one cache bust on the turn the gate trips (compiler-off is append-only), and a volatile provider block that re-sends everything behind it every turn — which is why pinning a block stays a host recipe (`resolve` once per session) rather than compiler behavior. Regenerate with `bun scripts/benchmark.mjs --scenario attention-compiler`.
|
|
342
342
|
|
|
343
343
|
## Related APIs
|
|
344
344
|
|
package/docs/cli-rpc.md
CHANGED
|
@@ -18,7 +18,7 @@ An env-gated e2e journey drives the packed `prism` bin end to end — `init` sca
|
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
20
|
PRISM_LIVE_PROVIDER_TESTS=1 OPENAI_API_KEY=sk-... \
|
|
21
|
-
|
|
21
|
+
bun test scripts/e2e-cli-live.test.mjs
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
The provider is the first init-catalog entry whose credential env var is present; override with `PRISM_LIVE_CLI_PROVIDER=<id>`. Wire legs skip (never fail) when the provider rejects the credential (401/403) — refresh the key and rerun. Registered in `scripts/live-matrix.json` as `cli/journey`.
|
|
@@ -227,7 +227,7 @@ prism init my-research --template deep-research
|
|
|
227
227
|
prism init my-assistant --template personal-assistant
|
|
228
228
|
prism init my-worker --template business-worker
|
|
229
229
|
prism init --list-templates
|
|
230
|
-
cd my-agent &&
|
|
230
|
+
cd my-agent && bun install && bun test
|
|
231
231
|
|
|
232
232
|
```
|
|
233
233
|
|
package/docs/coding-tools.md
CHANGED
|
@@ -5,14 +5,14 @@ The `@arnilo/prism-coding-tools` family package unifies Prism's coding agent too
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
|
|
8
|
+
bun add @arnilo/prism @arnilo/prism-coding-tools
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
For document reading or specialized integrations, install the optional peer dependencies as needed:
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
# PDF and DOCX document extraction
|
|
15
|
-
|
|
15
|
+
bun add pdf-parse mammoth
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
## Subpaths Map
|
|
@@ -31,7 +31,7 @@ npm install pdf-parse mammoth
|
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
33
|
# Start the loopback dev inspector
|
|
34
|
-
|
|
34
|
+
bunx prism-dev --port 4311
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
## Usage Examples
|
|
@@ -23,7 +23,7 @@ Observational memory composes four independent layers for long sessions (Mastra-
|
|
|
23
23
|
|
|
24
24
|
The opt-in work-scope index filters the observation and reflection layers for a host-selected working set. It is not a fifth context layer, retrieval system, or session scope.
|
|
25
25
|
|
|
26
|
-
Activation is explicit: `createObservationalMemory().attach()` coordinates post-run observe/reflect/drop and compaction — by default `context.compactAfterTokens`, or whatever host gate `trigger` / `shouldCompact` supplies. Import and extension `setup` start nothing. Recall, commands, and utilities fail closed on invalid ids, wrong `sessionId`, ambiguous tool input, or oversized pages. Pass `secrets` for exact-value redaction in render/recall/worker paths. Branch isolation: hosts supply current-branch `appendEntry` and `getEntries`; mismatched store/session pairs fail closed after append.
|
|
26
|
+
Activation is explicit: `createObservationalMemory().attach()` coordinates post-run observe/reflect/drop and compaction — by default `context.compactAfterTokens`, or whatever host gate `trigger` / `shouldCompact` supplies. Import and extension `setup` start nothing. Recall, commands, and utilities fail closed on invalid ids, wrong `sessionId`, ambiguous tool input, or oversized pages. Pass `secrets` for exact-value redaction in render/recall/worker paths. Branch isolation: hosts supply current-branch `appendEntry` and `getEntries`; mismatched store/session pairs fail closed after append. Automatic compaction is coverage-safe: entries no observation pass covers are retained, or compaction is deferred — see the coverage-safe admission contract under Outputs for the details and prompt-sizing trade-off.
|
|
27
27
|
|
|
28
28
|
See `examples/observational-memory-lifecycle.ts` for attach → turn → projection/recall/page without live credentials.
|
|
29
29
|
|
|
@@ -85,14 +85,14 @@ Key exports:
|
|
|
85
85
|
| `buildObservationalMemoryContextBlocks()` | Render observational-memory + recent-messages context blocks for provider input. Same `invalidatedIds` option. |
|
|
86
86
|
| `selectRecentMessageEntries()` / `renderRecentMessageWindow()` | Bounded exact recent-message suffix; count via `keepRecentEntries`, optional token trim via `estimateEntryTokens`. |
|
|
87
87
|
| `createFoldedMemoryDetails()` | Create JSON details for compaction `data.memory`. |
|
|
88
|
-
| `renderObservationalMemory()` | Render reflections and observations into a prepared memory summary. |
|
|
88
|
+
| `renderObservationalMemory()` | Render reflections and observations into a prepared memory summary. `advertiseRecall: false` omits the recall-evidence instruction; an empty pool never advertises recall. |
|
|
89
89
|
| `recallObservationalMemory()` | Recover source evidence for a known observation/reflection id from supplied current-branch entries. `invalidatedIds` withholds content (`reason: "revoked"`) without injecting derived text. |
|
|
90
90
|
| `listInvalidatedIds()` (`@arnilo/prism-memory`) | Read the ids one exact scope currently withholds (`corrected` stays) and pass them as `invalidatedIds`, so blocks that rest on a source revoked mid-turn go stale on the next build. Empty for stores without lineage invalidation. |
|
|
91
91
|
| `createObservationalMemoryDropHandler()` | The OM leg of `createDeletionPropagator`: folds the session ledger once and appends one `om.observations.dropped` entry naming every active observation whose id or `sourceEntryIds` intersect the tombstone set (`coversUpToId` omitted — a tombstone set is not a coverage position). Register with `{ session, appendEntry }`; pair it with `listInvalidatedIds()` for the read path. |
|
|
92
92
|
| `recallObservationalMemoryBranchPage()` | Page eligible user/assistant/tool messages around a cursor entry id (`forward`/`backward`, optional `detail: summary|full`). |
|
|
93
93
|
| `createMemoryId()` / `isMemoryId()` | Create/check 12-character ids. |
|
|
94
94
|
| `resolveObservationalMemorySettings()` | Merge `observational-memory` settings with defaults and overrides. |
|
|
95
|
-
| `createObservationalMemory()` / `attach()` | One activation wires post-run observe/reflect/drop and compaction (`compactAfterTokens`, or a host `trigger` / `shouldCompact`); returns proxied session, runtime, context provider, and strategy. |
|
|
95
|
+
| `createObservationalMemory()` / `attach()` | One activation wires post-run observe/reflect/drop and coverage-safe compaction (`compactAfterTokens`, or a host `trigger` / `shouldCompact`); returns proxied session, runtime, context provider, and strategy. |
|
|
96
96
|
| `createObservationalMemoryRuntime()` | Low-level explicit flush for advanced hosts or tests. |
|
|
97
97
|
| `createObservationalMemoryCompactionStrategy()` | Render existing folded memory as a standard Prism compaction summary with `data.memory`. |
|
|
98
98
|
| `createObservationalMemoryExtension()` | Inert extension helper that registers the strategy contribution unless disabled. |
|
|
@@ -102,6 +102,16 @@ Key exports:
|
|
|
102
102
|
|
|
103
103
|
Pure utilities create no events, workers, tools, commands, credentials, or provider requests. `createObservationalMemoryExtension()` and import alone start nothing. `createObservationalMemory().attach()` runs workers only after proxied `run`/`prompt`/`stream`/`compact` complete (or after `wrapResumeRun` / `wrapResumeStream`). `createObservationalMemoryRuntime().flush()` remains for manual/advanced use. Attached `contextProvider` renders two blocks each turn: `observational-memory` (active reflections/observations aligned to the recent-message boundary) and `recent-messages` (last `keepRecentEntries` message entries in branch order, optionally trimmed by `recentMessageMaxTokens` using `estimateEntryTokens`; oldest dropped first). Compaction uses the same `keepRecentEntries` setting. Observer input includes only eligible `message` entries (`user`, `assistant`, `tool`); memory/compaction/bookkeeping entries advance `coversUpToId` scan coverage without entering the observer prompt. Successful observer/reflector runs append coverage markers even when they record zero facts. Reflection uses only active observations recorded after the last `om.reflections.recorded` entry unless `flush({ fullReflectionRebuild: true })`. Attached `flush()` skips with `run_active` while a proxied run is in flight. The compaction strategy is O(n) over supplied entries and makes no provider call.
|
|
104
104
|
|
|
105
|
+
### Coverage-safe automatic compaction
|
|
106
|
+
|
|
107
|
+
Automatic compaction admits only entries an observation pass covers. `flush()` returns a typed `skipped` reason (`run_active`, `passive`, `in_flight`, `missing_model`, `missing_credentials`, `error`) distinct from a successful pass, including one that records zero observations. The attach loop then behaves as:
|
|
108
|
+
|
|
109
|
+
- Flush skipped or failed **and no `om.observations.recorded` coverage marker exists** — compaction is deferred: no compaction entry is written, so no message prefix is folded into a summary that does not cover it. `debug` receives `observational-memory:compaction-deferred` with the skip reason. A successful pass — even a zero-observation one — advances coverage and compacts normally, so "observer never ran" and "observer ran empty" stay distinguishable.
|
|
110
|
+
- Flush skipped or failed **with partial coverage** — compaction runs, but the strategy adds every eligible message after the coverage cursor to `keepEntryIds`, so uncovered entries survive the next `rebuildSessionContext()` beside the summary. Fully covered sessions get exactly the previous recent-window behavior. Manual `session.compact()` keeps its explicit-host-action semantics and relies on the same retention.
|
|
111
|
+
- Revocation still wins: an `om.observations.dropped` entry excludes an observation from the rendered summary and folded payload regardless of what admission retains.
|
|
112
|
+
|
|
113
|
+
Sizing trade-off: retaining uncovered entries grows the next prompt by their tokens until an observer pass covers them. The existing `compactAfterTokens` / `trigger` gate still decides when compaction is attempted — it no longer bounds context when retention is active, but the uncovered set is bounded by what arrived since the last successful observation pass, and a deferred compaction writes nothing. Hosts that expose no recall capability pass `compaction: { advertiseRecall: false }` to `createObservationalMemory()` (or `advertiseRecall: false` to `renderObservationalMemory()` / `buildObservationalMemoryContextBlocks()`); an empty pool never advertises recall even with the flag on.
|
|
114
|
+
|
|
105
115
|
### Revocation wiring (plan 102 Tasks 2/8)
|
|
106
116
|
|
|
107
117
|
A revoked source must stop feeding memory on both sides of the write. The two halves share one tombstone set:
|
|
@@ -98,7 +98,7 @@ async function installDesktop(hostSkills: { register(skill: Skill): void }, host
|
|
|
98
98
|
|
|
99
99
|
## Extension and configuration notes
|
|
100
100
|
|
|
101
|
-
- Install and configure the host binary separately: `
|
|
101
|
+
- Install and configure the host binary separately: `bun add -g @agent-sh/computer-use-linux` or another host-managed installation. Prism has no runtime dependency on that binary and never downloads it.
|
|
102
102
|
- The factory exposes unprefixed upstream names. Unknown or future upstream names are omitted until Prism classifies them.
|
|
103
103
|
- `setup_accessibility` and `setup_window_targeting` are host-only and omitted unless `includeSetupTools: true` is explicitly selected. The bundled skill never instructs agent turns to perform setup.
|
|
104
104
|
- `connect` is an injectable bridge factory for fake MCP tests. The package's normal path uses `connectMcpTools` with stdio `{ command, args: ["mcp"] }`.
|
|
@@ -118,7 +118,7 @@ A live leg drives the host's real `computer-use-linux` MCP binary over stdio —
|
|
|
118
118
|
|
|
119
119
|
```bash
|
|
120
120
|
PRISM_TEST_COMPUTER_USE=1 PRISM_COMPUTER_USE_BIN="$(command -v computer-use-linux)" \
|
|
121
|
-
|
|
121
|
+
bun test packages/prism-coding-tools/dist/computer-use-linux/__tests__/live.test.js
|
|
122
122
|
```
|
|
123
123
|
|
|
124
124
|
Skips (never fails) when the flag, binary path, or a desktop session is unavailable. The suite performs no network I/O, so screenshot bytes cannot leave the process. Registered in `scripts/live-matrix.json` as `coding-tools/computer-use-live`.
|