@arnilo/prism 0.0.1 → 0.0.2
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 +4 -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/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 +40 -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 +76 -26
- 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 +64 -8
- package/package.json +35 -2
|
@@ -18,6 +18,7 @@ Do not use it for tool execution, provider calls, file discovery, credential loo
|
|
|
18
18
|
import { createDefaultInputBuilder } from "@arnilo/prism";
|
|
19
19
|
|
|
20
20
|
const messages = await createDefaultInputBuilder().build("Summarize", {
|
|
21
|
+
inputLayout: "legacy", // default; "cache_aware" passes the cache-aware layout preference
|
|
21
22
|
systemInstructions: "Be accurate.",
|
|
22
23
|
developerInstructions: "Cite supplied context only.",
|
|
23
24
|
history,
|
|
@@ -46,6 +47,7 @@ import { assembleProviderInput, createDefaultPromptBuilder } from "@arnilo/prism
|
|
|
46
47
|
const request = await assembleProviderInput({
|
|
47
48
|
model: { provider: "mock", model: "demo" },
|
|
48
49
|
input: "Explain this file",
|
|
50
|
+
inputLayout: "cache_aware",
|
|
49
51
|
contextProviders: [projectContext],
|
|
50
52
|
promptBuilder: createDefaultPromptBuilder(),
|
|
51
53
|
tools: activeTools,
|
|
@@ -56,7 +58,8 @@ Useful exported types:
|
|
|
56
58
|
|
|
57
59
|
- `AgentInput`: `string | Message | readonly Message[]`.
|
|
58
60
|
- `DefaultInputBuilder`: the default `InputBuilder` with typed default context.
|
|
59
|
-
- `
|
|
61
|
+
- `InputAssemblyLayout`: `"legacy" | "cache_aware"`; legacy is default.
|
|
62
|
+
- `DefaultInputBuildContext`: optional input layout, instructions, history, summaries, attachments, resource loader/URIs, tool results, middleware, ids, metadata, and abort signal.
|
|
60
63
|
- `InputAttachment`: already-loaded text/content or an explicit URI loaded through a caller-provided `ResourceLoader`.
|
|
61
64
|
- `PromptInstruction`: labeled system instruction text.
|
|
62
65
|
- `DefaultPromptBuilder`: the default `PromptBuilder`.
|
|
@@ -69,10 +72,18 @@ The builder returns `readonly Message[]`.
|
|
|
69
72
|
|
|
70
73
|
- String input becomes one user text message.
|
|
71
74
|
- `Message` and `Message[]` input are preserved.
|
|
75
|
+
- Legacy layout is the default. Set `inputLayout: "cache_aware"` on the default builder, `assembleProviderInput()`, `AgentConfig`, or `RunOptions` to pass the cache-aware layout preference without replacing the builder.
|
|
76
|
+
|
|
77
|
+
| Layout | Input message order |
|
|
78
|
+
| --- | --- |
|
|
79
|
+
| `legacy` | instructions → summaries → history → current input → attachments/resources → tool results |
|
|
80
|
+
| `cache_aware` | instructions → attachments/resources → summaries → history → tool results → current input |
|
|
81
|
+
|
|
82
|
+
The default prompt builder still prepends context, selected skills, and tool declarations before those input messages. Cache-aware ordering gives cache-capable providers a stable prefix only while those stable inputs stay byte-stable; changing tools, context, resources, summaries, history, or attachments changes the prefix too.
|
|
72
83
|
- History is prepended before current input.
|
|
73
84
|
- Instructions and summaries are system messages; compacted branch summaries from `rebuildSessionContext()` use the same path.
|
|
74
85
|
- Text attachments and explicit text resources are user messages.
|
|
75
|
-
- 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.
|
|
86
|
+
- 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.
|
|
76
87
|
- Middleware runs only when `middleware` is supplied in the context.
|
|
77
88
|
- `assembleProviderInput()` returns a `ProviderRequest` with the caller's model/tools/provider options/metadata/signal and composed messages/context.
|
|
78
89
|
- `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" }`.
|
|
@@ -123,8 +134,12 @@ const messages = await createDefaultInputBuilder().build(prompt, {
|
|
|
123
134
|
},
|
|
124
135
|
middleware,
|
|
125
136
|
});
|
|
137
|
+
|
|
138
|
+
await session.run("Explain this", { inputLayout: "cache_aware" });
|
|
126
139
|
```
|
|
127
140
|
|
|
141
|
+
Cache-aware mode is opt-in; hosts that do nothing keep legacy order.
|
|
142
|
+
|
|
128
143
|
## Extension and configuration notes
|
|
129
144
|
|
|
130
145
|
Extensions can contribute `InputBuilder`, `PromptBuilder`, and `ContextProvider` objects through the extension API, but contributions stay inert until the host resolves and calls or passes them. The agent/session runtime uses configured builders/providers only when the host puts them on `AgentConfig`; it does not load extensions or registries itself. Defaults are built-ins; hosts can replace them with compatible builders. Prompt templates are caller-side string expansion only; they do not load resources or contributions.
|
|
@@ -147,7 +162,7 @@ const request = await assembleProviderInput({
|
|
|
147
162
|
|
|
148
163
|
## Security and performance notes
|
|
149
164
|
|
|
150
|
-
- The builder is linear in supplied messages, attachments, resources, and tool results.
|
|
165
|
+
- The builder is linear in supplied messages, attachments, resources, and tool results. Layout selection is one flattening branch over already-built groups.
|
|
151
166
|
- Template expansion is dependency-free string replacement over `{{name}}` variables. It does not evaluate expressions, filters, loops, partials, JavaScript, globals, or prototype properties.
|
|
152
167
|
- It performs no provider calls, tool execution, credential resolution, package discovery, filesystem scan, network access, timers, or watchers.
|
|
153
168
|
- URI attachments/resources load only through the caller-provided `ResourceLoader`.
|
|
@@ -157,6 +172,7 @@ const request = await assembleProviderInput({
|
|
|
157
172
|
|
|
158
173
|
## Related APIs
|
|
159
174
|
|
|
175
|
+
- [SDK customization guide](customization.md): high-level map of replaceable provider resolution, middleware, context, builder, injector, loop, compaction, retry, store, and skill seams.
|
|
160
176
|
- [Public contracts](public-contracts.md): `Message`, `ContentBlock`, `InputBuilder`, `InputBuildContext`, `ToolResult`, and `ResourceLoader` shapes.
|
|
161
177
|
- [Context and skills](context-and-skills.md): ordered context resolution feeding prompt composition.
|
|
162
178
|
- [Resource loading](resource-loading.md): `loadTextResource()` behavior used for explicit URI resources.
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Instruction injection
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
Instruction injectors let a package modify how context is formulated and inject its own instructions to modify agent behavior — on the first turn, every turn, or in response to user input — without forking the input/prompt pipeline and without hidden globals. Each injector contributes only `instructions` (text) and `contextBlocks`; it cannot register tools, skills, or permissions, and cannot bypass the validator or the permission gate.
|
|
6
|
+
|
|
7
|
+
Injectors are the package-side complement to the host-owned `systemInstructions` base path: the host sets base instructions, then selected package injectors layer additional instructions and context blocks per turn.
|
|
8
|
+
|
|
9
|
+
## When to use it
|
|
10
|
+
|
|
11
|
+
- A package wants to bias the model toward a response format (e.g. "answer in JSON") every turn.
|
|
12
|
+
- A package wants to inject project context (e.g. a repo summary) on the first turn only.
|
|
13
|
+
- A package wants to react to user input (via a `predicate`) without re-authoring the assembler.
|
|
14
|
+
- A package wants to ship a discoverable `.agents/instructions/<name>/` bundle that hosts opt into by name.
|
|
15
|
+
|
|
16
|
+
Injectors are **not** a way to grant tool access, change credentials, or mutate provider request options.
|
|
17
|
+
|
|
18
|
+
## Inputs / request
|
|
19
|
+
|
|
20
|
+
Injectors implement `InstructionInjector`:
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
type InstructionTiming = "first_turn" | "every_turn" | "on_input";
|
|
24
|
+
|
|
25
|
+
interface InstructionContext {
|
|
26
|
+
readonly sessionId: string;
|
|
27
|
+
readonly runId: string;
|
|
28
|
+
readonly turn: number; // 1-based; undefined is treated as turn 1
|
|
29
|
+
readonly input: readonly Message[]; // already redacted by the runtime
|
|
30
|
+
readonly history: readonly Message[]; // redacted messages from prior turns
|
|
31
|
+
readonly metadata: Readonly<Record<string, unknown>>;
|
|
32
|
+
readonly signal: AbortSignal;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface InstructionContribution {
|
|
36
|
+
readonly instructions?: string;
|
|
37
|
+
readonly contextBlocks?: readonly ContextBlock[];
|
|
38
|
+
readonly when: InstructionTiming;
|
|
39
|
+
readonly predicate?: (ctx: InstructionContext) => boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface InstructionInjector {
|
|
43
|
+
readonly name: string;
|
|
44
|
+
readonly description?: string;
|
|
45
|
+
apply(ctx: InstructionContext): InstructionContribution;
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`InstructionContext` fields are already redacted by the runtime before `apply` runs: current input is run through the active `AgentConfig.redactor` / `RunOptions.redactor` during assembly, and history holds previously-redacted messages. Direct `assembleProviderInput()` callers that use injectors should pass the same `redactor` option to keep this boundary.
|
|
50
|
+
|
|
51
|
+
### Lifecycle
|
|
52
|
+
|
|
53
|
+
| `when` | `predicate` | Applied when |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| `first_turn` | ignored | `ctx.turn === 1` |
|
|
56
|
+
| `every_turn` | ignored | every turn |
|
|
57
|
+
| `on_input` | absent | every turn (default) |
|
|
58
|
+
| `on_input` | present | turns where `predicate(ctx)` returns `true` |
|
|
59
|
+
|
|
60
|
+
Only `instructions` and `contextBlocks` are honored from a contribution; other fields grant nothing (see [Security and performance notes](#security-and-performance-notes)).
|
|
61
|
+
|
|
62
|
+
## Outputs / response / events
|
|
63
|
+
|
|
64
|
+
Injectors do not emit events. Their output is folded into the assembled `ProviderRequest`:
|
|
65
|
+
|
|
66
|
+
- **Instructions** layer via `composeSystemPrompt(injectorContributions, { base: systemInstructions })` as `source: "package"`, `mode: "append"`. Host base instructions come first, then injector package instructions appended. This keeps a single prompt-composition code path (no parallel prompt code in the assembler).
|
|
67
|
+
- **Context blocks** merge via `resolveContextProviders`, appended after host+skill provider blocks, before the context middleware hook runs. `ponytail:` the assembler threads `injectedBlocks` into `resolveContextProviders` so the existing context middleware flow is untouched and the diff stays minimal.
|
|
68
|
+
|
|
69
|
+
`runInstructionInjectors(injectors, ctx)` runs each selected injector against a turn-local `InstructionContext`, returning `{ instructions: SystemPromptContribution[]; contextBlocks: ContextBlock[] }`. It aborts on `ctx.signal`.
|
|
70
|
+
|
|
71
|
+
## Request/response example
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
const jsonInjector: InstructionInjector = {
|
|
75
|
+
name: "json-always",
|
|
76
|
+
apply: () => ({ instructions: "Always answer in JSON.", when: "every_turn" }),
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const projectContext: InstructionInjector = {
|
|
80
|
+
name: "project-context",
|
|
81
|
+
apply: () => ({
|
|
82
|
+
contextBlocks: [{ title: "Repo", content: "Prism monorepo — see docs/." }],
|
|
83
|
+
when: "first_turn",
|
|
84
|
+
}),
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const onInputJson: InstructionInjector = {
|
|
88
|
+
name: "json-on-json-input",
|
|
89
|
+
apply: (ctx) => ({
|
|
90
|
+
instructions: "Reply with JSON because the user asked for JSON.",
|
|
91
|
+
when: "on_input",
|
|
92
|
+
predicate: (c) => c.input.some((m) => /json/i.test(JSON.stringify(m.content))),
|
|
93
|
+
}),
|
|
94
|
+
};
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Implementation example
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import { createAgent, createMockProvider, providerDone, createSecretRedactor } from "@arnilo/prism";
|
|
101
|
+
|
|
102
|
+
const jsonInjector = { name: "json-always", apply: () => ({ instructions: "Answer in JSON.", when: "every_turn" as const }) };
|
|
103
|
+
|
|
104
|
+
const session = createAgent({
|
|
105
|
+
model: { provider: "mock", model: "demo" },
|
|
106
|
+
provider: createMockProvider([providerDone()]),
|
|
107
|
+
instructions: "You are helpful.",
|
|
108
|
+
instructionInjectors: [jsonInjector],
|
|
109
|
+
}).createSession();
|
|
110
|
+
|
|
111
|
+
await session.run("List primes under 10.");
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Selection and override semantics
|
|
115
|
+
|
|
116
|
+
`AgentConfig.instructionInjectors` configures a base injector list; `RunOptions.instructionInjectors` overrides it (last wins), mirroring `activeSkills`. `resolveInstructionInjectors` resolves names against a registry fail-closed:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import { resolveInstructionInjectors } from "@arnilo/prism";
|
|
120
|
+
|
|
121
|
+
const injectors = resolveInstructionInjectors({ registry, names: ["json-always", "project-context"] });
|
|
122
|
+
// Unknown name throws: Error: Unknown instruction injector: <name>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Phase 29 discovery loading
|
|
126
|
+
|
|
127
|
+
A discovered `.agents/instructions/<name>/manifest.json` (see [Contribution discovery](contribution-discovery.md)) becomes a live injector via the host-owned Node adapter — core performs no `import()`:
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
import { registerDiscoveredInstructionInjectors } from "@arnilo/prism/node/instruction-injectors";
|
|
131
|
+
|
|
132
|
+
// markdown-only (no `module` field) → static every_turn injector reading resource text;
|
|
133
|
+
// module-referenced → host-supplied moduleLoader (skipped when absent).
|
|
134
|
+
// resourceTrust is only needed for absolute/outside resource files.
|
|
135
|
+
await registerDiscoveredInstructionInjectors(registries, discovered, { moduleLoader, resourceTrust });
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The CLI wires this under `--discover` (see below). Hosts embedding the SDK keep the registry empty and supply injectors directly on `AgentConfig`/`RunOptions`.
|
|
139
|
+
|
|
140
|
+
### CLI and RPC
|
|
141
|
+
|
|
142
|
+
CLI:
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
# select a discovered injector by name (repeatable)
|
|
146
|
+
prism --discover --discover-kinds instructions --instruction json-always -p "Hi" --provider mock
|
|
147
|
+
|
|
148
|
+
# load a markdown file as a static every_turn injector (repeatable)
|
|
149
|
+
prism --injector-file ./rules/json.md -p "Hi" --provider mock
|
|
150
|
+
|
|
151
|
+
# disable all injectors for a run (including AgentConfig injectors)
|
|
152
|
+
prism --instruction false -p "Hi" --provider mock
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`--instruction false` disables; `--instruction <name>` fails closed (exit 1) on an unknown name. Names resolve against discovered injectors only when `--discover` ran; without discovery, `--instruction` requires a name present in the session's `instructionInjectors` (host-supplied).
|
|
156
|
+
|
|
157
|
+
RPC: `prompt`/`followUp` params accept an optional `instructionInjectors: readonly string[]` field. Names resolve against the `instructionInjectors` registry passed to `runRpcServer({ instructionInjectors })`; an unknown name fails closed with a correlated error response and no provider call.
|
|
158
|
+
|
|
159
|
+
## Extension and configuration notes
|
|
160
|
+
|
|
161
|
+
- Register via `ExtensionAPI.registerInstructionInjector(injector)` (Phase 30). Each injector is stored by `injector.name` (last-write-wins).
|
|
162
|
+
- Select on `AgentConfig.instructionInjectors` or `RunOptions.instructionInjectors` (`RunOptions` wins). `RunOptions.instructionInjectors` is a list of `InstructionInjector` instances; hosts embed names by passing instances resolved through `resolveInstructionInjectors`.
|
|
163
|
+
- Manifest `kind: "instructionInjector"` (Phase 30) declares contributions data-only; discovery of `kind: "instructions"` is the filesystem vehicle (see [Configuration and manifests](configuration-and-manifests.md)).
|
|
164
|
+
- `turn` is plumbed through `LoopContext.assemble(nextInput, toolResults?, turn?)` (Phase 30) so injectors see the loop-local turn, not a stale value.
|
|
165
|
+
|
|
166
|
+
## Security and performance notes
|
|
167
|
+
|
|
168
|
+
- **No privilege grant:** `InstructionContribution` exposes only `instructions`/`contextBlocks`/`when`/`predicate`. There is no `tools`, `skills`, `permissions`, or `execute` field; a malformed contribution smuggling those fields contributes only `instructions`. Registering an injector adds entries only to `instructionInjectors`; `tools`/`skills`/`contextProviders`/`systemPromptContributions` stay empty.
|
|
169
|
+
- **Cannot bypass validator or permissions:** injectors are layered into prompt assembly; tool dispatch still re-checks the active registry (`unknown_tool`), filters, arguments, the permission assertion, and `validate` (Phase 4/25/26).
|
|
170
|
+
- **Secrets never enter history/events:** secrets in injector-produced `instructions`/`contextBlocks` are redacted in the outgoing `ProviderRequest` (via `redactProviderRequest`) and in emitted events (via `redactAgentEvent`). Do not put secrets in injector text at authoring time; the redactor is a backstop, not an invitation.
|
|
171
|
+
- **Resource containment:** markdown-only discovered injectors resolve `resource` relative to their contribution directory and realpath-check it before read. Relative `..`, absolute paths, or symlinks that escape the contribution directory are rejected unless the host passes an explicit `resourceTrust` policy; `permission` is still checked before reading.
|
|
172
|
+
- No hidden globals: injectors are resolved explicitly per run; nothing is auto-activated or auto-imported by core.
|
|
173
|
+
|
|
174
|
+
## Related APIs
|
|
175
|
+
|
|
176
|
+
- [Input and prompt assembly](input-and-prompt-assembly.md): default prompt builder and `assembleProviderInput`, where injector instructions/blocks are merged.
|
|
177
|
+
- [System prompts](system-prompts.md): `composeSystemPrompt` and the `package`/`app`/`user`/`run` layering injectors layer into.
|
|
178
|
+
- [Context and skills](context-and-skills.md): `resolveContextProviders` merge order and skill `context`.
|
|
179
|
+
- [Contribution registries](contribution-registries.md): `instructionInjectors` registry.
|
|
180
|
+
- [Contribution discovery](contribution-discovery.md): `.agents/instructions/<name>/` discovery and the host-owned `loadInstructionInjector` adapter.
|
|
181
|
+
- [Extensions](extensions.md): `registerInstructionInjector` in the contribution-kinds list.
|
|
182
|
+
- [CLI and RPC](cli-rpc.md): `--instruction`/`--injector-file` flags and the RPC `instructionInjectors` field.
|
|
183
|
+
- [Credentials and redaction](credentials-and-redaction.md): `createSecretRedactor`, `redactProviderRequest`, `redactAgentEvent`.
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# Migration guide
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
This page is the single navigation entry for the two cross-cutting migrations external apps hit when moving from Prism's development defaults to its production persistence and explicit-capability surfaces:
|
|
6
|
+
|
|
7
|
+
1. **In-memory / JSONL → database-backed persistence** — swap the single-process development `SessionStore` for a host-implemented `ProductionPersistenceStore` / `SessionStore` adapter, and optionally attach a durable `RunLedger`.
|
|
8
|
+
2. **Permissive capability defaults → explicit capability activation** — move from "omitted tool/skill lists activate everything in scope" (pre-Phase 38 behavior) to named, fail-closed tool/skill activation.
|
|
9
|
+
|
|
10
|
+
It is a thin, link-first guide: it states before/after shapes and points at the detailed pages for schema, indexes, redaction, branch handles, capability semantics, and security.
|
|
11
|
+
|
|
12
|
+
## When to use it
|
|
13
|
+
|
|
14
|
+
Read this page when:
|
|
15
|
+
|
|
16
|
+
- you are taking an app from the `createMemorySessionStore()` / `createJsonlSessionStore()` path to a multi-process, multi-tenant, or durable database backend;
|
|
17
|
+
- you are hardening an agent that previously relied on "every scoped tool/skill is active" and need to name capabilities explicitly;
|
|
18
|
+
- you are adopting the Phase 34–40 production surfaces (atomic append, branch handles, run/event/tool/usage ledger, security boundary hardening) for the first time.
|
|
19
|
+
|
|
20
|
+
If you are new to Prism, start at [Session stores](session-stores.md) and [Agent/session runtime](agent-session-runtime.md) instead.
|
|
21
|
+
|
|
22
|
+
## Inputs / request
|
|
23
|
+
|
|
24
|
+
There is no runtime import for this page. The migrations below use these surfaces:
|
|
25
|
+
|
|
26
|
+
| Surface | Where | Migration role |
|
|
27
|
+
| --- | --- | --- |
|
|
28
|
+
| `SessionStore` | `@arnilo/prism` | Runtime seam swapped from memory/JSONL to DB. |
|
|
29
|
+
| `ProductionPersistenceStore` | `@arnilo/prism` | Adapter-facing contract for paginated, multi-tenant reads (`query*`, optional `readBranchPath`). |
|
|
30
|
+
| `RunLedger` / `RunLedgerRecord` | `@arnilo/prism` | Durable run/event/tool-call/usage ledger attached via `AgentConfig.runLedger` / `RunOptions.runLedger`. |
|
|
31
|
+
| `SessionAppendOptions` / `SessionAppendConflictError` / `SessionBranchHandle` | `@arnilo/prism` | Atomic append, retry dedup, durable branch handles. |
|
|
32
|
+
| `AgentDefinition.tools` / `skills` | `@arnilo/prism` | Named, fail-closed capability activation (Phase 38). |
|
|
33
|
+
| `activateAllCapabilities` | `@arnilo/prism` | Temporary all-tools/all-skills compatibility opt-in while migrating. |
|
|
34
|
+
|
|
35
|
+
## Outputs / response / events
|
|
36
|
+
|
|
37
|
+
These migrations are configuration swaps: they do not add `AgentEvent` variants or change runtime event order. The observable differences are:
|
|
38
|
+
|
|
39
|
+
- reads come from a database instead of an in-memory map / JSONL file;
|
|
40
|
+
- branches are addressable by a storable `(sessionId, leafId)` handle;
|
|
41
|
+
- a run leaves durable `RunRecord` / `AgentEventRecord` / `ToolCallRecord` / `UsageRecord` rows;
|
|
42
|
+
- an agent with omitted `tools`/`skills` activates **no** capabilities instead of every in-scope one.
|
|
43
|
+
|
|
44
|
+
## Request/response example
|
|
45
|
+
|
|
46
|
+
Persistence migration (before/after):
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
// Before — development SessionStore, single process, no ledger.
|
|
50
|
+
{
|
|
51
|
+
"store": "createMemorySessionStore() | createJsonlSessionStore(path)",
|
|
52
|
+
"runLedger": null,
|
|
53
|
+
"ownership": null
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
// After — host-implemented database-backed adapter + durable ledger.
|
|
59
|
+
{
|
|
60
|
+
"store": "createDbSessionStore({ pool })",
|
|
61
|
+
"runLedger": "createDbRunLedger({ pool })",
|
|
62
|
+
"ownership": { "tenantId": "t1", "accountId": "a1", "userId": "u1" }
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Capability migration (before/after):
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
// Before (pre-Phase 38) — omitted tools/skills could receive every scoped capability.
|
|
70
|
+
{ "name": "doc", "model": "openai/gpt-4o" }
|
|
71
|
+
|
|
72
|
+
// After — explicit names; omitted means none.
|
|
73
|
+
{ "name": "doc", "model": "openai/gpt-4o", "tools": ["read"], "skills": ["brief"] }
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Implementation example
|
|
77
|
+
|
|
78
|
+
### Migration 1 — in-memory / JSONL → database-backed persistence
|
|
79
|
+
|
|
80
|
+
A complete, network-free reference adapter that implements these contracts against in-memory tables (and wires a `RunLedger`, branch-handle checkout, fork, and prior-run timeline resume) lives at [`examples/external-app-db-backed.ts`](../examples/external-app-db-backed.ts). The steps below mirror its structure.
|
|
81
|
+
|
|
82
|
+
Step 1: implement a `SessionStore` (or the richer `ProductionPersistenceStore`) against your database. The runtime only requires `append(entry, options?)`, `list(sessionId)`, and optional `get(id)` / `readBranchPath(query)`.
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
// Before: development store, single process.
|
|
86
|
+
import { createJsonlSessionStore } from "@arnilo/prism/node/session-store-jsonl";
|
|
87
|
+
const store = createJsonlSessionStore("./sessions.jsonl");
|
|
88
|
+
|
|
89
|
+
// After: host-implemented database adapter implementing the documented contract, no real DB needed to satisfy the contract.
|
|
90
|
+
import type { SessionStore, SessionEntry, SessionAppendOptions, PersistencePage, SessionBranchRead } from "@arnilo/prism";
|
|
91
|
+
|
|
92
|
+
const store: SessionStore = {
|
|
93
|
+
async append(entry: SessionEntry, options?: SessionAppendOptions) {
|
|
94
|
+
// 1. idempotency dedup: insert (session_id, expected_parent_id, idempotency_key, entry_id)
|
|
95
|
+
// into prism_session_append_idempotency; unique hit => SessionAppendConflictError { idempotencyDuplicate: true }
|
|
96
|
+
// 2. expectedParentId existence check => SessionAppendConflictError { expectedParentId } if missing
|
|
97
|
+
// 3. insert prism_session_entries row; duplicate id fails the transaction
|
|
98
|
+
// 4. optionally compare-and-swap prism_branches.leaf_entry_id
|
|
99
|
+
},
|
|
100
|
+
async list(sessionId: string) { /* O(n) development fallback only */ return []; },
|
|
101
|
+
async readBranchPath(query: SessionBranchRead): Promise<PersistencePage<SessionEntry>> {
|
|
102
|
+
// one recursive CTE / ancestor query — do NOT list(sessionId)+in-memory walk for long sessions
|
|
103
|
+
return { items: [] };
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Step 2: optionally attach a durable run/event/tool/usage ledger and ownership scope so a process exit leaves enough to resume and bill:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import { createAgent, type RunLedger } from "@arnilo/prism";
|
|
112
|
+
|
|
113
|
+
const runLedger: RunLedger = {
|
|
114
|
+
// appendRun / appendEvent / appendToolCall / appendUsage — redact before storage, preserve per-run order
|
|
115
|
+
async appendRun(record) { /* insert prism_runs */ },
|
|
116
|
+
async appendEvent(record) { /* insert prism_agent_events with monotonic sequence per run_id */ },
|
|
117
|
+
async appendToolCall(record) { /* insert prism_tool_calls */ },
|
|
118
|
+
async appendUsage(record) { /* insert prism_usage */ },
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const agent = createAgent({
|
|
122
|
+
model,
|
|
123
|
+
provider,
|
|
124
|
+
store,
|
|
125
|
+
runLedger,
|
|
126
|
+
ownership: { tenantId: "t1", accountId: "a1", userId: "u1" },
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Step 3: store branch handles `(sessionId, leafId)` in your app state and use checkout to move an existing session to a previous or sibling leaf. The runtime's branch helpers (`getSessionBranchEntries`, `rebuildSessionContext`) consume `readBranchPath` so large sessions never require a full `list(sessionId)` load.
|
|
131
|
+
|
|
132
|
+
What you leave behind and why:
|
|
133
|
+
|
|
134
|
+
- `createMemorySessionStore()` — process-local maps; lost on restart, no cross-process locking. Keep for tests.
|
|
135
|
+
- `createJsonlSessionStore()` — single-process file adapter; reads are linear in file size, no cross-process lock, no durable idempotency table, two writers to the same file can race. Keep for local/dev only.
|
|
136
|
+
|
|
137
|
+
See [Database persistence](database-persistence.md) for the full reference schema, indexes, conditional-append transaction pattern, retention, and NoSQL mapping; [Session stores](session-stores.md) for the `SessionStore` contract and branch helpers; [Session stores and branching](session-stores-and-branching.md) for branch semantics; [Runs and usage ledger](runs-and-usage.md) for the `RunLedger` record shapes and ordering rules.
|
|
138
|
+
|
|
139
|
+
### Migration 2 — permissive capability defaults → explicit capability activation
|
|
140
|
+
|
|
141
|
+
Pre-Phase 38 behavior could treat an omitted `tools` list as "every scoped tool"; some hosts also expected all scoped skills to be available. Phase 38 changes the safe default: omitted `tools` and omitted `skills` mean no active capabilities.
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
import { resolveAgentDefinition } from "@arnilo/prism";
|
|
145
|
+
|
|
146
|
+
// Before: omitted tools could receive every scoped tool.
|
|
147
|
+
resolveAgentDefinition({ name: "doc", model: "openai/gpt-4o" }, context);
|
|
148
|
+
|
|
149
|
+
// After: list the capabilities this agent may use.
|
|
150
|
+
resolveAgentDefinition(
|
|
151
|
+
{ name: "doc", model: "openai/gpt-4o", tools: ["read"], skills: ["brief"] },
|
|
152
|
+
context,
|
|
153
|
+
);
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Temporary compatibility shim (use only while migrating old configs):
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
resolveAgentDefinition(
|
|
160
|
+
{ name: "legacy", model: "openai/gpt-4o" },
|
|
161
|
+
{ ...context, activateAllCapabilities: true },
|
|
162
|
+
);
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`activateAllCapabilities: true` intentionally scans/list-activates every in-scope tool/skill. New configs should list names and use strict contribution registries so a third-party package cannot silently shadow a capability name:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
import { createContributionRegistries } from "@arnilo/prism";
|
|
169
|
+
|
|
170
|
+
const registries = createContributionRegistries({ duplicate: "error" });
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Runtime skill activation remains explicit: `RunOptions.activeSkills` narrows per run after an agent has a skill registry configured, and `Skill.toolNames` is enforced fail-closed before the first provider turn. See [Agent definitions](agent-definitions.md), [Context and skills](context-and-skills.md), and [Contribution registries](contribution-registries.md) for the full capability semantics.
|
|
174
|
+
|
|
175
|
+
## Extension and configuration notes
|
|
176
|
+
|
|
177
|
+
- **Persistence is host-owned.** Prism ships no database adapter, no DDL, no migration runner. Hosts own connection pools, transactions, cursor encoding, retention jobs, and tenant isolation. The runtime only talks to `SessionStore` (+ optional `readBranchPath`) and `RunLedger`.
|
|
178
|
+
- **`RunLedger` is not a `SessionStore` replacement.** Messages, branches, and session entries still flow through `SessionStore.append()`; the ledger records run/event/tool/usage facts. See [Runs and usage ledger](runs-and-usage.md).
|
|
179
|
+
- **Capability activation is config over code.** Every seam lives on `AgentDefinition` / `AgentDefinitionResolutionContext` / `RunOptions`; no auto-activation, no privilege grant. A declaration cannot grant permissions or bypass `toolNames`.
|
|
180
|
+
- **Migration order is decoupled.** You can adopt database persistence without changing capability activation, and vice versa. Both migrations are independent config swaps.
|
|
181
|
+
- **Strict duplicate mode for new registries.** `createContributionRegistries({ duplicate: "error" })` makes a third-party package fail loud instead of silently shadowing a capability name during migration.
|
|
182
|
+
|
|
183
|
+
## Security and performance notes
|
|
184
|
+
|
|
185
|
+
- **Never store provider credentials or secrets in the persistence contract.** `ProductionPersistenceStore`, `RunLedger`, `AgentEventRecord`, `ToolCallRecord`, `UsageRecord`, and `AgentDefinitionRecord` never require API keys, resolvers, or provider instances. Redact `SessionEntry` / event / tool-call / usage payloads before storage; the runtime redacts `AgentEvent`s via `redactAgentEvent` and ledger records via `redactRunLedgerRecord` before calling the adapter.
|
|
186
|
+
- **JSONL is a development-only adapter.** No cross-process lock, no durable idempotency table, no tenant isolation, no retention enforcement, no migrations. Do not use it as a production multi-writer store.
|
|
187
|
+
- **Avoid full-session scans in production.** Implement `readBranchPath(query)` with a recursive CTE / ancestor query and cursor-paginate `query*` from indexed columns. `list(sessionId)` + in-memory parent walk is the development fallback only.
|
|
188
|
+
- **`activateAllCapabilities` widens blast radius.** It activates every in-scope tool/skill, so prefer named lists. Strict duplicate mode catches capability-name collisions early.
|
|
189
|
+
- **`toolNames` enforcement is fail-closed.** A skill demanding an inactive tool throws at activation, before any provider turn — for both the old and new migration paths.
|
|
190
|
+
|
|
191
|
+
## Related APIs
|
|
192
|
+
|
|
193
|
+
- [Database persistence](database-persistence.md): production persistence contracts, reference schema, indexes, conditional append, retention, migrations, NoSQL mapping.
|
|
194
|
+
- [Session stores](session-stores.md): `SessionStore` contract, `SessionAppendOptions`, `SessionAppendConflictError`, branch handles, `readBranchPath`.
|
|
195
|
+
- [Session stores and branching](session-stores-and-branching.md): detailed branch semantics and helper reference.
|
|
196
|
+
- [Runs and usage ledger](runs-and-usage.md): `RunLedger` record shapes, redaction, and event/usage ordering.
|
|
197
|
+
- [Node JSONL session store](node-jsonl-session-store.md): development-only JSONL adapter and its limits.
|
|
198
|
+
- [Agent definitions](agent-definitions.md): declarative `AgentDefinition`, `resolveAgentDefinition`, and the explicit-capability-activation migration.
|
|
199
|
+
- [Context and skills](context-and-skills.md): `RunOptions.activeSkills`, `Skill.context`, `toolNames` enforcement.
|
|
200
|
+
- [Contribution registries](contribution-registries.md): strict `duplicate: "error"` mode for capability shadowing prevention.
|
|
201
|
+
- [Release and install](release-and-install.md): packaged surfaces and the offline test budget that gate these migrations.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Model registry
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
The model registry stores explicit `ModelConfig` records by provider/model key. It keeps model metadata inert and host-owned: capabilities, limits, cost, cache support, provider compat data, parameters, and metadata are registered and resolved, not executed.
|
|
6
|
+
|
|
7
|
+
Public API:
|
|
8
|
+
|
|
9
|
+
- `createModelRegistry(models?, options?)`
|
|
10
|
+
- `ModelRegistry.register(model)`
|
|
11
|
+
- `ModelRegistry.get(provider, model)`
|
|
12
|
+
- `ModelRegistry.resolve(provider, model)`
|
|
13
|
+
- `ModelRegistry.list()`
|
|
14
|
+
- `ModelConfig.cache?: ModelCacheCapabilities`
|
|
15
|
+
|
|
16
|
+
## When to use it
|
|
17
|
+
|
|
18
|
+
Use the model registry when a host or provider package needs to:
|
|
19
|
+
|
|
20
|
+
- Fail closed when a provider/model is unknown.
|
|
21
|
+
- Publish model metadata from a provider package.
|
|
22
|
+
- Pick provider request behavior from generic metadata such as `ModelConfig.cache`.
|
|
23
|
+
- Keep pricing, limits, and capabilities near the model id without global state.
|
|
24
|
+
|
|
25
|
+
Do not use the registry for credential lookup, network model discovery, provider package discovery, or automatic SDK configuration.
|
|
26
|
+
|
|
27
|
+
## Inputs / request
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { createModelRegistry, type ModelConfig } from "@arnilo/prism";
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`ModelConfig` metadata fields:
|
|
34
|
+
|
|
35
|
+
| Field | Purpose |
|
|
36
|
+
| --- | --- |
|
|
37
|
+
| `provider` / `model` | Required registry key. |
|
|
38
|
+
| `displayName` | Human-readable label. |
|
|
39
|
+
| `capabilities` | Input/output modes plus reasoning/tools/streaming booleans. |
|
|
40
|
+
| `limits` | Context and output-token limits. |
|
|
41
|
+
| `cost` | Input/output/cache read/cache write pricing. |
|
|
42
|
+
| `cache` | Generic `ModelCacheCapabilities`. |
|
|
43
|
+
| `compat` | Provider-owned inert JSON escape hatch. |
|
|
44
|
+
| `parameters` | Host/provider default parameters. |
|
|
45
|
+
| `metadata` | Host-owned inert metadata. |
|
|
46
|
+
|
|
47
|
+
`ModelCacheCapabilities` fields:
|
|
48
|
+
|
|
49
|
+
| Field | Purpose |
|
|
50
|
+
| --- | --- |
|
|
51
|
+
| `kind` | `implicit`, `openai_key`, `cache_control`, `provider_specific`, or `none`. |
|
|
52
|
+
| `maxKeyLength` | Provider-safe cache key length. |
|
|
53
|
+
| `maxBreakpoints` | Maximum cache-control anchors. |
|
|
54
|
+
| `minCacheableTokens` | Minimum prompt size worth marking cacheable. |
|
|
55
|
+
| `longRetention` | Whether long retention is supported. |
|
|
56
|
+
|
|
57
|
+
## Outputs / response / events
|
|
58
|
+
|
|
59
|
+
`createModelRegistry()` returns a `ModelRegistry`:
|
|
60
|
+
|
|
61
|
+
| Method | Result |
|
|
62
|
+
| --- | --- |
|
|
63
|
+
| `register(model)` | Stores or replaces model. With `duplicate: "error"`, throws on duplicate key. |
|
|
64
|
+
| `get(provider, model)` | Returns `ModelConfig | undefined`. |
|
|
65
|
+
| `resolve(provider, model)` | Returns `ModelConfig` or throws `Unknown model: <provider>/<model>`. |
|
|
66
|
+
| `list()` | Returns registered models in insertion order. |
|
|
67
|
+
|
|
68
|
+
The registry emits no events and performs no I/O.
|
|
69
|
+
|
|
70
|
+
## Request/response example
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"model": {
|
|
75
|
+
"provider": "demo",
|
|
76
|
+
"model": "demo-large",
|
|
77
|
+
"capabilities": { "input": ["text"], "tools": true, "streaming": true },
|
|
78
|
+
"limits": { "contextWindow": 128000, "maxOutputTokens": 8192 },
|
|
79
|
+
"cost": { "input": 10, "output": 30, "cacheRead": 2, "currency": "USD", "unit": "1M tokens" },
|
|
80
|
+
"cache": { "kind": "cache_control", "maxBreakpoints": 4, "longRetention": true }
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Implementation example
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { createModelRegistry, type ModelConfig } from "@arnilo/prism";
|
|
89
|
+
|
|
90
|
+
const model: ModelConfig = {
|
|
91
|
+
provider: "demo",
|
|
92
|
+
model: "demo-large",
|
|
93
|
+
displayName: "Demo Large",
|
|
94
|
+
capabilities: { input: ["text"], output: ["text"], tools: true, streaming: true },
|
|
95
|
+
limits: { contextWindow: 128_000, maxOutputTokens: 8_192 },
|
|
96
|
+
cost: { input: 10, output: 30, cacheRead: 2, cacheWrite: 12, currency: "USD", unit: "1M tokens" },
|
|
97
|
+
cache: { kind: "cache_control", maxBreakpoints: 4, minCacheableTokens: 1024, longRetention: true },
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const registry = createModelRegistry([model], { duplicate: "error" });
|
|
101
|
+
const resolved = registry.resolve("demo", "demo-large");
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Extension and configuration notes
|
|
105
|
+
|
|
106
|
+
Provider packages register models through `ProviderPackageAPI.registerModel(model)`. The extension kernel stores those records in the host-owned registries. Static package metadata is allowed; dynamic model discovery remains provider/host code outside Prism core.
|
|
107
|
+
|
|
108
|
+
`ModelConfig.compat` remains for provider-owned inert JSON. Prefer typed fields (`capabilities`, `limits`, `cost`, `cache`) for generic behavior shared across providers.
|
|
109
|
+
|
|
110
|
+
## Security and performance notes
|
|
111
|
+
|
|
112
|
+
- Model metadata must not contain credentials or secrets.
|
|
113
|
+
- Registration is in-memory and O(1) by provider/model key.
|
|
114
|
+
- `ModelConfig.cache` is declarative capability info only; it does not grant permissions, select tools, or bypass auth.
|
|
115
|
+
- Provider-specific behavior belongs in provider packages, not Prism core.
|
|
116
|
+
|
|
117
|
+
## Related APIs
|
|
118
|
+
|
|
119
|
+
- [Provider layer](provider-layer.md): provider/model registry overview.
|
|
120
|
+
- [Provider caching](provider-caching.md): `ModelCacheCapabilities` and cache helpers.
|
|
121
|
+
- [Provider packages](provider-packages.md): package registration of model metadata.
|
|
122
|
+
- [Public contracts](public-contracts.md): `ModelConfig`, `ModelCost`, and cache type contracts.
|
|
@@ -13,7 +13,7 @@ APIs:
|
|
|
13
13
|
|
|
14
14
|
Use it in Node hosts that want a small durable `SessionStore` without adding a database.
|
|
15
15
|
|
|
16
|
-
Do not use it for browser code, automatic discovery, shared multi-process locking, migrations, compaction, credentials,
|
|
16
|
+
Do not use it for browser code, automatic discovery, shared multi-process locking, migrations, compaction, credentials, app-specific tools, or production multi-writer storage. Use a database-backed `SessionStore` adapter for multi-process or multi-writer durability.
|
|
17
17
|
|
|
18
18
|
## Inputs / request
|
|
19
19
|
|
|
@@ -32,12 +32,12 @@ import { createJsonlSessionStore } from "@arnilo/prism/node/session-store-jsonl"
|
|
|
32
32
|
|
|
33
33
|
`createJsonlSessionStore()` returns a `SessionStore`:
|
|
34
34
|
|
|
35
|
-
- `append(entry)` appends one JSON line
|
|
35
|
+
- `append(entry, options?)` appends one JSON line, rejects duplicate entry ids, honors `expectedParentId` existence checks, and deduplicates exact idempotency retries within this store instance.
|
|
36
36
|
- `list(sessionId)` reads the file and returns valid entries for that session id. Corrupt or shape-invalid lines are skipped; they do not poison the whole file.
|
|
37
37
|
- `get(id)` reads the file and returns the matching valid entry, if any.
|
|
38
38
|
- `readJsonlSessionEntries(path)` returns `{ entries: SessionEntry[]; errors: SessionEntryParseError[] }` so hosts/tests can inspect per-line parse errors.
|
|
39
39
|
|
|
40
|
-
Missing files read as empty stores. Invalid JSON, missing required fields, or wrong per-kind shapes (`message`, `summary`, `model_change`, `custom`, `compaction`, `label`, or non-string `parentId`) are quarantined per line with line number and reason; the raw line is included in `SessionEntryParseError.raw`.
|
|
40
|
+
Missing files read as empty stores. Invalid JSON, missing required fields, unsupported `schemaVersion`, unknown `kind`, or wrong per-kind shapes (`message`, `summary`, `model_change`, `custom`, `compaction`, `label`, `event`, `metadata`, or non-string `parentId`) are quarantined per line with line number and reason; the raw line is included in `SessionEntryParseError.raw`. Unknown entry kinds and future schema versions fail closed: the line is skipped and never returned by `list()` or `get()`.
|
|
41
41
|
|
|
42
42
|
## Request/response example
|
|
43
43
|
|
|
@@ -65,6 +65,7 @@ Use `createMemorySessionStore()` for tests or throwaway sessions; use the JSONL
|
|
|
65
65
|
- This adapter is an explicit Node subpath. Importing `@arnilo/prism` does not touch the filesystem.
|
|
66
66
|
- Hosts choose the file path. Prism does not discover, watch, rotate, compact, or migrate files.
|
|
67
67
|
- The adapter stores only `SessionEntry` data passed to `append()`.
|
|
68
|
+
- `SessionAppendOptions` idempotency tracking is in memory for the store instance. It is a development guard, not a durable cross-process coordination mechanism.
|
|
68
69
|
|
|
69
70
|
## Security and performance notes
|
|
70
71
|
|
|
@@ -72,7 +73,7 @@ Use `createMemorySessionStore()` for tests or throwaway sessions; use the JSONL
|
|
|
72
73
|
- Errors include path/reason or line number, not file contents.
|
|
73
74
|
- Do not put secrets in messages, metadata, summaries, labels, or custom entries.
|
|
74
75
|
- Reads are linear in file size. Appends are serialized per store instance.
|
|
75
|
-
- There is no cross-process lock;
|
|
76
|
+
- There is no cross-process lock or durable idempotency table; two processes writing the same file can race. Add a database or external lock if multiple processes write the same file.
|
|
76
77
|
|
|
77
78
|
## Related APIs
|
|
78
79
|
|