@arnilo/prism 0.9.0 → 0.11.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 +50 -1
- package/README.md +19 -16
- package/dist/agent-approval.d.ts +7 -1
- package/dist/agent-approval.js +15 -6
- package/dist/agent-run-lifecycle.d.ts +2 -1
- package/dist/agent-run-lifecycle.js +20 -6
- package/dist/agent-run-state.d.ts +26 -5
- package/dist/agent-run-state.js +97 -1
- package/dist/agent-session/event-subscriber.d.ts +2 -0
- package/dist/agent-session/event-subscriber.js +3 -0
- package/dist/agent-session/session/assemble.js +165 -16
- package/dist/agent-session/session/persist.js +11 -5
- package/dist/agent-session/session/provider-round.js +54 -13
- package/dist/agent-session/session/tool-round.d.ts +2 -2
- package/dist/agent-session/session/tool-round.js +86 -23
- package/dist/agent-session/session/types.d.ts +21 -2
- package/dist/agent-session/session.d.ts +66 -4
- package/dist/agent-session/session.js +159 -18
- package/dist/checkpoint-restore.d.ts +50 -14
- package/dist/checkpoint-restore.js +104 -28
- package/dist/context-budget.d.ts +11 -0
- package/dist/context-budget.js +33 -2
- package/dist/contracts-core/agent.d.ts +26 -5
- package/dist/contracts-core/extensions.d.ts +3 -0
- package/dist/contracts-core/guardrail-packs.d.ts +8 -3
- package/dist/contracts-core/loop.d.ts +36 -0
- package/dist/contracts-core/provider.d.ts +6 -1
- package/dist/contracts-core/run-limits.d.ts +10 -1
- package/dist/contracts-core/session.d.ts +2 -1
- package/dist/contracts-protocol.d.ts +6 -4
- package/dist/contracts-run-state.d.ts +48 -6
- package/dist/contributions.d.ts +2 -1
- package/dist/contributions.js +1 -0
- package/dist/extensions.d.ts +15 -1
- package/dist/extensions.js +68 -0
- package/dist/guardrail-packs/types.d.ts +10 -0
- package/dist/guardrail-packs/validation-respect.js +16 -0
- package/dist/guardrails.d.ts +42 -1
- package/dist/guardrails.js +124 -15
- package/dist/index.d.ts +7 -7
- package/dist/index.js +4 -4
- package/dist/leases.js +32 -6
- package/dist/middleware.d.ts +1 -1
- package/dist/node/contribution-discovery.d.ts +16 -1
- package/dist/node/contribution-discovery.js +47 -0
- package/dist/node/session-store-jsonl.js +67 -17
- package/dist/run-bundle.d.ts +6 -1
- package/dist/run-bundle.js +4 -1
- package/dist/run-limits.d.ts +11 -5
- package/dist/run-limits.js +13 -0
- package/dist/session-stores.js +61 -12
- package/dist/testing/prefix-stability-conformance.d.ts +73 -1
- package/dist/testing/prefix-stability-conformance.js +158 -27
- package/dist/tools.js +10 -3
- package/dist/usage-estimation.d.ts +7 -1
- package/dist/usage-estimation.js +16 -10
- package/docs/acp.md +2 -2
- package/docs/agent-events.md +15 -10
- package/docs/agent-session-runtime.md +10 -7
- package/docs/coding-agent-tools.md +1 -1
- package/docs/coding-tools.md +7 -11
- package/docs/compaction-llm.md +2 -0
- package/docs/compaction-observational-memory.md +21 -1
- package/docs/context-and-skills.md +6 -7
- package/docs/contribution-discovery.md +13 -0
- package/docs/durable-runs.md +14 -6
- package/docs/embeddings.md +7 -1
- package/docs/execution-timeline.md +9 -2
- package/docs/extensions.md +21 -5
- package/docs/guardrails.md +16 -6
- package/docs/hooks.md +282 -0
- package/docs/impeccable.md +1 -2
- package/docs/index.md +28 -21
- package/docs/input-and-prompt-assembly.md +1 -1
- package/docs/instruction-injection.md +1 -0
- package/docs/live-testing.md +3 -2
- package/docs/memory-fabric.md +29 -0
- package/docs/middleware-hooks.md +54 -4
- package/docs/migrate-to-0.11.md +65 -0
- package/docs/migration.md +24 -0
- package/docs/node-jsonl-session-store.md +4 -3
- package/docs/operations.md +1 -1
- package/docs/options-index.md +3 -1
- package/docs/peer-dependencies.md +3 -5
- package/docs/policy-and-audit.md +15 -2
- package/docs/prefix-stability-conformance.md +82 -9
- package/docs/provider-packages.md +20 -20
- package/docs/public-contracts.md +2 -1
- package/docs/rag.md +94 -7
- package/docs/release-and-install.md +62 -59
- package/docs/runs-and-usage.md +21 -10
- package/docs/scoped-agent-memory.md +17 -9
- package/docs/scoped-memory.md +138 -0
- package/docs/session-stores.md +2 -2
- package/docs/supervisors.md +14 -6
- package/docs/testing.md +17 -9
- package/docs/tools.md +1 -1
- package/docs/wiki.md +4 -2
- package/docs/workflows.md +2 -2
- package/package.json +8 -5
- package/docs/caveman.md +0 -130
- package/docs/graft.md +0 -149
- package/docs/ponytail.md +0 -129
package/docs/runs-and-usage.md
CHANGED
|
@@ -44,7 +44,7 @@ All methods may be sync or async (`void | Promise<void>`). The runtime awaits th
|
|
|
44
44
|
|
|
45
45
|
## Run limits
|
|
46
46
|
|
|
47
|
-
`RunLimits` bounds one `session.run()` across turns, provider attempts, tool rounds/calls, elapsed wall time, request/response bytes, token usage, and
|
|
47
|
+
`RunLimits` bounds one `session.run()` across turns, provider attempts, tool rounds/calls, elapsed wall time, request/response bytes, token usage, optional cost, and stop-hook continuations (`maxStopContinuations`, default 3). Configure defaults on `AgentConfig.limits`; `RunOptions.limits` can only narrow an agent-configured value.
|
|
48
48
|
|
|
49
49
|
```ts
|
|
50
50
|
await session.run("Summarize", {
|
|
@@ -63,7 +63,7 @@ Defaults are the unconfigured fence (OWASP LLM10): turns 16, provider attempts 2
|
|
|
63
63
|
labeled estimate (or zero with `usageEstimation: "off"`) to the token counters and never a
|
|
64
64
|
price, so a configured `maxCost` stays the fail-closed envelope for usage-less vendors.
|
|
65
65
|
|
|
66
|
-
Prism charges turns before assembly, provider attempts before generation, request bytes per request payload, response bytes per provider event (each frame must fit the byte cap on its own), tool rounds before a batch, tool calls before dispatch, and usage before another turn. A breach stops new work, aborts active work through the run signal, emits exactly one redacted `run_limit_exceeded` event/ledger row, and throws `AgentRunError` with `result.limit` (`limit`, `maximum`, `observed`, optional `currency`). Just before the terminal `error`, the run also emits one `budget_exhausted` attribution event — the axis that fired, run counters at exhaustion, the three closest other axes, and hashes of the last ten dispatched tool calls ([Agent events § Run limit events](agent-events.md#run-limit-events)). Provider-reported token/cost totals arrive after generation, so that completed provider turn can be the unavoidable overshoot boundary.
|
|
66
|
+
Prism charges turns before assembly, provider attempts before generation, request bytes per request payload, response bytes per provider event (each frame must fit the byte cap on its own), tool rounds before a batch, tool calls before dispatch, and usage before another turn. A breach stops new work, aborts active work through the run signal, emits exactly one redacted `run_limit_exceeded` event/ledger row, and throws `AgentRunError` with `result.limit` (`limit`, `maximum`, `observed`, optional `currency`) plus `result.attribution` (`consumed`, `closestOtherAxes`, `recentToolCalls`). Just before the terminal `error`, the run also emits one `budget_exhausted` attribution event carrying that same payload — the axis that fired, run counters at exhaustion, the three closest other axes, and hashes of the last ten dispatched tool calls ([Agent events § Run limit events](agent-events.md#run-limit-events)). Provider-reported token/cost totals arrive after generation, so that completed provider turn can be the unavoidable overshoot boundary.
|
|
67
67
|
|
|
68
68
|
`createRunLimitTracker()` and `resolveRunLimits()` are public for adapters that need the same validation and accounting semantics. Workflow agent nodes forward `RunWorkflowOptions.limits`; supervisor delegation narrows its step/tool/token/timeout budget into core limits; MCP tool calls use a per-call tracker.
|
|
69
69
|
|
|
@@ -78,18 +78,29 @@ const estimate = estimateMessageTokens(messages, "claude-sonnet-4.5"); // model
|
|
|
78
78
|
// { tokens: 41_200, confidence: "medium", lowConfidence: false }
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
`MODEL_FAMILY_TOKENS` holds the chars/token ratio, per-message overhead, and confidence label per family (`anthropic`, `openai`, `google`, `deepseek`, `openrouter-generic`, `mistral`, `unknown`). `resolveModelFamily(modelId)` maps a model id or provider id to a table key; unmatched input resolves to `unknown`, whose row is the most conservative (highest estimated token count) and carries `confidence: "low"` / `lowConfidence: true`. Estimates are heuristics, not tokenizers: prose, fenced code, and CJK content are weighted separately, and every calibrated family is `confidence: "medium"` because Prism ships no real tokenizer. The estimator is pure — no network, no I/O, and no content retention.
|
|
81
|
+
`MODEL_FAMILY_TOKENS` holds the chars/token ratio, per-message overhead, and confidence label per family (`anthropic`, `openai`, `google`, `deepseek`, `openrouter-generic`, `mistral`, `unknown`). `resolveModelFamily(modelId)` maps a model id or provider id to a table key; unmatched input resolves to `unknown`, whose row is the most conservative (highest estimated token count) and carries `confidence: "low"` / `lowConfidence: true`. Estimates are heuristics, not tokenizers: prose, fenced code, and CJK content are weighted separately, and every calibrated family is `confidence: "medium"` because Prism ships no real tokenizer. The estimator is pure — no network, no I/O, and no content retention. `MODEL_FAMILY_TOKENS` is frozen at runtime (every row and the table), so ratios cannot be overridden in place — recalibration stays a source change plus the live leg below, never a runtime override.
|
|
82
|
+
|
|
83
|
+
Row provenance and recalibration: reference counts per family are frozen in `src/__tests__/fixtures/usage-calibration.json` — `openai` measured against `o200k_base` (dev-time oracle; no tokenizer ships), `anthropic`/`google`/`mistral` as their published chars/token guidance, and `deepseek`/`openrouter-generic` as row-basis values (no public count endpoint). `src/__tests__/usage-calibration.test.ts` fails when a shipped row drifts outside the recorded bands (prose ±12%, CJK ±20%, per-message overhead ±1 token). To re-measure, run `PRISM_LIVE_PROVIDER_TESTS=1` with `ANTHROPIC_API_KEY` and/or `GEMINI_API_KEY`/`GOOGLE_API_KEY` and `node --test scripts/usage-calibration-live.test.mjs` (matrix suite `calibration/vendor-count-tokens`): it posts the fixed corpus to `POST /v1/messages/count_tokens` and `POST /v1beta/models/{model}:countTokens`, asserts each shipped row against the measured count, and refreshes `docs/_evidence/phase103-family-token-calibration.md`. A measurement outside its band means updating the row in `src/usage-estimation.ts` and the fixture counts in one change.
|
|
82
84
|
|
|
83
85
|
### Automatic fallback (`AgentConfig.usageEstimation`)
|
|
84
86
|
|
|
85
|
-
`usageEstimation` is `"fallback"` (default) or `"
|
|
87
|
+
`usageEstimation` is `"fallback"` (default), `"off"`, or `"strict"`. With the default, a provider turn that reports no usage records one labeled estimate at the existing usage seam — no adapter changes:
|
|
86
88
|
|
|
87
|
-
- the `provider_turn_finished.usage` carries `{ inputTokens, estimated: true, confidence }`, and its `budgets.inputTokens`/`runInputUsed` use that estimate, so the attention axes and run limits from plans 086/087 work on non-reporting models;
|
|
89
|
+
- the `provider_turn_finished.usage` carries `{ inputTokens, estimated: true, confidence }`, and its `budgets.inputTokens`/`runInputUsed` use that estimate, with `budgets.inputTokensSource` labeling the figure's provenance (below), so the attention axes and run limits from plans 086/087 work on non-reporting models;
|
|
88
90
|
- ledger `appendUsage` rows (`scope: "provider_turn"` and the `run_total` aggregate) and `AgentRunResult.usage` keep `estimated: true` (plus `confidence`) — a billing surface can always tell an estimate from a report;
|
|
89
91
|
- estimates are **never priced**: the cost catalog is not consulted, and estimated usage carries no `cost`/`currency`, so a `maxCost` limit still fails closed instead of blocking on invented numbers;
|
|
90
92
|
- `"off"` leaves absent usage absent — no ledger row, no run total, never a zero.
|
|
93
|
+
- `"strict"` (plan 103 T5) records nothing and refuses the turn instead. The run ends with `AgentRunResult.error` `{ name: "UsageMissingError", code: "usage_missing" }`, the terminal `error` event carries the same info, exactly one provider attempt is made — the refusal is an observable failure, so no retry policy retries it. No ledger usage row, no `run_total` aggregate, and no cost-catalog lookup happen: `usage` stays absent, never zero. The refusal is a harness decision, not a provider failure: no `failureClass` is stamped, and `metadata.stopReason: "provider_error"` on that turn is the shared error-path stop, not a claim about the provider. Because the refusal is decided before the existing fail-closed `maxCost` breach, a cost-limited host gets `usage_missing` instead of a confusing `budget_exhausted` attribution. A provider that *fails* a turn without reporting usage is not refused — its own error (and the retry policy) still applies, so strict never masks a provider failure.
|
|
94
|
+
|
|
95
|
+
The estimate covers the turn's own request — messages plus tool declarations and context blocks — and prefers the most exact measurement that already exists (plan 103 T6), in this order:
|
|
96
|
+
|
|
97
|
+
1. **The budget pass's own measurement.** When `AgentConfig.contextBudget` sets `reportOmissions: true`, the request carries a `ContextBudgetReport`, and the fallback reuses its `keptTokens` verbatim — the same whole-request figure (post-eviction messages, context, skills, tool declarations) that decided evictions, so usage accounting and budget decisions can never show two different numbers for the same request. It is measured at budget time, so content added afterwards (tail segments, middleware edits) is not included — a measured 19.2–19.4% shortfall with an ≈8k-character post-budget append and 0.0% without one (`docs/_evidence/phase112-primitive-review.md` §4.4) — and `confidence` names the basis: `"high"` when a host `tokenEstimator` made the measurement, `"low"` for the built-in ÷4 basis (uncalibrated).
|
|
98
|
+
2. **The host tokenizer.** With `contextBudget.tokenEstimator` and no report, the fallback projects the request through that tokenizer — per message plus tool/context portions — using the assembler's own text shapes (the `measureAll` tool-list line and context-block text, never `JSON.stringify` of the schemas), validated exactly like the budget pass validates it. `confidence: "high"`: a host tokenizer's count is still an estimate (`estimated: true`), never `"reported"`.
|
|
99
|
+
3. **The family heuristic** (plan 091): the model id's family table for messages (per-message overhead included), and those same assembler text shapes for the tool/context portions.
|
|
100
|
+
|
|
101
|
+
All three paths keep `estimated: true` and are never priced. `usageEstimation: "off"` and `"strict"` never consult the report or the tokenizer — their behavior is decided before any measurement.
|
|
91
102
|
|
|
92
|
-
|
|
103
|
+
`budgets.inputTokensSource` labels the figure's provenance: `"reported"` when the provider reported that turn's own usage, `"estimated"` for any of the three fallback paths above, and absent together with `inputTokens`. The payload shape is in [Agent events § Run limit events](agent-events.md#run-limit-events).
|
|
93
104
|
|
|
94
105
|
### `session.contextMeter()`
|
|
95
106
|
|
|
@@ -100,13 +111,13 @@ const meter = session.contextMeter();
|
|
|
100
111
|
// { inputTokens: 43_000, source: "estimated", inputCap: 200_000, runInputBudget: 500_000, usedRatio: 0.215 }
|
|
101
112
|
```
|
|
102
113
|
|
|
103
|
-
`inputTokens` is the latest provider turn's input tokens — `source: "reported"` when the provider reported them, `"estimated"` when they are the labeled fallback (or, before any provider turn in the session, an estimate of stored history, so a fresh non-reporting model still shows a working meter). `inputCap` is resolved exactly like `provider_turn_finished.budgets.inputCap` (model window minus output reserve minus `attentionCompiler.reserveTokens`), `runInputBudget` is `RunLimits.maxInputTokens` while a run is active, and `usedRatio` is `inputTokens / inputCap`. Cap/budget/ratio are omitted when the model or run cannot derive them. The meter is never billing and never rewrites reported usage; `compact()` drops the pre-compaction reading so the next read re-estimates.
|
|
114
|
+
`inputTokens` is the latest provider turn's input tokens — `source: "reported"` when the provider reported them, `"estimated"` when they are the labeled fallback (or, before any provider turn in the session, an estimate of stored history, so a fresh non-reporting model still shows a working meter). `inputCap` is resolved exactly like `provider_turn_finished.budgets.inputCap` (model window minus output reserve minus `attentionCompiler.reserveTokens`), `runInputBudget` is `RunLimits.maxInputTokens` while a run is active, and `usedRatio` is `inputTokens / inputCap`. Cap/budget/ratio are omitted when the model or run cannot derive them. The meter is never billing and never rewrites reported usage; `compact()` drops the pre-compaction reading so the next read re-estimates. Reads are cached — the same frozen object is returned until the history changes (an appended entry, a steer push, a compaction), the branch leaf moves, or the active run's identity changes, so polling the meter per frame costs one estimate per mutation instead of one per read (measured: 1,000 reads over a 200k-character history in 0.11 ms, against ≈0.47 ms for one uncached estimate).
|
|
104
115
|
|
|
105
116
|
## Clean stops and stop reasons
|
|
106
117
|
|
|
107
|
-
A run can end without an error but also without the model finishing its thought: a host `RunOptions.turnPolicy.stop`, a `turnPolicy.maxTurns` cap,
|
|
118
|
+
A run can end without an error but also without the model finishing its thought: a host `RunOptions.turnPolicy.stop`, a `turnPolicy.maxTurns` cap, a loop ceiling, or the stop-hook continuation cap. `AgentRunResult.stopReason` names that outcome — `"host_policy"` for a host policy stop, `"hook_limit"` when `limits.maxStopContinuations` refused a continuation, `"turn_limit"`, `"token_limit"`, or `"refusal"` for loop ceilings — with `turnPolicy.stop`'s own string in `stopDetail`. A natural end carries neither field, so hosts that only care about "did it stop early?" check truthiness. The same values ride the emitted `agent_finished` event (as `finishReason`/`stopDetail`), the finish `RunRecord`, and the projected [Execution Timeline](execution-timeline.md).
|
|
108
119
|
|
|
109
|
-
A `host_policy` stop is terminal for the run yet resumable: with `runState: { checkpointPolicy: "every-turn" }` the stopped state keeps its frontier, and `resumeAgentRun(..., { decision: "continue" })` picks the loop up at the boundary. Every other terminal state is final. See [Agent loops § Turn policy](agent-loops.md#turn-policy).
|
|
120
|
+
A `host_policy` stop is terminal for the run yet resumable: with `runState: { checkpointPolicy: "every-turn" }` the stopped state keeps its frontier, and `resumeAgentRun(..., { decision: "continue" })` picks the loop up at the boundary. A `hook_limit` stop is resumable the same way. Every other terminal state is final. See [Agent loops § Turn policy](agent-loops.md#turn-policy) and [Hooks](hooks.md).
|
|
110
121
|
|
|
111
122
|
## Provider failure classes
|
|
112
123
|
|
|
@@ -135,7 +146,7 @@ The adapter receives these record shapes:
|
|
|
135
146
|
| `status` | `queued` \| `running` \| `suspended` \| `denied` \| `succeeded` \| `failed` \| `aborted`. |
|
|
136
147
|
| `startedAt` / `finishedAt` | ISO timestamps. |
|
|
137
148
|
| `abortReason` | Set when status is `aborted`. |
|
|
138
|
-
| `stopReason` | Why the loop stopped cleanly instead of reaching a natural end: `host_policy` (`RunOptions.turnPolicy.stop`), `turn_limit`, `token_limit`, or `refusal`. Absent on a natural end. |
|
|
149
|
+
| `stopReason` | Why the loop stopped cleanly instead of reaching a natural end: `host_policy` (`RunOptions.turnPolicy.stop`), `hook_limit` (stop-hook continuation cap), `turn_limit`, `token_limit`, or `refusal`. Absent on a natural end. |
|
|
139
150
|
| `stopDetail` | Host stop detail from `turnPolicy.stop` (≤256 bytes, redacted). |
|
|
140
151
|
| `error` | `ErrorInfo` when status is `failed`. |
|
|
141
152
|
| `tenantId` / `accountId` / `userId` | From active ownership scope. |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Scoped persistent agent memory — design concept
|
|
2
2
|
|
|
3
|
-
Status: **concept
|
|
3
|
+
Status: **concept**. The implemented contract is [Scoped memory](scoped-memory.md) (`@arnilo/prism-memory/scoped`). This page is the design rationale for workspace-scoped persistent agent memory — durable facts and procedures that are recorded, updated, and used automatically during agentic work. Terminology deliberately aligns with the existing Prism memory surfaces ([memory fabric](memory-fabric.md), [observational memory](compaction-observational-memory.md), [working and semantic memory](working-and-semantic-memory.md)) — see [Relationship to existing Prism memory surfaces](#relationship-to-existing-prism-memory-surfaces).
|
|
4
4
|
|
|
5
5
|
## Problem and goals
|
|
6
6
|
|
|
@@ -98,7 +98,7 @@ links: [rec_deploy_rollback, rec_pg_pool]
|
|
|
98
98
|
|
|
99
99
|
Rationale: human override and review are non-negotiable for professional work — diffs, blame, and PR review come free. This mirrors the Hermes `journey edit/delete` lesson (users *must* be able to prune) and generalizes it to full version control.
|
|
100
100
|
|
|
101
|
-
**Prism realization.** In the composed Prism stack (next section) the [memory fabric](memory-fabric.md) is the source of truth for records and the session store/observational ledger owns episodes; `<workspace>/.memory/`
|
|
101
|
+
**Prism realization.** In the composed Prism stack (next section) the [memory fabric](memory-fabric.md) is the source of truth for records and the session store/observational ledger owns episodes; usage/status/staging live in a gitignored JSON ledger (`<workspace>/.memory/state.json`); `<workspace>/.memory/` markdown is a **git audit mirror** — a rendered export of fabric notes + ledger counters for diff/review — not a second storage engine. One write path, two views.
|
|
102
102
|
|
|
103
103
|
### Write path — reflect, adjudicate, gate
|
|
104
104
|
|
|
@@ -132,7 +132,8 @@ An idle/nightly consolidation pass (cheap model, off the interaction path):
|
|
|
132
132
|
|
|
133
133
|
### Trust boundary
|
|
134
134
|
|
|
135
|
-
- **
|
|
135
|
+
- **Secret redaction** of record content via the shipped needle redactor (`createSecretRedactor` / observational `secrets` / working-memory `redactJson`).
|
|
136
|
+
- **Injection/exfiltration scanning** of record content before any prompt injection (patterns, invisible Unicode) — Hermes does this for `MEMORY.md`; extend to all records. This is a new scoped primitive (`scanScopedMemoryContent`); it is not the secret redactor.
|
|
136
137
|
- **Staged approval.** Writes may be staged for human review (`write_approval`-style). Default: off for personal scopes, on for team/professional scopes.
|
|
137
138
|
- **Scope isolation.** Records never leak across workspace roots; the global user layer is opt-in per record.
|
|
138
139
|
- **Provenance on every record.** Any memory-driven decision can be traced to the session and turn that produced the record (same philosophy as observational memory's source-backed ids and the recall path).
|
|
@@ -171,15 +172,18 @@ The placement principle: **the scoped layer is a policy and lifecycle layer, not
|
|
|
171
172
|
| Episodic ledger | [Observational memory](compaction-observational-memory.md) | source-backed observations/reflections (12-hex ids, `sourceEntryIds`), exact-id recall and branch pages, optional work-scope index; observer/reflector/dropper workers are the only writers | no semantic retrieval, no wholesale prompt injection, no downstream re-observation |
|
|
172
173
|
| Memory engines | [Working and semantic memory](working-and-semantic-memory.md) | `Embedder`/vector/working-store contracts, consent lifecycle, lineage invalidation, importance, recall scoring | no policy |
|
|
173
174
|
| Durable records | [Memory fabric](memory-fabric.md) | typed notes (`fact`/`procedure`/`file`/`working`/`episode`), validity windows, consolidation folding, linker/evolution workers, five governed tools, file jail, context provider, `forget`/legal hold | no autonomy — every write is an explicit caller decision |
|
|
174
|
-
| Policy + lifecycle |
|
|
175
|
+
| Policy + lifecycle | [Scoped memory](scoped-memory.md) (`@arnilo/prism-memory/scoped`) | conservative post-run writer, promotion ladder, usage-decay GC, abstain floor + activation budget, workspace-root scope identity, git audit mirror, usage/status/staging JSON ledger | no store, no engine, no context-block type, no second write path |
|
|
176
|
+
| Knowledge compiler | [LLM wiki](wiki.md) (`@arnilo/prism-memory/wiki`) | regenerable `.wiki/` pages with line-anchored citations over raw sources; `wiki_ingest` / `wiki_record_insight` | session-derived experience (that is scoped memory); it is not a memory store |
|
|
177
|
+
|
|
178
|
+
**Wiki boundary / routing.** Wiki compiles *source-cited knowledge* (files, docs, papers — regenerable, `file://…#Lxx-Lyy`). Scoped memory holds *session-derived experience* (primary fabric records, provenance `sourceEntryIds`). No storage overlap: wiki writes `.wiki/` + `raw/ingest/`; scoped writes fabric notes + `<scopeRoot>/.memory/state.json` + the git mirror. Route source-cited material to wiki; route session-derived experience to scoped policy. The post-run reviewer must not file a wiki-pageable insight as a scoped fact.
|
|
175
179
|
|
|
176
180
|
Two invariants carry over unchanged: observational memory stays **episodic** (promotion out of the ledger is an explicit host write — fabric's `promotedFrom` over a closed work scope), and the fabric never widens consent or visibility.
|
|
177
181
|
|
|
178
182
|
### Ideal composition for a persistent-memory agent
|
|
179
183
|
|
|
180
184
|
```ts
|
|
181
|
-
// 1. Engines — workspace root
|
|
182
|
-
const memory = createMemory({ tenantId: host, resourceId: workspaceRoot, embedder, stores });
|
|
185
|
+
// 1. Engines — workspace root is resourceId; threadId is a stable silo id (not the session id)
|
|
186
|
+
const memory = createMemory({ tenantId: host, resourceId: workspaceRoot, threadId: "scoped", embedder, stores });
|
|
183
187
|
// 2. Durable records — folding, links, evolution on by policy
|
|
184
188
|
const fabric = createMemoryFabric({ memory, observational, consolidate: { threshold: 0.85 },
|
|
185
189
|
linker: { enabled: true }, evolution: { enabled: true } });
|
|
@@ -188,7 +192,8 @@ om.attach(session);
|
|
|
188
192
|
// 4. Gate fabric tools + workers to this session
|
|
189
193
|
fabric.attach(session);
|
|
190
194
|
// 5. Injection: ONLY the bounded working facts block reaches the prompt
|
|
191
|
-
registries.contextProviders.register("memory-fabric",
|
|
195
|
+
registries.contextProviders.register("memory-fabric",
|
|
196
|
+
fabric.createContextProvider({ includeWorking: true, includeSemantic: false }));
|
|
192
197
|
const agent = await resolveAgentDefinition(
|
|
193
198
|
{ name: "assistant", model, context: ["memory-fabric"], tools: ["memory.recall"] },
|
|
194
199
|
{ registries, providerSource });
|
|
@@ -212,7 +217,7 @@ End-to-end flow:
|
|
|
212
217
|
| Adjudicate ADD/UPDATE/supersede | fabric consolidation folding (cosine threshold 0.85) |
|
|
213
218
|
| Close contradicted facts | fabric `validTo` + `supersedes` |
|
|
214
219
|
| Link + evolve neighbors | fabric linker/evolution workers |
|
|
215
|
-
| Status `candidate`, staging/approval | scoped
|
|
220
|
+
| Status `candidate`, usage counters, staging/approval | scoped JSON ledger (`<scopeRoot>/.memory/state.json`) — not fabric note metadata |
|
|
216
221
|
|
|
217
222
|
| Read-path step | Owner |
|
|
218
223
|
| --- | --- |
|
|
@@ -240,6 +245,8 @@ How the research findings land on shipped surfaces versus policy added by this c
|
|
|
240
245
|
| Off-path consolidation | Letta sleep-time | — (idle-job placement) | GC/promotion/facts consolidation on a cheap model |
|
|
241
246
|
| Tiny always-loaded layer, disclosure on demand | Anthropic skills | recall tools + working block only | abstain floor + top-3 activation budget |
|
|
242
247
|
| Hybrid BM25 + cosine + graph walk | Zep/Graphiti | partial: embedding score + link traversal; lexical on branch search | fusing lexical into one recall — open question |
|
|
248
|
+
| Known-secret redaction | runtime / OM | `createSecretRedactor`, observational `secrets`, working `redactJson` | — |
|
|
249
|
+
| Injection / exfil / invisible Unicode scan | Hermes MEMORY.md | — (redactor is needle-only) | `scanScopedMemoryContent` (pure patterns) |
|
|
243
250
|
|
|
244
251
|
### Deliberate deviations
|
|
245
252
|
|
|
@@ -258,5 +265,6 @@ How the research findings land on shipped surfaces versus policy added by this c
|
|
|
258
265
|
|
|
259
266
|
- Exact promotion thresholds (N reuses, decay τ, similarity floors) — must be empirically tuned per workload class (coding vs. research vs. professional ops).
|
|
260
267
|
- Whether the global user-profile layer reuses the working-memory store or a separate facts silo.
|
|
261
|
-
- Evaluation harness: reuse the existing evaluations/trajectory tooling vs. a purpose-built memory replay suite.
|
|
262
268
|
- Team-scope semantics: per-user silos sharing one workspace root, or one shared silo with author-attributed records.
|
|
269
|
+
|
|
270
|
+
Closed at primitive review: evaluation harness reuses `@arnilo/prism-core/governance/evals` scorer/dataset contracts and adds only scoped fixtures + `runScopedMemoryEval`.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Scoped memory (`@arnilo/prism-memory/scoped`)
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
`createScopedMemoryPolicy()` is a **policy and lifecycle layer** over stores a host already configured with `createMemory()` and `createMemoryFabric()`. It does not add a fifth store. It owns workspace-root identity, a conservative post-run writer, a candidate→verified promotion ladder, usage-decay GC proposals, an abstain floor plus activation budget on recall, a bounded working facts block, a git audit mirror, and a JSON usage/staging ledger at `<scopeRoot>/.memory/state.json`.
|
|
6
|
+
|
|
7
|
+
**Routing.** Source-cited knowledge belongs in [`@arnilo/prism-memory/wiki`](wiki.md) (regenerable, line-anchored). Session-derived experience belongs in scoped memory (primary fabric records with `sourceEntryIds` provenance). The post-run reviewer must not duplicate a wiki-pageable insight as a scoped fact.
|
|
8
|
+
|
|
9
|
+
## When to use it
|
|
10
|
+
|
|
11
|
+
Use it when a host wants durable facts and procedures to accumulate across sessions for one workspace without injecting the whole library into the prompt. Leave it off when an eval A/B shows no win-rate lift. Do not use it as a wiki, an observational-memory replacement, or a second vector store.
|
|
12
|
+
|
|
13
|
+
## Inputs / request
|
|
14
|
+
|
|
15
|
+
### `createScopedMemoryPolicy(options)`
|
|
16
|
+
|
|
17
|
+
| Field | Type | Required | Default | Description |
|
|
18
|
+
| :--- | :--- | :--- | :--- | :--- |
|
|
19
|
+
| `memory` | `Memory` | yes | — | `createMemory()` instance. `scope.resourceId` must equal `resolve(scopeRoot)`; `scope.threadId` is required (stable workspace silo, not a session id). |
|
|
20
|
+
| `fabric` | `MemoryFabric` | yes | — | `createMemoryFabric()` over that memory. |
|
|
21
|
+
| `scopeRoot` | `string` | yes | — | Workspace root. Create is inert: no attach, no files. |
|
|
22
|
+
| `policy` | `ScopedMemoryPolicyKnobs` | no | see knobs | Tuning. Unknown fields ignored; invalid values fail closed. |
|
|
23
|
+
|
|
24
|
+
### Knobs (`policy`)
|
|
25
|
+
|
|
26
|
+
| Knob | Default | Role |
|
|
27
|
+
| :--- | :--- | :--- |
|
|
28
|
+
| `promotion.reuseThreshold` | `2` | Flip ledger `candidate` → `verified` after this many successful recall uses. No fabric rewrite. |
|
|
29
|
+
| `decay.tauDays` | `30` | Time constant for `score = fabricScore × exp(−ageDays/tauDays) × (1 + ln(1 + uses))`. |
|
|
30
|
+
| `decay.candidateArchiveDays` | `30` | Unused candidates this old become GC archive proposals. |
|
|
31
|
+
| `activation.topK` | `3` | Recall budget after the floor. |
|
|
32
|
+
| `activation.minSimilarity` | `0.35` | Abstain floor (`hit.similarity ?? hit.score`). |
|
|
33
|
+
| `facts.block` | `"facts"` | Working-block label for `rememberFact`. |
|
|
34
|
+
| `facts.maxChars` | `2200` | Overflow throws `MemoryLimitError` (`consolidate first`). |
|
|
35
|
+
| `approval.default` | `"off"` | `"off"` writes reviewer proposals immediately; `"staged"` queues them on `pending()`. |
|
|
36
|
+
|
|
37
|
+
### Methods
|
|
38
|
+
|
|
39
|
+
| Method | Input | Notes |
|
|
40
|
+
| :--- | :--- | :--- |
|
|
41
|
+
| `recall(query, opts?)` | query string | Wraps `fabric.recall` with oversample, floor, topK, usage increment. |
|
|
42
|
+
| `reviewSession(digest, { reviewer, prompt? })` | string or entry array | One fake/real reviewer call. Strict JSON: `{kind, content, sourceEntryIds}` only; `kind` is `fact` or `procedure`; `sourceEntryIds` non-empty. Garbage → zero writes, no throw. |
|
|
43
|
+
| `promotionPass()` | — | Ledger status only. |
|
|
44
|
+
| `gcPass()` | — | Proposes archives onto `pending()`. Never deletes. Skips `legal_hold`. |
|
|
45
|
+
| `health()` | — | Counts + conversion/activation/duplication rates. |
|
|
46
|
+
| `rememberFact(text)` | non-empty string | Appends the facts block after `scanScopedMemoryContent`. |
|
|
47
|
+
| `pending()` | — | Reviewer stages and GC archives. |
|
|
48
|
+
| `approve(id)` / `reject(id)` | pending id | Approve writes/forgets; reject drops the proposal (archive restore uses `prevStatus`). |
|
|
49
|
+
| `renderMirror()` | — | Deterministic markdown under `<scopeRoot>/.memory/`; gitignores `state.json`. |
|
|
50
|
+
|
|
51
|
+
### Eval and scan
|
|
52
|
+
|
|
53
|
+
- `runScopedMemoryEval({ fixtures, fakeProvider? })` — fixture-only. Rejects `memory` / `policy` / `fabric` / `vectorStore`. Default fake answers from recall context or `"unknown"`. Reports A/B `winRate`, Precision@3 (mean `\|relevant ∩ top3\| / 3`, alert below floor 0.5), LoCoMo (`failedClosed` when `expectedId` was never seeded), and `health()`.
|
|
54
|
+
- `createScopedMemoryHealthCommand({ policy })` — `scoped-memory:health`.
|
|
55
|
+
- `scanScopedMemoryContent(text)` — `{ ok: true }` or `{ ok: false, class: "prompt-injection" \| "exfil" \| "invisible-unicode" }`. Writes fail closed on a match.
|
|
56
|
+
- `scoreScopedHit(score, uses, ageDays, tauDays)` — the read-policy formula.
|
|
57
|
+
|
|
58
|
+
## Outputs / response / events
|
|
59
|
+
|
|
60
|
+
Create returns a frozen `ScopedMemoryPolicy` (`scopeRoot`, `settings`, methods). No events; fabric/memory events are unchanged.
|
|
61
|
+
|
|
62
|
+
`recall` → `{ hits, abstained, explain }`. Empty hits + `abstained: true` when nothing clears the floor.
|
|
63
|
+
|
|
64
|
+
`reviewSession` → `{ proposed, written, staged, status: { candidate } }`.
|
|
65
|
+
|
|
66
|
+
`promotionPass` → `{ promoted }`. `gcPass` → `{ proposed, archived }` (`archived` stays 0 until the host `approve`s).
|
|
67
|
+
|
|
68
|
+
`health` → `{ notes: { candidate, verified, archived }, conversionRate, activationRate, duplicationRate }`.
|
|
69
|
+
|
|
70
|
+
`rememberFact` → `void` or `MemoryLimitError` / `MemoryValidationError`. `renderMirror` writes `notes/<id>.md`, `facts.md`, `.gitignore`.
|
|
71
|
+
|
|
72
|
+
## Request/response example
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"scopeRoot": "/tmp/workspace",
|
|
77
|
+
"policy": {
|
|
78
|
+
"promotion": { "reuseThreshold": 2 },
|
|
79
|
+
"decay": { "tauDays": 30, "candidateArchiveDays": 30 },
|
|
80
|
+
"activation": { "topK": 3, "minSimilarity": 0.35 },
|
|
81
|
+
"facts": { "block": "facts", "maxChars": 2200 },
|
|
82
|
+
"approval": { "default": "off" }
|
|
83
|
+
},
|
|
84
|
+
"recall": {
|
|
85
|
+
"hits": [{ "id": "aaaaaaaaaaaa", "kind": "fact", "content": "SSH listens on 2222", "score": 0.91 }],
|
|
86
|
+
"abstained": false
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Implementation example
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
import { createHashEmbedder, createMemory } from "@arnilo/prism-memory";
|
|
95
|
+
import { createMemoryFabric } from "@arnilo/prism-memory/fabric";
|
|
96
|
+
import { createScopedMemoryPolicy } from "@arnilo/prism-memory/scoped";
|
|
97
|
+
|
|
98
|
+
const memory = createMemory({
|
|
99
|
+
tenantId: "host",
|
|
100
|
+
resourceId: workspaceRoot,
|
|
101
|
+
threadId: "scoped",
|
|
102
|
+
embedder: createHashEmbedder({ dimensions: 8 }),
|
|
103
|
+
});
|
|
104
|
+
const fabric = createMemoryFabric({ memory, consolidate: false });
|
|
105
|
+
const policy = createScopedMemoryPolicy({ memory, fabric, scopeRoot: workspaceRoot });
|
|
106
|
+
|
|
107
|
+
await policy.rememberFact("SSH jump host listens on 2222");
|
|
108
|
+
await policy.reviewSession(digest, { reviewer });
|
|
109
|
+
const { hits, abstained } = await policy.recall("SSH 2222");
|
|
110
|
+
await policy.promotionPass();
|
|
111
|
+
await policy.gcPass();
|
|
112
|
+
await policy.renderMirror();
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Runnable walk (scope guard → overflow → review → recall → promotion → GC approve → mirror): `examples/scoped-memory.ts`.
|
|
116
|
+
|
|
117
|
+
Inject only the facts block: `fabric.createContextProvider({ includeWorking: true, includeSemantic: false })`.
|
|
118
|
+
|
|
119
|
+
## Extension and configuration notes
|
|
120
|
+
|
|
121
|
+
The policy is off until the host constructs it. `reviewer` is host-supplied (LLM or fake). `approval.default: "staged"` makes reviewer writes host-gated. `createScopedMemoryHealthCommand` follows the observational `om:status` command factory. Eval fixtures live next to the runner; import eval APIs from `@arnilo/prism-memory/scoped` (no `./scoped/eval` subpath).
|
|
122
|
+
|
|
123
|
+
## Security and performance notes
|
|
124
|
+
|
|
125
|
+
- **Scope identity:** create throws if `resourceId !== resolve(scopeRoot)` or `threadId` is missing. Observational session mismatch stays `fabric.attach`.
|
|
126
|
+
- **Writes fail closed:** `scanScopedMemoryContent` then the memory redactor. Matches name a class; payloads are not logged.
|
|
127
|
+
- **GC never silently deletes.** `legal_hold` notes are not proposed. Host `approve` calls `fabric.forget`.
|
|
128
|
+
- **Sizing:** one reviewer call per run; ledger I/O per recall; off by default. Activation is top-3 after the floor, not the whole library.
|
|
129
|
+
- **Mirror** skips scan failures. `state.json` is gitignored; markdown is the audit copy, not a second engine.
|
|
130
|
+
|
|
131
|
+
## Related APIs
|
|
132
|
+
|
|
133
|
+
- [Memory fabric](memory-fabric.md): typed notes this policy writes and recalls through.
|
|
134
|
+
- [Working and semantic memory](working-and-semantic-memory.md): `createMemory`, consent, redaction, `exportMemory`.
|
|
135
|
+
- [Observational memory](compaction-observational-memory.md): episodic ledger; scoped facts keep `sourceEntryIds`.
|
|
136
|
+
- [LLM wiki](wiki.md): source-cited knowledge compiler — not session-derived experience.
|
|
137
|
+
- [Scoped agent memory design concept](scoped-agent-memory.md): rationale, Hermes case study, research basis.
|
|
138
|
+
- [Evaluations](evaluations.md): trajectory/outcome scorers; scoped eval is fixture-only on this subpath.
|
package/docs/session-stores.md
CHANGED
|
@@ -108,7 +108,7 @@ Recognize it with `isSessionAppendConflict(error)`, not message text. Built-in s
|
|
|
108
108
|
- Store adapters own id generation policy, ordering, duplicate detection, idempotency storage, and error handling.
|
|
109
109
|
- `AgentSession` uses `AgentSessionConfig.store` before `AgentConfig.store`; otherwise it falls back to a private memory store.
|
|
110
110
|
- Branch semantics are parent links plus a leaf id. External UIs should keep branch handles as `(sessionId, leafId)`; RPC exposes an additional `handleId` for active handles.
|
|
111
|
-
-
|
|
111
|
+
- The memory store implements `readBranchPath` (ancestor chain root→leaf, numeric offset cursor, one clone on the way out). The JSONL store still omits it; the runtime falls back to `list(sessionId)`. Database-backed stores should implement `readBranchPath` so `entries()`, `clone()`, and context rebuild read only the selected ancestor chain.
|
|
112
112
|
|
|
113
113
|
## Session search
|
|
114
114
|
|
|
@@ -152,7 +152,7 @@ Sizing (plan 095): SQLite FTS5 and the Postgres `tsvector` column are maintained
|
|
|
152
152
|
|
|
153
153
|
- Do not store provider credentials, credential resolvers, provider instances, or unredacted secrets in session entries, append options, idempotency keys, or branch records.
|
|
154
154
|
- Use `AgentConfig.redactor` or `RunOptions.redactor` to redact secrets before entries reach durable stores. Stores receive already-redacted `SessionEntry` values.
|
|
155
|
-
- `createMemorySessionStore()` keeps O(1) duplicate/idempotency/parent checks in process-local maps; it is not durable.
|
|
155
|
+
- `createMemorySessionStore()` keeps O(1) duplicate/idempotency/parent checks in process-local maps; it is not durable. Idempotency dedup remembers the latest 4,096 keys; an older replay appends as a new entry.
|
|
156
156
|
- The JSONL adapter serializes appends per store instance, has no cross-process lock, and is not suitable for production multi-writer storage.
|
|
157
157
|
- Database-backed stores should follow the indexes and retention guidance in [Database persistence](database-persistence.md). Implement `readBranchPath` as a single branch-path query (for example a recursive CTE) and avoid loading entire large sessions into memory when only one branch is needed.
|
|
158
158
|
|
package/docs/supervisors.md
CHANGED
|
@@ -28,7 +28,7 @@ Use a supervisor when a host or agent must choose a child dynamically. Use `@arn
|
|
|
28
28
|
|
|
29
29
|
## Outputs / response / events
|
|
30
30
|
|
|
31
|
-
`delegate()` returns the child's `AgentRunResult` or throws its `AgentRunError`/a supervisor denial or limit error. A failure (an error or a run-limit death) publishes `child_failed` before the terminal `delegation_error`: the redacted `reason`, the terminal `status` and `stopReason`, the plan-086/087 `RunLimitBreach` (`limit`, `maximum`, `observed`) in `limit` when a configured ceiling fired,
|
|
31
|
+
`delegate()` returns the child's `AgentRunResult` or throws its `AgentRunError`/a supervisor denial or limit error. A child that died on its own run limit throws `SupervisorLimitError` with that result in `result` — the same breach and `attribution` the `child_failed` event carries, so a host that catches it needs no event join; a supervisor-level ceiling (depth, active children, delegation or wait timeout, cycle, input bytes) has no child run behind it and carries none. A failure (an error or a run-limit death) publishes `child_failed` before the terminal `delegation_error`: the redacted `reason`, the terminal `status` and `stopReason`, the plan-086/087 `RunLimitBreach` (`limit`, `maximum`, `observed`) in `limit` when a configured ceiling fired, terminal `usage`, and — on that ceiling death — the child's own plan-087 attribution (`consumed`, `closestOtherAxes`, `recentToolCalls`: counters, axis ratios, and `sha256:` tool-call hashes, the same payload as the child result's `attribution` and the child's `budget_exhausted` event). Host cancels, policy denials, and hook rejections are not failures and never emit it. `delegateAsync()` returns a local running handle; `wait()` returns its result (or `{ status: "cancelled" }` after `cancel()`), and stays idempotent while its terminal record is retained (bounded by `limits.maxQueuedEvents`; an evicted or foreign id returns the same non-enumerating error). `subscribe()` emits bounded `delegation_started`, `delegation_finished`, `delegation_rejected`, and `delegation_error` metadata events, plus the opt-in child-event family below. Aborting `CreateSupervisorOptions.signal` aborts every running child (session- and task-lifetime) and closes the stream. Hosts routing child events onto a parent session stream pass `childEventSink`; it receives the identical payload the supervisor stream carries — a redacted, capped, rate-coalesced `AgentEvent` tagged with `child: { childId, delegationId, depth }` (contract type `ChildEventOrigin`) — so a parent subscriber can route it with `event.child` and no per-type handling. Hosts may project the lifecycle events through observability `handleDelegation()` using the parent Prism run ID; no OpenTelemetry dependency enters this package.
|
|
32
32
|
|
|
33
33
|
### Child lifetime, reporting, and budget share
|
|
34
34
|
|
|
@@ -45,12 +45,20 @@ Every `SupervisorChild` may carry a `policy` of host ceilings/defaults; a `Deleg
|
|
|
45
45
|
|
|
46
46
|
### Recovery telemetry
|
|
47
47
|
|
|
48
|
-
`summary()` returns one frozen row per allow-listed child — `{ childId, attempts, retries, failures, failureRadius, outcome }` — maintained incrementally (O(1) per delegation, O(children) to read)
|
|
48
|
+
`summary()` returns one frozen row per allow-listed child — `{ childId, attempts, retries, failures, failureRadius, outcome }` — maintained incrementally (O(1) per delegation, O(children) to read). Counters are cumulative for the supervisor's lifetime, so a host can diff snapshots per root run or watch a long-lived supervisor without host-side aggregation; `summary({ reset: true })` instead zeroes every counter and starts a new window, which is the direct per-root-run read:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
supervisor.summary({ reset: true }); // root run start: new window (a no-op before the first delegation)
|
|
52
|
+
const run = await agent.run(input);
|
|
53
|
+
const perChild = supervisor.summary().children; // this root run's counters
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
A reset restates `outcome` for the window — `running` when that child has a live delegation, else `idle` — and leaves live delegations untouched, so a run that settles after the reset still records its failures and failure radius. Because the pre-window outcome is gone, a dispatch after a `failed`/`aborted` outcome from *before* the window counts `attempts: 1` with no `retries` bump; `retries` counts only failures inside the window. `reset: false` or no argument is the cumulative read. There is no `supervisor_run_summary` event: the supervisor has no root-run boundary — a turn boundary or a momentary `activeChildren === 0` between two delegations is not one — so the window is host-declared.
|
|
49
57
|
|
|
50
58
|
- `outcome` is `idle` before the first delegation, `running` while any is live, otherwise the `delegation_finished.status` vocabulary (`succeeded`/`failed`/`aborted`/`suspended`/`denied`) or `rejected` for a hook denial. Resuming a suspended run updates the outcome but is not a new attempt.
|
|
51
59
|
- `attempts` counts started delegations, hook rejections included. `retries` counts attempts started after a `failed`/`aborted` outcome — the recovery re-dispatch metric.
|
|
52
|
-
- `failures` counts delegations that died on an error or a limit; host cancels, denials, and hook rejections are excluded.
|
|
53
|
-
- `failureRadius` is the blast radius of the child's most recent failure:
|
|
60
|
+
- `failures` counts delegations that died on an error or a limit, in their live run or on a resume of it; host cancels, denials, and hook rejections are excluded.
|
|
61
|
+
- `failureRadius` is the blast radius of the child's most recent failure: descendant delegations (any depth) still live at that moment. Ancestry is tracked per delegation, so two concurrent delegations of the same child id stay distinct; an unrelated or already-finished child is not counted, and a session-lifetime child is detached from the failed subtree by design. Liveness is per supervisor instance: a delegation resumed after a restart is live again from the moment its rebuilt run starts, so it counts as a live descendant and holds its child's `outcome` at `running` — and a delegation started after that resume is numbered past the resumed id, so the two never share one. A suspended child that is never resumed is not counted: nothing runs for it in this process.
|
|
54
62
|
|
|
55
63
|
Failure attribution is the same object the `child_failed` event carries, so a host that only keeps the summary and one that only keeps events read the same taxonomy.
|
|
56
64
|
|
|
@@ -102,7 +110,7 @@ await parent.createSession().run("Research auth and billing", {
|
|
|
102
110
|
|
|
103
111
|
## Durable child approvals
|
|
104
112
|
|
|
105
|
-
With `checkpoints` + `definitionRevision`, every child run is durable with `interruptBeforeTool: true`. A child that suspends on pending decisions throws `AgentDelegationSuspendedError` out of `delegate()`; when the delegation runs inside a root agent's tool, core converts it into a root suspension whose `interruption.pendingDecisions` carry hashed root-visible approval ids (`sub_<sha256(runId:childApprovalId)>`) and `attribution.path` (redacted child ids, root first, at most 8 deep). Root decisions route back through the same CAS rules: pass `supervisor.resumeNestedRun` as `resumeNestedRun` in the root run's `runState` and in every `resumeAgentRun` options object. The supervisor rebuilds the child from a bounded delegation mapping stored in the same checkpoint store (child id, delegation/thread ids, redacted input, version), re-runs the `before` hook so its narrowing applies to the resumed run (hooks must be idempotent), and re-attributes re-suspensions recursively, so grandchild decisions surface with the full path. A delegating child's own `interruptBeforeTool` also gates its delegate tool, so hosts approve delegation and the child's own side effects as separate stages. Root `*_for_run` stickies record the attribution path and only match the same delegation path; child stickies live on the child run and expire with it. A root approval never widens the child: the child's narrowed permission re-runs at dispatch. Unknown or foreign nested run ids fail closed with one non-enumerating error. A resumed attempt is terminal-symmetric with live `delegate()`: it publishes `delegation_finished` (`delegation_rejected` when the re-run `before` hook denies) and runs `hooks.after` once with the original `childId`/`delegationId`, which is what lets an isolated child's worktree be cleaned up. A suspended child stays non-terminal — no finish event, no `after` — and a rebuild that throws before the run starts (stale version, fingerprint drift) publishes nothing and runs no terminal hook, so a duplicate resume attempt can never clean up a live suspended child. Child factories must return stable configs and a durable (or rebuild-stable) session store for resume to work.
|
|
113
|
+
With `checkpoints` + `definitionRevision`, every child run is durable with `interruptBeforeTool: true`. A child that suspends on pending decisions throws `AgentDelegationSuspendedError` out of `delegate()`; when the delegation runs inside a root agent's tool, core converts it into a root suspension whose `interruption.pendingDecisions` carry hashed root-visible approval ids (`sub_<sha256(runId:childApprovalId)>`) and `attribution.path` (redacted child ids, root first, at most 8 deep). Root decisions route back through the same CAS rules: pass `supervisor.resumeNestedRun` as `resumeNestedRun` in the root run's `runState` and in every `resumeAgentRun` options object. The supervisor rebuilds the child from a bounded delegation mapping stored in the same checkpoint store (child id, delegation/thread ids, redacted input, version), re-runs the `before` hook so its narrowing applies to the resumed run (hooks must be idempotent), and re-attributes re-suspensions recursively, so grandchild decisions surface with the full path. A delegating child's own `interruptBeforeTool` also gates its delegate tool, so hosts approve delegation and the child's own side effects as separate stages. Root `*_for_run` stickies record the attribution path and only match the same delegation path; child stickies live on the child run and expire with it. A root approval never widens the child: the child's narrowed permission re-runs at dispatch. Unknown or foreign nested run ids fail closed with one non-enumerating error. A resumed attempt is terminal-symmetric with live `delegate()`: it publishes `delegation_finished` (`delegation_rejected` when the re-run `before` hook denies; `child_failed` plus `delegation_error` when the resumed run dies) and runs `hooks.after` once with the original `childId`/`delegationId`, which is what lets an isolated child's worktree be cleaned up. A suspended child stays non-terminal — no finish event, no `after` — and a rebuild that throws before the run starts (stale version, fingerprint drift) publishes nothing and runs no terminal hook, so a duplicate resume attempt can never clean up a live suspended child. Child factories must return stable configs and a durable (or rebuild-stable) session store for resume to work.
|
|
106
114
|
|
|
107
115
|
## Extension and configuration notes
|
|
108
116
|
|
|
@@ -129,7 +137,7 @@ Supervisors propagate parent `identity` and `effectStore` to every child agent/r
|
|
|
129
137
|
- [A2A interoperability](a2a.md): separate remote protocol boundary. `A2ATaskLifecycle` adapts host durable agent/workflow state directly; it does not route A2A execution through local supervisor child planning.
|
|
130
138
|
- [Workflows](workflows.md): preferred deterministic orchestration.
|
|
131
139
|
- [Coding workspaces](coding-workspaces.md): opt-in per-child worktree isolation via `createWorktreeChildFactory`.
|
|
132
|
-
- [Coding agent tools](coding-agent-tools.md): opt-in `observeSupervisorLifecycle` bridges supervisor `delegation_*` events to coding `subagent_started` / `subagent_stopped` for host timelines; `supervisor.summary()` covers recovery counters (`retries`, `failures`, `failureRadius`) that the
|
|
140
|
+
- [Coding agent tools](coding-agent-tools.md): opt-in `observeSupervisorLifecycle` bridges supervisor `delegation_*` events to coding `subagent_started` / `subagent_stopped` for host timelines; `supervisor.summary()` covers recovery counters (`retries`, `failures`, `failureRadius`), and the bridge carries that row plus the redacted failure attribution only when the host opts into `includeRecovery` / `includeFailure`.
|
|
133
141
|
- Examples: [`examples/autonomous-coding-loop.ts`](../examples/autonomous-coding-loop.ts) — per-child models, factory returns `Agent`; [`examples/spawn-agent-tool.ts`](../examples/spawn-agent-tool.ts) — two model-requested explore children in one tool turn.
|
|
134
142
|
- [Working and semantic memory](working-and-semantic-memory.md): child scope construction.
|
|
135
143
|
- [Host security](host-security.md): permission and credential boundaries.
|
package/docs/testing.md
CHANGED
|
@@ -11,15 +11,23 @@ Documents how the hermetic suite runs, which stage a new suite belongs to, and t
|
|
|
11
11
|
|
|
12
12
|
## Running the suite
|
|
13
13
|
|
|
14
|
-
`npm test` delegates to `scripts/run-all-tests.mjs`, which runs
|
|
14
|
+
`npm test` delegates to `scripts/run-all-tests.mjs`, which runs every stage in `STAGES` and reports each one even when an earlier stage fails:
|
|
15
15
|
|
|
16
|
-
| stage | contents |
|
|
17
|
-
| :--- | :--- |
|
|
18
|
-
| build | `npm run build`
|
|
19
|
-
|
|
|
20
|
-
|
|
|
21
|
-
|
|
|
22
|
-
|
|
|
16
|
+
| stage | runner | contents |
|
|
17
|
+
| :--- | :--- | :--- |
|
|
18
|
+
| build | `npm run build` | TypeScript emit for the root and every workspace |
|
|
19
|
+
| performance budget | `node --test` | `scripts/budget-gate.test.mjs`, alone because its ceiling measures host contention |
|
|
20
|
+
| root suites | `node --test` | `dist/__tests__/*.test.js` |
|
|
21
|
+
| sqlite suites | `bun test --timeout=0` | `packages/prism-core/dist/sessions/sqlite/__tests__/*.test.js` — the one file set the Bun 1.4.2 inventory measured `bun-ok` and faster than Node; prism-core's own Node run excludes this glob |
|
|
22
|
+
| gate suites | `node --test` | `scripts/*.test.mjs` — the protection, truth, benchmark, journey, and conformance gates listed in `GATE_FILES` (`scripts/run-all-tests.mjs`), including the three split `scripts/phase54-legacy-registry-{dry-run,apply,fail-closed}.test.mjs` scenario files |
|
|
23
|
+
| build race | `node --test` | `scripts/phase23-build-race.test.mjs` |
|
|
24
|
+
| workspace suites | `npm run test --workspace <dir> --if-present` × 11 | each package's `node --test` file list, two packages in flight at a time; every leaf takes `scripts/with-build-lock.mjs --shared` |
|
|
25
|
+
| examples execution | `node --test` | `scripts/examples-execution.test.mjs` — spawns `examples/*.ts` not already run by docs demos or a dedicated spawn; manifest skips use a fixed vocabulary |
|
|
26
|
+
| branch coverage | `node` | `scripts/branch-coverage-audit.mjs` — Node instrument, core `dist/**` only; floor 83.49; Bun's gate still records `branches: null` |
|
|
27
|
+
|
|
28
|
+
Only the SQLite stage and the coverage instrument run on Bun. The root glob is ~2.3× slower under `bun test` and the prism-core workspace glob ~2.4× slower, so the split follows that measured classification instead of a wholesale switch; `npm run test:coverage` measures with `bun test --coverage` (Bun-measured floors, per-package `bunfig.toml` scoping) and runs the core suite once: the stage captures that run's output and exit code and `coverage-summary.mjs` parses the capture for the core row instead of spawning a second run (no seam set — a standalone summary, or a captured non-zero exit — still measures), while the PostgreSQL TAP leg stays on `node --test`.
|
|
29
|
+
|
|
30
|
+
The build lock has two modes: `tsc`/emit leaves keep the exclusive default, and dist-consuming test leaves pass `--shared`. Readers overlap each other (the workspace stage depends on it), while a writer still excludes every reader and a reader excludes writers — `scripts/phase23-build-race.test.mjs` proves both directions with concurrent children, plus stale-reader reclaim. The workspace stage's pool is bounded at two in-flight packages because some package suites carry soft real-time ceilings that a busier host starves; the bound and its upgrade path are documented at the stage in `scripts/run-all-tests.mjs`.
|
|
23
31
|
|
|
24
32
|
Protected-environment legs (Postgres, PTY, NATS, live credentials) are not part of `npm test`; they fail closed with one canonical `BLOCKED GATE <id> requires=<names> evidence=<surface> hint=<how to unblock>` record and a non-zero exit when their infrastructure is absent (registry and audit: `node scripts/blocked-gate.mjs`). A successful `PRISM_TEST_POSTGRES_URL=… npm run test:postgres` first removes stale evidence, then writes gitignored `scripts/postgres-evidence.json` with only current `gitHead`, capture time, and TAP counts; release evidence accepts it only at the same `HEAD`. Retired phase freeze/release gates live in `scripts/` for audit but are deliberately kept out of the chain. 0.7.0 host-completeness packed proof is `scripts/fixtures/e2e-070-host-completeness-journey.mjs` (same packed consumer as the full-surface journey) plus `scripts/host-completeness-evidence.test.mjs`; live legs stay skip-not-fail. R16/R17 stay blocked until plans 077/074 ship.
|
|
25
33
|
|
|
@@ -28,7 +36,7 @@ Protected-environment legs (Postgres, PTY, NATS, live credentials) are not part
|
|
|
28
36
|
- **Scratch roots come from the OS.** A suite that writes anything creates its root with `mkdtempSync(join(tmpdir(), "prism-…"))` and removes it in `after()`. Never rely on `process.cwd()` for write targets: the same suite runs with different working directories (workspace stage vs. root stage), so a cwd-relative root silently writes into the repository.
|
|
29
37
|
- **Pass explicit roots.** Wiki, memory, and store helpers default `workspaceRoot` to `process.cwd()`; suites pass their scratch root (and a `wikiRoot` relative to it) instead of accepting the default.
|
|
30
38
|
- **Tracked fixtures stay byte-identical.** `packages/memory/.wiki/` is a tracked wiki fixture and `docs/` is a tracked corpus. `scripts/wiki-scratch-isolation.test.mjs` runs the wiki suites from the package and from the repository root and fails if the tracked fixture hashes change, if a new file appears inside the fixture, if `<repo>/.wiki/` is scaffolded, or if the old cwd-relative scratch directories reappear.
|
|
31
|
-
- **Gates never write inside the repository.** A gate asserts against tracked content and spawns suites in temporary directories only. A gate that spawns `node --test` must strip `NODE_TEST_CONTEXT`/`NODE_TEST_WORKER_ID` from the child environment (an inherited value makes the nested runner skip every file and still exit 0) and assert the child reported a non-zero pass count. The wiki gate additionally uses `--test-isolation=none`: all nested files run in its one runner process, avoiding process-worker IPC deserialization without retrying failures.
|
|
39
|
+
- **Gates never write inside the repository.** A gate asserts against tracked content and spawns suites in temporary directories only. A gate that spawns `node --test` must strip `NODE_TEST_CONTEXT`/`NODE_TEST_WORKER_ID` from the child environment (an inherited value makes the nested runner skip every file and still exit 0) and assert the child reported a non-zero pass count. `bun test` sets neither `BUN_*` nor `NODE_TEST_*` (measured on 1.4.2), so no `BUN_TEST_*` strip is added. The wiki gate additionally uses `--test-isolation=none`: all nested files run in its one runner process, avoiding process-worker IPC deserialization without retrying failures. A child that runs a Node-only test flag (such as `--test` or `--test-isolation`) spawns `node` by name instead of `process.execPath`: under a Bun parent `process.execPath` is a Bun child, and `bun --test` is a script run, not a test runner. Runner-agnostic spawns (`-e` snippets, CLI invocations) keep `process.execPath` on purpose — Bun's `-e` exists, which is why the root `bun test` run works. `scripts/tooling-gate.test.mjs` scans the repository for the violation.
|
|
32
40
|
- **Wait by polling, not by sleeping.** Async browser state (download quarantine, idle reaping) is not awaitable from the outside — `manager.ts` settles it on a fire-and-forget listener promise — so a fixed sleep is a race that loses under CPU load and fails the assertion for a reason unrelated to the behavior under test. Suites poll observable state through `waitFor(read, ok, label, { timeoutMs, intervalMs })` in `packages/web-tools/src/browser/__tests__/wait-for.ts`, which returns as soon as the state appears and otherwise throws naming the label and the last observed value. Fixed sleeps remain only where real elapsed time is the subject of the test (idle TTLs).
|
|
33
41
|
|
|
34
42
|
## Related APIs
|
package/docs/tools.md
CHANGED
|
@@ -297,7 +297,7 @@ Limits (mirroring the skill-disclosure DEFAULT/HARD cap pattern):
|
|
|
297
297
|
|
|
298
298
|
## Guardrails
|
|
299
299
|
|
|
300
|
-
`DispatchToolCallOptions.guardrails` evaluates `tool_input` after `tool_call` middleware normalization and before lookup, permission, validation, execution policy, or side effect. `tool_output` evaluates raw completed results before redaction, event emission, ledger rows, and transcript append. A block returns a blocked result; tripwire fails the enclosing run. See [Guardrails](guardrails.md).
|
|
300
|
+
`DispatchToolCallOptions.guardrails` evaluates `tool_input` after `tool_call` middleware normalization and before lookup, permission, validation, execution policy, or side effect. `tool_output` evaluates raw completed results before redaction, event emission, ledger rows, and transcript append. A block returns a blocked result; tripwire fails the enclosing run. A blocked call's `error.message` names the refusing rule — `Blocked by guardrail rule pack:<pack>/<rule>`, plus the pack's reason when it set one, bounded to 200 bytes and redacted; a hand-written guardrail keeps `Tool call blocked by guardrail` / `Tool result blocked by guardrail`. The `tool_execution_blocked` event carries the same text in `error.message` while its `reason` stays the machine code. See [Guardrails](guardrails.md).
|
|
301
301
|
|
|
302
302
|
## Related APIs
|
|
303
303
|
|
package/docs/wiki.md
CHANGED
|
@@ -45,7 +45,7 @@ The Karpathy LLM Wiki pattern is structured into 3 distinct tiers:
|
|
|
45
45
|
|
|
46
46
|
- `/wiki-init`: Scaffolds `.wiki/`, instantiates `SCHEMA.md`, `index.md`, and `log.md`, deploys skills, and adds the `qmd` collection.
|
|
47
47
|
- `/wiki-refresh`: Detects modified source files via SHA-256 Merkle diffing, compiles updates to affected entity pages, reconciles contradictions in `log.md`, and runs `qmd update`.
|
|
48
|
-
- `/wiki-lint`: Checks OKF frontmatter (`type`, ISO `generated.at`), leftover `[[wikilinks]]`, unresolved relative markdown links, dead line anchors, and
|
|
48
|
+
- `/wiki-lint`: Checks OKF frontmatter (`type`, ISO `generated.at`), leftover `[[wikilinks]]`, unresolved relative markdown links, dead line anchors, orphan pages, and pruned sources (an entity page whose raw sources were retired or deleted — reported for re-filing, never a failure).
|
|
49
49
|
- `/wiki-ingest`: `{ text?, path?, url?, title? }` — stages one external source into `raw/ingest/<utc>-<slug>/` (`source.*` original + `extract.md`), then returns a brief (staged paths, extract preview, source URL when applicable, Karpathy filing checklist). When the host injects `drivers`, the command calls `drivers.startRun(brief, { activeSkills: ["wiki-maintainer"] })` so the maintainer skill files the source into the wiki; without drivers it stages only and reports `runStarted: false`. Results are labeled `metadata.trust: "untrusted_external"`.
|
|
50
50
|
|
|
51
51
|
### Standalone CLI Commands
|
|
@@ -57,7 +57,7 @@ npx prism-wiki init --profile codebase
|
|
|
57
57
|
# Refresh wiki after code edits
|
|
58
58
|
npx prism-wiki refresh
|
|
59
59
|
|
|
60
|
-
# Check wiki health
|
|
60
|
+
# Check wiki health: dead anchors, broken links, orphans, pruned sources
|
|
61
61
|
npx prism-wiki lint
|
|
62
62
|
|
|
63
63
|
# Search wiki from terminal
|
|
@@ -71,6 +71,8 @@ npx prism-wiki ingest --path notes/paper.pdf --title "Paper"
|
|
|
71
71
|
npx prism-wiki ingest --url https://example.com/rfc.pdf # → exit 1
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
+
Lint output is one summary line plus per-issue detail, and the report shape is `{ deadAnchors, brokenLinks, orphans, gaps, prunedSources, ok }`. **Pruned sources are not a failure**: a page whose raw sources were retired (`retireWikiSources`), re-pointed to a path that does not exist, or deleted out-of-band is maintainer work, so it is reported as `prunedSources` entries (`{ page, missing }`, both workspace-relative, capped to the first few paths in command/CLI text) while the health check stays `ok` and the CLI exits 0. The `wiki-maintainer` skill covers the response: re-read the surviving sources and re-file the page, or delete it when none remain.
|
|
75
|
+
|
|
74
76
|
## Outputs / response / events
|
|
75
77
|
|
|
76
78
|
- `wiki_search` returns a structured markdown payload containing section breadcrumbs, conceptual summaries, and clickable source line links (`file:///path#Lxx-Lyy`).
|
package/docs/workflows.md
CHANGED
|
@@ -89,14 +89,14 @@ All workflow limits and runtime `concurrency` reject non-safe integers, zero, ne
|
|
|
89
89
|
| `resume` | For suspended runs: `{ decision: "approve" | "deny", input?, expectedVersion }`; version is mandatory for an exact-once CAS claim |
|
|
90
90
|
| `validateResume` | Host validator for resume input; required when `suspend()` declares `resumeSchema` |
|
|
91
91
|
| `metadata` | Sidecar map (`Record<string, unknown>`) persisted on the checkpoint value; a resume that does not re-state it keeps the recorded map |
|
|
92
|
-
| `restoreHooks` | External-state restore hooks (`CheckpointRestoreHook`) run sequentially on every resume before the scheduler writes; the first failure/timeout throws `CheckpointRestoreError` (`ERR_PRISM_CHECKPOINT_RESTORE`) and leaves the checkpoint untouched |
|
|
92
|
+
| `restoreHooks` | External-state restore hooks (`CheckpointRestoreHook` bare functions, or `{ id?, restore, compensate? }` objects shared with agent resumes) run sequentially on every resume before the scheduler writes; the first failure/timeout throws `CheckpointRestoreError` (`ERR_PRISM_CHECKPOINT_RESTORE`) after compensating the applied layers in reverse and leaves the checkpoint untouched |
|
|
93
93
|
| `restoreHookTimeoutMs` | Per-hook restore ceiling in ms; defaults to `DEFAULT_CHECKPOINT_RESTORE_TIMEOUT_MS` (10 s) |
|
|
94
94
|
| `validateState` | Host validator for every initial/restored/updated state; required when workflow declares `state.schema` |
|
|
95
95
|
| `initialState` | Optional host initial state override; nested workflows receive parent state automatically |
|
|
96
96
|
|
|
97
97
|
A function node returns `suspend({ reason, data?, resumeSchema? })` to persist `status: "suspended"`. Its next invocation receives `ctx.resume` only after an approved resume. `resumeWorkflow(workflow, { runId }, options)` validates schema/version/ownership/`definitionHash`, claims the checkpoint before node execution, and continues the suspended node. Denial persists terminal `denied` status without invoking it. Existing failed/aborted checkpoint resume remains available without a human decision.
|
|
98
98
|
|
|
99
|
-
Restore hooks make the resume all-or-nothing across layers: workflow checkpoints carry the host's `metadata` (git commit, document version, workspace fingerprint), `restoreHooks` put each recorded layer back, and only when every hook succeeds does the scheduler claim the checkpoint and continue. Each hook receives `{ workflowId, runId, version, status, metadata, checkpoint }` and an `AbortSignal`; the successful run's `workflow_resumed` event carries `restore: { hooks: [{ hook, durationMs }], durationMs }`. No hooks ⇒ no hook call and no `restore` field.
|
|
99
|
+
Restore hooks make the resume all-or-nothing across layers: workflow checkpoints carry the host's `metadata` (git commit, document version, workspace fingerprint), `restoreHooks` put each recorded layer back, and only when every hook succeeds does the scheduler claim the checkpoint and continue. Each hook receives `{ workflowId, runId, version, status, metadata, checkpoint }` and an `AbortSignal`; the successful run's `workflow_resumed` event carries `restore: { hooks: [{ hook, durationMs }], durationMs }`. No hooks ⇒ no hook call and no `restore` field. Compensation is the same rule as agent resumes: an object-form handler may declare `compensate`, a failed restore undoes the applied layers in reverse (failing hook first) under the same per-hook timeout, and `CheckpointRestoreError.compensation` reports `{ ran, failed? }` best-effort while the checkpoint stays unclaimed and resumable. A successful resume's audit also reaches the review surface: `projectWorkflowTimeline` carries the same `ExecutionTimeline.restore` as the agent timeline.
|
|
100
100
|
|
|
101
101
|
> **Contract — resume-aware nodes.** After an approved resume, the **same** node's `execute` is re-invoked with `ctx.resume`. Returning `suspend(...)` unconditionally re-suspends silently; downstream nodes never run. Branch on `ctx.resume`:
|
|
102
102
|
>
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arnilo/prism",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Agent harness for AI providers, agents, sessions, and tools.",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"packageManager": "bun@1.4.2",
|
|
6
7
|
"main": "./dist/index.js",
|
|
7
8
|
"types": "./dist/index.d.ts",
|
|
8
9
|
"exports": {
|
|
@@ -135,17 +136,18 @@
|
|
|
135
136
|
"packages/prism-coding-tools",
|
|
136
137
|
"packages/ag-ui",
|
|
137
138
|
"packages/web-tools",
|
|
138
|
-
"packages/acp-agent"
|
|
139
|
+
"packages/acp-agent",
|
|
140
|
+
"packages/hooks"
|
|
139
141
|
],
|
|
140
142
|
"scripts": {
|
|
141
143
|
"build:core": "node scripts/with-build-lock.mjs tsc",
|
|
142
144
|
"clean": "rm -rf dist packages/*/dist",
|
|
143
|
-
"build": "npm run build:core &&
|
|
144
|
-
"typecheck": "
|
|
145
|
+
"build": "npm run build:core && for p in @arnilo/prism-mcp @arnilo/prism-providers @arnilo/prism-memory @arnilo/prism-work @arnilo/prism-core @arnilo/prism-channels @arnilo/prism-coding-tools @arnilo/prism-ag-ui @arnilo/prism-web-tools @arnilo/prism-acp-agent @arnilo/prism-hooks; do bun run --filter \"$p\" build || exit 1; done",
|
|
146
|
+
"typecheck": "bun run build && bun run --workspaces --if-present typecheck && tsc -p examples --noEmit",
|
|
145
147
|
"sweep:unused": "node scripts/sweep-unused.mjs --json",
|
|
146
148
|
"test:live": "node scripts/live-matrix.mjs",
|
|
147
149
|
"test": "node scripts/run-all-tests.mjs",
|
|
148
|
-
"test:coverage": "node scripts/with-build-lock.mjs
|
|
150
|
+
"test:coverage": "node scripts/with-build-lock.mjs bun test --coverage --timeout=0 dist/__tests__/*.test.js > node_modules/.prism-core-coverage.out 2>&1; core_exit=$?; cat node_modules/.prism-core-coverage.out; PRISM_COVERAGE_CORE_OUTPUT=node_modules/.prism-core-coverage.out PRISM_COVERAGE_CORE_EXIT=$core_exit node scripts/with-build-lock.mjs node scripts/coverage-summary.mjs && node --test scripts/phase23-coverage.test.mjs && node --test scripts/phase23-skip-manifest.test.mjs; stage_exit=$?; rm -f node_modules/.prism-core-coverage.out; exit $stage_exit",
|
|
149
151
|
"coverage:summary": "node scripts/with-build-lock.mjs node scripts/coverage-summary.mjs",
|
|
150
152
|
"lint": "biome lint . --reporter=sarif --reporter-file=scripts/lint-report.sarif",
|
|
151
153
|
"format": "biome format --write .",
|
|
@@ -155,6 +157,7 @@
|
|
|
155
157
|
"test:postgres:run": "node scripts/require-postgres-url.mjs && npm run test:postgres --workspace @arnilo/prism-core --if-present && npm run test:postgres --workspace @arnilo/prism-memory && npm run test:postgres --workspace @arnilo/prism-channels && node --test scripts/phase7-conformance.test.mjs scripts/phase12-restart-recovery.test.mjs scripts/phase22-conformance.test.mjs",
|
|
156
158
|
"test:nats": "node scripts/require-nats-url.mjs && npm run test:nats --workspace @arnilo/prism-core --if-present",
|
|
157
159
|
"release:dry-run": "npm run sdk:ready",
|
|
160
|
+
"post-publish:smoke": "node scripts/post-publish-smoke.mjs",
|
|
158
161
|
"release:check": "node scripts/release.mjs check",
|
|
159
162
|
"release:publish": "node scripts/release.mjs publish",
|
|
160
163
|
"release:evidence": "node scripts/release-skip-manifest.mjs",
|