@arnilo/prism 0.0.1 → 0.0.3
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 +19 -2
- package/README.md +17 -7
- package/dist/agent-definitions.d.ts +12 -0
- package/dist/agent-definitions.js +131 -0
- package/dist/agent-loops.d.ts +14 -0
- package/dist/agent-loops.js +161 -0
- package/dist/agents.js +263 -76
- package/dist/cache-helpers.d.ts +28 -0
- package/dist/cache-helpers.js +73 -0
- package/dist/cli-runner.d.ts +38 -2
- package/dist/cli-runner.js +167 -5
- package/dist/compaction.js +2 -0
- package/dist/config.js +47 -12
- package/dist/contracts.d.ts +581 -6
- package/dist/contracts.js +41 -1
- package/dist/contribution-parsing.d.ts +19 -0
- package/dist/contribution-parsing.js +124 -0
- package/dist/contributions.d.ts +13 -3
- package/dist/contributions.js +96 -20
- package/dist/extensions.js +3 -0
- package/dist/index.d.ts +19 -9
- package/dist/index.js +10 -4
- package/dist/input.d.ts +7 -1
- package/dist/input.js +52 -11
- package/dist/instruction-injection.d.ts +28 -0
- package/dist/instruction-injection.js +55 -0
- package/dist/manifests.d.ts +1 -1
- package/dist/manifests.js +3 -3
- package/dist/models.d.ts +4 -1
- package/dist/models.js +5 -2
- package/dist/node/agent-definitions.d.ts +98 -0
- package/dist/node/agent-definitions.js +389 -0
- package/dist/node/contribution-discovery.d.ts +17 -0
- package/dist/node/contribution-discovery.js +163 -0
- package/dist/node/instruction-injectors.d.ts +32 -0
- package/dist/node/instruction-injectors.js +72 -0
- package/dist/node/session-store-jsonl.d.ts +1 -1
- package/dist/node/session-store-jsonl.js +42 -4
- package/dist/node/system-project-prompts.d.ts +30 -0
- package/dist/node/system-project-prompts.js +53 -0
- package/dist/provider-events.d.ts +3 -1
- package/dist/provider-events.js +34 -0
- package/dist/provider-request-policy.js +15 -1
- package/dist/providers/openai-compatible.js +1 -1
- package/dist/providers.d.ts +6 -2
- package/dist/providers.js +15 -1
- package/dist/redaction.d.ts +2 -1
- package/dist/redaction.js +3 -0
- package/dist/registry-options.d.ts +5 -0
- package/dist/registry-options.js +5 -0
- package/dist/rpc.d.ts +6 -2
- package/dist/rpc.js +71 -13
- package/dist/session-stores.d.ts +3 -1
- package/dist/session-stores.js +67 -6
- package/dist/skills.d.ts +4 -1
- package/dist/skills.js +3 -1
- package/dist/system-prompts.js +6 -2
- package/dist/testing/compaction-conformance.d.ts +17 -0
- package/dist/testing/compaction-conformance.js +61 -0
- package/dist/testing/extension-conformance.d.ts +26 -0
- package/dist/testing/extension-conformance.js +55 -0
- package/dist/testing/provider-conformance.d.ts +7 -0
- package/dist/testing/provider-conformance.js +18 -31
- package/dist/testing/session-store-conformance.d.ts +20 -0
- package/dist/testing/session-store-conformance.js +92 -0
- package/dist/testing/tool-conformance.d.ts +39 -0
- package/dist/testing/tool-conformance.js +79 -0
- package/dist/tools.d.ts +7 -2
- package/dist/tools.js +50 -13
- package/docs/agent-definitions.md +251 -0
- package/docs/agent-events.md +199 -0
- package/docs/agent-loops.md +217 -0
- package/docs/agent-session-runtime.md +20 -8
- package/docs/cli-rpc.md +39 -4
- package/docs/coding-agent-tools.md +208 -0
- package/docs/compaction-and-retry.md +2 -2
- package/docs/compaction-conformance.md +76 -0
- package/docs/compaction-llm.md +6 -3
- package/docs/compaction-observational-memory.md +4 -4
- package/docs/configuration-and-manifests.md +6 -1
- package/docs/context-and-skills.md +79 -6
- package/docs/contribution-discovery.md +149 -0
- package/docs/contribution-registries.md +9 -6
- package/docs/credentials-and-redaction.md +2 -0
- package/docs/customization.md +191 -0
- package/docs/database-persistence.md +407 -0
- package/docs/extension-authoring.md +193 -0
- package/docs/extension-conformance.md +80 -0
- package/docs/extensions.md +6 -0
- package/docs/host-security.md +141 -0
- package/docs/index.md +41 -19
- package/docs/input-and-prompt-assembly.md +19 -3
- package/docs/instruction-injection.md +183 -0
- package/docs/migration.md +201 -0
- package/docs/model-registry.md +122 -0
- package/docs/node-jsonl-session-store.md +5 -4
- package/docs/performance.md +127 -0
- package/docs/provider-caching.md +206 -0
- package/docs/provider-conformance.md +32 -5
- package/docs/provider-layer.md +51 -11
- package/docs/provider-packages.md +65 -5
- package/docs/provider-request-policies.md +113 -0
- package/docs/providers/kimi.md +22 -0
- package/docs/providers/neuralwatt.md +388 -0
- package/docs/providers/openai-compatible.md +1 -0
- package/docs/providers/openai.md +21 -0
- package/docs/providers/opencode-go.md +31 -3
- package/docs/providers/openrouter.md +29 -0
- package/docs/providers/zai.md +17 -0
- package/docs/public-contracts.md +87 -12
- package/docs/release-and-install.md +79 -27
- package/docs/runs-and-usage.md +236 -0
- package/docs/session-store-conformance.md +78 -0
- package/docs/session-stores-and-branching.md +10 -6
- package/docs/session-stores.md +126 -0
- package/docs/settings-auth-trust-security.md +18 -4
- package/docs/structured-output.md +247 -0
- package/docs/system-prompts.md +104 -2
- package/docs/tool-conformance.md +87 -0
- package/docs/tools.md +65 -8
- package/package.json +36 -2
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# Coding agent tools (first-party package)
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
`@arnilo/prism-coding-agent` is an optional first-party package that provides host shell/filesystem tools as Prism `ToolDefinition` objects. It ships four tools — `shell`, `read`, `write`, `edit` — plus aggregator factories. The tools are **inert** until a host imports them and registers them into a `ToolRegistry`. Behavior is a behavioral port of the pi coding agent's `bash`/`read`/`write`/`edit` tools, adapted to Prism's `ToolDefinition` / `ToolResult` contracts (no `@earendil-works/*` or `typebox` dependencies; only `diff` plus the Node standard library).
|
|
6
|
+
|
|
7
|
+
| Export | Purpose |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| `createShellTool(cwd, options?)` | `shell` tool: run a shell command and return combined output + exit code. |
|
|
10
|
+
| `createReadTool(cwd, options?)` | `read` tool: read a text or image file into `TextContent` / `ImageContent`. |
|
|
11
|
+
| `createWriteTool(cwd, options?)` | `write` tool: create or overwrite a file, creating parent directories. |
|
|
12
|
+
| `createEditTool(cwd, options?)` | `edit` tool: precise exact-then-fuzzy text replacement in an existing file. |
|
|
13
|
+
| `createCodingTools(cwd, options?)` | All four tools (`shell`, `read`, `write`, `edit`). |
|
|
14
|
+
| `createReadOnlyTools(cwd, options?)` | Read-only subset: `read` only. |
|
|
15
|
+
| `createAllTools(cwd, options?)` | Every tool the package provides (currently identical to `createCodingTools`). |
|
|
16
|
+
| `detectSupportedImageMimeType(buf)` / `detectSupportedImageMimeTypeFromFile(path)` | Magic-byte image MIME detection (PNG/JPEG/GIF/WebP/BMP) used by `read`. |
|
|
17
|
+
| `withFileMutationQueue(path, fn)` | Per-path serialization primitive re-exported for hosts. |
|
|
18
|
+
|
|
19
|
+
Each factory returns a plain `ToolDefinition` (no auto-registration). Register what you need:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { createToolRegistry } from "@arnilo/prism";
|
|
23
|
+
import { createCodingTools } from "@arnilo/prism-coding-agent";
|
|
24
|
+
|
|
25
|
+
const tools = createToolRegistry(createCodingTools(process.cwd()));
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## When to use it
|
|
29
|
+
|
|
30
|
+
Use this package when a host wants ready-made coding tools for an agent, session, or run, registered explicitly into a `ToolRegistry` and dispatched through the normal Prism tool harness. The tools perform **real** shell and filesystem operations on the host — they are not mocked or sandboxed. Use the individual factories when you need per-tool options or custom operation backends; use the aggregators when you want the default set.
|
|
31
|
+
|
|
32
|
+
Do not use this package as a sandbox, permission policy, secret store, or provider loop. Prism gates tool dispatch with `PermissionPolicy` / `ToolValidator` / trust policies; the package performs no gating of its own. Do not register these tools for an untrusted provider.
|
|
33
|
+
|
|
34
|
+
### pi name mapping
|
|
35
|
+
|
|
36
|
+
| Prism (`@arnilo/prism-coding-agent`) | pi coding agent |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| `shell` | `bash` |
|
|
39
|
+
| `read` | `read` |
|
|
40
|
+
| `write` | `write` |
|
|
41
|
+
| `edit` | `edit` |
|
|
42
|
+
|
|
43
|
+
## Tools
|
|
44
|
+
|
|
45
|
+
### `shell`
|
|
46
|
+
|
|
47
|
+
Run a shell command and return combined stdout+stderr.
|
|
48
|
+
|
|
49
|
+
**Inputs:**
|
|
50
|
+
|
|
51
|
+
| Field | Type | Purpose |
|
|
52
|
+
| --- | --- | --- |
|
|
53
|
+
| `command` | `string` | Shell command to execute (required). |
|
|
54
|
+
| `timeout` | `number` | Timeout in **seconds** (optional; no default). |
|
|
55
|
+
|
|
56
|
+
**Outputs:** a `ToolResult` whose `content[0]` is a `TextContent` with the combined output. Non-zero exit is **not** a tool error: it is returned as a normal result with `[Command exited with code N]` appended to the content and `exitCode` in metadata. Timeout and abort are error results that still carry the partial output captured so far.
|
|
57
|
+
|
|
58
|
+
`shell` result `metadata`:
|
|
59
|
+
|
|
60
|
+
| Field | Present when | Purpose |
|
|
61
|
+
| --- | --- | --- |
|
|
62
|
+
| `exitCode` | always | Process exit code, or `null` when the process was killed by timeout/abort. |
|
|
63
|
+
| `truncation` | always | `TruncationResult` from the bounded output accumulator. |
|
|
64
|
+
| `fullOutputPath?` | truncated only | Path to the spilled temp file holding the full output. |
|
|
65
|
+
|
|
66
|
+
Shell resolution honors `options.shellPath` → `SHELL` env → `/bin/bash` → `sh`, and the process group is killed on timeout/abort (`process.kill(-pid)` on Unix, `taskkill /F /T` on Windows).
|
|
67
|
+
|
|
68
|
+
### `read`
|
|
69
|
+
|
|
70
|
+
Read a text or image file.
|
|
71
|
+
|
|
72
|
+
**Inputs:**
|
|
73
|
+
|
|
74
|
+
| Field | Type | Purpose |
|
|
75
|
+
| --- | --- | --- |
|
|
76
|
+
| `path` | `string` | Path to the file (relative or absolute; `~` and `file://` expanded). Required. |
|
|
77
|
+
| `offset` | `number` | Line to start reading from (1-indexed). |
|
|
78
|
+
| `limit` | `number` | Maximum number of lines to read. |
|
|
79
|
+
|
|
80
|
+
**Outputs:** text files become a single `TextContent`, truncated to `maxLines`/`maxBytes` (defaults 2000 lines / 50 KB) with a `Use offset=N to continue` footer when more remains. Image files (PNG/JPEG/GIF/WebP/BMP by magic bytes) become `[TextContent note, ImageContent]` with base64 `data` and `mimeType`. Read failures (missing file, offset beyond end, abort) are error results.
|
|
81
|
+
|
|
82
|
+
`read` result `metadata`:
|
|
83
|
+
|
|
84
|
+
| Field | Present when | Purpose |
|
|
85
|
+
| --- | --- | --- |
|
|
86
|
+
| `truncation` | text reads | `TruncationResult`. |
|
|
87
|
+
| `image` | image reads | `{ mimeType, resized: false }`. |
|
|
88
|
+
|
|
89
|
+
> `autoResizeImages` is accepted but is currently a documented no-op (deferred); images are returned at their original size with `image.resized = false`.
|
|
90
|
+
|
|
91
|
+
### `write`
|
|
92
|
+
|
|
93
|
+
Create or overwrite a file, creating parent directories as needed.
|
|
94
|
+
|
|
95
|
+
**Inputs:**
|
|
96
|
+
|
|
97
|
+
| Field | Type | Purpose |
|
|
98
|
+
| --- | --- | --- |
|
|
99
|
+
| `path` | `string` | Path to the file to write (relative or absolute). Required. |
|
|
100
|
+
| `content` | `string` | Content to write (empty string creates an empty file). Required. |
|
|
101
|
+
|
|
102
|
+
**Outputs:** a `TextContent` confirmation naming the **absolute path** with UTF-8 byte and line counts (e.g. `Successfully wrote 42 bytes (3 lines) to /abs/path.txt`). Write failures and abort are error results. Empty `content` is valid.
|
|
103
|
+
|
|
104
|
+
`write` result `metadata`: `{ bytes, lines, path }` (absolute path). Concurrent writes to the same path serialize through `withFileMutationQueue`; writes to different paths run in parallel.
|
|
105
|
+
|
|
106
|
+
### `edit`
|
|
107
|
+
|
|
108
|
+
Precise text replacement in an existing file via exact-then-fuzzy matching.
|
|
109
|
+
|
|
110
|
+
**Inputs:**
|
|
111
|
+
|
|
112
|
+
| Field | Type | Purpose |
|
|
113
|
+
| --- | --- | --- |
|
|
114
|
+
| `path` | `string` | Path to the file to edit. Required. |
|
|
115
|
+
| `edits` | `Array<{ oldText: string, newText: string }>` | Targeted replacements, each matched against the **original** file (not incrementally). No overlapping/nested edits. Required, non-empty. |
|
|
116
|
+
|
|
117
|
+
Each `edits[].oldText` must match a unique, non-overlapping region of the original file. Matching is exact first, then fuzzy (unicode normalization / whitespace collapse). A BOM is stripped before matching and re-prepended on write; original line endings are restored.
|
|
118
|
+
|
|
119
|
+
**Outputs:** a `TextContent` confirmation (`Successfully replaced N block(s) in {path}.`) plus `metadata`. Any failure — missing/unreadable file, no match, duplicate (non-unique) match, overlap, empty `oldText`, no-op edit, or abort — is an error result, and the file is left **unchanged** (the match runs before the write).
|
|
120
|
+
|
|
121
|
+
`edit` result `metadata`: `{ diff, patch, firstChangedLine }` — a display-oriented diff, a standard unified patch, and the first changed line in the new file. These are host-readable; the model only sees the short confirmation (keeps model context small).
|
|
122
|
+
|
|
123
|
+
## Outputs / response / events
|
|
124
|
+
|
|
125
|
+
Every tool returns a `ToolResult` with `toolCallId`, `name`, `content` (`readonly ContentBlock[]`), optional `error`, and optional `metadata`. Mutating tools (`shell` with same cwd, `write`, `edit`) serialize per realpath through `withFileMutationQueue` so concurrent calls targeting one file do not interleave. The package emits no events of its own; hosts observe tool execution through the normal Prism `AgentEvent` stream via `dispatchToolCall`.
|
|
126
|
+
|
|
127
|
+
## Request/response example
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
// edit request
|
|
131
|
+
{ "path": "src/app.ts", "edits": [{ "oldText": "const x = 1;", "newText": "const x = 2;" }] }
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
// edit success result
|
|
136
|
+
{
|
|
137
|
+
"toolCallId": "call_1",
|
|
138
|
+
"name": "edit",
|
|
139
|
+
"content": [{ "type": "text", "text": "Successfully replaced 1 block(s) in src/app.ts." }],
|
|
140
|
+
"metadata": { "diff": "...", "patch": "--- src/app.ts\n+++ src/app.ts\n...", "firstChangedLine": 3 }
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
```json
|
|
145
|
+
// edit no-match result (file unchanged)
|
|
146
|
+
{
|
|
147
|
+
"toolCallId": "call_2",
|
|
148
|
+
"name": "edit",
|
|
149
|
+
"error": { "message": "Could not find edits[0] in src/app.ts. The oldText must match exactly including all whitespace and newlines." }
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Implementation example
|
|
154
|
+
|
|
155
|
+
Minimal drop-in for any Prism app:
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
import { createToolRegistry } from "@arnilo/prism";
|
|
159
|
+
import { createCodingTools, createReadOnlyTools } from "@arnilo/prism-coding-agent";
|
|
160
|
+
|
|
161
|
+
// Full coding set (shell + read + write + edit) against the project root:
|
|
162
|
+
const tools = createToolRegistry(createCodingTools(process.cwd()));
|
|
163
|
+
|
|
164
|
+
// Or a read-only set for inspection-only agents:
|
|
165
|
+
const ro = createToolRegistry(createReadOnlyTools(process.cwd()));
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Customizing a single tool (force bash, cap output, delegate writes to a remote backend):
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
import { createShellTool, createWriteTool } from "@arnilo/prism-coding-agent";
|
|
172
|
+
|
|
173
|
+
const shell = createShellTool("/repo", {
|
|
174
|
+
shellPath: "/bin/bash",
|
|
175
|
+
commandPrefix: "set -euo pipefail",
|
|
176
|
+
maxLines: 500,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const remoteWrite = createWriteTool("/repo", {
|
|
180
|
+
operations: {
|
|
181
|
+
writeFile: async (abs, content) => { /* ship to remote */ },
|
|
182
|
+
mkdir: async (dir) => { /* mkdir -p remotely */ },
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Extension and configuration notes
|
|
188
|
+
|
|
189
|
+
- **Pluggable operation backends.** Every tool accepts an `operations` seam so a host can delegate to a remote system (e.g. SSH) while keeping the tool's matching/serialization behavior: `BashOperations` (`shell`), `ReadOperations` (`read`), `WriteOperations` (`write`), `EditOperations` (`edit`).
|
|
190
|
+
- **Per-tool options.** `ShellToolOptions` (`shellPath`, `commandPrefix`, `maxLines`, `maxBytes`, `tempFilePrefix`, `operations`, `spawnHook`); `ReadToolOptions` (`operations`, `autoResizeImages`, `maxLines`, `maxBytes`); `WriteToolOptions` (`operations`); `EditToolOptions` (`operations`).
|
|
191
|
+
- **Aggregator options.** `ToolsOptions` (`{ shell?, read?, write?, edit? }`) threads each sub-object to the matching tool.
|
|
192
|
+
- **`ToolsOptions`** and the per-tool option types are exported from the package barrel for host configuration.
|
|
193
|
+
- No auto-discovery or manifest registration: import and register explicitly. This package registers no extensions and owns no globals (the mutation queue is a process-wide per-path map — see `ponytail:` note in the source).
|
|
194
|
+
|
|
195
|
+
## Security and performance notes
|
|
196
|
+
|
|
197
|
+
- **Host shell/filesystem access.** These tools run real commands and read/write real files. They provide **no sandbox**. Gate them with Prism `PermissionPolicy` / `ToolValidator` / trust policies before registering them for any provider turn. See [Host security guide](host-security.md) and [Security/auth/trust](settings-auth-trust-security.md).
|
|
198
|
+
- **Non-zero exit is not an error.** A failing command is a normal `shell` result (exit code in metadata); only timeout/abort/spawn failures are error results. Do not assume `error == undefined` means the command succeeded.
|
|
199
|
+
- **Bounded output.** `shell`/`read` accumulate output into a rolling tail bounded by `maxLines`/`maxBytes`; oversized output spills to a temp file (`fullOutputPath`), so memory use is bounded regardless of command output size.
|
|
200
|
+
- **Per-path serialization.** Concurrent mutations to the same file serialize; concurrent mutations to different files do not block each other. The queue is a process-wide map — across sessions in one process, same-path writes still serialize (upgrade path: scope per registry if throughput matters).
|
|
201
|
+
- **Single runtime dependency.** `diff` (for `edit` unified patch/diff generation) plus the Node standard library. No native modules; no image-processing native dependency (image auto-resize is deferred, so no `sharp`/WASM dependency).
|
|
202
|
+
|
|
203
|
+
## Related APIs
|
|
204
|
+
|
|
205
|
+
- [Tools](tools.md): the host-owned tool harness — `createToolRegistry`, `dispatchToolCall`, filtering, and the `ToolDefinition` contract these factories satisfy.
|
|
206
|
+
- [Public contracts](public-contracts.md): `ToolDefinition`, `ToolResult`, `ToolExecutionContext`, `ContentBlock`, and `JsonObject` shapes.
|
|
207
|
+
- [Host security guide](host-security.md): fail-closed checklist for permission policies, tool validation, and trust boundaries that must gate these tools.
|
|
208
|
+
- [Tool conformance](tool-conformance.md): assertions for the tool-dispatch blocked-reason matrix these tools participate in.
|
|
@@ -145,11 +145,11 @@ await session.compact({ keepRecentEntries: 4 });
|
|
|
145
145
|
|
|
146
146
|
## Extension and configuration notes
|
|
147
147
|
|
|
148
|
-
Retry policies are ordinary `RetryPolicy` implementations and can be registered as `retryPolicy` contributions. Hosts must still pass the selected policy/config to `createAgent()` or `session.run()`. Retry middleware receives `{ context, decision }` before a retry is scheduled and can reduce delay or stop retrying.
|
|
148
|
+
Retry policies are ordinary `RetryPolicy` implementations and can be registered as `retryPolicy` contributions. Hosts must still pass the selected policy/config to `createAgent()` or `session.run()`. Retry middleware receives `{ context, decision }` before a retry is scheduled and can reduce delay or stop retrying. First-party providers emit `ErrorInfo.code` as the numeric HTTP status so the default policy's transient-code set (`429`/`500`/`502`/`503`) classifies retryability without provider-specific core branches; `@arnilo/prism-provider-neuralwatt` additionally exports `classifyNeuralWattError()` for hosts that want structured `Retry-After`/`retry_strategy` metadata.
|
|
149
149
|
|
|
150
150
|
Compaction strategies are ordinary `CompactionStrategy` implementations. Extensions can register strategies through the existing compaction strategy contribution registry, but registration is inert until a host explicitly selects and passes a strategy to runtime code. Extensions can also register `compaction` middleware; the runtime calls it only when the agent/session has that middleware registry configured.
|
|
151
151
|
|
|
152
|
-
The default strategy does not call a provider. Hosts that need model-generated summaries can use the optional [`@arnilo/prism-compaction-llm` package](compaction-llm.md). Hosts that need prepared source-backed memory without a compaction-time model call can use [`@arnilo/prism-compaction-observational-memory`](compaction-observational-memory.md).
|
|
152
|
+
The default strategy does not call a provider. Hosts that need model-generated summaries can use the optional [`@arnilo/prism-compaction-llm` package](compaction-llm.md); its `maxOutputTokens`/`maxSummaryTokens` budget is passed through `model.parameters.maxTokens` and first-party providers serialize that to provider output-token fields. Hosts that need prepared source-backed memory without a compaction-time model call can use [`@arnilo/prism-compaction-observational-memory`](compaction-observational-memory.md).
|
|
153
153
|
|
|
154
154
|
## Security and performance notes
|
|
155
155
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Compaction conformance
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
Compaction conformance helpers are dependency-free assertions for `CompactionStrategy` adapter tests. They exercise the summary-result shape, secret redaction, and abort observation of any `CompactionStrategy` without network or credentials.
|
|
6
|
+
|
|
7
|
+
Exported from `@arnilo/prism/testing/compaction-conformance`:
|
|
8
|
+
|
|
9
|
+
- `assertCompactionStrategyConforms(strategy, options?)`
|
|
10
|
+
- `CompactionConformanceOptions`
|
|
11
|
+
|
|
12
|
+
## When to use it
|
|
13
|
+
|
|
14
|
+
Use this helper when implementing a custom `CompactionStrategy` (the core default strategy and the first-party LLM compaction package both conform). It asserts:
|
|
15
|
+
|
|
16
|
+
- `compact()` returns a `CompactionResult` with a non-empty string `summary`
|
|
17
|
+
- known secrets are redacted from the summary and any returned `entries`
|
|
18
|
+
- (when `exerciseAbort: true`) an already-aborted `signal` is observed
|
|
19
|
+
|
|
20
|
+
## Inputs / request
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { assertCompactionStrategyConforms } from "@arnilo/prism/testing/compaction-conformance";
|
|
24
|
+
import type { CompactionStrategy } from "@arnilo/prism";
|
|
25
|
+
|
|
26
|
+
const { summary } = await assertCompactionStrategyConforms(myStrategy, {
|
|
27
|
+
secrets: ["api-key-value"],
|
|
28
|
+
exerciseAbort: true,
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`CompactionConformanceOptions`:
|
|
33
|
+
- `secrets?: readonly string[]` — secrets that must not appear in the summary or returned entries
|
|
34
|
+
- `exerciseAbort?: boolean` — assert the strategy observes an already-aborted `signal`
|
|
35
|
+
|
|
36
|
+
## Outputs / response / events
|
|
37
|
+
|
|
38
|
+
Returns `Promise<{ summary: string }>`; throws a plain `Error` on the first contract violation. No events, no runner.
|
|
39
|
+
|
|
40
|
+
## Request/response example
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { assertCompactionStrategyConforms } from "@arnilo/prism/testing/compaction-conformance";
|
|
44
|
+
|
|
45
|
+
await assertCompactionStrategyConforms(myStrategy, { secrets: ["secret-value"] });
|
|
46
|
+
// throws if the summary is empty, a secret leaks into the summary, or a
|
|
47
|
+
// secret leaks into the returned entries.
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Implementation example
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { assertCompactionStrategyConforms } from "@arnilo/prism/testing/compaction-conformance";
|
|
54
|
+
import { createDefaultCompactionStrategy } from "@arnilo/prism";
|
|
55
|
+
|
|
56
|
+
await assertCompactionStrategyConforms(
|
|
57
|
+
createDefaultCompactionStrategy({ keepRecentEntries: 1, secrets: ["secret-value"] }),
|
|
58
|
+
{ secrets: ["secret-value"] },
|
|
59
|
+
);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Extension and configuration notes
|
|
63
|
+
|
|
64
|
+
- The helper builds a tiny two-message fixture; it does not call your strategy with production entries.
|
|
65
|
+
- Abort observation is optional because not every strategy performs async work that can be aborted.
|
|
66
|
+
|
|
67
|
+
## Security and performance notes
|
|
68
|
+
|
|
69
|
+
- No credentials, no network, no real secrets required; pass fake secret strings.
|
|
70
|
+
- The helper asserts redaction of exactly the secrets you supply (mirroring `createSecretRedactor`'s exact-match behavior); it does not detect arbitrary secret patterns.
|
|
71
|
+
|
|
72
|
+
## Related APIs
|
|
73
|
+
|
|
74
|
+
- [Compaction and retry](compaction-and-retry.md)
|
|
75
|
+
- [Provider conformance](provider-conformance.md)
|
|
76
|
+
- [Session store conformance](session-store-conformance.md)
|
package/docs/compaction-llm.md
CHANGED
|
@@ -33,7 +33,7 @@ Key exports:
|
|
|
33
33
|
| `thinkingLevel` | Passed as `ProviderRequest.options.extra.thinkingLevel`. |
|
|
34
34
|
| `reserveTokens` | Output budget basis; defaults to `16384`. |
|
|
35
35
|
| `keepRecentTokens` | Approximate recent-token budget; defaults to `20000`. |
|
|
36
|
-
| `maxSummaryTokens` / `maxOutputTokens` |
|
|
36
|
+
| `maxSummaryTokens` / `maxOutputTokens` | Computes the summary output budget, writes it to `summaryModel/model.parameters.maxTokens`, and truncates oversized collected summaries. First-party providers serialize that generic field to their real request field (`max_output_tokens` for OpenAI Responses, `max_tokens` for OpenAI-compatible/Anthropic-style providers). |
|
|
37
37
|
| `maxToolResultChars` | Tool-result JSON truncation limit; defaults to `2000`. |
|
|
38
38
|
| `trackFileOperations`, `includeFileOperations` | Control file path extraction and final summary blocks. |
|
|
39
39
|
| `secrets` | Exact strings to redact from serialized prompts and final summaries. |
|
|
@@ -61,12 +61,15 @@ import { createLlmCompactionStrategy } from "@arnilo/prism-compaction-llm";
|
|
|
61
61
|
|
|
62
62
|
const strategy = createLlmCompactionStrategy({
|
|
63
63
|
provider: summaryProvider,
|
|
64
|
-
model: { provider: "
|
|
64
|
+
model: { provider: "openai", model: "gpt-4.1-mini" },
|
|
65
65
|
keepRecentTokens: 20_000,
|
|
66
66
|
reserveTokens: 16_384,
|
|
67
|
+
maxOutputTokens: 800,
|
|
67
68
|
providerOptions: { cacheRetention: "short" },
|
|
68
69
|
customInstructions: "Focus on current files and failing tests.",
|
|
69
70
|
});
|
|
71
|
+
// Provider request model includes: { parameters: { maxTokens: 800 } }.
|
|
72
|
+
// First-party serializers map it to max_output_tokens/max_tokens on the wire.
|
|
70
73
|
|
|
71
74
|
await session.compact({ strategy, secrets: [apiKey] });
|
|
72
75
|
```
|
|
@@ -99,7 +102,7 @@ const agent = createAgent({ model, provider, compaction: { strategy, thresholdEn
|
|
|
99
102
|
Registration only contributes an inert strategy. The host must resolve and pass it to runtime config.
|
|
100
103
|
|
|
101
104
|
## Security and performance notes
|
|
102
|
-
Preparation is O(n) over branch entries and uses only arrays, strings, and JSON serialization. The strategy makes only the needed provider call(s): one history summary plus one split-turn prefix summary when needed. It does not discover credentials, read files, start background jobs, or add provider SDK dependencies. Redaction is exact-string only; pass every known secret that may appear in history or provider output.
|
|
105
|
+
Preparation is O(n) over branch entries and uses only arrays, strings, and JSON serialization. Output-budget calculation is O(1) and does not add an extra summarization call. The strategy makes only the needed provider call(s): one history summary plus one split-turn prefix summary when needed. It does not discover credentials, read files, start background jobs, or add provider SDK dependencies. Redaction is exact-string only; pass every known secret that may appear in history or provider output.
|
|
103
106
|
|
|
104
107
|
## Related APIs
|
|
105
108
|
- [Compaction and retry policies](compaction-and-retry.md): core compaction strategy surface.
|
|
@@ -38,7 +38,7 @@ Key exports:
|
|
|
38
38
|
| `recallObservationalMemory()` | Recover source evidence for a known observation/reflection id from supplied current-branch entries. |
|
|
39
39
|
| `createMemoryId()` / `isMemoryId()` | Create/check 12-character ids. |
|
|
40
40
|
| `resolveObservationalMemorySettings()` | Merge `observational-memory` settings with defaults and overrides. |
|
|
41
|
-
| `createObservationalMemoryRuntime()` | Explicitly run observer/reflector/dropper workers for a supplied session
|
|
41
|
+
| `createObservationalMemoryRuntime()` | Explicitly run observer/reflector/dropper workers for a supplied session, owned append callback, and provider. |
|
|
42
42
|
| `createObservationalMemoryCompactionStrategy()` | Render existing folded memory as a standard Prism compaction summary with `data.memory`. |
|
|
43
43
|
| `createObservationalMemoryExtension()` | Inert extension helper that registers the strategy contribution unless disabled. |
|
|
44
44
|
| `createRecallMemoryTool()` | Optional exact-id `recall` tool factory backed by host-supplied current-branch entries. |
|
|
@@ -74,7 +74,7 @@ const evidence = recallObservationalMemory(entries, "aaaaaaaaaaaa");
|
|
|
74
74
|
|
|
75
75
|
const memory = createObservationalMemoryRuntime({
|
|
76
76
|
session,
|
|
77
|
-
store,
|
|
77
|
+
appendEntry: (entry) => store.append(entry),
|
|
78
78
|
workerProvider,
|
|
79
79
|
workerModel: { provider: "mock", model: "memory" },
|
|
80
80
|
});
|
|
@@ -92,7 +92,7 @@ await kernel.load([createObservationalMemoryExtension({ recallTool: { getEntries
|
|
|
92
92
|
|
|
93
93
|
Settings are read from the `observational-memory` key only when a host calls `resolveObservationalMemorySettings()` or `runtime.flush()`. Defaults are `observeAfterTokens: 10000`, `reflectAfterTokens: 20000`, `compactAfterTokens: 81000`, `observationsPoolMaxTokens: 20000`, `observationsPoolTargetTokens: 10000`, `agentMaxTurns: 16`, `passive: false`, and `debugLog: false`.
|
|
94
94
|
|
|
95
|
-
The runtime requires host-supplied `session`,
|
|
95
|
+
The runtime requires host-supplied `session`, an `appendEntry` callback bound to that session's owning store/branch, `workerProvider`, and `workerModel`. It no longer accepts a separate `store` option because mismatched session/store pairs can append memory entries outside the active branch. After each memory append, the runtime checks the appended entry is visible at the session leaf and fails closed/restores the previous checkout if the callback points elsewhere. Optional credential resolution is explicit; missing requested credentials skip worker execution.
|
|
96
96
|
|
|
97
97
|
`createObservationalMemoryCompactionStrategy()` keeps recent message entries like the default compaction strategy, renders existing observations/reflections as the summary, and returns a standard Prism compaction entry. Its `data` includes `throughEntryId`, `keepEntryIds`, `strategy`, `trigger`, and `memory: { type: "om.folded", version: 1, fullFold, observations, reflections, droppedObservationIds }`. When active observations exceed `observationsPoolMaxTokens`, it performs a full fold into `data.memory`.
|
|
98
98
|
|
|
@@ -108,7 +108,7 @@ The runtime requires host-supplied `session`, matching `store`, `workerProvider`
|
|
|
108
108
|
- Recall tool and commands only see current-branch entries supplied by the host callback.
|
|
109
109
|
- Invalid or missing ids fail closed; invalid recall tool ids skip entry lookup.
|
|
110
110
|
- Utilities and fast compaction are O(n) over supplied entries and use no provider, network, filesystem, timer, worker, credential, or settings access.
|
|
111
|
-
- Workers serialize only supplied branch entries, enforce `agentMaxTurns`, and run one consolidation pipeline at a time per runtime.
|
|
111
|
+
- Workers serialize only supplied branch entries, enforce `agentMaxTurns`, and run one consolidation pipeline at a time per runtime. Worker transcripts replay assistant `tool_call` messages before matching role `tool` `tool_result` messages so provider requests stay valid for call/result-pairing providers.
|
|
112
112
|
- Compaction preserves raw history; Prism appends one standard compaction entry and rebuilds provider context from its summary plus kept recent messages.
|
|
113
113
|
- Pass known secrets to render/recall/runtime/tool/command helpers to redact exact values from prompts, records, structured results, and text output.
|
|
114
114
|
- Live tests are opt-in with `PRISM_LIVE_OBSERVATIONAL_MEMORY_TESTS=1`.
|
|
@@ -69,6 +69,7 @@ parsePrismManifest(value: unknown): PrismManifest
|
|
|
69
69
|
| `authMethod` | `authMethods` | Auth method descriptor; uses `credentialName`, never a resolved credential value. |
|
|
70
70
|
| `providerRequestPolicy` | `providerRequestPolicies` | Provider request policy declaration. |
|
|
71
71
|
| `systemPromptContribution` | `systemPromptContributions` | System prompt contribution declaration. |
|
|
72
|
+
| `instructionInjector` | `instructionInjectors` | Instruction injector declaration; selected on `AgentConfig`/`RunOptions.instructionInjectors` (Phase 30). |
|
|
72
73
|
|
|
73
74
|
## Outputs / response / events
|
|
74
75
|
|
|
@@ -76,6 +77,7 @@ parsePrismManifest(value: unknown): PrismManifest
|
|
|
76
77
|
- Later config layers override earlier layers.
|
|
77
78
|
- Nested plain objects merge recursively.
|
|
78
79
|
- Arrays and primitives replace previous values.
|
|
80
|
+
- Config and manifest JSON object keys named `__proto__`, `prototype`, or `constructor` are rejected at any depth before merge/clone output is built.
|
|
79
81
|
- `parsePrismManifest()` returns a validated manifest or throws a field-specific validation error.
|
|
80
82
|
- No events are emitted and no registries are modified by these helpers.
|
|
81
83
|
|
|
@@ -106,6 +108,7 @@ const manifest = definePrismManifest({
|
|
|
106
108
|
{ kind: "authMethod", name: "demo.api-key", metadata: { credentialName: "apiKey" } },
|
|
107
109
|
{ kind: "providerRequestPolicy", name: "demo.cache" },
|
|
108
110
|
{ kind: "systemPromptContribution", name: "demo.prompt" },
|
|
111
|
+
{ kind: "instructionInjector", name: "demo.injector" },
|
|
109
112
|
],
|
|
110
113
|
resources: [{ uri: "package://demo/prompt.md", purpose: "prompt" }],
|
|
111
114
|
});
|
|
@@ -123,13 +126,14 @@ console.log(config.demo);
|
|
|
123
126
|
|
|
124
127
|
- Hosts choose the layer order. Prism documents `built-in -> manifest defaults -> host app -> optional user/global -> runtime overrides` but does not load those layers automatically.
|
|
125
128
|
- Manifest contribution declarations are data. Hosts may later choose to import the declared module/export and register it, but parsing the manifest never does that.
|
|
126
|
-
- Contribution `kind` values match `createContributionRegistries()` categories, including the Phase 14 provider primitives `providerPackage`, `authMethod`, `providerRequestPolicy`, and `
|
|
129
|
+
- Contribution `kind` values match `createContributionRegistries()` categories, including the Phase 14 provider primitives `providerPackage`, `authMethod`, `providerRequestPolicy`, `systemPromptContribution`, and the Phase 30 `instructionInjector`.
|
|
127
130
|
- Filesystem config loading is intentionally outside the root API and belongs to the optional [`@arnilo/prism/node/config`](node-filesystem-config.md) subpath.
|
|
128
131
|
- Manifest `resources` entries are URI declarations; use [resource loading](resource-loading.md) helpers with a host-provided loader to fetch them.
|
|
129
132
|
|
|
130
133
|
## Security and performance notes
|
|
131
134
|
|
|
132
135
|
- Config and manifest values must be JSON-compatible data.
|
|
136
|
+
- Config/manifest JSON rejects `__proto__`, `prototype`, and `constructor` keys at every depth to block prototype pollution rather than silently dropping unsafe input.
|
|
133
137
|
- Do not put resolved credential values, tokens, headers, or executable code in config defaults, manifests, or metadata.
|
|
134
138
|
- Manifest parsing does not execute package code, dynamically import modules, resolve credentials, call providers/tools, or read resources.
|
|
135
139
|
- Config merging is dependency-free and proportional to the total number of JSON fields.
|
|
@@ -140,3 +144,4 @@ console.log(config.demo);
|
|
|
140
144
|
- [Extension kernel and event bus](extensions.md): hosts can load extensions after they decide to execute package code.
|
|
141
145
|
- [Resource loading](resource-loading.md): load manifest, prompt, skill, and package resources through host-provided loaders.
|
|
142
146
|
- [Credentials and redaction](credentials-and-redaction.md): credential values stay out of manifests and config layers.
|
|
147
|
+
- [Contribution discovery (workspace)](contribution-discovery.md): opt-in scanner that resolves non-skill on-disk entries into `ManifestContributionDeclaration` envelopes.
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
## When to use it
|
|
8
8
|
|
|
9
|
-
Use context resolution when a host wants project/session/context blocks resolved before prompt composition. Use the skill registry when a host wants explicit progressive skill disclosure.
|
|
9
|
+
Use context resolution when a host wants project/session/context blocks resolved before prompt composition. Use the skill registry when a host wants explicit progressive skill disclosure. Declarative `AgentDefinition.skills` are inactive unless listed; omitted skills means none unless the host uses the migration-only `activateAllCapabilities: true` option.
|
|
10
10
|
|
|
11
11
|
Do not use these helpers as an agent loop, package discovery mechanism, context cache, token budgeter, retrier, credential resolver, semantic skill ranker, tool activator, or permission system.
|
|
12
12
|
|
|
@@ -32,7 +32,10 @@ Skill selection:
|
|
|
32
32
|
```ts
|
|
33
33
|
import { createSkillRegistry, resolveActiveSkills } from "@arnilo/prism";
|
|
34
34
|
|
|
35
|
-
const registry = createSkillRegistry(
|
|
35
|
+
const registry = createSkillRegistry(
|
|
36
|
+
[{ name: "brief", instructions: "Answer briefly.", toolNames: ["echo"] }],
|
|
37
|
+
{ duplicate: "error" },
|
|
38
|
+
);
|
|
36
39
|
const active = resolveActiveSkills({
|
|
37
40
|
registry,
|
|
38
41
|
names: ["brief"],
|
|
@@ -40,13 +43,15 @@ const active = resolveActiveSkills({
|
|
|
40
43
|
});
|
|
41
44
|
```
|
|
42
45
|
|
|
46
|
+
`createSkillRegistry(skills?, options?)` stores skills by `skill.name`. Duplicate names replace deterministically by default for compatibility. Pass `{ duplicate: "error" }` to throw `Duplicate skill: <name>` and prevent silent shadowing.
|
|
47
|
+
|
|
43
48
|
`ResolveActiveSkillsOptions` accepts a `SkillRegistry`, requested skill names, and host-active `ToolDefinition[]`.
|
|
44
49
|
|
|
45
50
|
## Outputs / response / events
|
|
46
51
|
|
|
47
52
|
`resolveContextProviders()` returns `readonly ContextBlock[]` in provider order. If a middleware registry is supplied, the `context` hook can transform the final block array.
|
|
48
53
|
|
|
49
|
-
`resolveActiveSkills()` returns requested skills in requested order. Unknown skills and skills that reference inactive tools throw before prompt composition.
|
|
54
|
+
`resolveActiveSkills()` returns requested skills in requested order. Unknown skills, duplicate skill registrations in strict mode, and skills that reference inactive tools throw before prompt composition.
|
|
50
55
|
|
|
51
56
|
## Request/response example
|
|
52
57
|
|
|
@@ -85,7 +90,7 @@ const request = await assembleProviderInput({
|
|
|
85
90
|
|
|
86
91
|
## Extension and configuration notes
|
|
87
92
|
|
|
88
|
-
Extensions can contribute context providers and skills with `registerContextProvider()` and `registerSkill()`, but those contributions stay inert until the host selects providers or registers/selects skills. The agent/session runtime uses the `context` and selected `skills` arrays passed on `AgentConfig`; it does not auto-select contributions.
|
|
93
|
+
Extensions can contribute context providers and skills with `registerContextProvider()` and `registerSkill()`, but those contributions stay inert until the host selects providers or registers/selects skills. `resolveAgentDefinition()` only selects skills named in `AgentDefinition.skills` by default; omitted declarative skills activate none. The agent/session runtime uses the `context` and selected `skills` arrays passed on `AgentConfig`; it does not auto-select contributions.
|
|
89
94
|
|
|
90
95
|
```ts
|
|
91
96
|
const providers = [kernel.registries.contextProviders.resolve("project")];
|
|
@@ -95,19 +100,87 @@ const skills = resolveActiveSkills({ registry: skillRegistry, names: ["brief"],
|
|
|
95
100
|
|
|
96
101
|
`context` middleware runs only when a middleware registry is supplied to the helper. Middleware transforms context data; it does not grant tool access. Skills can reference tool names, but only host-active tools satisfy those references.
|
|
97
102
|
|
|
103
|
+
## Runtime skill selection and activation
|
|
104
|
+
|
|
105
|
+
The agent/session runtime resolves skills per run and wires each active skill's `context` into the assembled provider input. Runtime `AgentConfig.skills` and declarative `AgentDefinition.skills` have different defaults:
|
|
106
|
+
|
|
107
|
+
| Surface | Config shape | Run override | Active skills |
|
|
108
|
+
| --- | --- | --- | --- |
|
|
109
|
+
| Runtime agent | `AgentConfig.skills: SkillRegistry` | `RunOptions.activeSkills: ["brief"]` | Named skills only, resolved with `resolveActiveSkills({ registry, names, tools })`. |
|
|
110
|
+
| Runtime agent | `AgentConfig.skills: SkillRegistry` | no `activeSkills` / no `skills` | All registry skills (`SkillRegistry.list()`). |
|
|
111
|
+
| Runtime agent | `AgentConfig.skills: Skill[]` | `RunOptions.skills: [...]` | Override array only. |
|
|
112
|
+
| Runtime agent | `AgentConfig.skills: Skill[]` | no `RunOptions.skills` | All configured array skills. |
|
|
113
|
+
| Declarative definition | `AgentDefinition.skills: ["brief"]` | later runtime `activeSkills` optional | Listed names only. |
|
|
114
|
+
| Declarative definition | omitted `AgentDefinition.skills` | `activateAllCapabilities` false/default | No skills active. |
|
|
115
|
+
| Declarative definition | omitted `AgentDefinition.skills` | `activateAllCapabilities: true` | All registry skills, migration-only. |
|
|
116
|
+
|
|
117
|
+
Runtime selection precedence mirrors the other `RunOptions` overrides (`redactor`, `validate`):
|
|
118
|
+
|
|
119
|
+
1. `AgentConfig.skills` is a `SkillRegistry` and `RunOptions.activeSkills: readonly string[]` (names) is set → the runtime calls `resolveActiveSkills({ registry, names, tools })`.
|
|
120
|
+
2. `RunOptions.skills: readonly Skill[]` is set → that array replaces `AgentConfig.skills` for the run. This override exists for the case where `AgentConfig.skills` is a plain `Skill[]` (no registry), so name resolution is impossible.
|
|
121
|
+
3. Neither set → all configured runtime skills are active (current behavior; `SkillRegistry.list()` or the plain array as-is). This is not the declarative default.
|
|
122
|
+
|
|
123
|
+
names win when a registry exists. `RunOptions.activeSkills` cannot be used against a plain-array `AgentConfig.skills` — use `RunOptions.skills` instead. Use `RunOptions.skills: []` for an explicit no-skills runtime run.
|
|
124
|
+
|
|
125
|
+
Each active skill contributes two things the runtime now wires together:
|
|
126
|
+
|
|
127
|
+
- `Skill.instructions` → rendered as system messages by `skillMessages()` (active set only).
|
|
128
|
+
- `Skill.context: ContextProvider[]` → collected across active skills (`activeSkills.flatMap(s => s.context ?? [])`), resolved through the existing `resolveContextProviders(...)`, and merged into the request's `context` **after** host `AgentConfig.context` blocks. Inactive skills contribute neither instructions nor context.
|
|
129
|
+
|
|
130
|
+
`toolNames` enforcement is live: because selection routes through `resolveActiveSkills()`, a skill demanding a host-inactive tool throws with `Skill ${name} requires inactive tool: ${missing}` **before the first provider turn** — no provider call, no store write, no partial side effect. This is the fail-fast contract the docs already claimed; the runtime now honors it.
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
import { createAgent, createSkillRegistry, type ContextProvider } from "@arnilo/prism";
|
|
134
|
+
|
|
135
|
+
const schema: ContextProvider = { name: "schema", resolve: () => [{ title: "Schema", content: "selected schema" }] };
|
|
136
|
+
const skills = createSkillRegistry([
|
|
137
|
+
{ name: "summarize", instructions: "Summarize.", context: [schema], toolNames: ["echo"] },
|
|
138
|
+
{ name: "translate", instructions: "Translate." },
|
|
139
|
+
]);
|
|
140
|
+
|
|
141
|
+
const agent = createAgent({ model, provider, skills, tools: [echo] });
|
|
142
|
+
|
|
143
|
+
// Only summarize this run: its instructions render and schema context resolves;
|
|
144
|
+
// translate stays inactive and contributes neither. If `echo` were not in the
|
|
145
|
+
// active tool set, this run would throw before the first provider turn.
|
|
146
|
+
await agent.createSession().run(input, { activeSkills: ["summarize"] });
|
|
147
|
+
|
|
148
|
+
// Same config, different active skills on the next run:
|
|
149
|
+
await agent.createSession().run(input, { activeSkills: ["translate"] });
|
|
150
|
+
|
|
151
|
+
// Plain-array override (no registry on AgentConfig.skills):
|
|
152
|
+
await session.run(input, { skills: [{ name: "verbose", instructions: "Be verbose." }] });
|
|
153
|
+
await session.run(input, { skills: [] }); // explicit no skills for this run
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Skill selection grants no tool access and cannot bypass permissions — a skill's `toolNames` can only *require* host-active tools, never activate or grant them. Declarative skills also do not activate themselves by presence in a registry; list names on `AgentDefinition.skills` (or pass runtime `activeSkills`) when wanted. Per-skill token budgeting is deferred; the merge order (host context, then skill context) is the only priority knob today.
|
|
157
|
+
|
|
158
|
+
### Migration note
|
|
159
|
+
|
|
160
|
+
For declarative agents, old configs that omitted `skills` should now add explicit names:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
// New safe default: no skill activates by omission.
|
|
164
|
+
resolveAgentDefinition({ name: "doc", model, skills: ["brief"] }, context);
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Use `activateAllCapabilities: true` only as a temporary all-skills/all-tools compatibility opt-in during migration. Runtime `RunOptions.activeSkills` remains the per-run narrowing tool after an agent has a skill registry configured.
|
|
168
|
+
|
|
98
169
|
## Security and performance notes
|
|
99
170
|
|
|
100
171
|
- Context providers run sequentially and deterministically in caller order.
|
|
101
|
-
- Skill registry lookup is `Map`-backed, and selection is linear in requested skills plus active tools.
|
|
172
|
+
- Skill registry lookup is `Map`-backed, and selection is linear in requested skills plus active tools. Strict duplicate mode adds one O(1) `Map.has()` check during registration only.
|
|
102
173
|
- These helpers perform no provider calls, tool execution, resource loading, package discovery, filesystem/network access, retries, timers, or watchers by themselves.
|
|
103
174
|
- Context and skill output is host/extension data. Do not include secrets unless the host explicitly accepts that prompt exposure.
|
|
104
|
-
- Active tools remain host-supplied; skills and middleware do not activate tools or grant permissions.
|
|
175
|
+
- Active tools remain host-supplied; skills and middleware do not activate tools or grant permissions. Use `duplicate: "error"` when loading third-party skills to prevent silent name shadowing.
|
|
105
176
|
|
|
106
177
|
## Related APIs
|
|
107
178
|
|
|
108
179
|
- [Agent/session runtime](agent-session-runtime.md): consumes host-selected context providers and skills from explicit agent config.
|
|
109
180
|
- [Input and prompt assembly](input-and-prompt-assembly.md): default prompt builder and provider-input assembly helper.
|
|
181
|
+
- [Instruction injection](instruction-injection.md): package injectors contribute `contextBlocks` that merge after host+skill provider blocks.
|
|
110
182
|
- [Public contracts](public-contracts.md): `ContextProvider`, `ContextResolutionContext`, `ContextBlock`, `Skill`, `SkillRegistry`, `PromptBuilder`, and `PromptBuildRequest`.
|
|
111
183
|
- [Middleware hooks](middleware-hooks.md): `context` and `prompt_build` hooks.
|
|
112
184
|
- [Contribution registries](contribution-registries.md): inert context provider and skill contributions.
|
|
185
|
+
- [Contribution discovery (workspace)](contribution-discovery.md): opt-in filesystem scanner that turns `SKILL.md`/`manifest.json` into registered skills and descriptor stubs. (Per-agent `AGENT.md` bundles live under an app-controlled `configRoot`; see [Agent definitions](agent-definitions.md).)
|
|
113
186
|
- [Tools](tools.md): host-owned active tools and permissions.
|