@arnilo/prism 0.7.0 → 0.9.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 +73 -0
- package/README.md +12 -11
- package/dist/agent-approval.d.ts +15 -2
- package/dist/agent-approval.js +5 -1
- package/dist/agent-event-source.d.ts +9 -1
- package/dist/agent-event-source.js +10 -3
- package/dist/agent-loops.js +7 -4
- package/dist/agent-run-lifecycle.d.ts +15 -1
- package/dist/agent-run-lifecycle.js +91 -10
- package/dist/agent-run-state.d.ts +34 -2
- package/dist/agent-run-state.js +68 -6
- package/dist/agent-session/helpers.js +20 -1
- package/dist/agent-session/session/assemble.js +250 -27
- package/dist/agent-session/session/persist.d.ts +27 -0
- package/dist/agent-session/session/persist.js +94 -12
- package/dist/agent-session/session/provider-round.d.ts +14 -4
- package/dist/agent-session/session/provider-round.js +197 -25
- package/dist/agent-session/session/tool-round.js +24 -2
- package/dist/agent-session/session/types.d.ts +36 -2
- package/dist/agent-session/session.d.ts +40 -4
- package/dist/agent-session/session.js +78 -5
- package/dist/attention-compiler.d.ts +51 -2
- package/dist/attention-compiler.js +282 -21
- package/dist/cache-helpers.d.ts +4 -2
- package/dist/cache-helpers.js +8 -6
- package/dist/checkpoint-restore.d.ts +45 -0
- package/dist/checkpoint-restore.js +54 -0
- package/dist/checkpoints.js +7 -11
- package/dist/context-budget.d.ts +2 -1
- package/dist/context-budget.js +24 -2
- package/dist/contracts-core/agent.d.ts +30 -0
- package/dist/contracts-core/attention.d.ts +95 -0
- package/dist/contracts-core/content.d.ts +15 -0
- package/dist/contracts-core/guardrail-packs.d.ts +41 -0
- package/dist/contracts-core/guardrail-packs.js +2 -0
- package/dist/contracts-core/loop.d.ts +42 -0
- package/dist/contracts-core/provider.d.ts +25 -0
- package/dist/contracts-core/run-limits.d.ts +21 -0
- package/dist/contracts-core/session.d.ts +23 -5
- package/dist/contracts-core/session.js +21 -2
- package/dist/contracts-core/usage.d.ts +40 -0
- package/dist/contracts-core/usage.js +8 -0
- package/dist/contracts-core.d.ts +2 -0
- package/dist/contracts-core.js +2 -0
- package/dist/contracts-protocol.d.ts +90 -4
- package/dist/contracts-run-state.d.ts +82 -6
- package/dist/evidence-grounding.d.ts +29 -0
- package/dist/evidence-grounding.js +162 -0
- package/dist/guardrail-packs/coding-standard.d.ts +3 -0
- package/dist/guardrail-packs/coding-standard.js +63 -0
- package/dist/guardrail-packs/destructive-commands.d.ts +3 -0
- package/dist/guardrail-packs/destructive-commands.js +46 -0
- package/dist/guardrail-packs/errors.d.ts +7 -0
- package/dist/guardrail-packs/errors.js +9 -0
- package/dist/guardrail-packs/index.d.ts +4 -0
- package/dist/guardrail-packs/index.js +15 -0
- package/dist/guardrail-packs/secrets-hygiene.d.ts +3 -0
- package/dist/guardrail-packs/secrets-hygiene.js +23 -0
- package/dist/guardrail-packs/types.d.ts +16 -0
- package/dist/guardrail-packs/types.js +2 -0
- package/dist/guardrail-packs/validation-respect.d.ts +3 -0
- package/dist/guardrail-packs/validation-respect.js +53 -0
- package/dist/guardrails.d.ts +20 -1
- package/dist/guardrails.js +268 -0
- package/dist/host-composition.d.ts +13 -0
- package/dist/host-composition.js +33 -2
- package/dist/index.d.ts +19 -10
- package/dist/index.js +11 -6
- package/dist/input.d.ts +8 -1
- package/dist/input.js +68 -6
- package/dist/middleware.d.ts +37 -2
- package/dist/middleware.js +41 -0
- package/dist/node/session-store-jsonl.js +18 -3
- package/dist/observability.js +6 -0
- package/dist/provider-events.d.ts +11 -3
- package/dist/provider-events.js +62 -4
- package/dist/providers/openai-compatible.js +6 -3
- package/dist/providers/transport.d.ts +3 -1
- package/dist/providers/transport.js +36 -0
- package/dist/redaction.js +18 -2
- package/dist/run-bundle.d.ts +89 -0
- package/dist/run-bundle.js +150 -0
- package/dist/run-limits.d.ts +11 -1
- package/dist/run-limits.js +46 -0
- package/dist/session-stores.d.ts +12 -1
- package/dist/session-stores.js +21 -4
- package/dist/testing/agent-event-source-conformance.js +41 -2
- package/dist/testing/prefix-stability-conformance.d.ts +30 -0
- package/dist/testing/prefix-stability-conformance.js +104 -0
- package/dist/testing/session-store-conformance.d.ts +3 -2
- package/dist/testing/session-store-conformance.js +48 -0
- package/dist/testing/state-concurrency-conformance.js +5 -12
- package/dist/tools.d.ts +5 -0
- package/dist/tools.js +11 -3
- package/dist/usage-estimation.d.ts +29 -0
- package/dist/usage-estimation.js +79 -0
- package/docs/ag-ui.md +5 -0
- package/docs/agent-events.md +68 -1
- package/docs/agent-loops.md +33 -0
- package/docs/agent-session-runtime.md +5 -3
- package/docs/attention-compiler.md +89 -8
- package/docs/coding-agent-tools.md +1 -1
- package/docs/coding-security.md +1 -0
- package/docs/coding-tools.md +0 -1
- package/docs/compaction-and-retry.md +1 -1
- package/docs/compaction-observational-memory.md +34 -7
- package/docs/connected-apps.md +116 -0
- package/docs/context-and-skills.md +13 -0
- package/docs/core.md +1 -1
- package/docs/diagrams.md +6 -6
- package/docs/document-reader.md +9 -9
- package/docs/documents.md +32 -11
- package/docs/durable-runs.md +129 -0
- package/docs/embeddings.md +5 -0
- package/docs/enterprise-postgres-state.md +4 -0
- package/docs/evaluations.md +5 -0
- package/docs/execution-timeline.md +84 -1
- package/docs/guardrails.md +71 -2
- package/docs/history/079-messaging-primitive-review.md +391 -0
- package/docs/history/080-messaging-followon-primitive-review.md +234 -0
- package/docs/history/081-connected-apps-primitive-review.md +74 -0
- package/docs/history/083-prism-work-primitive-review.md +84 -0
- package/docs/history/084-primitive-review.md +96 -0
- package/docs/history/085-honesty-and-cut-primitive-review.md +91 -0
- package/docs/history/README.md +5 -0
- package/docs/history/release-handoffs.md +38 -0
- package/docs/host-compositions.md +8 -6
- package/docs/host-security.md +2 -2
- package/docs/index.md +66 -29
- package/docs/input-and-prompt-assembly.md +3 -3
- package/docs/knowledge-sync.md +4 -0
- package/docs/live-testing.md +5 -3
- package/docs/mcp-tools.md +1 -0
- package/docs/messaging-channel-operations.md +166 -0
- package/docs/messaging-channels.md +150 -0
- package/docs/middleware-hooks.md +38 -2
- package/docs/migrate-to-0.8.md +124 -0
- package/docs/migrate-to-0.9.md +210 -0
- package/docs/migration.md +43 -0
- package/docs/model-registry.md +12 -2
- package/docs/multi-agent-patterns.md +25 -2
- package/docs/node-jsonl-session-store.md +7 -1
- package/docs/observability.md +7 -3
- package/docs/openapi-tools.md +1 -1
- package/docs/operations.md +1 -3
- package/docs/options-index.md +36 -3
- package/docs/peer-dependencies.md +6 -6
- package/docs/policy-and-audit.md +13 -1
- package/docs/postgres-persistence.md +1 -1
- package/docs/prefix-stability-conformance.md +93 -0
- package/docs/provider-caching.md +4 -4
- package/docs/provider-conformance.md +16 -0
- package/docs/provider-layer.md +2 -2
- package/docs/provider-packages.md +20 -20
- package/docs/providers/neuralwatt.md +5 -1
- package/docs/public-contracts.md +2 -2
- package/docs/rag.md +102 -4
- package/docs/release-and-install.md +55 -47
- package/docs/run-bundle.md +92 -0
- package/docs/runs-and-usage.md +57 -6
- package/docs/scoped-agent-memory.md +262 -0
- package/docs/server.md +2 -0
- package/docs/session-store-conformance.md +1 -2
- package/docs/session-stores.md +17 -17
- package/docs/sheets.md +9 -9
- package/docs/signal-channel.md +112 -0
- package/docs/speech.md +5 -1
- package/docs/sqlite-persistence.md +1 -1
- package/docs/supervisors.md +32 -12
- package/docs/telegram-channel.md +157 -0
- package/docs/testing.md +2 -2
- package/docs/tools.md +17 -0
- package/docs/wiki.md +1 -1
- package/docs/work-artifacts-and-review.md +1 -1
- package/docs/work-connectors.md +9 -9
- package/docs/work-sandbox.md +115 -0
- package/docs/work-tools.md +38 -16
- package/docs/workflows.md +5 -0
- package/package.json +9 -3
- package/templates/business-worker/manifest.json +2 -1
- package/templates/business-worker/src/agent.ts.tmpl +1 -1
- package/templates/business-worker/src/tests/agent.test.ts.tmpl +1 -1
package/docs/migration.md
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
1
1
|
# Migration guide
|
|
2
2
|
|
|
3
|
+
## 0.8.0 → 0.9.0 (attention budget axes, turn traces, tool narrowing, guardrail packs, background agents, session search, deterministic turns, shared scopes)
|
|
4
|
+
|
|
5
|
+
**Prism 0.9.0 is a lockstep minor for all eleven publishable packages.** Node `>=22` stays the floor. Nothing was removed: no import path moved, no export was dropped, and every new surface defaults to 0.8 behavior. The full guide — the four deltas inside existing surfaces, every new option with its sizing line, upgrade steps, and rollback — is [migrate-to-0.9.md](migrate-to-0.9.md).
|
|
6
|
+
|
|
7
|
+
What a 0.8.0 host must check before upgrading:
|
|
8
|
+
|
|
9
|
+
- **A limit death no longer ends a stream early.** `run_limit_exceeded` and `budget_exhausted` are not terminal; keep reading until `error` (or `isTerminalAgentEventType(type)` is true) to see the breach, its attribution, and the run's outcome in order.
|
|
10
|
+
- **`provider_turn_finished` metadata grew** (`stopReason`, `budgets`, `tools`, `cache`), and `agent_finished` now carries `finishReason` / `stopDetail`; consumers that deep-equal `metadata` must allow the new fields.
|
|
11
|
+
- **`AgentConfig.usageEstimation` defaults to `"fallback"`**: a provider that reports no usage is charged one labeled estimate (`estimated: true` + `confidence`) instead of zero. Set `"off"` for the old behavior; billing code must read the label.
|
|
12
|
+
- **Progressive disclosure is cache-stable**: skill bodies and deferred schemas append at the tail instead of rewriting the prefix. Defaults keep 0.8 bytes for hosts that never load late context.
|
|
13
|
+
- **One type-level change**: the `recordUsage` callback of `generateProviderTurn` / `generateWithRetry` returns `Promise<Usage | undefined>` instead of `Promise<void>`.
|
|
14
|
+
- **Additive, inert by default**: attention `trigger` axes and `durable` folding, per-turn `toolNarrowing`, `guardrailPacks`, session-lifetime child agents with child-event passthrough, `checkpointMetadata` / `restoreHooks`, `searchSessions`, `beforeProviderTurn` deterministic turns, observability shared work scopes, deletion propagation, and the local reranker. One additive migration (004) adds the session-search index; no existing table or column changes.
|
|
15
|
+
|
|
16
|
+
## 0.7.0 → 0.8.0 (messaging channels, connected apps, work family, durable runs)
|
|
17
|
+
|
|
18
|
+
**Prism 0.8.0 is a lockstep minor for all eleven publishable packages.** Node `>=22` stays the floor. The only import-map break is `@arnilo/prism-office` → `@arnilo/prism-work` (plus the work/document-reader subpath moves). A host that never imported those paths upgrades by moving every `@arnilo/*` dependency and peer to `^0.8.0`. The full guide — per-item actions, opt-in activation, and rollback — is [migrate-to-0.8.md](migrate-to-0.8.md).
|
|
19
|
+
|
|
20
|
+
What a 0.7.0 host must check before upgrading:
|
|
21
|
+
|
|
22
|
+
- **One import-map break.** Replace `@arnilo/prism-office/*`, `@arnilo/prism-core/integrations/work*`, and `@arnilo/prism-coding-tools/document-reader` with `@arnilo/prism-work` subpaths. Catch work-idempotency by `error.code`, not class. No pre-1.0 shim.
|
|
23
|
+
- **Eleventh package.** `@arnilo/prism-channels` is new and optional; omit it if the host has no messaging ingress.
|
|
24
|
+
- **Additive, inert by default:** connected-app MCP sessions, work HTTP adapters, turn-boundary checkpoints / `decision: "continue"`, turn-stop policy, run-bundle snapshots, claim-grounding guardrail, work sandbox/skills.
|
|
25
|
+
- **Behavioral pins inside existing surfaces:** observational-memory workers stay tool-only (text-only turns are successful no-ops); channel lease release clears in-memory state only after the store acknowledges; AG-UI `inputPolicy.clientState: "ignore"` is opt-in (default honor matches 0.7.0); a foreign checkpoint/run-status load is a miss, not an existence leak.
|
|
26
|
+
|
|
27
|
+
## Next lockstep cut — work family package move
|
|
28
|
+
|
|
29
|
+
`@arnilo/prism-office` and the work/document-reader subpaths are removed with no pre-1.0 compatibility shim. Install `@arnilo/prism-work` with `@arnilo/prism` and update imports:
|
|
30
|
+
|
|
31
|
+
| Old import | Replacement |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| `@arnilo/prism-office/documents` | `@arnilo/prism-work/documents` |
|
|
34
|
+
| `@arnilo/prism-office/sheets` | `@arnilo/prism-work/sheets` |
|
|
35
|
+
| `@arnilo/prism-office/diagrams` | `@arnilo/prism-work/diagrams` |
|
|
36
|
+
| `@arnilo/prism-core/integrations/work` | `@arnilo/prism-work/connectors` |
|
|
37
|
+
| `@arnilo/prism-core/integrations/work/microsoft365` | `@arnilo/prism-work/connectors/microsoft365` |
|
|
38
|
+
| `@arnilo/prism-core/integrations/work/google-workspace` | `@arnilo/prism-work/connectors/google-workspace` |
|
|
39
|
+
| `@arnilo/prism-core/integrations/work/drafts` | `@arnilo/prism-work/connectors/drafts` |
|
|
40
|
+
| `@arnilo/prism-coding-tools/document-reader` | `@arnilo/prism-work/document-reader` |
|
|
41
|
+
|
|
42
|
+
The coding `createReadTool({ documentReader })` injection seam is unchanged; pass the reader created by the new subpath. Core keeps its durable adapter behind `createPostgresEnterpriseState({ pool }).workIdempotency` at `@arnilo/prism-core/enterprise/postgres` with type-only structural coupling.
|
|
43
|
+
|
|
44
|
+
**Match work-idempotency conflicts by `code`, not by error class.** The portable codes are unchanged across the move — `ERR_PRISM_WORK_IDEMPOTENCY` for a rejected claim/transition and `ERR_PRISM_WORK_IDEMPOTENCY_CONFLICT` for a lost race or stale claim token — and a host that catches them by `error.code` (the pattern `docs/work-tools.md` documents) needs no change. The *class* is adapter-specific: `createMemoryIdempotencyStore()` throws `WorkToolError` (an upstream `Error` subclass) while the PostgreSQL adapter throws `EnterprisePostgresError`, since `@arnilo/prism-core` cannot depend on `@arnilo/prism-work` at runtime. A pre-existing adapter that caught the old import path's error by `instanceof` must switch to `code` matching; `packages/prism-core/src/enterprise/postgres/__tests__/work-idempotency.integration.test.ts` runs both adapters through the same conflict scenarios and asserts the two agree.
|
|
45
|
+
|
|
3
46
|
## 0.6.0 → 0.7.0 (host completeness, evidence, and capability boundaries)
|
|
4
47
|
|
|
5
48
|
**Prism 0.7.0 is a lockstep minor for all ten publishable packages.** Node `>=22` stays the floor; no import path was removed and no store schema changed, so a host that does not touch the ACP agent or the model-router facade upgrades by moving every `@arnilo/*` dependency and peer to `^0.7.0`. The full guide — per-item migration actions, opt-in activation, and rollback — is [migrate-to-0.7.md](migrate-to-0.7.md).
|
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 (`text`, `image`, `audio`, `file`, `document`) plus reasoning/tools/streaming booleans
|
|
39
|
+
| `capabilities` | Input/output modes (`text`, `image`, `audio`, `file`, `document`) plus reasoning/tools/streaming booleans, optional `structuredOutput` (`true` or `"json_schema"`) for native JSON-schema requests, and advisory `toolCallStrictness`. |
|
|
40
40
|
| `limits` | Context and output-token limits (`ModelLimits`). |
|
|
41
41
|
| `cost` | Input/output/cache read/cache write pricing. |
|
|
42
42
|
| `cache` | Generic `ModelCacheCapabilities`. |
|
|
@@ -101,9 +101,19 @@ const registry = createModelRegistry([model], { duplicate: "error" });
|
|
|
101
101
|
const resolved = registry.resolve("demo", "demo-large");
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
+
## Tool-call reliability metadata
|
|
105
|
+
|
|
106
|
+
`ModelCapabilities.toolCallStrictness?: "strict" | "lenient" | "legacy"` is advisory evidence metadata for hosts pinning models. Omission means **unknown** and must never be inferred as `"strict"`; it changes neither tool disclosure, argument validation, parallel dispatch, retries, nor provider requests.
|
|
107
|
+
|
|
108
|
+
- `"strict"`: the provider catalog has a network-free conformance fixture covering parallel-call reconstruction, a schema-shaped argument object, and an empty `{}` argument object.
|
|
109
|
+
- `"lenient"`: a catalog has tested tool support but a known relaxed behavior; hosts should retain extra guardrails.
|
|
110
|
+
- `"legacy"`: a catalog has tested compatibility-only tool behavior; hosts should avoid relying on strict multi-call/schema semantics.
|
|
111
|
+
|
|
112
|
+
Current first-party evidence is generated at [tool-call coverage matrix](_evidence/toolcall-coverage-2026-09-17.md). Only NeuralWatt's curated catalog is stamped `"strict"`: its fixture checks all three behaviors. Every other first-party static catalog with `tools: true` is explicitly unstamped-unknown until it has that fixture coverage. Dynamic discovery records remain unknown because provider responses are untrusted catalog metadata. Hosts must still validate every tool argument against its schema.
|
|
113
|
+
|
|
104
114
|
## Extension and configuration notes
|
|
105
115
|
|
|
106
|
-
Provider packages register models through `ProviderPackageAPI.registerModel(model)
|
|
116
|
+
Provider packages register models through `ProviderPackageAPI.registerModel(model). The extension kernel stores those records in the host-owned registries. Static package metadata is allowed; dynamic model discovery remains provider/host code outside Prism core.
|
|
107
117
|
|
|
108
118
|
`ModelConfig.compat` remains for provider-owned inert JSON. Prefer typed fields (`capabilities`, `limits`, `cost`, `cache`) for generic behavior shared across providers.
|
|
109
119
|
|
|
@@ -17,7 +17,7 @@ Maps five Prism answers for "more than one agent" onto one decision table. All f
|
|
|
17
17
|
| In-session handoff | One host, one ongoing conversation; the model decides **when** to transfer; specialists are alternate definitions of the same app | One continuous transcript chain (same store, session id, `leafId`) | Same session scope; give the specialist its own identity via its definition (`AgentConfig.identity` / `RunOptions.identity`) | Attribution is per-run: each `session.run()`'s events/result belong to the active definition — record the swap in host bookkeeping; no `delegated_agent_step` event exists for in-process swaps |
|
|
18
18
|
| Hierarchical crew | A goal requires dynamic decomposition by a manager LLM, parallel execution by role specialists, host aggregation, and conditional validation/revision loop | Workflow DAG execution — each specialist executes a bounded child task session; final deliverable returns to host | Workflow tenant/ownership scopes propagate; specialists activate only their own narrowed `tools` | Workflow node events (`node_started`/`node_finished`/`agent_event`); task attribution per role in the aggregated deliverable |
|
|
19
19
|
| Supervisor delegation | Host code dynamically selects a bounded child run | Separate runs; child result returns to the host | Parent identity/effectStore propagate; child factories receive derived resource/thread ids and AND-composed permission | Dedicated `delegation_started/finished/rejected/error` events, projectable through observability `handleDelegation()`; opt-in `delegation_child_event` passthrough |
|
|
20
|
-
| In-process spawn tool | Parent model needs an allow-listed child as a non-exclusive tool call | Separate runs; sync result returns through `spawn_agent`, async handle joins through `wait_agent` | Host owns catalog, tools, scopes, limits, and local handles; schema accepts only child ID/input/thread ID/mode | Same supervisor `delegation_*` events |
|
|
20
|
+
| In-process spawn tool | Parent model needs an allow-listed child as a non-exclusive tool call | Separate runs; sync result returns through `spawn_agent`, async handle joins through `wait_agent` | Host owns catalog, tools, scopes, limits, and local handles; schema accepts only child ID/input/thread ID/mode plus policy args the host ceiling allows | Same supervisor `delegation_*` events; with host opt-in, `child_milestone` / `delegation_child_event` (redacted, capped, rate-coalesced) |
|
|
21
21
|
| A2A 1.0 | The other agent is owned by a **different service/deployment**; cross-org or cross-cluster; needs durable task lifecycle, push configs, streaming | Protocol boundary (JSON-RPC/HTTPS agent card); replay/reconnect via host-owned task adapter | Exact-origin verified client, `A2AAuthorization` per operation, principal-scoped push configs | Host-owned task adapter records the remote lifecycle; Prism creates no worker/store |
|
|
22
22
|
|
|
23
23
|
Rule of thumb: same conversation → handoff; dynamic task decomposition + parallel execution → hierarchical crew; host-selected same-process subtask → supervisor delegation; model-requested allow-listed subtask → in-process spawn tool; different deployment/trust boundary → A2A.
|
|
@@ -151,6 +151,29 @@ Live demo: [`examples/crew-hierarchy.ts`](../examples/crew-hierarchy.ts) — man
|
|
|
151
151
|
|
|
152
152
|
Live demo: [`examples/spawn-agent-tool.ts`](../examples/spawn-agent-tool.ts) — a narrowed read-only explore child spawned twice in parallel, an uncatalogued child refused, and both handles joined.
|
|
153
153
|
|
|
154
|
+
### Background agents (session lifetime)
|
|
155
|
+
|
|
156
|
+
A host can start a background child at session open that reports without occupying the conversation:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
const supervisor = createSupervisor({
|
|
160
|
+
ownership,
|
|
161
|
+
signal: sessionAbort.signal, // host session end stops every child and closes the stream
|
|
162
|
+
children: {
|
|
163
|
+
researcher: {
|
|
164
|
+
policy: { lifetime: "session", report: "milestones", milestone: { everyTurns: 5 }, budgetShare: 0.2 },
|
|
165
|
+
createAgent: ({ resourceId, threadId, permission, signal, delegate }) => createResearchAgent(/* ... */),
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Host code or the parent model (spawn_agent routes session lifetime to the async path):
|
|
171
|
+
const handle = await supervisor.delegateAsync({ childId: "researcher", input: "watch the build", lifetime: "session" });
|
|
172
|
+
await supervisor.wait(handle.delegationId); // join later; cancel(handle.delegationId) ends it explicitly
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Session-lifetime children detach from the caller and ancestor-child abort signals, hold one `activeChildren` slot until they end, and stop on `cancel_agent` / `cancel(delegationId)` or the supervisor `signal`. `budgetShare` scales the inherited steps/tool-calls/tokens/timeout limits — never above the parent or host ceiling. Reporting stays host-opt-in and redacted: `milestones` emits `child_milestone` every N turns or on a host predicate, `stream` forwards every per-turn provider/tool/turn event (never token deltas), and both are rate-coalesced at `limits.maxChildEventsPerSecond` (10/s per child default) with a `delegation_child_events_coalesced` marker. To surface them on a parent session stream, pass `childEventSink` — it receives the same redacted payload tagged `child: { childId, delegationId, depth }`, so the parent subscriber only reads `event.child`.
|
|
176
|
+
|
|
154
177
|
## Where Prism is stronger
|
|
155
178
|
|
|
156
179
|
- **Durable Human-in-the-Loop (HITL)**: Prism workflows support durable pause and resume via [`suspend()`](workflows.md#durable-suspension-and-resumption) and [`resumeWorkflow()`](workflows.md) across worker restarts or approval gates ([Agent durable approval](agent-session-runtime.md)).
|
|
@@ -166,7 +189,7 @@ Live demo: [`examples/spawn-agent-tool.ts`](../examples/spawn-agent-tool.ts) —
|
|
|
166
189
|
- **Narrowing on transfer, never widening.** If the specialist needs the caller's verified identity, project it through `narrowIdentity` / `assertIdentityPropagation` ([Agent identity](agent-identity.md)) so scopes and tenant cannot widen across the swap. For delegation the same discipline is built in (`narrowIdentity`, AND-composed policies); for A2A the exact-origin client plus per-operation authorization is the boundary.
|
|
167
190
|
- **Manager-generated task plans are untrusted model output.** Manager plan outputs are validated against the typed schema via `ArtifactValidator` before being persisted to workflow state or dispatched to `fan_out`. Malformed or invalid plans trigger the artifact repair loop or fail closed before any specialist is invoked.
|
|
168
191
|
- **Redaction of carried context.** Handoff carries the raw transcript by design — same rows a human replay would read. Apply the session egress seams on the way out: `redactSessionEntry` / `redactMessage` with a host field policy (see [Data classification](data-classification.md)) and `AgentConfig.redactor`; for durable replay across tenants reuse the redacted transcript seam discipline used by ACP `sessions.transcript` ([ACP interop](acp.md)).
|
|
169
|
-
- **Telemetry attribution.** Which agent produced which turn is not stored on message entries; the host knows (it performed the swap or aggregated fan-out results) and should pin it per run via `RunOptions.identity` (principal kind `agent`) so `identityTelemetryAttributes` (`prism.identity.*`) carries redacted attribution on telemetry, or via observability metadata. Supervisor runs emit dedicated `delegation_*` events
|
|
192
|
+
- **Telemetry attribution.** Which agent produced which turn is not stored on message entries; the host knows (it performed the swap or aggregated fan-out results) and should pin it per run via `RunOptions.identity` (principal kind `agent`) so `identityTelemetryAttributes` (`prism.identity.*`) carries redacted attribution on telemetry, or via observability metadata. Supervisor runs emit dedicated `delegation_*` events plus `child_failed` attribution (terminal `status`/`stopReason`, plan-086/087 `RunLimitBreach` when a ceiling fired), and `supervisor.summary()` reports per-child `attempts`/`retries`/`failures`/`failureRadius`/`outcome` — the recovery and cascade-radius counters a host cannot reconstruct from totals alone. An in-process definition swap has no session seam to emit one, so the host records attribution.
|
|
170
193
|
- **Performance.** The swap performs zero provider calls; it costs one registry resolution plus one session open (~sub-millisecond in the example fixture). The transferred turn costs what any tool round costs.
|
|
171
194
|
|
|
172
195
|
## Extension and configuration notes
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## What it does
|
|
4
4
|
|
|
5
|
-
The optional `@arnilo/prism/node/session-store-jsonl` subpath stores `SessionEntry` records in a caller-named JSONL file: one JSON object per line. `searchSessions` is
|
|
5
|
+
The optional `@arnilo/prism/node/session-store-jsonl` subpath stores `SessionEntry` records in a caller-named JSONL file: one JSON object per line. `searchSessions` is supported as an unindexed linear scan of the file: the memory-store matcher (workspace/label/summary/`kind` filters, text `query`, cursor pagination, hit pointers) with the contract linear scan caps. Every query reads and parses the whole file (O(corpus) time and memory), so indexed SQLite/Postgres adapters remain the recommended path for search over large corpora.
|
|
6
6
|
|
|
7
7
|
APIs:
|
|
8
8
|
|
|
@@ -35,6 +35,7 @@ import { createJsonlSessionStore } from "@arnilo/prism/node/session-store-jsonl"
|
|
|
35
35
|
- `append(entry, options?)` appends one JSON line, rejects duplicate entry ids, honors `expectedParentId` existence checks, and deduplicates exact idempotency retries within this store instance. Append **fails closed** when the file already contains any corrupt or shape-invalid line (`Invalid JSONL at line N: …`) so writers cannot extend a damaged log.
|
|
36
36
|
- `list(sessionId)` reads the file and returns valid entries for that session id. Corrupt or shape-invalid lines are skipped; they do not poison the whole file.
|
|
37
37
|
- `get(id)` reads the file and returns the matching valid entry, if any.
|
|
38
|
+
- `searchSessions(query)` reads the file and runs the shared linear session matcher. Corrupt or shape-invalid lines are quarantined exactly as in `list()`/`get()`, the contract linear caps bound sessions/entries/bytes scanned, and hits carry the same shape as the indexed adapters (`sessionId`, `leafId`, `entryId`, `runId`, `turn`, `snippet`) — without `score`, since a linear scan has no index relevance.
|
|
38
39
|
- `readJsonlSessionEntries(path)` returns `{ entries: SessionEntry[]; errors: SessionEntryParseError[] }` so hosts/tests can inspect per-line parse errors.
|
|
39
40
|
|
|
40
41
|
Missing files read as empty stores (typed Node `ENOENT`). Invalid JSON, missing required fields, unsupported `schemaVersion`, unknown `kind`, or wrong per-kind shapes (`message`, `summary`, `model_change`, `custom`, `compaction`, `label`, `event`, `metadata`, or non-string `parentId`) are quarantined per line with line number and reason; the raw line is included in `SessionEntryParseError.raw`. Unknown entry kinds and future schema versions fail closed for reads: the line is skipped and never returned by `list()` or `get()`. For writes, any parse error blocks `append()` until the host repairs or replaces the file.
|
|
@@ -56,6 +57,10 @@ import { createJsonlSessionStore, readJsonlSessionEntries } from "@arnilo/prism/
|
|
|
56
57
|
const store = createJsonlSessionStore("./sessions.jsonl");
|
|
57
58
|
const { entries, errors } = await readJsonlSessionEntries("./sessions.jsonl");
|
|
58
59
|
if (errors.length) console.warn("quarantined lines", errors);
|
|
60
|
+
|
|
61
|
+
// Linear search (unindexed): the same query API as the SQLite/Postgres adapters.
|
|
62
|
+
const page = await store.searchSessions!({ workspaceRoot: "/repo", query: "flake", kind: "any", limit: 20 });
|
|
63
|
+
// [{ sessionId, leafId, entryId, runId, turn, snippet, ... }]
|
|
59
64
|
```
|
|
60
65
|
|
|
61
66
|
Use `createMemorySessionStore()` for tests or throwaway sessions; use the JSONL store when entries should survive a process restart.
|
|
@@ -73,6 +78,7 @@ Use `createMemorySessionStore()` for tests or throwaway sessions; use the JSONL
|
|
|
73
78
|
- Errors include path/reason or line number, not file contents.
|
|
74
79
|
- Do not put secrets in messages, metadata, summaries, labels, or custom entries.
|
|
75
80
|
- Reads are linear in file size. Appends also re-read and re-parse the whole file for duplicate/parent/corruption checks before writing one line, and are serialized per store instance. A rejected append does not poison later appends; the rejected line is not written.
|
|
81
|
+
- `searchSessions` is linear in file size too (there is no index): every query reads and parses the whole file before the capped scan, so latency and peak memory grow with the corpus. Use a SQLite/Postgres `SessionStore` when search latency matters, and treat search here as resume/filter tooling on small stores.
|
|
76
82
|
- There is no cross-process lock or durable idempotency table; two processes writing the same file can race. Add a database or external lock if multiple processes write the same file.
|
|
77
83
|
- Treat this adapter as development/single-process storage. Production multi-writer hosts should use an indexed database `SessionStore` adapter.
|
|
78
84
|
|
package/docs/observability.md
CHANGED
|
@@ -41,7 +41,7 @@ New agent event variants (metadata only):
|
|
|
41
41
|
| Variant | When | Key fields |
|
|
42
42
|
| --- | --- | --- |
|
|
43
43
|
| `provider_turn_started` | Before each provider `generate()` attempt | `turn`, `metadata: ProviderTurnMetadata` |
|
|
44
|
-
| `provider_turn_finished` | After success or failure of that attempt | `metadata` (includes `latencyMs`, optional `httpStatus`), `usage?`, `error?` |
|
|
44
|
+
| `provider_turn_finished` | After success or failure of that attempt | `metadata` (includes `latencyMs`, optional `httpStatus`, `stopReason`, `budgets`, `cache`), `usage?`, `error?` |
|
|
45
45
|
|
|
46
46
|
`ToolExecutionMetadata` on terminal tool events:
|
|
47
47
|
|
|
@@ -91,6 +91,7 @@ Provider turn metadata fields:
|
|
|
91
91
|
| `latencyMs` | Set on `provider_turn_finished` |
|
|
92
92
|
| `httpStatus` | Numeric `ErrorInfo.code` when present |
|
|
93
93
|
| `rateLimitRemaining` / `rateLimitResetMs` | Reserved for provider adapters (optional) |
|
|
94
|
+
| `cache` | Provider-reported `{ cacheReadTokens?, cacheWriteTokens?, hitRate? }`; absent when cache usage is unknown. |
|
|
94
95
|
|
|
95
96
|
OpenTelemetry mapping (when enabled):
|
|
96
97
|
|
|
@@ -190,8 +191,8 @@ const found = await retrieveContext("policy", { embedder, store, scope, telemetr
|
|
|
190
191
|
|
|
191
192
|
Host cockpits and dashboard cards need fast aggregate summaries of an execution without re-walking every raw event or risking prompt/secret leaks:
|
|
192
193
|
|
|
193
|
-
- `summarizeTimeline(timeline)`: rolls up an `ExecutionTimeline` into a `TimelineSummary` containing duration, turn count, tool call counts, provider attempts, total tokens, cost, error counts, and
|
|
194
|
-
- `summarizeSession(timelines)`: rolls up an array of `ExecutionTimeline`s for a session/conversation into a `SessionSummary` with aggregated tokens, costs, run counts, and
|
|
194
|
+
- `summarizeTimeline(timeline)`: rolls up an `ExecutionTimeline` into a `TimelineSummary` containing duration, turn count (split into model vs deterministic turns), tool call counts, provider attempts, total tokens, cost, error counts, suspension state, and — for a run that died on a run limit — an `exhaustion` line (`"maxTurns exhausted (13/12); closest: maxToolCalls 0.625"`).
|
|
195
|
+
- `summarizeSession(timelines)`: rolls up an array of `ExecutionTimeline`s for a session/conversation into a `SessionSummary` with aggregated tokens, costs, run counts, duration, and the same model/deterministic turn split.
|
|
195
196
|
|
|
196
197
|
```ts
|
|
197
198
|
import { summarizeTimeline, summarizeSession } from "@arnilo/prism-core/governance/observability";
|
|
@@ -201,6 +202,7 @@ const summary = summarizeTimeline(timeline);
|
|
|
201
202
|
// {
|
|
202
203
|
// durationMs: 1250,
|
|
203
204
|
// turnCount: 2,
|
|
205
|
+
// turns: { model: 1, deterministic: 1 },
|
|
204
206
|
// toolCallCount: 3,
|
|
205
207
|
// toolCounts: { search: 2, lookup: 1 },
|
|
206
208
|
// providerAttempts: 2,
|
|
@@ -210,6 +212,7 @@ const summary = summarizeTimeline(timeline);
|
|
|
210
212
|
// blockedToolCount: 0,
|
|
211
213
|
// suspended: false,
|
|
212
214
|
// status: "succeeded",
|
|
215
|
+
// exhaustion: "maxTurns exhausted (13/12); closest: maxToolCalls 0.625", // only when a limit fired
|
|
213
216
|
// }
|
|
214
217
|
|
|
215
218
|
const sessionSummary = summarizeSession([run1Timeline, run2Timeline]);
|
|
@@ -218,6 +221,7 @@ const sessionSummary = summarizeSession([run1Timeline, run2Timeline]);
|
|
|
218
221
|
|
|
219
222
|
Cardinality and correctness guarantees:
|
|
220
223
|
- **Bounded cardinality**: `toolCounts` is capped to `MAX_SUMMARY_DISTINCT_TOOLS = 64` distinct tool names. If more tools are invoked, lowest-frequency tool names overflow into an `"other"` bucket.
|
|
224
|
+
- **Honest turn attribution**: `turns.model` counts turns with a provider step; `turns.deterministic` counts turns answered by host middleware (plan 096, `deterministic` step kind). A no-model turn is never rolled into model counts, and its usage stays absent rather than zero.
|
|
221
225
|
- **No double counting**: Token usage is derived from the root run's `run_total` (or aggregated across `turn` / `provider` steps if no run-level total exists), avoiding double counting between provider turn steps and run totals. Costs are rounded to 6 decimal places to prevent floating-point drift.
|
|
222
226
|
- **Payload-free**: Summaries contain counts, durations, status codes, and usage metrics only — zero prompt text, tool arguments, or credentials.
|
|
223
227
|
|
package/docs/openapi-tools.md
CHANGED
|
@@ -4,7 +4,7 @@ Optional `createOpenApiTools` compiles host-selected OpenAPI 3.1 operations into
|
|
|
4
4
|
|
|
5
5
|
## When to use it
|
|
6
6
|
|
|
7
|
-
Hosts that already expose a JSON API with an OpenAPI 3.1 document and want the agent to call a **fixed, host-chosen subset** of it — never model-driven discovery, never a raw method/path passthrough. For vendor web search/extraction use `@arnilo/prism-web-tools`; for M365/GWS use `@arnilo/prism-
|
|
7
|
+
Hosts that already expose a JSON API with an OpenAPI 3.1 document and want the agent to call a **fixed, host-chosen subset** of it — never model-driven discovery, never a raw method/path passthrough. For vendor web search/extraction use `@arnilo/prism-web-tools`; for M365/GWS use `@arnilo/prism-work/connectors`; this adapter is for arbitrary host APIs.
|
|
8
8
|
|
|
9
9
|
## Usage
|
|
10
10
|
|
package/docs/operations.md
CHANGED
|
@@ -82,9 +82,7 @@ tenant's reads, writes, and lease takeover all fail closed.
|
|
|
82
82
|
fencing token), and a stale token's renewal returns `null`. There is
|
|
83
83
|
intentionally no "force unlock" operation — deleting a lease row manually
|
|
84
84
|
bypasses fencing and can cause split-brain writes; never do it.
|
|
85
|
-
- Tenant ownership is checked on every durable read/write
|
|
86
|
-
reads, saves, and lease acquisitions fail closed with ownership-mismatch
|
|
87
|
-
errors.
|
|
85
|
+
- Tenant ownership is checked on every durable read/write: a foreign checkpoint read or delete is a miss (never an existence oracle), a foreign checkpoint write fails as `ERR_PRISM_CHECKPOINT_CONFLICT`, and lease acquisition still fails closed with an ownership-mismatch error.
|
|
88
86
|
- An uncertain commit (side effect landed, cursor not advanced) is resolved
|
|
89
87
|
by replaying the effect with its stable id — never by guessing. Reload
|
|
90
88
|
durable state before any retry; retries that skip the reload risk
|
package/docs/options-index.md
CHANGED
|
@@ -18,7 +18,7 @@ Field-level detail (defaults, bounds, failure modes) lives on the owning page
|
|
|
18
18
|
| --- | --- | --- |
|
|
19
19
|
| `AgentConfig` | The reusable agent: provider, model, tools, skills, stores, retry, compaction, prompts, limits | [Agent/session runtime](agent-session-runtime.md) |
|
|
20
20
|
| `RunOptions` | One run's overrides: model, limits, thinking level, skills, middleware, metadata, signal | [Agent/session runtime](agent-session-runtime.md) |
|
|
21
|
-
| `AgentSessionConfig` | Session creation: id, agent, store, branch leaf, snapshot cache TTL | [Agent/session runtime](agent-session-runtime.md) |
|
|
21
|
+
| `AgentSessionConfig` | Session creation: id, agent, store, branch leaf, snapshot cache TTL, guardrail packs | [Agent/session runtime](agent-session-runtime.md) |
|
|
22
22
|
| `ModelConfig` | A registered model record: capabilities, limits, cost, cache and thinking metadata | [Model registry](model-registry.md) |
|
|
23
23
|
| `ProviderRequestOptions` | Per-request provider hints: session/cache/header/compat/extra, applied after host policies | [Provider layer](provider-layer.md) |
|
|
24
24
|
|
|
@@ -29,6 +29,25 @@ Field-level detail (defaults, bounds, failure modes) lives on the owning page
|
|
|
29
29
|
- **Request-time overrides narrow, never widen.** `RunOptions.limits` can only tighten `AgentConfig.limits`; a configured finite ceiling wins over `null`.
|
|
30
30
|
- **Byte caps are not estimator-dependent.** Token-budget options that accept a host estimator (`tokenEstimator`) affect eviction accounting only; byte caps and redaction stay authoritative.
|
|
31
31
|
|
|
32
|
+
## Durable runs
|
|
33
|
+
|
|
34
|
+
**Agent run state** — [`durable-runs.md`](durable-runs.md)
|
|
35
|
+
`AgentRunStateOptions`
|
|
36
|
+
|
|
37
|
+
`AgentRunStateOptions.checkpointPolicy` (`"decision"` | `"every-turn"`) writes turn-boundary checkpoints so a crashed worker resumes with `decision: "continue"`; that action stays host-only and is never reachable from AG-UI or the server boundary.
|
|
38
|
+
|
|
39
|
+
## Host stop, snapshot, and honesty surfaces
|
|
40
|
+
|
|
41
|
+
| Surface | What it controls | Owning page |
|
|
42
|
+
| --- | --- | --- |
|
|
43
|
+
| `RunOptions.turnPolicy` (`TurnPolicyOptions`) | Synchronous host stop at a turn boundary; a stop lands as `stopReason: "host_policy"` and stays resumable | [Agent loops](agent-loops.md) |
|
|
44
|
+
| `CreateAgUiHandlerOptions.inputPolicy` (`AgUiInputPolicyOptions`) | `clientState: "honor"` \| `"ignore"` — whether the server honors client-supplied AG-UI state and tools | [Frontend interoperability](ag-ui.md) |
|
|
45
|
+
| `snapshotRunBundle(...)` → `RunBundleSnapshot` | Inspectable digest projection of the effective run bundle (prompt/skill/tool/guardrail digests, limits, storage kinds) | [Run bundle](run-bundle.md) |
|
|
46
|
+
| `createClaimGroundingGuardrail` (`ClaimGroundingGuardrailOptions`) | `"output"`-stage guardrail that blocks or flags numeric claims no tool result or host evidence supports | [Guardrails](guardrails.md) |
|
|
47
|
+
| `ErrorInfo.failureClass` (`ProviderFailureClass`) | Typed provider failure on run outcomes, ledger rows, and tool results (`quota`, `rate_limited`, `auth`, `transient`, `permanent`) | [Runs and usage](runs-and-usage.md) |
|
|
48
|
+
| `AgentConfig.usageEstimation` | `"fallback"` (default) records a labeled estimate when a provider reports no usage; `"off"` leaves usage absent; estimates are never priced | [Runs and usage](runs-and-usage.md#automatic-fallback-agentconfigusageestimation) |
|
|
49
|
+
| `ModelConfig.capabilities.toolCallStrictness` | Advisory tool-call reliability (`"strict"` \| `"lenient"` \| `"legacy"`); catalog conformance, not a promise | [Model registry](model-registry.md) |
|
|
50
|
+
|
|
32
51
|
## Agent/session runtime
|
|
33
52
|
|
|
34
53
|
**Agent definitions** — [`agent-definitions.md`](agent-definitions.md)
|
|
@@ -38,7 +57,10 @@ Field-level detail (defaults, bounds, failure modes) lives on the owning page
|
|
|
38
57
|
`SubscribeOptions`
|
|
39
58
|
|
|
40
59
|
**Agent loops** — [`agent-loops.md`](agent-loops.md)
|
|
41
|
-
`AgentLoopOptions`
|
|
60
|
+
`AgentLoopOptions`, `TurnPolicyOptions`
|
|
61
|
+
|
|
62
|
+
**Guardrails** — [`guardrails.md`](guardrails.md)
|
|
63
|
+
`ClaimGroundingGuardrailOptions`
|
|
42
64
|
|
|
43
65
|
**Agent/session runtime** — [`agent-session-runtime.md`](agent-session-runtime.md)
|
|
44
66
|
`AgentConfig`, `AgentRunResumeOptions`, `AgentRunResumeStreamOptions`, `AgentSessionCloneOptions`, `AgentSessionConfig`, `AgentSessionForkOptions`, `RunOptions`, `SteerOptions`
|
|
@@ -230,7 +252,7 @@ Field-level detail (defaults, bounds, failure modes) lives on the owning page
|
|
|
230
252
|
`AcpCapabilitiesOptions`, `CreatePrismAcpAgentOptions`
|
|
231
253
|
|
|
232
254
|
**Frontend interoperability (AG-UI and ACP)** — [`ag-ui.md`](ag-ui.md)
|
|
233
|
-
`AgUiLimitOptions`
|
|
255
|
+
`AgUiInputPolicyOptions`, `AgUiLimitOptions`
|
|
234
256
|
|
|
235
257
|
**Supervisors and subagents** — [`supervisors.md`](supervisors.md)
|
|
236
258
|
`CreateSupervisorOptions`, `SupervisorLimits`, `ResolvedSupervisorLimits`, `DelegationWaitOptions`, `CreateSpawnAgentToolOptions`, `CreateDelegationControlToolOptions`, `WorktreeChildFactoryOptions`, `ObserveSupervisorLifecycleOptions`
|
|
@@ -240,6 +262,17 @@ Field-level detail (defaults, bounds, failure modes) lives on the owning page
|
|
|
240
262
|
**Workflows** — [`workflows.md`](workflows.md)
|
|
241
263
|
`RunWorkflowOptions`
|
|
242
264
|
|
|
265
|
+
## Third-party integrations
|
|
266
|
+
|
|
267
|
+
**Messaging channels** — [`messaging-channels.md`](messaging-channels.md)
|
|
268
|
+
`MessagingRuntimeOptions`, `ChannelLimits`, `ChannelDeliveryJournalOptions`, `ChannelPairingStoreOptions`, `ChannelStateStoreOptions`
|
|
269
|
+
|
|
270
|
+
**Telegram channel** — [`telegram-channel.md`](telegram-channel.md)
|
|
271
|
+
`TelegramAdapterOptions`, `TelegramWebhookHandlerOptions`
|
|
272
|
+
|
|
273
|
+
**Signal channel (experimental)** — [`signal-channel.md`](signal-channel.md)
|
|
274
|
+
`SignalAdapterOptions`
|
|
275
|
+
|
|
243
276
|
## Conformance harnesses
|
|
244
277
|
|
|
245
278
|
These option objects configure the shipped test doubles a host runs against its own adapters (provider, store, run ledger, compaction, tool, extension). They are host-facing, but only in test code.
|
|
@@ -22,8 +22,8 @@ One row per declaration. `Unlocks` names the subpath whose import reaches the pe
|
|
|
22
22
|
| `zod` | `^3.25.0 \|\| ^4.0.0` | no | `@arnilo/prism-ag-ui` | `./acp` | `npm i zod` | no |
|
|
23
23
|
| `@nanonets/graft` | `^0.16.0 \|\| ^0.18.0` | yes | `@arnilo/prism-memory` | `./graft` | `npm i @nanonets/graft` | no |
|
|
24
24
|
| `@dietrichgebert/ponytail` | `^4.9.0` | yes | `@arnilo/prism-coding-tools` | `./ponytail` | `npm i @dietrichgebert/ponytail` | no |
|
|
25
|
-
| `mammoth` | `^1.8.0` | yes | `@arnilo/prism-
|
|
26
|
-
| `pdf-parse` | `^2.4.5` | yes | `@arnilo/prism-
|
|
25
|
+
| `mammoth` | `^1.8.0` | yes | `@arnilo/prism-work` | `./document-reader` | `npm i mammoth` | no |
|
|
26
|
+
| `pdf-parse` | `^2.4.5` | yes | `@arnilo/prism-work` | `./document-reader` | `npm i pdf-parse` | no |
|
|
27
27
|
| `e2b` | `2.49.1` | yes | `@arnilo/prism-coding-tools` | `./security` | `npm i e2b@2.49.1` | yes |
|
|
28
28
|
| `better-sqlite3` | `^13.0.3` | yes | `@arnilo/prism-core` | `./sessions/sqlite`, `./governance/prompts` | `npm i better-sqlite3` | no |
|
|
29
29
|
| `pg` | `^8.23.0` | yes | `@arnilo/prism-core` | `./sessions/postgres`, `./enterprise/postgres`, `./governance/prompts` | `npm i pg` | yes |
|
|
@@ -51,7 +51,7 @@ Two peers are pinned to an exact version instead of a range, because the pin is
|
|
|
51
51
|
- **No secrets are read by the peers.** Prism resolves credentials through host providers and redacts them at the boundary; peers only ever receive a resolved connection string or model object. See [Credentials and redaction](credentials-and-redaction.md) and [Host security guide](host-security.md).
|
|
52
52
|
- **Nothing is installed implicitly.** Optional peers are never auto-installed by npm; a missing one fails closed at the call site with a typed error naming the peer and the subpath. Required peers (today only `zod`) are installed by npm with the package.
|
|
53
53
|
|
|
54
|
-
Test-only dependencies are *not* peers. `playwright-core` appears in `@arnilo/prism-
|
|
54
|
+
Test-only dependencies are *not* peers. `playwright-core` appears in `@arnilo/prism-work` as a devDependency only, because the office diagrams embed takes a host-supplied iframe and the sole consumer is the gated live draw.io conformance test.
|
|
55
55
|
|
|
56
56
|
## Implementation example
|
|
57
57
|
|
|
@@ -66,7 +66,7 @@ npm i @arnilo/prism-core pg
|
|
|
66
66
|
npm i @arnilo/prism-core @nats-io/transport-node @nats-io/jetstream
|
|
67
67
|
|
|
68
68
|
# Document reader: pick the parser you need (both are independent)
|
|
69
|
-
npm i @arnilo/prism-
|
|
69
|
+
npm i @arnilo/prism-work pdf-parse mammoth
|
|
70
70
|
```
|
|
71
71
|
|
|
72
72
|
```ts
|
|
@@ -80,7 +80,7 @@ const tools = await createBrowserTools({ browser });
|
|
|
80
80
|
## Extension and configuration notes
|
|
81
81
|
|
|
82
82
|
- A peer is an *implementation the host owns*. When a peer's default wiring is not what you want, pass your own implementation instead of installing theirs: the document reader accepts host parsers (`createReadTool({ documentReader })`), the memory `/graft` resolver accepts an explicit package root, and the browser surfaces accept a host `Browser`.
|
|
83
|
-
- Subpaths that need a peer isolate that import, so importing another subpath of the same package never evaluates it. The office family is the extreme case: zero peers, because it takes structural inputs.
|
|
83
|
+
- Subpaths that need a peer isolate that import, so importing another subpath of the same package never evaluates it. The office family is the extreme case: zero peers, because it takes structural inputs. `@arnilo/prism-channels` also has no third-party peers: Telegram uses native `fetch`, and signal-cli is a host-operated binary rather than an npm peer.
|
|
84
84
|
- Adding a peer to a Prism package is a release-gated change: the declaration must be optional unless a hard dependency's own peer forces it (the `zod` case), and exact pins must come with a version-gate or compatibility rationale.
|
|
85
85
|
|
|
86
86
|
## Security and performance notes
|
|
@@ -93,4 +93,4 @@ const tools = await createBrowserTools({ browser });
|
|
|
93
93
|
|
|
94
94
|
- [Release and install](release-and-install.md): install profiles that pair with each peer.
|
|
95
95
|
- [Configuration options index](options-index.md): the option surfaces each peer unlocks.
|
|
96
|
-
- Package-level detail: [Coding tools](coding-tools.md), [Core runtime](core.md), [Session stores](session-stores.md), [Browser automation](browser-automation.md), [Document reader](document-reader.md), [Graft](graft.md), [Ponytail](ponytail.md), [Provider packages](provider-packages.md).
|
|
96
|
+
- Package-level detail: [Coding tools](coding-tools.md), [Core runtime](core.md), [Session stores](session-stores.md), [Browser automation](browser-automation.md), [Document reader](document-reader.md), [Graft](graft.md), [Ponytail](ponytail.md), [Provider packages](provider-packages.md), [Messaging channels](messaging-channels.md).
|
package/docs/policy-and-audit.md
CHANGED
|
@@ -107,7 +107,19 @@ for await (const page of exportPolicyDecisions({
|
|
|
107
107
|
|
|
108
108
|
## Extension and configuration notes
|
|
109
109
|
|
|
110
|
-
Policy is optional. Hosts wire `record*` helpers or `evaluateAndAppend` at permission/guardrail/tool-approval/router/connector boundaries. Model-router and work-connector packages (later Phase 8 tasks) may call the same store when configured. Replace file/memory adapters with host WORM/KMS without changing record shape.
|
|
110
|
+
Policy is optional. Hosts wire `record*` helpers or `evaluateAndAppend` at permission/guardrail/tool-approval/router/connector boundaries. Model-router and work-connector packages (later Phase 8 tasks) may call the same store when configured. Replace file/memory adapters with host WORM/KMS without changing record shape. Guardrail-pack denials record through `recordGuardrailDecision` like any other guardrail: the target id is the rule identity `pack:<pack>/<rule>`, `block`/`tripwire` map to outcome `deny`, and the evidence ref is `guardrail:pack:<pack>/<rule>:<stage>`. Pack config appears in run bundles as identity rows (`pack:<pack>/<rule>`, stage, `pack@version`) — never inline predicate code or tool arguments.
|
|
111
|
+
|
|
112
|
+
## Memory retrieval ACL denials and re-pointing (plan 089)
|
|
113
|
+
|
|
114
|
+
Memory retrieval keeps its own audit events next to policy decisions; hosts forward them to the same append-only sink:
|
|
115
|
+
|
|
116
|
+
| Event | Shape | When |
|
|
117
|
+
| --- | --- | --- |
|
|
118
|
+
| `rag.acl_denied` | `{ sourceId, scope: { tenantId, resourceId, threadId }, reason: "no_grant" \| "check_failed", hits, error? }` via `retrieveContext({ onAccessDenied })` | A source was withheld at the retrieval boundary: revoked/absent/version-mismatched grant, or the grant lookup threw (`error` is redacted, capped at 256 chars) |
|
|
119
|
+
| `Repointed` log line + result | `repointSource()` → `{ from, to, movedChunks, rewrittenEdges, layers, batched }` | A source's grant identity moved and derived artifacts followed |
|
|
120
|
+
| Invalidation rows | `store.invalidate()` rows (`{ id, reason: "corrected" \| "revoked" \| "forgotten" \| "legal_hold", at }`) read back by `listInvalidatedIds()` | A source was revoked/forgotten/held; tombstones stay for explainability |
|
|
121
|
+
|
|
122
|
+
Events are per *source*, not per hit, and are emitted once per query. They never contain document text, grant contents, or credentials; `check_failed` messages pass through the same redactor as retrieved content. Denials are fail-closed: a source is excluded whether the grant is absent, revoked, or the lookup failed, and the query returns the remaining hits. Aborts are not denials and are never recorded as such.
|
|
111
123
|
|
|
112
124
|
## Security and performance notes
|
|
113
125
|
|
|
@@ -60,7 +60,7 @@ Hosts own TLS (`ssl` in `poolConfig`), credentials, connection limits, and backu
|
|
|
60
60
|
| `RunLedger.append*` | Inserts run/event/tool/usage rows; events receive monotonic per-run `sequence` values. |
|
|
61
61
|
| `events` | Durable `AgentEventSource`; `LISTEN`/`NOTIFY` only wakes exact owned indexed reads, while polling remains recovery fallback. |
|
|
62
62
|
| `ProductionPersistenceStore.query*` | Parameterized cursor pagination on indexed columns with tenant/account/user filters. |
|
|
63
|
-
| `checkpoints` | Generic versioned `CheckpointStore` backed by `prism_checkpoints`; ownership, CAS/fencing checks, bounded pagination, and workflow suspended/denied/schedule/state/replay values without a schema migration. |
|
|
63
|
+
| `checkpoints` | Generic versioned `CheckpointStore` backed by `prism_checkpoints`; ownership, CAS/fencing checks, bounded pagination, and workflow suspended/denied/schedule/state/replay values without a schema migration. A load or delete under a non-matching ownership scope reads as absent and a cross-scope write fails as a generic `ERR_PRISM_CHECKPOINT_CONFLICT` (plan 080 Task 3) — no ownership-shaped existence oracle. |
|
|
64
64
|
| `leases` | Atomic `LeaseStore` backed by `prism_leases`; database-clock expiry, opaque renew/release token, monotonic takeover fence. |
|
|
65
65
|
| `close()` | Ends the pool when the adapter created it from `connectionString`. |
|
|
66
66
|
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Prefix stability conformance
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
Prefix stability conformance drives a real agent session through two staggered skill loads and asserts that each provider request keeps a byte-identical leading prefix with its predecessor — messages **and** tool schemas. It is the host-runnable form of the golden check behind [provider caching](provider-caching.md) and progressive skill disclosure: a late `load_skill` must append a body after the stable prefix instead of rewriting it.
|
|
6
|
+
|
|
7
|
+
Exported from `@arnilo/prism/testing/prefix-stability-conformance`:
|
|
8
|
+
|
|
9
|
+
- `runPrefixStabilityConformance(options)`
|
|
10
|
+
- `PrefixStabilityConformanceOptions`
|
|
11
|
+
- `PrefixStabilityConformanceResult`
|
|
12
|
+
|
|
13
|
+
## When to use it
|
|
14
|
+
|
|
15
|
+
Use it when a host owns any part of prompt assembly — custom `inputBuilder`, `promptBuilder`, context providers, instruction injectors, input/prompt middleware, or an explicit `inputLayout` — and wants to prove that progressive disclosure still holds the cache prefix. The runner:
|
|
16
|
+
|
|
17
|
+
- installs a fixture provider (no network) that loads `skills[0]` on the first turn and `skills[1]` on the second, two provider requests per turn;
|
|
18
|
+
- keeps everything else in `host` exactly as production: system prompt, context providers, builders, middleware, disclosure settings;
|
|
19
|
+
- measures, for each consecutive captured request, the byte-shared prefix as a fraction of the previous request and fails below `minContinuity` (default `0.95`);
|
|
20
|
+
- fails when a loaded body never reaches a provider request, so a builder that drops the tail cannot pass vacuously.
|
|
21
|
+
|
|
22
|
+
## Inputs / request
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { runPrefixStabilityConformance } from "@arnilo/prism/testing/prefix-stability-conformance";
|
|
26
|
+
|
|
27
|
+
const result = await runPrefixStabilityConformance({
|
|
28
|
+
host: {
|
|
29
|
+
model: { provider: "anthropic", model: "claude-sonnet-4-6" },
|
|
30
|
+
systemPrompt: { text: "..." },
|
|
31
|
+
context: [projectContextProvider],
|
|
32
|
+
},
|
|
33
|
+
skills: [skillA, skillB],
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`PrefixStabilityConformanceOptions`:
|
|
38
|
+
- `host` — the host's `AgentConfig` minus `provider`, `providerSource`, and `skills`; the runner supplies the fixture provider and fixture skill registry
|
|
39
|
+
- `skills` — exactly two distinct `Skill` values with non-empty `instructions`, loaded in turn order
|
|
40
|
+
- `minContinuity?` — minimum shared-prefix fraction between consecutive requests (default `0.95`)
|
|
41
|
+
- `inputs?` — the two turn inputs (default fixed strings, so runs stay comparable across hosts)
|
|
42
|
+
|
|
43
|
+
## Outputs / response / events
|
|
44
|
+
|
|
45
|
+
Returns `Promise<{ requests: number; minContinuity: number }>`: the captured request count (four) and the lowest shared-prefix fraction observed. Throws a plain `Error` naming the offending request pair and the measured percentage on the first violation. No events, no test runner, no network.
|
|
46
|
+
|
|
47
|
+
## Request/response example
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { runPrefixStabilityConformance } from "@arnilo/prism/testing/prefix-stability-conformance";
|
|
51
|
+
|
|
52
|
+
const { minContinuity } = await runPrefixStabilityConformance({
|
|
53
|
+
host: myAgentAssembly,
|
|
54
|
+
skills: [alphaSkill, betaSkill],
|
|
55
|
+
});
|
|
56
|
+
// throws: "request 2 → 3 kept 41.2% of the previous provider prefix (minimum 95.0%)"
|
|
57
|
+
// when a context block or the skill catalog is recomposed in place.
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Implementation example
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { runPrefixStabilityConformance } from "@arnilo/prism/testing/prefix-stability-conformance";
|
|
64
|
+
|
|
65
|
+
// The runner owns the provider and skills, so the same helper is the negative control too:
|
|
66
|
+
// add a deliberately volatile context provider to prove the assertion can fail.
|
|
67
|
+
await runPrefixStabilityConformance({
|
|
68
|
+
host: {
|
|
69
|
+
model: myModel,
|
|
70
|
+
context: [{ name: "volatile", resolve: () => [{ title: "Now", content: `${Date.now()}` }] }],
|
|
71
|
+
},
|
|
72
|
+
skills: [alphaSkill, betaSkill],
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Extension and configuration notes
|
|
77
|
+
|
|
78
|
+
- Loaded skill bodies and URI resources are re-sent after new transcript content by design (the tail is append-only, not immutable); a body larger than `1 - minContinuity` of the whole prompt lowers the fraction without indicating a prefix regression. Raise the fixture's stable prefix or lower `minContinuity` for body-heavy hosts.
|
|
79
|
+
- Prompt builders that render skill bodies outside the tail are welcome — the check measures the provider-visible prefix, not where the body sits.
|
|
80
|
+
- Attention/tool-result folding and context-budget eviction are explicit invalidation boundaries: run this check on an assembly path that neither folds nor evicts, or expect the fold to reset the measured prefix at that turn.
|
|
81
|
+
|
|
82
|
+
## Security and performance notes
|
|
83
|
+
|
|
84
|
+
- No credentials, no network, no real skills required; the fixture provider is a local generator.
|
|
85
|
+
- Four small provider requests per run, in-memory session store (unless `host.store` says otherwise); cheap enough for a conformance suite.
|
|
86
|
+
|
|
87
|
+
## Related APIs
|
|
88
|
+
|
|
89
|
+
- [Provider caching](provider-caching.md)
|
|
90
|
+
- [Input and prompt assembly](input-and-prompt-assembly.md)
|
|
91
|
+
- [Context and skills](context-and-skills.md)
|
|
92
|
+
- [Provider conformance](provider-conformance.md)
|
|
93
|
+
- [Compaction conformance](compaction-conformance.md)
|
package/docs/provider-caching.md
CHANGED
|
@@ -66,11 +66,11 @@ Cache helpers return plain data:
|
|
|
66
66
|
| `canonicalizeJsonSchema(value)` | Clone with sorted object keys and `required` names; semantic arrays stay ordered. Used by first-party tool serializers. |
|
|
67
67
|
| `cacheHitRate(usage)` | Cached input ratio or `undefined`. |
|
|
68
68
|
| `cacheSavings(usage, model)` | Estimated read-token savings or `undefined` without pricing. |
|
|
69
|
-
| `cacheUsageReport(usage, model?)` | Normalized read/write tokens, hit rate, estimated savings, and currency when available; `undefined` when no
|
|
69
|
+
| `cacheUsageReport(usage, model?)` | Normalized reported read/write tokens, hit rate, estimated savings, and currency when available; `undefined` when no cache token field is reported. Missing fields stay absent, never become `0`. |
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
Cache accounting stays in normalized `Usage.cacheReadTokens` and `Usage.cacheWriteTokens`. Terminal `provider_turn_finished.metadata.cache` carries the same numeric report for a reporting provider; unavailable cache usage stays absent.
|
|
72
72
|
|
|
73
|
-
For stable-prefix payloads, `inputLayout: "cache_aware"` is the default on the default input builder, `assembleProviderInput()`, `AgentConfig`, and `RunOptions`; set `inputLayout: "legacy"` to restore the prior order. The default prompt builder's cache-aware order is leading system instructions → resolved context blocks → selected/progressively disclosed
|
|
73
|
+
For stable-prefix payloads, `inputLayout: "cache_aware"` is the default on the default input builder, `assembleProviderInput()`, `AgentConfig`, and `RunOptions`; set `inputLayout: "legacy"` to restore the prior order. The default prompt builder's cache-aware order is leading system instructions → resolved context blocks → selected/progressively disclosed skill catalogs → fallback text tool declarations → attachments/resources → summaries → prior history → pending tool results → current input → optional session tail. `RuntimeAgentSession` uses that tail for URI resources and loaded skill bodies: first insertion fixes `resource:<uri>` / `skill:<name>` order, so a later skill load appends instead of rewriting its catalog slot. Re-deriving the same id keeps its position; changed bytes explicitly invalidate from that tail segment. Declared tool schemas remain in `ProviderRequest.tools` and are never granted by prompt middleware. First-party tool serializers run `canonicalizeJsonSchema` so property insertion order cannot break that prefix. Context-budget eviction, custom builders/middleware, `toolResultFold`, and attention compilation are explicit invalidation boundaries; folding cannot move to an append-only tail without retaining the raw payload it exists to remove. The prefix is byte-stable only when those stable inputs are unchanged; Prism still does not guarantee provider cache hits.
|
|
74
74
|
|
|
75
75
|
## Request/response example
|
|
76
76
|
|
|
@@ -128,7 +128,7 @@ const retention = mapCacheRetention(hints.retention, model);
|
|
|
128
128
|
const stamped = applyCacheControl(messages, hints.breakpoints ?? [], { maxBreakpoints: model.cache?.maxBreakpoints });
|
|
129
129
|
const hitRate = cacheHitRate({ inputTokens: 1000, cacheReadTokens: 800 });
|
|
130
130
|
const report = cacheUsageReport({ inputTokens: 1000, cacheReadTokens: 800 }, model);
|
|
131
|
-
// { cacheReadTokens: 800,
|
|
131
|
+
// { cacheReadTokens: 800, hitRate: 0.8, ... }
|
|
132
132
|
|
|
133
133
|
await session.run("Explain this", { inputLayout: "cache_aware" });
|
|
134
134
|
```
|
|
@@ -223,6 +223,22 @@ Canonical contract: [Thinking and reasoning](thinking-and-reasoning.md).
|
|
|
223
223
|
|
|
224
224
|
Canonical contract: [AI SDK provider adapter](providers/ai-sdk.md).
|
|
225
225
|
|
|
226
|
+
## Stop-reason checklist
|
|
227
|
+
|
|
228
|
+
Every adapter that parses a native completion reason must map it through the shared
|
|
229
|
+
`mapProviderStopReason` table and emit it on the normalized `done` event
|
|
230
|
+
(`providerDone(usage, mapped)`); `provider_turn_finished.metadata.stopReason` then carries it to
|
|
231
|
+
hosts (see [Agent events](agent-events.md#outputs--response--events)). Cover:
|
|
232
|
+
|
|
233
|
+
1. **One mapped native reason per protocol** — a fake stream whose wire reason means truncation
|
|
234
|
+
(`finish_reason: "length"`, `stop_reason: "max_tokens"`, `finishReason: "MAX_TOKENS"`,
|
|
235
|
+
Converse `stopReason: "max_tokens"`) reaches `done.stopReason === "max_output_tokens"`.
|
|
236
|
+
2. **Tool-call turns** — a native tool reason (`tool_calls` / `tool_use` / `tool-calls`) maps to
|
|
237
|
+
`tool_calls`; a generic completion reason on a turn that produced tool calls is normalized to
|
|
238
|
+
`tool_calls` by the session, not the adapter.
|
|
239
|
+
3. **Unknown degrades** — a new or unmapped wire value yields `unknown` and never fails the stream.
|
|
240
|
+
4. **No extra fields** — the adapter adds nothing else to `done`; redaction and bounds are unchanged.
|
|
241
|
+
|
|
226
242
|
## Extension and configuration notes
|
|
227
243
|
|
|
228
244
|
The helpers are a testing subpath only. Provider packages can use them with their own mocked fetch/transport or `createMockProvider()`. Live provider tests should stay opt-in and env-gated outside Prism's default test suite.
|
package/docs/provider-layer.md
CHANGED
|
@@ -127,7 +127,7 @@ const agent = createAgent({ model: { provider: own.id, model: "demo" }, provider
|
|
|
127
127
|
|
|
128
128
|
- Registry `resolve()` returns the matching provider/model or throws before any provider `generate()` call.
|
|
129
129
|
- Provider event helpers return plain `ProviderEvent` objects.
|
|
130
|
-
- `providerError()` converts unknown errors to redacted `ErrorInfo
|
|
130
|
+
- `providerError()` converts unknown errors to redacted `ErrorInfo`, preserves safe string/number `code` fields for retry classification, and stamps advisory `failureClass` (`quota`, `auth`, `rate_limited`, `transient`, `permanent`, or `unknown`) from already-captured status/body evidence. It never changes retry behavior or exposes response bodies/headers; see [Runs and usage ledger](runs-and-usage.md#provider-failure-classes).
|
|
131
131
|
- `createMockProvider()` returns an `AIProvider` whose `generate()` yields the scripted events in order and checks `request.signal?.aborted` before each event.
|
|
132
132
|
- The agent/session runtime passes its per-run abort signal as `ProviderRequest.signal`. `ProviderRequestOptions.structuredOutput` requests provider-native JSON-schema output when the model declares `capabilities.structuredOutput`; unsupported models fail before fetch. Timeouts are host-owned: pass `RunOptions.signal`/host abort controllers; retries are runtime-owned via `AgentConfig.retry`/`RunOptions.retry`. Provider-level timeout/retry hints were removed in 0.1.5.
|
|
133
133
|
|
|
@@ -200,7 +200,7 @@ for await (const event of resolvedProvider.generate({
|
|
|
200
200
|
- `createMockProvider()` uses scripted events only: no timers, credentials, SDKs, or network.
|
|
201
201
|
- Do not hide real secrets in mock event fixtures. If an error event must include secret-like text, use fake placeholders and redaction helpers.
|
|
202
202
|
- `providerError(error, secrets)` only redacts the provided secret values. It is not a general secret scanner.
|
|
203
|
-
- Providers may set safe `ErrorInfo.code` values such as `429`, `503`, or `ETIMEDOUT`; retry policy code treats them as classification hints, not trusted provider metadata.
|
|
203
|
+
- Providers may set safe `ErrorInfo.code` values such as `429`, `503`, or `ETIMEDOUT`; retry policy code treats them as classification hints, not trusted provider metadata. The shared `classifyProviderFailure()` transport helper maps those already-captured values to advisory run outcome metadata; `unknown` is always the fallback.
|
|
204
204
|
|
|
205
205
|
## Related APIs
|
|
206
206
|
|