@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.
Files changed (121) hide show
  1. package/CHANGELOG.md +19 -2
  2. package/README.md +17 -7
  3. package/dist/agent-definitions.d.ts +12 -0
  4. package/dist/agent-definitions.js +131 -0
  5. package/dist/agent-loops.d.ts +14 -0
  6. package/dist/agent-loops.js +161 -0
  7. package/dist/agents.js +263 -76
  8. package/dist/cache-helpers.d.ts +28 -0
  9. package/dist/cache-helpers.js +73 -0
  10. package/dist/cli-runner.d.ts +38 -2
  11. package/dist/cli-runner.js +167 -5
  12. package/dist/compaction.js +2 -0
  13. package/dist/config.js +47 -12
  14. package/dist/contracts.d.ts +581 -6
  15. package/dist/contracts.js +41 -1
  16. package/dist/contribution-parsing.d.ts +19 -0
  17. package/dist/contribution-parsing.js +124 -0
  18. package/dist/contributions.d.ts +13 -3
  19. package/dist/contributions.js +96 -20
  20. package/dist/extensions.js +3 -0
  21. package/dist/index.d.ts +19 -9
  22. package/dist/index.js +10 -4
  23. package/dist/input.d.ts +7 -1
  24. package/dist/input.js +52 -11
  25. package/dist/instruction-injection.d.ts +28 -0
  26. package/dist/instruction-injection.js +55 -0
  27. package/dist/manifests.d.ts +1 -1
  28. package/dist/manifests.js +3 -3
  29. package/dist/models.d.ts +4 -1
  30. package/dist/models.js +5 -2
  31. package/dist/node/agent-definitions.d.ts +98 -0
  32. package/dist/node/agent-definitions.js +389 -0
  33. package/dist/node/contribution-discovery.d.ts +17 -0
  34. package/dist/node/contribution-discovery.js +163 -0
  35. package/dist/node/instruction-injectors.d.ts +32 -0
  36. package/dist/node/instruction-injectors.js +72 -0
  37. package/dist/node/session-store-jsonl.d.ts +1 -1
  38. package/dist/node/session-store-jsonl.js +42 -4
  39. package/dist/node/system-project-prompts.d.ts +30 -0
  40. package/dist/node/system-project-prompts.js +53 -0
  41. package/dist/provider-events.d.ts +3 -1
  42. package/dist/provider-events.js +34 -0
  43. package/dist/provider-request-policy.js +15 -1
  44. package/dist/providers/openai-compatible.js +1 -1
  45. package/dist/providers.d.ts +6 -2
  46. package/dist/providers.js +15 -1
  47. package/dist/redaction.d.ts +2 -1
  48. package/dist/redaction.js +3 -0
  49. package/dist/registry-options.d.ts +5 -0
  50. package/dist/registry-options.js +5 -0
  51. package/dist/rpc.d.ts +6 -2
  52. package/dist/rpc.js +71 -13
  53. package/dist/session-stores.d.ts +3 -1
  54. package/dist/session-stores.js +67 -6
  55. package/dist/skills.d.ts +4 -1
  56. package/dist/skills.js +3 -1
  57. package/dist/system-prompts.js +6 -2
  58. package/dist/testing/compaction-conformance.d.ts +17 -0
  59. package/dist/testing/compaction-conformance.js +61 -0
  60. package/dist/testing/extension-conformance.d.ts +26 -0
  61. package/dist/testing/extension-conformance.js +55 -0
  62. package/dist/testing/provider-conformance.d.ts +7 -0
  63. package/dist/testing/provider-conformance.js +18 -31
  64. package/dist/testing/session-store-conformance.d.ts +20 -0
  65. package/dist/testing/session-store-conformance.js +92 -0
  66. package/dist/testing/tool-conformance.d.ts +39 -0
  67. package/dist/testing/tool-conformance.js +79 -0
  68. package/dist/tools.d.ts +7 -2
  69. package/dist/tools.js +50 -13
  70. package/docs/agent-definitions.md +251 -0
  71. package/docs/agent-events.md +199 -0
  72. package/docs/agent-loops.md +217 -0
  73. package/docs/agent-session-runtime.md +20 -8
  74. package/docs/cli-rpc.md +39 -4
  75. package/docs/coding-agent-tools.md +208 -0
  76. package/docs/compaction-and-retry.md +2 -2
  77. package/docs/compaction-conformance.md +76 -0
  78. package/docs/compaction-llm.md +6 -3
  79. package/docs/compaction-observational-memory.md +4 -4
  80. package/docs/configuration-and-manifests.md +6 -1
  81. package/docs/context-and-skills.md +79 -6
  82. package/docs/contribution-discovery.md +149 -0
  83. package/docs/contribution-registries.md +9 -6
  84. package/docs/credentials-and-redaction.md +2 -0
  85. package/docs/customization.md +191 -0
  86. package/docs/database-persistence.md +407 -0
  87. package/docs/extension-authoring.md +193 -0
  88. package/docs/extension-conformance.md +80 -0
  89. package/docs/extensions.md +6 -0
  90. package/docs/host-security.md +141 -0
  91. package/docs/index.md +41 -19
  92. package/docs/input-and-prompt-assembly.md +19 -3
  93. package/docs/instruction-injection.md +183 -0
  94. package/docs/migration.md +201 -0
  95. package/docs/model-registry.md +122 -0
  96. package/docs/node-jsonl-session-store.md +5 -4
  97. package/docs/performance.md +127 -0
  98. package/docs/provider-caching.md +206 -0
  99. package/docs/provider-conformance.md +32 -5
  100. package/docs/provider-layer.md +51 -11
  101. package/docs/provider-packages.md +65 -5
  102. package/docs/provider-request-policies.md +113 -0
  103. package/docs/providers/kimi.md +22 -0
  104. package/docs/providers/neuralwatt.md +388 -0
  105. package/docs/providers/openai-compatible.md +1 -0
  106. package/docs/providers/openai.md +21 -0
  107. package/docs/providers/opencode-go.md +31 -3
  108. package/docs/providers/openrouter.md +29 -0
  109. package/docs/providers/zai.md +17 -0
  110. package/docs/public-contracts.md +87 -12
  111. package/docs/release-and-install.md +79 -27
  112. package/docs/runs-and-usage.md +236 -0
  113. package/docs/session-store-conformance.md +78 -0
  114. package/docs/session-stores-and-branching.md +10 -6
  115. package/docs/session-stores.md +126 -0
  116. package/docs/settings-auth-trust-security.md +18 -4
  117. package/docs/structured-output.md +247 -0
  118. package/docs/system-prompts.md +104 -2
  119. package/docs/tool-conformance.md +87 -0
  120. package/docs/tools.md +65 -8
  121. package/package.json +36 -2
@@ -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, or app-specific tools.
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 and rejects duplicate entry ids.
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; add a database or external lock if multiple processes write the same file.
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
 
@@ -0,0 +1,127 @@
1
+ # Performance limits
2
+
3
+ ## What it does
4
+
5
+ This page states Prism runtime limits that keep slow consumers and long sessions from becoming unbounded memory or latency problems.
6
+
7
+ Current surfaces:
8
+
9
+ - `SubscribeOptions` for bounded live `AgentEvent` subscriber queues.
10
+ - `SessionStore.readBranchPath(query)` for branch reads that avoid full-session scans.
11
+ - `ProductionPersistenceStore` cursor queries for entries, events, runs, tool calls, and usage.
12
+ - JSONL and memory stores documented as development/local adapters, not production multi-writer stores.
13
+
14
+ ## When to use it
15
+
16
+ Use these limits when embedding Prism in a UI, API server, job worker, or multi-tenant app that may have slow event consumers or long-lived sessions.
17
+
18
+ Do not treat Prism's live event subscribers as a durable queue. Use `RunLedger` / database persistence for replay, audit, billing, and timelines.
19
+
20
+ ## Inputs / request
21
+
22
+ ```ts
23
+ import { createAgent, type SubscribeOptions } from "@arnilo/prism";
24
+
25
+ const options: SubscribeOptions = {
26
+ maxQueuedEvents: 256,
27
+ overflow: "close",
28
+ };
29
+
30
+ const events = session.subscribe(options);
31
+ ```
32
+
33
+ `SubscribeOptions` fields:
34
+
35
+ | Field | Default | Purpose |
36
+ | --- | --- | --- |
37
+ | `maxQueuedEvents` | `1024` | Maximum events queued for one subscriber while it is not awaiting `next()`. Values below `1` are clamped to `1`. |
38
+ | `overflow` | `"close"` | Overflow policy: `"close"`, `"drop_oldest"`, or `"drop_newest"`. |
39
+
40
+ ## Outputs / response / events
41
+
42
+ On default overflow, the affected subscriber receives one `event_subscriber_overflow` event and then finishes:
43
+
44
+ ```json
45
+ {
46
+ "type": "event_subscriber_overflow",
47
+ "sessionId": "session_1",
48
+ "droppedEvents": 257,
49
+ "maxQueuedEvents": 256,
50
+ "overflow": "close"
51
+ }
52
+ ```
53
+
54
+ `drop_oldest` keeps the newest queued events. `drop_newest` ignores incoming events while the queue is full. These policies are live-view policies only; they do not affect `RunLedger` writes or stored session entries.
55
+
56
+ ## Request/response example
57
+
58
+ ```json
59
+ {
60
+ "subscribe": { "maxQueuedEvents": 256, "overflow": "close" },
61
+ "store": "database-backed SessionStore with readBranchPath",
62
+ "eventLedger": "cursor-paginated by runId and sequence"
63
+ }
64
+ ```
65
+
66
+ ## Implementation example
67
+
68
+ ```ts
69
+ import { createAgent, createMockProvider, providerDone, providerTextDelta } from "@arnilo/prism";
70
+
71
+ const agent = createAgent({
72
+ model: { provider: "mock", model: "demo" },
73
+ provider: createMockProvider([providerTextDelta("Hello"), providerDone()]),
74
+ });
75
+
76
+ const session = agent.createSession();
77
+ const reader = (async () => {
78
+ for await (const event of session.subscribe({ maxQueuedEvents: 256, overflow: "close" })) {
79
+ if (event.type === "event_subscriber_overflow") break;
80
+ render(event);
81
+ }
82
+ })();
83
+
84
+ await session.run("Hi");
85
+ await reader;
86
+
87
+ function render(_event: unknown) {}
88
+ ```
89
+
90
+ For production branch reads, implement `SessionStore.readBranchPath` instead of loading every entry:
91
+
92
+ ```ts
93
+ const store = {
94
+ async append(entry, options) { /* transaction + parent/idempotency checks */ },
95
+ async list(sessionId) { /* development fallback only */ return []; },
96
+ async readBranchPath(query) {
97
+ // Use one ancestor query / recursive CTE and return a cursor page.
98
+ return { items: [], nextCursor: undefined };
99
+ },
100
+ };
101
+ ```
102
+
103
+ ## Extension and configuration notes
104
+
105
+ - `SubscribeOptions` is per subscriber. One slow UI can be closed or dropped without affecting other subscribers, the active run, ledger writes, or session storage.
106
+ - `RunLedger` remains the durable event/timeline surface. Hosts may batch inside their ledger adapter, but Prism awaits ledger writes at safe boundaries; preserve per-run event order before acknowledging a batch.
107
+ - Database-backed stores should implement `readBranchPath` and cursor-paginated `ProductionPersistenceStore` queries. Memory and JSONL stores intentionally use full-session/file reads.
108
+ - Cursor pagination should use indexed keys, not offsets: `(run_id, sequence)` for events, `(session_id, started_at, id)` for runs, `(run_id, recorded_at, id)` for usage, and `(session_id, timestamp, id)` for entries.
109
+ - Hosts own queue sizes, page-size caps, database indexes, connection pools, transaction timeouts, retention jobs, partitioning, and multi-process coordination.
110
+
111
+ ## Security and performance notes
112
+
113
+ - Overflow events contain only counts and policy, never message text, tool arguments, prompts, provider payloads, or credentials.
114
+ - Runtime event payloads can be large (`Message`, content deltas, tool results, summaries, artifact metadata). Size queues by events and keep payload size in mind.
115
+ - Live subscriber queues are bounded by default. Durable replay belongs to host storage.
116
+ - `SessionStore.list(sessionId)` is a full-session read. It is fine for memory/JSONL development stores, but production adapters should use `readBranchPath` for provider context and branch views.
117
+ - The JSONL store rereads/parses the file for validation/list/get and serializes appends only within one process. It has no cross-process lock, pagination, migrations, tenant isolation, or retention.
118
+ - Recommended database indexes: session id, run id, parent id, branch leaf id, timestamps, tenant/account/user, event type, entry kind, `(run_id, sequence)` for event timelines, and `(run_id, recorded_at, id)` for usage. Allocate event `sequence` per run for stable timeline pagination.
119
+
120
+ ## Related APIs
121
+
122
+ - [Agent events](agent-events.md): `SubscribeOptions` and `event_subscriber_overflow` event details.
123
+ - [Agent/session runtime](agent-session-runtime.md): `session.subscribe()` and runtime event flow.
124
+ - [Session stores](session-stores.md): `SessionStore.readBranchPath` and dev-vs-production branch reads.
125
+ - [Database persistence](database-persistence.md): cursor queries, reference schema, indexes, and event sequence guidance.
126
+ - [Runs and usage ledger](runs-and-usage.md): durable event, tool-call, and usage persistence.
127
+ - [Node JSONL session store](node-jsonl-session-store.md): development-only JSONL limits.