@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/hooks.md
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# Hooks
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
Hooks are the seams where host- or package-owned code reacts to a run. Prism splits them by what the
|
|
6
|
+
code is allowed to do, so a hook never gains authority it was not granted:
|
|
7
|
+
|
|
8
|
+
| Family | Seam | May decide | Owning page |
|
|
9
|
+
| --- | --- | --- | --- |
|
|
10
|
+
| Middleware hooks | Transform a payload at a named boundary: `session_start`, `session_shutdown`, `beforeProviderTurn`, `provider_request`, `input_assembly`, `prompt_build`, `context`, `tool_call`, `tool_result`, `retry`, `compaction_request`, `compaction` | Returns the payload; cannot end the run | [Middleware hooks](middleware-hooks.md) |
|
|
11
|
+
| Guardrails | `input`, `output`, `tool_input`, `tool_output` stages | `allow` / `block` / `tripwire` / `interrupt` — the only seams that can reject | [Guardrails](guardrails.md) |
|
|
12
|
+
| Instruction injectors | Prompt and context injection on the first turn, every turn, or on matching input | Returns `instructions` and `contextBlocks`; cannot mutate the provider request | [Instruction injection](instruction-injection.md) |
|
|
13
|
+
| Stop hooks | A natural loop end, after the model produced an answer | `continue` (bounded) or `stop` | This page |
|
|
14
|
+
| Extension bus | Every emitted `AgentEvent`, optionally bridged read-only | Observes only | [Extensions](extensions.md), [Agent events](agent-events.md) |
|
|
15
|
+
|
|
16
|
+
This page owns stop hooks and the map from Claude Code / Codex hook events onto Prism seams.
|
|
17
|
+
|
|
18
|
+
APIs:
|
|
19
|
+
|
|
20
|
+
- `AgentConfig.stopHooks` / `RunOptions.stopHooks`
|
|
21
|
+
- `StopHook`, `StopHookContext`, `StopHookDecision`
|
|
22
|
+
- `RunLimits.maxStopContinuations`
|
|
23
|
+
- `ExtensionAPI.registerStopHook()` and `activateKernel().stopHooks`
|
|
24
|
+
- `session_start` / `session_shutdown` middleware and `session.close()`
|
|
25
|
+
- `compaction_request` / `compaction` middleware
|
|
26
|
+
- `forwardAgentEvents()` for hosts that already listen on the extension bus
|
|
27
|
+
- `@arnilo/prism-hooks`: `parseHooksConfig()` / `createHooksExtension()` for a declarative `hooks.json`
|
|
28
|
+
|
|
29
|
+
## When to use it
|
|
30
|
+
|
|
31
|
+
Use stop hooks for end-of-run policy: nudge the model to address deferred items, verify a checklist
|
|
32
|
+
before finishing, or ask for a final answer shape. Stop hooks run *after* the loop finished naturally,
|
|
33
|
+
when the model produced its answer.
|
|
34
|
+
|
|
35
|
+
Do not use them as middleware (payload transformation), as guardrails (policy denials), or as turn
|
|
36
|
+
control. `RunOptions.turnPolicy.stop` and the loop ceilings end a run *before* the next provider
|
|
37
|
+
request — see [Agent loops](agent-loops.md).
|
|
38
|
+
|
|
39
|
+
## Inputs / request
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
interface StopHookContext {
|
|
43
|
+
readonly sessionId: string;
|
|
44
|
+
readonly runId: string;
|
|
45
|
+
/** Provider turns assembled in this run (resumption continues the run's counter). */
|
|
46
|
+
readonly turn: number;
|
|
47
|
+
/** Read-only live transcript at loop end. */
|
|
48
|
+
readonly history: readonly Message[];
|
|
49
|
+
readonly metadata: Readonly<Record<string, unknown>>;
|
|
50
|
+
readonly signal: AbortSignal;
|
|
51
|
+
/** True on every invocation after the first continuation in this run. */
|
|
52
|
+
readonly stopHookActive: boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
type StopHookDecision =
|
|
56
|
+
| { readonly action: "stop" }
|
|
57
|
+
| { readonly action: "continue"; readonly reason: string; readonly steer?: string | Message };
|
|
58
|
+
|
|
59
|
+
interface StopHook {
|
|
60
|
+
readonly name: string;
|
|
61
|
+
decide(context: StopHookContext): StopHookDecision | Promise<StopHookDecision>;
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Configuration: `AgentConfig.stopHooks` is the agent default; `RunOptions.stopHooks` appends to it for
|
|
66
|
+
one run (agent hooks first). Hooks run serially in order, the first `continue` wins, and every hook
|
|
67
|
+
is consulted before the run settles. `RunLimits.maxStopContinuations` caps continuations — default
|
|
68
|
+
3, `0` observes hooks but never continues, `null` disables the cap — and a run overlay may only
|
|
69
|
+
narrow the agent value (resolution takes the minimum across layers).
|
|
70
|
+
|
|
71
|
+
## Outputs / response / events
|
|
72
|
+
|
|
73
|
+
- `continue` queues `reason` (and `steer` after it) as steered input. The next turn sees them in
|
|
74
|
+
history; input guardrails re-check them exactly like `session.steer()`, so a `block`/`tripwire`
|
|
75
|
+
decision drops the message (emitting `steer_rejected`) while the continuation still runs, and an
|
|
76
|
+
`interrupt` decision fails the run.
|
|
77
|
+
- The continuation turn is a normal provider turn: it charges `maxTurns`/token limits, emits
|
|
78
|
+
`turn_started`/`turn_finished`, and its output joins the transcript.
|
|
79
|
+
- Exceeding the cap ends the run cleanly with `AgentRunResult.stopReason: "hook_limit"`, riding
|
|
80
|
+
`agent_finished.finishReason` and the finish `RunRecord`; no `error` is set. With
|
|
81
|
+
`runState: { checkpointPolicy: "every-turn" }` the checkpoint keeps its frontier and
|
|
82
|
+
`resumeAgentRun(..., { decision: "continue" })` resumes it.
|
|
83
|
+
- A throwing or malformed hook fails the run closed with `ERR_PRISM_STOP_HOOK`. Hooks never run
|
|
84
|
+
after a loop ceiling (`turn_limit`/`token_limit`/`refusal`), a `turnPolicy` stop (`host_policy`),
|
|
85
|
+
or a `generate-validate-revise` artifact failure.
|
|
86
|
+
|
|
87
|
+
## Request/response example
|
|
88
|
+
|
|
89
|
+
```text
|
|
90
|
+
turn 1: provider answers "draft"
|
|
91
|
+
stop hook: { action: "continue", reason: "address the deferred items" }
|
|
92
|
+
turn 2: provider sees "address the deferred items" in history and answers again
|
|
93
|
+
stop hook: ctx.stopHookActive === true → { action: "stop" }
|
|
94
|
+
result: natural end (no stopReason)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Implementation example
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import { createAgent } from "@arnilo/prism";
|
|
101
|
+
|
|
102
|
+
const agent = createAgent({
|
|
103
|
+
model: { provider: "mock", model: "demo" },
|
|
104
|
+
provider,
|
|
105
|
+
stopHooks: [
|
|
106
|
+
{
|
|
107
|
+
name: "checklist",
|
|
108
|
+
decide: (ctx) =>
|
|
109
|
+
ctx.stopHookActive
|
|
110
|
+
? { action: "stop" }
|
|
111
|
+
: { action: "continue", reason: "Verify the checklist before finishing." },
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
limits: { maxStopContinuations: 3 },
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const result = await agent.createSession().run("Write the report");
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Session lifecycle and boundary hooks
|
|
121
|
+
|
|
122
|
+
The session and compaction boundaries are middleware, dispatched once each by the agent/session
|
|
123
|
+
runtime (never per turn):
|
|
124
|
+
|
|
125
|
+
| Hook | Fires | Payload |
|
|
126
|
+
| --- | --- | --- |
|
|
127
|
+
| `session_start` | First run start of a session — after `agent_started`/`agent_resumed`, before the first provider turn. A session rebuilt from a durable checkpoint is a new runtime session, so it opens again. | `{ sessionId, runId }` |
|
|
128
|
+
| `session_shutdown` | `await session.close()`, before every subscriber is closed. Idempotent. | `{ sessionId }` |
|
|
129
|
+
| `compaction_request` | Before a compaction strategy runs — `session.compact()` and auto-compaction both route through it, ordinary turns do not. The payload is the strategy's input, and the return value is what it compacts. | `CompactionContext` (`sessionId`, `entries`, `keepRecentEntries`, `trigger`, `secrets`, `metadata`, `signal`) |
|
|
130
|
+
| `compaction` | After the strategy returns, with the final compacted result. | `{ context, result }` |
|
|
131
|
+
|
|
132
|
+
They are middleware, not stop hooks: they observe a boundary and must return the payload. A throw
|
|
133
|
+
follows the registry's `errorPolicy` — an `extension_error` event with the run continuing (default),
|
|
134
|
+
or a rejection from `run()`/`close()` with `errorPolicy: "throw"`. Hosts register them through
|
|
135
|
+
`api.use("session_start", ...)`, `api.use("session_shutdown", ...)`, `api.use("compaction_request", ...)`,
|
|
136
|
+
or `api.use("compaction", ...)` and pass the registry as `AgentConfig.middleware`; without it none of
|
|
137
|
+
them fire. `compaction_request` cannot skip compaction (an empty entry set is the strategy's own
|
|
138
|
+
error) and the post-strategy `compaction` hook still sees the result — see
|
|
139
|
+
[Middleware hooks](middleware-hooks.md). Use [Agent events](agent-events.md) or
|
|
140
|
+
`forwardAgentEvents(source, kernel.events)` for finer-grained per-turn observation — the bridge maps
|
|
141
|
+
`agent_started` → `before_agent_start`, `turn_started`/`turn_finished` → `turn`, and
|
|
142
|
+
`tool_execution_started`/`tool_execution_finished` → `tool_call`/`tool_result` as read-only
|
|
143
|
+
notifications.
|
|
144
|
+
|
|
145
|
+
## Claude Code and Codex event map
|
|
146
|
+
|
|
147
|
+
Every Claude Code / Codex hook event maps onto one of the seams above — or is a documented non-goal:
|
|
148
|
+
|
|
149
|
+
| Claude Code / Codex event | Prism surface | `hooks.json` adapter | Notes |
|
|
150
|
+
| --- | --- | --- | --- |
|
|
151
|
+
| `SessionStart` | `session_start` middleware (+ instruction-injector queue) | compiled | `additionalContext` lands in the next assembly. Claude/Codex `source` values `startup` and `resume` both select — the seam cannot tell a fresh session from a resumed one — while `clear` and `compact` do not: compaction never reopens the session here (use the `compaction_request`/`compaction` seams). |
|
|
152
|
+
| `SessionEnd` | `session_shutdown` middleware | not compiled | Teardown cannot inject context or block a run, so cleanup belongs in the middleware or `LoadedExtension.dispose()`. A `hooks.json` file that needs it uses `api.use("session_shutdown", ...)` alongside the adapter. |
|
|
153
|
+
| `UserPromptSubmit` | `input` guardrail (deny) + injector queue | compiled | Exit `2`, `decision: "block"`, or `continue: false` rejects the run with `GuardrailError`; `additionalContext` is injected. |
|
|
154
|
+
| `PreToolUse` | `tool_call` middleware (`updatedInput`, `additionalContext`) + `tool_input` guardrail (deny) | compiled | `permissionDecision: "deny"` and exit `2` return a blocked `ToolResult`; `updatedInput` rewrites the arguments before dispatch; `additionalContext` is queued for the next assembly and dropped when the call is denied. Approvals are host policy — see `PermissionRequest`. |
|
|
155
|
+
| `PermissionRequest` | non-goal — host permission policy | not compiled | A hook that can grant a permission is a privilege-escalation vector: config-authored or third-party code would gain approval authority over the host's tools. Prism keeps grants in host-owned policy — `ExecutionPolicy`, `requiresApproval`, `interruptBeforeTool` — and guardrail packs' [`ask` rules](guardrails.md#asking-for-approval-ask-rules), which suspend a durable run for an explicit host decision. Hook seams (and `@arnilo/prism-hooks`) only ever deny. |
|
|
156
|
+
| `PostToolUse` | `tool_result` middleware (`additionalContext`, rewrite) + `tool_output` guardrail (deny) | compiled | `decision: "block"` and exit `2` replace the result with a refusal-shaped `ToolResult`; `additionalContext` is queued for the next assembly. |
|
|
157
|
+
| `Stop` | stop hooks (`AgentConfig.stopHooks` / `RunOptions.stopHooks`) | compiled | Both harnesses continue with `decision: "block"` + `reason`, or with `additionalContext` (exit `2` also continues, using stderr as the reason): that becomes `{ action: "continue" }`, with the reason as the continuation prompt and `ctx.stopHookActive` true on re-entry. The common-field `continue: false` is the *stop* signal and wins over continuation decisions in the same event. `RunLimits.maxStopContinuations` (default 3) caps how often the loop re-enters before a clean `hook_limit` stop. |
|
|
158
|
+
| `PreCompact` | `compaction_request` middleware | not compiled | Claude/Codex `PreCompact` is decision-only (block the compaction, no replaceable input). Prism's seam adds input rewrite: the returned `CompactionContext` is exactly what the strategy compacts. |
|
|
159
|
+
| `PostCompact` | `compaction` middleware + `compaction_finished` event | not compiled | Observes or rewrites the compacted result after the strategy returned. |
|
|
160
|
+
| `Interrupt` (Codex) | non-goal — hooks run at boundaries | not compiled | Hooks run before and after a turn, not inside one, so they cannot interrupt an in-flight turn. Interruption is the host's abort path: the run's `AbortSignal`, `session.close()`, or a guardrail `interrupt` at the input stage of a durable run (which suspends for approval). |
|
|
161
|
+
| `SubagentStart` / `SubagentStop` | non-goal — no subagent primitive | not compiled | Prism has no implicit child-agent lifecycle: delegation is a host-authored tool or a separate agent/run, so the parent loop is unaffected. Observe child runs with `agent_started`/`agent_finished` and `forwardAgentEvents()`. |
|
|
162
|
+
|
|
163
|
+
Claude Code's remaining harness-specific events (`Setup`, `Notification`, `StopFailure`,
|
|
164
|
+
`PostToolUseFailure`, `PostToolBatch`, `TeammateIdle`, `TaskCreated`, `TaskCompleted`,
|
|
165
|
+
`Elicitation`, `ElicitationResult`, `PreToolBatch`) have no Prism surface: they belong to a
|
|
166
|
+
terminal/CI harness (init, notifications, multi-teammate gating, MCP elicitation) rather than to the
|
|
167
|
+
agent loop. `StopFailure` and `PostToolUseFailure` are covered as outcomes — `agent_finished.error`
|
|
168
|
+
and a failed `ToolResult` — and the rest are observed through `AgentEvent`s.
|
|
169
|
+
|
|
170
|
+
## `hooks.json` adapter (`@arnilo/prism-hooks`)
|
|
171
|
+
|
|
172
|
+
Hosts that already keep a Claude Code or Codex hooks file do not have to rewrite it as SDK
|
|
173
|
+
callbacks. The opt-in capability package `@arnilo/prism-hooks` parses the file and compiles each
|
|
174
|
+
event onto the seams above — no Prism internals, `@arnilo/prism` as its only peer, and no new
|
|
175
|
+
runtime dependency. It compiles the five events marked *compiled* in the map.
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
npm install @arnilo/prism @arnilo/prism-hooks
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
import { activateKernel, createAgent, createExtensionKernel } from "@arnilo/prism";
|
|
183
|
+
import { createHooksExtension, hookCommandHash, parseHooksConfig } from "@arnilo/prism-hooks";
|
|
184
|
+
|
|
185
|
+
const config = parseHooksConfig(await readFile("hooks.json", "utf8"));
|
|
186
|
+
const audit = { type: "command", command: "node audit-tool.js" } as const;
|
|
187
|
+
const hooks = createHooksExtension(config, { trusted: { [audit.command]: hookCommandHash(audit) } });
|
|
188
|
+
|
|
189
|
+
const kernel = createExtensionKernel();
|
|
190
|
+
await kernel.load([hooks]);
|
|
191
|
+
const activated = activateKernel(kernel);
|
|
192
|
+
const agent = createAgent({
|
|
193
|
+
model,
|
|
194
|
+
provider,
|
|
195
|
+
guardrails: hooks.guardrails, // required for UserPromptSubmit / PreToolUse / PostToolUse
|
|
196
|
+
instructionInjectors: activated.instructionInjectors,
|
|
197
|
+
stopHooks: activated.stopHooks,
|
|
198
|
+
middleware: activated.middleware,
|
|
199
|
+
});
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Schema and semantics:
|
|
203
|
+
|
|
204
|
+
- Both authoring shapes parse: the flat Claude map (`{ "PreToolUse": [{ "matcher": "Bash", "hooks": [...] }] }`) and Codex's nested `{ "hooks": { "PreToolUse": [...] } }` with bare handler objects. An unknown event name, unknown handler `type`, empty command, non-positive `timeout`, or `"shell": true` throws `ERR_PRISM_HOOKS_CONFIG` — never a silent no-op.
|
|
205
|
+
- Handlers are `{ type: "command", command, args?, timeout?, async?, asyncRewake?, statusMessage? }` or `{ type: "mcp_tool", server, tool, input? }`. HTTP hooks are not implemented.
|
|
206
|
+
- Matchers follow Claude's hybrid rule: letters/digits/`_`/`-`/space/`,`/`|` select literally (with `|`/`,` alternatives), anything else is an unanchored regex, and no `$1` capture substitution exists. `UserPromptSubmit` and `Stop` have no matcher subject, so a matcher there warns and is ignored; `SessionStart` cannot distinguish a fresh session from a checkpoint resume at that seam, so `startup` and `resume` both select.
|
|
207
|
+
- Exit codes are Claude/Codex's: `0` success (JSON stdout parsed), `2` block with stderr as the reason, any other code a non-blocking error reported as a `hooks:warning` extension event. There is no exit code `64`.
|
|
208
|
+
- The common-field `continue: false` is read as the stop signal: on `Stop` it returns `{ action: "stop" }` and wins over continuation decisions from the same event (Codex precedence); on a tool or prompt event it reads as a refusal, the conservative reading, since Claude halts processing there and Codex marks the field unsupported.
|
|
209
|
+
- `timeout` is in **seconds** (default 600). Expiry warns and never blocks: pre-events allow with a warning, additive events simply drop their context.
|
|
210
|
+
- `additionalContextLimit` is a token threshold measured at ~4 code units per token — Codex's unit (default 2500, `0` = unlimited). It is read per handler, and a handler without the field falls back to the config-level value beside `hooks`. Claude's fixed 10,000-character `persistHookOutput` is the same spill behavior at a different unit. Over the limit that handler's text is written to `<temp_dir>/hook_outputs/` and a pointer line is injected instead — the only filesystem write this package performs.
|
|
211
|
+
- `async: true` handlers run detached (`asyncRewake` is accepted for config parity). Their context lands at the next assembly, never on the event path.
|
|
212
|
+
- Trust is a hash allowlist: `options.trusted` maps a command string to a digest from `hookCommandHash()` (default SHA-256 over the tokenized argv, so appending an argument invalidates the entry). A missing or mismatched entry skips the handler with a `hooks:warning`. `trusted: "all"` is the documented escape hatch and logs loudly. `mcp_tool` handlers use the host-supplied client and are trusted by construction.
|
|
213
|
+
- Commands are tokenized locally (quotes and escapes only — no expansion, pipes, or substitutions) and spawned with an argv array; there is no shell. Guardrails and stop hooks stay inert until the host activates them, like every other kernel contribution.
|
|
214
|
+
|
|
215
|
+
## Migrating from Claude Code or Codex configs
|
|
216
|
+
|
|
217
|
+
1. Keep the file where it is and parse it: `parseHooksConfig(text)` accepts both the flat
|
|
218
|
+
`.claude/settings.json` map and Codex's `{ "hooks": { ... } }` shape, including Claude's extra
|
|
219
|
+
settings keys next to `hooks`.
|
|
220
|
+
2. Drop the events the adapter does not compile — an unknown event name is a loud error, not a
|
|
221
|
+
silent skip, so filter them explicitly instead of stripping the file by hand:
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
// SessionEnd, PermissionRequest, Interrupt, PreCompact/PostCompact, SubagentStart/Stop are not compiled.
|
|
225
|
+
const { SessionEnd, PermissionRequest, Interrupt, PreCompact, PostCompact, SubagentStart, SubagentStop, ...compiled } = raw;
|
|
226
|
+
const config = parseHooksConfig(compiled);
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
For the non-goals, use the Prism surface named in the map (host permission policy, the
|
|
230
|
+
`session_shutdown` / `compaction_request` / `compaction` middleware) rather than a handler file.
|
|
231
|
+
3. Decide trust before the first run: `trusted: { [command]: hookCommandHash(handler) }` for each
|
|
232
|
+
command you have reviewed, or `"all"` only for a config you own end to end. Handlers run
|
|
233
|
+
out-of-process but with your privileges, so the allowlist is the boundary.
|
|
234
|
+
4. Activate the two halves together: `guardrails: hooks.guardrails` on the agent, plus
|
|
235
|
+
`middleware` / `instructionInjectors` / `stopHooks` from `activateKernel()` — activating one
|
|
236
|
+
without the other logs a `hooks:warning` and the blocking decisions do not apply.
|
|
237
|
+
5. Re-check the differences: `timeout` is seconds (default 600), only exit code `2` blocks,
|
|
238
|
+
`decision: "block"` is what continues a `Stop` (never `continue: false`, which stops), no `$1`
|
|
239
|
+
capture substitution in matchers, `additionalContext` over `additionalContextLimit` spills to a
|
|
240
|
+
file pointer, and matchers on `UserPromptSubmit`/`Stop` are ignored with a warning.
|
|
241
|
+
|
|
242
|
+
## Extension and configuration notes
|
|
243
|
+
|
|
244
|
+
- `api.registerStopHook({ name, decide })` contributes an inert hook to `kernel.registries.stopHooks`;
|
|
245
|
+
pass `activateKernel(kernel).stopHooks` into `createAgent({ stopHooks })`. Disposing the loaded
|
|
246
|
+
extension unwinds the registration.
|
|
247
|
+
- Registrations are keyed by `name` (last write wins, like other contribution registries).
|
|
248
|
+
- `RunOptions.stopHooks` cannot replace agent hooks; it appends. `maxStopContinuations` narrows only.
|
|
249
|
+
- No dedicated `AgentEvent` is added: observe decisions through your own callback and the run outcome.
|
|
250
|
+
|
|
251
|
+
## Security and performance notes
|
|
252
|
+
|
|
253
|
+
- In-process seams — middleware, guardrails, injectors, and stop hooks — are host code with the
|
|
254
|
+
host's full privileges. Registering one is trusting it; Prism validates payload shapes, bounds
|
|
255
|
+
what reaches the provider, and redacts what reaches events, but it cannot sandbox a callback.
|
|
256
|
+
- Hooks never receive credentials and cannot mutate history: stop hooks get metadata plus the
|
|
257
|
+
read-only transcript, and continuation text enters through the steer queue, so the 8-message /
|
|
258
|
+
64 KiB caps bound it and the host redactor applies before it reaches the provider.
|
|
259
|
+
- No hook seam grants authority, by design. Permission and approval decisions stay with host policy
|
|
260
|
+
(`ExecutionPolicy`, `requiresApproval`, `interruptBeforeTool`, guardrail-pack `ask` rules), so a
|
|
261
|
+
hook can only deny or add context — the `PermissionRequest` non-goal above.
|
|
262
|
+
- Out-of-process hooks (`@arnilo/prism-hooks`) are the opposite trade: they run in a separate
|
|
263
|
+
process, but with your user's privileges and an inherited environment, so the hash allowlist plus
|
|
264
|
+
the per-handler `timeout` are the trust boundary. `mcp_tool` handlers add the MCP client's own
|
|
265
|
+
transport and auth.
|
|
266
|
+
- Hooks run serially, awaited, once per natural loop end. Each continuation costs full provider
|
|
267
|
+
turns until the model stops naturally; size `maxStopContinuations` accordingly (`0` = observe
|
|
268
|
+
only). With no stop hooks configured, the wrapper is skipped entirely. `session_start` /
|
|
269
|
+
`session_shutdown` fire once per session, `compaction_request` / `compaction` once per compaction,
|
|
270
|
+
and tool seams once per tool call in their stage.
|
|
271
|
+
|
|
272
|
+
## Related APIs
|
|
273
|
+
|
|
274
|
+
- [Agent loops](agent-loops.md): loop strategies, turn policy, and `finishReason` ceilings.
|
|
275
|
+
- [Guardrails](guardrails.md): input/output/tool decisions, steer re-checking, and the `ask` rules that own approvals.
|
|
276
|
+
- [Middleware hooks](middleware-hooks.md): payload transforms at named boundaries, including the session and compaction seams.
|
|
277
|
+
- [Instruction injection](instruction-injection.md): where `additionalContext` from session and prompt hooks lands.
|
|
278
|
+
- [Extensions](extensions.md): `ExtensionAPI.registerStopHook()`, the contribution registries, and the `forwardAgentEvents()` bridge.
|
|
279
|
+
- [Runs and usage ledger](runs-and-usage.md): `maxStopContinuations` and clean stop reasons.
|
|
280
|
+
- [Agent/session runtime](agent-session-runtime.md): `session.close()`, steer queue semantics, and durable resume.
|
|
281
|
+
- [Agent events](agent-events.md): the events the extension bus observes, including `hook_limit`.
|
|
282
|
+
- [Public contracts](public-contracts.md): `RunOptions`, `RunLimits`, and `AgentConfig` surfaces.
|
package/docs/impeccable.md
CHANGED
|
@@ -98,7 +98,6 @@ Keep `skillsDisclosure: "progressive"` so the full `SKILL.md` stays catalog-only
|
|
|
98
98
|
|
|
99
99
|
## Related APIs
|
|
100
100
|
|
|
101
|
-
- [Caveman behavior integration](caveman.md)
|
|
102
|
-
- [Ponytail behavior integration](ponytail.md)
|
|
103
101
|
- [Extension kernel and event bus](extensions.md)
|
|
104
102
|
- [Context and skills](context-and-skills.md)
|
|
103
|
+
- [Contribution discovery](contribution-discovery.md): `loadSkillDirectory` for host-owned upstream skill trees (the removed Caveman/Ponytail pattern).
|
package/docs/index.md
CHANGED
|
@@ -2,7 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
Prism is a TypeScript/Node.js agent harness. Hosts own providers, tools, credentials, storage, and behavior; Prism supplies contracts, registries, events, and replaceable runtime primitives.
|
|
4
4
|
|
|
5
|
-
## Current line (0.
|
|
5
|
+
## Current line (0.11.0)
|
|
6
|
+
|
|
7
|
+
- **Memory-store branch reads**: the built-in memory session store implements `readBranchPath`. A snapshot walks the branch once and clones each kept entry once.
|
|
8
|
+
- **JSONL parse cache**: a read after an in-process append reuses the parsed file when size and mtime match. A same-size write inside one filesystem timestamp tick can still look unchanged.
|
|
9
|
+
- **Idempotency window**: memory and JSONL stores remember the latest 4,096 dedup keys. Replaying an older key appends a new entry instead of rejecting the write.
|
|
10
|
+
- **In-memory lease sweep**: expired lease rows are deleted once the map reaches 1,024. A swept key starts its next fence at 1. A released key still in the map keeps `fencingToken + 1`. SQLite and Postgres adapters still keep the counter on the row.
|
|
11
|
+
- **Shared text token estimate**: plain-text estimates use one `ceil(length/4)` helper. Message and entry estimates are unchanged.
|
|
12
|
+
- **12 publishable packages** at current **0.11.0** lockstep, with the migration guide reachable from the release section below — inventory below.
|
|
13
|
+
|
|
14
|
+
### Carried from the 0.10.0 line
|
|
6
15
|
|
|
7
16
|
- **Attention budget axes**: `attentionCompiler.trigger` accepts one axis, a predicate, or an any-of array (`input_ratio`, `run_input_ratio`, `token_floor`), and `durable: true` keeps the fold ledger and sticky frontier in the checkpoint across a resume.
|
|
8
17
|
- **Turn traces and exhaustion attribution**: `provider_turn_finished` carries a closed `stopReason`, a `budgets` snapshot, the effective tool menu (`count` / `idsHash`), and provider cache counts; `agent_finished` carries the run outcome, and the execution timeline adds per-turn stop reasons plus `exhaustion`.
|
|
@@ -17,8 +26,6 @@ Prism is a TypeScript/Node.js agent harness. Hosts own providers, tools, credent
|
|
|
17
26
|
- **Shared work scopes**: explicitly granted observational-memory scopes shared across sessions, deny-by-default, audited, and revocable at the next read; session-private scopes stay the default.
|
|
18
27
|
- **Retrieval revocation and local reranking**: deletion and revocation propagate through derived vector/wiki artifacts under bounded walks, and an in-process cross-encoder reranker ships with no declared inference dependency.
|
|
19
28
|
- **Live-stream terminal semantics**: one `isTerminalAgentEventType` predicate (`agent_finished` / `agent_denied` / `error`) shared by every source, so a limit death delivers `run_limit_exceeded` → `budget_exhausted` → `error` before a stream or replay ends.
|
|
20
|
-
- **11 publishable packages** at current **0.9.0** lockstep, with the migration guide reachable from the release section below — inventory below.
|
|
21
|
-
|
|
22
29
|
### Carried from the 0.8.0 line
|
|
23
30
|
|
|
24
31
|
- **Messaging channels**: `@arnilo/prism-channels` transport-neutral runtime with deny-by-default authorization, owned bindings, one-use durable approvals, official Telegram (private DMs, opt-in granted groups/topics, drafts, bounded media/voice, opt-in notices) and experimental pinned signal-cli Signal.
|
|
@@ -50,7 +57,6 @@ Prism is a TypeScript/Node.js agent harness. Hosts own providers, tools, credent
|
|
|
50
57
|
- **Peer and options truth**: the optional peer-dependency matrix and the configuration options index (both linked below) cover every third-party peer and public option surface (plan 070).
|
|
51
58
|
- **Trusted extension activation**: `activateKernel(kernel)` returns ready-to-spread `AgentConfig` contributions; CLI loads allow-listed `--extension` packages (plan 069).
|
|
52
59
|
- **Wiki ingest**: `/wiki-ingest` + `ingestWikiSource` stage text/file/image/PDF (and URLs via a host `fetchUrl` hook) into `raw/ingest/` with an OKF filing brief (plan 069).
|
|
53
|
-
- **Graft graph commands**: `/graft-init`, `/graft-build`, `/graft-build-deep` (host-configured `deepModel`, key env-only) (plan 069).
|
|
54
60
|
- **Run limits**: HARD caps are request/response bytes only; policy axes accept `null` (plan 067).
|
|
55
61
|
- **Tool-result fold**: content-only `ToolResult`s fold into `tool_result.result` (0.5.3).
|
|
56
62
|
- **Stream token coalesce**: adjacent text/thinking deltas merge on persist (0.5.2).
|
|
@@ -76,6 +82,7 @@ Prism is a TypeScript/Node.js agent harness. Hosts own providers, tools, credent
|
|
|
76
82
|
- [Durable runs](durable-runs.md): turn-boundary `checkpointPolicy: "every-turn"` checkpoints and `decision: "continue"` crash recovery for long runs.
|
|
77
83
|
- [Agent definitions](agent-definitions.md): declarative `AgentDefinition` resolution and `AGENT.md` bundle discovery, fail-closed activation.
|
|
78
84
|
- [Agent loops](agent-loops.md): replaceable loops with `limits.maxToolRounds` budgets and durable revision/restore hooks.
|
|
85
|
+
- [Hooks](hooks.md): the hook model — stop hooks with bounded continuation, session/compaction boundary seams, and the Claude Code / Codex event map plus the `hooks.json` adapter.
|
|
79
86
|
- [Guardrails](guardrails.md): typed fail-closed input/output/tool checks with redacted decision records.
|
|
80
87
|
- [Agent events](agent-events.md): `turn_started`/`tool_call_delta` stream plus durable page/resume sources for reconnect.
|
|
81
88
|
- [Observability](observability.md): OTel GenAI span hierarchy, workflow spans, cockpit aggregations, RAG span tree, bounded trace linkage, exporter isolation.
|
|
@@ -95,6 +102,7 @@ Prism is a TypeScript/Node.js agent harness. Hosts own providers, tools, credent
|
|
|
95
102
|
- [Observational memory compaction subpath](compaction-observational-memory.md): source-backed observations/reflections, an optional work-scope index for the current working set, and exact-id recall; `invalidatedIds` withhold derived injection.
|
|
96
103
|
- [Working and semantic memory](working-and-semantic-memory.md): working-memory store, semantic recall, pgvector path, consent lifecycle, lineage invalidation, parent-child share grants.
|
|
97
104
|
- [Memory fabric](memory-fabric.md): opt-in typed notes (fact/procedure/file/working/episode) with validity windows over the existing vector and working stores.
|
|
105
|
+
- [Scoped memory](scoped-memory.md): workspace-scope guard, gated writes, promotion ladder, decay reads, audit mirror (`@arnilo/prism-memory/scoped`).
|
|
98
106
|
- [Scoped agent memory design concept](scoped-agent-memory.md): workspace-scoped persistent memory — gated writes, promotion ladder, decay-based reads; case study and research basis.
|
|
99
107
|
- [Session stores](session-stores.md): `SessionStore` contract, append options, branches, bounded search — start here for persistence.
|
|
100
108
|
- [Conversations](conversations.md): durable user-scoped threads with versioned metadata and legal-hold-aware deletion.
|
|
@@ -222,7 +230,7 @@ Prism is a TypeScript/Node.js agent harness. Hosts own providers, tools, credent
|
|
|
222
230
|
|
|
223
231
|
## Testing and examples
|
|
224
232
|
|
|
225
|
-
- [Test layout and isolation](testing.md): the
|
|
233
|
+
- [Test layout and isolation](testing.md): the `npm test` stage table (build, performance budget, root, SQLite, gate, build-race, workspace, examples, and branch-coverage stages), scratch-root rule, and the tracked-fixture isolation gate.
|
|
226
234
|
- [Contribution quality budgets](contributing.md): the non-null assertion allowance, export-surface ceilings, and the rule that keeps them shrinking.
|
|
227
235
|
- [Live and end-to-end testing](live-testing.md): opt-in live matrix with skip-not-fail contract and credential scoping table.
|
|
228
236
|
- Provider test doubles: `createMockProvider()` and provider event helpers are documented on the canonical Provider layer page above.
|
|
@@ -233,13 +241,10 @@ Prism is a TypeScript/Node.js agent harness. Hosts own providers, tools, credent
|
|
|
233
241
|
- [Prefix stability conformance](prefix-stability-conformance.md): assert progressive disclosure keeps the provider cache prefix stable.
|
|
234
242
|
- [Tool conformance](tool-conformance.md): assert blocked-reason matrix and success-path dispatch behavior.
|
|
235
243
|
- [Extension conformance](extension-conformance.md): assert inert contributions and redacted setup errors.
|
|
236
|
-
- `examples/`: compile-checked typed examples ([`conversation-durable-replay.ts`](../examples/conversation-durable-replay.ts), [`artifact-review-delivery.ts`](../examples/artifact-review-delivery.ts), [`enterprise-identity.ts`](../examples/enterprise-identity.ts), [`enterprise-policy-audit.ts`](../examples/enterprise-policy-audit.ts), [`enterprise-work-connectors.ts`](../examples/enterprise-work-connectors.ts), [`connected-slack-mcp.ts`](../examples/connected-slack-mcp.ts), [`server-deployment-seams.ts`](../examples/server-deployment-seams.ts), [`neuralwatt-agent-run.ts`](../examples/neuralwatt-agent-run.ts), [`cache-aware-prompt-assembly.ts`](../examples/cache-aware-prompt-assembly.ts), [`ag-ui-server.ts`](../examples/ag-ui-server.ts), [`acp-coding-host.ts`](../examples/acp-coding-host.ts), [`telegram-agent.ts`](../examples/telegram-agent.ts), [`signal-agent.ts`](../examples/signal-agent.ts), [`messaging-agent.ts`](../examples/messaging-agent.ts), and more), plus runnable mock demos.
|
|
244
|
+
- `examples/`: compile-checked typed examples ([`conversation-durable-replay.ts`](../examples/conversation-durable-replay.ts), [`artifact-review-delivery.ts`](../examples/artifact-review-delivery.ts), [`enterprise-identity.ts`](../examples/enterprise-identity.ts), [`enterprise-policy-audit.ts`](../examples/enterprise-policy-audit.ts), [`enterprise-work-connectors.ts`](../examples/enterprise-work-connectors.ts), [`connected-slack-mcp.ts`](../examples/connected-slack-mcp.ts), [`server-deployment-seams.ts`](../examples/server-deployment-seams.ts), [`neuralwatt-agent-run.ts`](../examples/neuralwatt-agent-run.ts), [`cache-aware-prompt-assembly.ts`](../examples/cache-aware-prompt-assembly.ts), [`guardrail-packs.ts`](../examples/guardrail-packs.ts), [`ag-ui-server.ts`](../examples/ag-ui-server.ts), [`acp-coding-host.ts`](../examples/acp-coding-host.ts), [`telegram-agent.ts`](../examples/telegram-agent.ts), [`signal-agent.ts`](../examples/signal-agent.ts), [`messaging-agent.ts`](../examples/messaging-agent.ts), and more), plus runnable mock demos.
|
|
237
245
|
|
|
238
246
|
## Third-party integrations
|
|
239
247
|
|
|
240
|
-
- [Caveman behavior integration](caveman.md): upstream Caveman skills with injector, persistence, and progressive catalog.
|
|
241
|
-
- [Ponytail behavior integration](ponytail.md): upstream Ponytail skills with injector and peer resolution; opt-in.
|
|
242
|
-
- [Graft context-graph integration](graft.md): graft CLI pull tools, retrieval-pack context provider, blast-radius middleware, and `/graft-init` / `/graft-build` / `/graft-build-deep` commands (host-configured `deepModel`).
|
|
243
248
|
- [Impeccable behavior integration](impeccable.md): upstream Impeccable skill behind `load_skill`; host supplies the compiled `SKILL.md`.
|
|
244
249
|
- [Messaging channels](messaging-channels.md): `@arnilo/prism-channels` transport-neutral runtime — deny-by-default sender authorization, owned session binding, serialized turns, current-run replies, one-use durable approvals, bounded attachment refs (images reach the model only when it declares image input), and opt-in host notices to one already-bound pair.
|
|
245
250
|
- [Telegram channel](telegram-channel.md): official `@arnilo/prism-channels/telegram` long polling and mountable webhook ingress with durable offset/lease handling, approval callbacks, opt-in granted group/topic text, bounded media with optional voice transcription/synthesis, and opt-in streaming drafts.
|
|
@@ -249,6 +254,7 @@ Prism is a TypeScript/Node.js agent harness. Hosts own providers, tools, credent
|
|
|
249
254
|
## Release and install
|
|
250
255
|
|
|
251
256
|
- [Release and install](release-and-install.md): install rules, package graph, and deterministic resumable publication.
|
|
257
|
+
- [Migrate 0.10 → 0.11](migrate-to-0.11.md): idempotency window, in-memory lease fence reset, and the persona/graft subpath removals.
|
|
252
258
|
- [Migrate 0.8 → 0.9](migrate-to-0.9.md): the four behavior deltas inside existing surfaces (limit-death stream order, turn-trace metadata, cache-stable disclosure, labeled usage estimates), every new option with its sizing line, and 0.9.0 host migration steps.
|
|
253
259
|
- [Migrate 0.7 → 0.8](migrate-to-0.8.md): work-family import map, messaging channels, connected apps, durable runs, and 0.8.0 host migration steps.
|
|
254
260
|
- [Migrate 0.6 → 0.7](migrate-to-0.7.md): ACP MCP allow-list URL normalization, model router facade fail-closed governance, and 0.7.0 host migration steps.
|
|
@@ -262,19 +268,20 @@ Prism is a TypeScript/Node.js agent harness. Hosts own providers, tools, credent
|
|
|
262
268
|
The generated inventory below derives from [`scripts/package-truth.json`](../scripts/package-truth.json) — regenerate with `node scripts/package-truth.mjs --emit-docs`, never hand-edit.
|
|
263
269
|
|
|
264
270
|
<!-- generated:package-truth:inventory begin -->
|
|
265
|
-
**
|
|
271
|
+
**12 publishable manifests** — root `@arnilo/prism` plus 11 workspace packages (4 `prism-*` family packages, 7 capability packages). Generated by `node scripts/package-truth.mjs --emit-docs` — do not hand-edit.
|
|
266
272
|
|
|
267
273
|
| package | version | notes |
|
|
268
274
|
| --- | --- | --- |
|
|
269
|
-
| `@arnilo/prism` | 0.
|
|
270
|
-
| `@arnilo/prism-channels` | 0.
|
|
271
|
-
| `@arnilo/prism-coding-tools` | 0.
|
|
272
|
-
| `@arnilo/prism-core` | 0.
|
|
273
|
-
| `@arnilo/prism-providers` | 0.
|
|
274
|
-
| `@arnilo/prism-acp-agent` | 0.
|
|
275
|
-
| `@arnilo/prism-ag-ui` | 0.
|
|
276
|
-
| `@arnilo/prism-
|
|
277
|
-
| `@arnilo/prism-
|
|
278
|
-
| `@arnilo/prism-
|
|
279
|
-
| `@arnilo/prism-
|
|
275
|
+
| `@arnilo/prism` | 0.11.0 | core — runtime, CLI/RPC, templates, docs |
|
|
276
|
+
| `@arnilo/prism-channels` | 0.11.0 | family — transport-neutral messaging runtime, durable journal, pairing and one-use approvals; official /telegram (private DMs, opt-in granted groups/topics) and experimental pinned signal-cli /signal |
|
|
277
|
+
| `@arnilo/prism-coding-tools` | 0.11.0 | family — /agent, /security, /openapi, /computer-use-linux, /dev, /impeccable subpaths |
|
|
278
|
+
| `@arnilo/prism-core` | 0.11.0 | family — /runtime, /sessions, /governance, /credentials, /enterprise, /validation subpaths |
|
|
279
|
+
| `@arnilo/prism-providers` | 0.11.0 | family — all provider adapters as `/<adapter>` subpaths |
|
|
280
|
+
| `@arnilo/prism-acp-agent` | 0.11.0 | capability — ACP adapter |
|
|
281
|
+
| `@arnilo/prism-ag-ui` | 0.11.0 | capability — AG-UI/A2A/A2UI adapter |
|
|
282
|
+
| `@arnilo/prism-hooks` | 0.11.0 | capability — Claude/Codex-compatible hooks.json adapter compiled onto middleware, guardrail, injector, and stop-hook seams |
|
|
283
|
+
| `@arnilo/prism-mcp` | 0.11.0 | capability — MCP client/server/OAuth interop |
|
|
284
|
+
| `@arnilo/prism-memory` | 0.11.0 | capability — memory plus /rag, /compaction/*, /fabric, /wiki subpaths |
|
|
285
|
+
| `@arnilo/prism-web-tools` | 0.11.0 | capability — Brave/Exa/Firecrawl plus peer-gated /browser and /obscura subpaths |
|
|
286
|
+
| `@arnilo/prism-work` | 0.11.0 | capability — /connectors, /documents, /sheets, /diagrams, /document-reader, /sandbox, /skills, /tools subpaths |
|
|
280
287
|
<!-- generated:package-truth:inventory end -->
|
|
@@ -91,7 +91,7 @@ In cache-aware mode, leading system instructions form the stable boundary before
|
|
|
91
91
|
- Tool results are tool messages containing `tool_result` content; the agent/session runtime uses this to feed dispatched tool results into the next provider turn, placing the assistant `tool_call` and the matching role `tool` `tool_result` before any final assistant content. Cache-aware layout keeps tool results before the current user suffix so it does not split tool transcripts. A result with no `value`, no `type:text` content, and no error carries the constant `EMPTY_TOOL_RESULT_TEXT` (`"(tool completed with no output)"`) as its `result`, so no provider route serializes an empty or absent tool payload.
|
|
92
92
|
- Middleware runs only when `middleware` is supplied in the context.
|
|
93
93
|
- `assembleProviderInput()` returns a `ProviderRequest` with the caller's model/tools/provider options/metadata/signal and composed messages/context. It stamps missing `sessionId`/`cacheKey` via `applyDefaultProviderRequestOptions` when `sessionId` is passed (agent sessions always pass `session.id`). It also calls `assertMessagesSupportModelCapabilities()` so unsupported `audio`/`file`/`document`/`image` blocks fail with `UnsupportedModalityError` when the model declares `capabilities.input`.
|
|
94
|
-
- Optional `contextBudget` (at least one of `maxInputTokens` / `maxInputBytes`) runs after default message groups are built and before final flatten. `tokenEstimator` replaces the built-in ÷4 heuristic for **eviction accounting
|
|
94
|
+
- Optional `contextBudget` (at least one of `maxInputTokens` / `maxInputBytes`) runs after default message groups are built and before final flatten. Sessions forward `AgentConfig.contextBudget` here, so an agent-level budget gets the same semantics. `tokenEstimator` replaces the built-in ÷4 heuristic for **eviction accounting** — and, with `usageEstimation: "fallback"`, also feeds the missing-usage estimate ([Runs and usage](runs-and-usage.md#automatic-fallback-agentconfigusageestimation)); it never reaches billing, provider usage reports, or the wire, byte caps (`maxInputBytes`) stay estimator-independent and are always enforced, and an estimator that returns a non-finite or negative count (or is not a function) fails the assembly closed with `TypeError` instead of making eviction decisions unsound. Eviction drops droppable sections first (toolResults → history → summaries → context → skills → attachments; layout-aware). Within `history`, oldest messages drop first. Protected instructions + current user `input` (+ tools catalog) fail closed with `ContextBudgetError` if they alone exceed the budget. When `reportOmissions: true`, attach `ProviderRequest.metadata[CONTEXT_BUDGET_REPORT_METADATA_KEY]` and read via `getContextBudgetReport(request)` (kinds/ids/sizes only — no secrets). Raw session store entries are never deleted.
|
|
95
95
|
- `renderPromptTemplate()` replaces top-level `{{name}}` variables with caller-supplied JSON-compatible values. Strings are inserted directly; numbers, booleans, `null`, arrays, and objects are stringified deterministically with sorted object keys. Missing variables throw by default or stay unchanged with `{ missing: "preserve" }`.
|
|
96
96
|
|
|
97
97
|
## Request/response example
|
|
@@ -181,3 +181,4 @@ RPC: `prompt`/`followUp` params accept an optional `instructionInjectors: readon
|
|
|
181
181
|
- [Extensions](extensions.md): `registerInstructionInjector` in the contribution-kinds list.
|
|
182
182
|
- [CLI and RPC](cli-rpc.md): `--instruction`/`--injector-file` flags and the RPC `instructionInjectors` field.
|
|
183
183
|
- [Credentials and redaction](credentials-and-redaction.md): `createSecretRedactor`, `redactProviderRequest`, `redactAgentEvent`.
|
|
184
|
+
- [Hooks](hooks.md): the Claude Code / Codex event map, where hook `additionalContext` is queued for the next assembly.
|
package/docs/live-testing.md
CHANGED
|
@@ -87,8 +87,10 @@ Set only the rows you want to run; everything else skips. Least-privilege scope
|
|
|
87
87
|
| `providers/ollama` | active | `PRISM_LIVE_PROVIDER_TESTS` + `OLLAMA_BASE_URL` | `PRISM_LIVE_OLLAMA_MODEL` (default `(first model served by OLLAMA_BASE_URL)`) | No credential for local ollama serve; Ollama Cloud key optional via provider options. | 3-4 requests on the first locally pulled model. |
|
|
88
88
|
| `providers/ai-sdk` | active | `PRISM_LIVE_PROVIDER_TESTS` + `OPENAI_API_KEY` | `PRISM_LIVE_AISDK_MODEL` (default `gpt-5.1`) | Reuses OPENAI_API_KEY through the real @ai-sdk/openai provider. | 3 requests on gpt-5.1. |
|
|
89
89
|
| `providers/model-discovery` | active | `PRISM_LIVE_PROVIDER_TESTS`; any of: `OPENAI_API_KEY` / `GEMINI_API_KEY` | — | Reuses OPENAI_API_KEY or GEMINI_API_KEY for a real listing request. | 2 GET /models requests (second cached in TTL). |
|
|
90
|
+
| `calibration/vendor-count-tokens` | active | `PRISM_LIVE_PROVIDER_TESTS`; any of: `ANTHROPIC_API_KEY` / `GEMINI_API_KEY` / `GOOGLE_API_KEY` | `PRISM_LIVE_ANTHROPIC_MODEL` (default `claude-haiku-4-5`), `PRISM_LIVE_GOOGLE_MODEL` (default `gemini-2.5-flash-lite`) | Count-only token requests (no generation) on non-sensitive fixture text, using the chat key each vendor already needs. | 3 token-count requests per credentialed vendor (prose, CJK, chat transcript); no generation, no streaming. |
|
|
90
91
|
| `cli/live-journey` | planned | `PRISM_LIVE_PROVIDER_TESTS` + `OPENAI_API_KEY` | — | Packed CLI: init/provider-add/print/json/rpc over a real provider; transcript secret-scanned. | 1 pack + install, offline scaffold tests, <=3 wire prompts on the selected provider model (wire legs skip on 401/403). |
|
|
91
92
|
| `memory/rag-rerankers-live` | active | any of: `PRISM_TEST_TEI_RERANKER_URL` / `PRISM_TEST_HOSTED_RERANK_URL`; optional: `PRISM_TEST_HOSTED_RERANK_URL` | `PRISM_LIVE_TEI_RERANKER_MODEL` (default `(endpoint default model)`), `PRISM_LIVE_HOSTED_RERANK_MODEL` (default `(endpoint default model)`) | Real TEI / OpenAI-compatible rerank endpoints; each leg self-skips when its endpoint env is unset. | 1 rerank request per configured endpoint (≤2 total). |
|
|
93
|
+
| `memory/local-rerank-live` | active | `PRISM_TEST_LOCAL_RERANK`; optional: `PRISM_TEST_LOCAL_RERANK_CACHE_DIR` | `PRISM_LIVE_LOCAL_RERANK_MODEL` (default `Xenova/bge-reranker-base`) | In-process cross-encoder (transformers.js, q8/cpu) plus a semantic embedder on this machine; no service and no credential. Weights come from the documented model ids into a host cache dir; a cold run downloads a ≈280 MB reranker and a ≈23 MB embedder. | Two model downloads on a cold cache (≈280 MB reranker + ≈23 MB embedder); zero network after load, no API spend. ≈50 s wall clock after the cache is warm (the recall legs score 24 × 96 query/document pairs three times). |
|
|
92
94
|
| `memory/drive-sync-live` | active | `PRISM_TEST_DRIVE_ACCESS_TOKEN`; optional: `PRISM_TEST_DRIVE_FOLDER_ID` `PRISM_TEST_DRIVE_SHARED_DRIVE_ID` | — | Delegated Drive readonly token; least privilege: one throwaway folder. Folder/shared-drive ids optional. | <=2 Drive list/changes pages plus file media for that page; replay must not re-embed. |
|
|
93
95
|
| `coding-tools/openapi-live` | active | `PRISM_LIVE_OPENAPI_TOOLS` | — | Real public OpenAPI 3.1 spec (warnely.com) + real GET tool calls; no credential. | 3 HTTP requests against example.com-class public hosts (plan budget ≤5). |
|
|
94
96
|
| `coding-tools/computer-use-live` | active | `PRISM_TEST_COMPUTER_USE` + `PRISM_COMPUTER_USE_BIN` | — | Real host computer-use-linux MCP binary over stdio; real tool inventory + one bounded read-only screenshot. | Local desktop only; ≤30s ceiling. |
|
|
@@ -98,8 +100,7 @@ Set only the rows you want to run; everything else skips. Least-privilege scope
|
|
|
98
100
|
| `core/webhooks-live` | active | `PRISM_TEST_WEBHOOK_URL` | — | Operator-controlled signed webhook receiver. | <=3 webhook deliveries (1 signed target + 1 loopback retry receiver). |
|
|
99
101
|
| `core/artifact-bodies-s3-live` | active | `PRISM_TEST_S3_ENDPOINT` + `PRISM_TEST_S3_KEY` + `PRISM_TEST_S3_SECRET` + `PRISM_TEST_S3_BUCKET` | — | Dedicated throwaway S3-compatible bucket. | <=5 S3 requests (put/get/presign/delete x2). |
|
|
100
102
|
| `cli/journey` | active | `PRISM_LIVE_PROVIDER_TESTS`; any of: `OPENAI_API_KEY` / `OPENROUTER_API_KEY` / `KIMI_API_KEY` / `ZAI_API_KEY` / `OPENCODE_API_KEY` / `NEURALWATT_API_KEY` / `DASHSCOPE_API_KEY` / `OLLAMA_API_KEY` | — | First init-catalog provider credential present in the environment. | 4-5 one-shot requests on the default catalog model. |
|
|
101
|
-
| `memory/postgres` | active | `PRISM_TEST_POSTGRES_URL` | — | Postgres memory store + pgvector index round-trips on the operator database. | Bounded insert/query cycles against the configured Postgres. |
|
|
102
|
-
| `memory/graft` | active | — (hermetic leg) | — | Graft upstream CLI child-process protocol (real binary spawns against the in-repo fixture graft bin). | Hermetic; no network. |
|
|
103
|
+
| `memory/postgres` | active | `PRISM_TEST_POSTGRES_URL` | — | Postgres memory store + pgvector index round-trips on the operator database, including durable deletion propagation (1,000 derived rows tombstoned in one transaction) and grant-change re-pointing. | Bounded insert/query cycles against the configured Postgres. |
|
|
103
104
|
| `memory/wiki` | active | — (hermetic leg) | — | Wiki lifecycle over real fs trees (init/refresh/lint/search fallback; qmd child-process client degrades without the binary). | Hermetic; no network. |
|
|
104
105
|
| `coding-tools/lsp-forge` | active | — (hermetic leg) | — | LSP/language-intelligence + forge suites: real child-process spawns over the real LSP/forge wire protocols against fixture binaries. | Hermetic; no network. |
|
|
105
106
|
| `ag-ui/conformance` | active | — (hermetic leg) | — | AG-UI + ACP conformance suites: real-event replay over the acp/a2a/ag-ui protocol surfaces (fixture agents, real event-source wire semantics). | Hermetic; no network. |
|
package/docs/memory-fabric.md
CHANGED
|
@@ -57,6 +57,35 @@ keyed by block label, and `episode` views write nothing. A rewrite keeps the row
|
|
|
57
57
|
consent, importance, and every non-fabric metadata key; an explicit `id` or `supersedes` from the
|
|
58
58
|
caller disables auto-folding for that write.
|
|
59
59
|
|
|
60
|
+
### Following the file (path moves and deletes)
|
|
61
|
+
|
|
62
|
+
A `file` note names its document by `metadata.path`, and that path — not a lineage edge — is the only
|
|
63
|
+
link between note and file. Notes are store-backed metadata rather than derived chunk rows, so the
|
|
64
|
+
`_lineage` walk that revokes derived records can never find them: this handler is the only path.
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import { createFabricRepointHandler } from "@arnilo/prism-memory/fabric";
|
|
68
|
+
|
|
69
|
+
// One handler, both seams: a move rewrites `metadata.path`, a delete tombstones.
|
|
70
|
+
const notes = createFabricRepointHandler({ scope, vectorStore: store });
|
|
71
|
+
await repointSource({ scope, vectorStore: store, from: "docs/a.md", to: "docs/b.md", authorization: principal, handlers: [notes] });
|
|
72
|
+
await createDeletionPropagator({ scope, vectorStore: store, authorization: principal, handlers: [notes] }).propagate("docs/a.md");
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
- Move: only `kind: "file"` notes whose `path` is the moved id are touched. Id, text, embedding,
|
|
76
|
+
`sourceEntryIds`, and every other metadata field are reused verbatim — no re-embed, no re-score, and
|
|
77
|
+
no `_lineage` field invented, so the walk stays for records that really are derived. Non-file notes
|
|
78
|
+
and notes for other paths are untouched, and the write joins one store transaction when the store
|
|
79
|
+
has one (a plain `upsert` otherwise).
|
|
80
|
+
- Delete: the notes recorded against the deleted path are tombstoned through the store's own
|
|
81
|
+
invalidation path (`invalidate`, batched at `HARD_INVALIDATION_BATCH`; the propagation's resolved
|
|
82
|
+
reason wins, `forgotten` by default, `legal_hold` stamps `hold: true` — the handler's own `reason`
|
|
83
|
+
option is only the fallback for a hand-built context), so recall stops serving them
|
|
84
|
+
with no second revocation plane and no background cleanup to wait for.
|
|
85
|
+
- One scope read per leg, selecting on `metadata.fabric.path` — never on content. A note in another
|
|
86
|
+
scope is never visible, and a composition whose scope differs from the handler's is refused
|
|
87
|
+
(`MemoryScopeError`) instead of writing across threads.
|
|
88
|
+
|
|
60
89
|
### Workers (opt-in)
|
|
61
90
|
|
|
62
91
|
Both workers run inline (awaited) after the write, see redacted text only, call no model and no
|