@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.
Files changed (120) hide show
  1. package/CHANGELOG.md +4 -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/compaction-and-retry.md +2 -2
  76. package/docs/compaction-conformance.md +76 -0
  77. package/docs/compaction-llm.md +6 -3
  78. package/docs/compaction-observational-memory.md +4 -4
  79. package/docs/configuration-and-manifests.md +6 -1
  80. package/docs/context-and-skills.md +79 -6
  81. package/docs/contribution-discovery.md +149 -0
  82. package/docs/contribution-registries.md +9 -6
  83. package/docs/credentials-and-redaction.md +2 -0
  84. package/docs/customization.md +191 -0
  85. package/docs/database-persistence.md +407 -0
  86. package/docs/extension-authoring.md +193 -0
  87. package/docs/extension-conformance.md +80 -0
  88. package/docs/extensions.md +6 -0
  89. package/docs/host-security.md +141 -0
  90. package/docs/index.md +40 -19
  91. package/docs/input-and-prompt-assembly.md +19 -3
  92. package/docs/instruction-injection.md +183 -0
  93. package/docs/migration.md +201 -0
  94. package/docs/model-registry.md +122 -0
  95. package/docs/node-jsonl-session-store.md +5 -4
  96. package/docs/performance.md +127 -0
  97. package/docs/provider-caching.md +206 -0
  98. package/docs/provider-conformance.md +32 -5
  99. package/docs/provider-layer.md +51 -11
  100. package/docs/provider-packages.md +65 -5
  101. package/docs/provider-request-policies.md +113 -0
  102. package/docs/providers/kimi.md +22 -0
  103. package/docs/providers/neuralwatt.md +388 -0
  104. package/docs/providers/openai-compatible.md +1 -0
  105. package/docs/providers/openai.md +21 -0
  106. package/docs/providers/opencode-go.md +31 -3
  107. package/docs/providers/openrouter.md +29 -0
  108. package/docs/providers/zai.md +17 -0
  109. package/docs/public-contracts.md +87 -12
  110. package/docs/release-and-install.md +76 -26
  111. package/docs/runs-and-usage.md +236 -0
  112. package/docs/session-store-conformance.md +78 -0
  113. package/docs/session-stores-and-branching.md +10 -6
  114. package/docs/session-stores.md +126 -0
  115. package/docs/settings-auth-trust-security.md +18 -4
  116. package/docs/structured-output.md +247 -0
  117. package/docs/system-prompts.md +104 -2
  118. package/docs/tool-conformance.md +87 -0
  119. package/docs/tools.md +64 -8
  120. package/package.json +35 -2
@@ -0,0 +1,191 @@
1
+ # SDK customization guide
2
+
3
+ ## What it does
4
+
5
+ This guide maps every supported Prism customization seam to the existing public API. Use it when an embedding app wants to replace provider resolution, middleware, context, input/prompt builders, instruction injectors, agent loops, compaction, retry, session stores, or skill selection without forking the runtime.
6
+
7
+ Prism customization is explicit wiring. There is no hidden global middleware, package auto-activation, provider discovery, tool grant, or background runtime.
8
+
9
+ ## When to use it
10
+
11
+ Use this guide when the default agent/session runtime is close enough, but your host app needs one or more custom policies:
12
+
13
+ - route models to providers dynamically
14
+ - add middleware at documented runtime boundaries
15
+ - resolve host context or selected skills
16
+ - replace input/prompt assembly
17
+ - add inert instruction injectors
18
+ - choose a built-in or custom agent loop
19
+ - configure compaction/retry strategies
20
+ - use a durable session store
21
+
22
+ Do not use customization hooks as a sandbox, permission system, credential manager, package loader, workflow engine, vector memory, or hidden tool activator. Host policy and activation stay outside the hook.
23
+
24
+ ## Inputs / request
25
+
26
+ Most seams are fields on `AgentConfig` or per-run `RunOptions`. Per-run options win where both exist.
27
+
28
+ | Customize | Entry point | Detailed page |
29
+ | --- | --- | --- |
30
+ | Provider resolution | `provider`, `providerSource`, `createProviderResolver()` | [Provider layer](provider-layer.md) |
31
+ | Middleware | `middleware`, `createMiddlewareRegistry()` | [Middleware hooks](middleware-hooks.md) |
32
+ | Context | `context`, `resolveContextProviders()` | [Context and skills](context-and-skills.md) |
33
+ | Skills | `skills`, `activeSkills`, `createSkillRegistry()`, `resolveActiveSkills()` | [Context and skills](context-and-skills.md) |
34
+ | Input builder | `inputBuilder`, `createDefaultInputBuilder()` | [Input and prompt assembly](input-and-prompt-assembly.md) |
35
+ | Prompt builder | `promptBuilder`, `createDefaultPromptBuilder()` | [Input and prompt assembly](input-and-prompt-assembly.md) |
36
+ | Instruction injectors | `instructionInjectors`, `resolveInstructionInjectors()` | [Instruction injection](instruction-injection.md) |
37
+ | Agent loops | `loop`, `singleShotLoop`, `generateValidateReviseLoop()` | [Agent loops](agent-loops.md) |
38
+ | Compaction | `compaction`, `createDefaultCompactionStrategy()` | [Compaction and retry](compaction-and-retry.md) |
39
+ | Retry | `retry`, `createDefaultRetryPolicy()` | [Compaction and retry](compaction-and-retry.md) |
40
+ | Session store | `store`, `SessionStore`, `createMemorySessionStore()` | [Session stores](session-stores.md) |
41
+
42
+ ## Outputs / response / events
43
+
44
+ Customization changes what the existing runtime calls. It does not create new runtime phases.
45
+
46
+ - provider resolution happens once per run before any provider turn
47
+ - input/prompt builders run during provider-request assembly
48
+ - context providers and skills are resolved only when passed to the agent/run
49
+ - middleware runs only for documented hook call sites when a registry is supplied
50
+ - instruction injectors contribute only instructions/context blocks during assembly
51
+ - loops orchestrate shared runtime primitives and return usage
52
+ - compaction/retry run only when configured and triggered
53
+ - session stores receive redacted `SessionEntry` values when a redactor is active
54
+
55
+ Events remain the normal `AgentEvent` stream for runs, tools, compaction, retry, and artifact validation.
56
+
57
+ ## Request/response example
58
+
59
+ ```json
60
+ {
61
+ "agent": {
62
+ "providerSource": "host resolver",
63
+ "middleware": "host registry",
64
+ "inputBuilder": "custom input builder",
65
+ "promptBuilder": "custom prompt builder",
66
+ "context": ["project"],
67
+ "skills": ["brief"],
68
+ "instructionInjectors": ["json"],
69
+ "loop": "generate-validate-revise",
70
+ "compaction": "host strategy",
71
+ "retry": "host policy",
72
+ "store": "host session store"
73
+ }
74
+ }
75
+ ```
76
+
77
+ The JSON is a map of seams, not a Prism config format. Hosts wire concrete public API objects into `createAgent()` or `session.run()`.
78
+
79
+ ## Implementation example
80
+
81
+ ```ts
82
+ import {
83
+ createAgent,
84
+ createDefaultCompactionStrategy,
85
+ createDefaultRetryPolicy,
86
+ createMemorySessionStore,
87
+ createMiddlewareRegistry,
88
+ createMockProvider,
89
+ createProviderResolver,
90
+ createProviderRegistry,
91
+ createSkillRegistry,
92
+ createToolRegistry,
93
+ generateValidateReviseLoop,
94
+ providerDone,
95
+ type ArtifactValidator,
96
+ type ContextProvider,
97
+ type InputBuilder,
98
+ type PromptBuilder,
99
+ } from "@arnilo/prism";
100
+
101
+ const provider = createMockProvider([providerDone()]);
102
+ const providerSource = createProviderResolver(createProviderRegistry([provider]));
103
+ const middleware = createMiddlewareRegistry();
104
+ middleware.use("provider_request", (request, next) => next(request));
105
+
106
+ const context: ContextProvider = {
107
+ name: "project",
108
+ resolve: () => [{ title: "Project", content: "Host-selected context." }],
109
+ };
110
+
111
+ const inputBuilder: InputBuilder = {
112
+ name: "custom-input",
113
+ build: async (input) => [{ role: "user", content: [{ type: "text", text: String(input) }] }],
114
+ };
115
+
116
+ const promptBuilder: PromptBuilder = {
117
+ name: "custom-prompt",
118
+ build: async (request) => request.messages,
119
+ };
120
+
121
+ const validator: ArtifactValidator<unknown> = (value) =>
122
+ typeof value === "string" && value.length > 0
123
+ ? { ok: true }
124
+ : { ok: false, errors: [{ message: "empty output" }] };
125
+
126
+ const agent = createAgent({
127
+ model: { provider: "mock", model: "demo" },
128
+ providerSource,
129
+ middleware,
130
+ context: [context],
131
+ skills: createSkillRegistry([{ name: "brief", instructions: "Be brief." }]),
132
+ tools: createToolRegistry([], { duplicate: "error" }),
133
+ inputBuilder,
134
+ promptBuilder,
135
+ instructionInjectors: [{ name: "json", apply: () => ({ when: "every_turn", instructions: "Use JSON." }) }],
136
+ loop: generateValidateReviseLoop({ validator }),
137
+ compaction: { strategy: createDefaultCompactionStrategy(), thresholdEntries: 40 },
138
+ retry: { policy: createDefaultRetryPolicy({ maxAttempts: 3 }) },
139
+ store: createMemorySessionStore(),
140
+ });
141
+
142
+ await agent.createSession().run("Hello", { activeSkills: ["brief"] });
143
+ ```
144
+
145
+ ### Per-run override examples
146
+
147
+ ```ts
148
+ await session.run("Use a different provider just this run", { providerSource: otherResolver });
149
+ await session.run("No auto compaction here", { compaction: false });
150
+ await session.run("No retry here", { retry: false });
151
+ await session.run("Use only this skill", { activeSkills: ["brief"] });
152
+ await session.run("Use a different loop", { loop: { strategy: "single-shot" } });
153
+ ```
154
+
155
+ ## Extension and configuration notes
156
+
157
+ - Direct `AgentConfig.provider` takes first precedence and bypasses `providerSource`. Without a direct provider, `RunOptions.providerSource` wins over `AgentConfig.providerSource`.
158
+ - A custom `ProviderResolver` returns an `AIProvider | undefined`; `undefined` fails closed before the provider turn.
159
+ - Middleware is not global. It runs only when the host passes a `MiddlewareRegistry` to `createAgent()`, `assembleProviderInput()`, `dispatchToolCall()`, compaction, or retry paths that document a hook.
160
+ - Context providers are host-selected arrays. Extension-contributed providers remain inert until resolved from registries and passed into config.
161
+ - Skills are selected by the host. `toolNames` only require active tools; they never register, allow, permit, or execute tools.
162
+ - Input and prompt builders are replaceable objects. Default builders remain available when omitted.
163
+ - Instruction injectors can add instructions and context blocks only. They grant no tools, skills, permissions, validators, credentials, resource access, or provider options.
164
+ - Agent loops should use `LoopContext` primitives instead of reimplementing provider calls, retry, abort, store appends, redaction, or event emission.
165
+ - Compaction and retry strategies are inert until selected on `AgentConfig` or `RunOptions`. `RunOptions.compaction: false` and `RunOptions.retry: false` disable configured defaults for one run.
166
+ - Session stores are explicit. `AgentSessionConfig.store` wins over `AgentConfig.store`; otherwise the session uses a private in-memory store.
167
+ - Extension packages can register builders, strategies, policies, providers, tools, context, skills, and injectors, but host code still chooses which contributions become active.
168
+
169
+ ## Security and performance notes
170
+
171
+ - Customization cannot grant tools or permissions unless the host explicitly activates tools and permission policies at the tool-dispatch boundary.
172
+ - Middleware, skills, context providers, prompt builders, and instruction injectors cannot bypass tool lookup, allow/deny filters, object-argument checks, permission checks, or `ToolValidator`.
173
+ - Do not put credentials in models, prompts, context, skills, instruction injectors, middleware payloads, cache keys, session entries, ledgers, or examples.
174
+ - Use `createSecretRedactor()` on agent/run config when custom components may handle known secret values.
175
+ - Replaceable hooks are in-process calls on the active path. Prism adds no hidden global middleware, background workers, watchers, package scans, provider calls, resource loads, tool execution, or credential resolution unless the host wires that operation.
176
+ - Custom providers, stores, tools, middleware, loops, and extensions are host code. Prism does not sandbox them.
177
+ - Strict duplicate registries (`{ duplicate: "error" }`) prevent silent shadowing when loading third-party contributions.
178
+
179
+ ## Related APIs
180
+
181
+ - [Provider layer](provider-layer.md): provider registries/resolvers and model routing.
182
+ - [Middleware hooks](middleware-hooks.md): hook names and runtime call sites.
183
+ - [Input and prompt assembly](input-and-prompt-assembly.md): default/custom builders, templates, context, and tools in provider requests.
184
+ - [Instruction injection](instruction-injection.md): inert package instructions/context blocks.
185
+ - [Agent loops](agent-loops.md): `singleShotLoop`, `generateValidateReviseLoop`, custom `AgentLoopStrategy`, and `LoopContext`.
186
+ - [Compaction and retry](compaction-and-retry.md): compaction/retry options, strategies, middleware, and disabling per run.
187
+ - [Context and skills](context-and-skills.md): ordered context providers, skill registries, active-skill selection, and `toolNames` fail-closed behavior.
188
+ - [Session stores](session-stores.md): store selection, branch reads, and conformance.
189
+ - [Tools](tools.md): active tool registry, filtering, permission, validation, and no sandbox.
190
+ - [Extension authoring guide](extension-authoring.md): publishing inert contributions for hosts to select.
191
+ - [Host security guide](host-security.md): fail-closed security checklist for embedding apps.
@@ -0,0 +1,407 @@
1
+ # Database persistence
2
+
3
+ ## What it does
4
+
5
+ The production persistence contracts describe database-neutral types for durable, multi-tenant storage of Prism sessions, branch handles, session entries, runs, agent-event ledger rows, tool-call rows, usage rows, agent-definition versions, retention policies, and migration records. They also define cursor-paginated query shapes so hosts can implement SQL, NoSQL, or object-store adapters without changing Prism runtime internals.
6
+
7
+ Prism itself does not ship a production database adapter. The built-in `SessionStore` contract (`append` / `list` / optional `get`) remains the runtime seam; `ProductionPersistenceStore` is the optional adapter-facing contract for hosts that need paginated reads, tenant isolation, audit tables, and retention.
8
+
9
+ ## When to use it
10
+
11
+ Use these contracts when you write a database-backed `SessionStore` or a separate persistence adapter that needs:
12
+
13
+ - paginated branch/session/event reads via cursor, limit, and order
14
+ - filters by `sessionId`, `runId`, `parentId`, branch `leafId`, timestamps, tenant/account/user, event type, and entry kind
15
+ - durable tables for runs, events, tool calls, usage, and agent-definition versions
16
+ - retention policies and migration records
17
+
18
+ Do not use these contracts as a required runtime dependency. The agent/session runtime only requires `SessionStore`. `ProductionPersistenceStore` is an extension point for hosts that want richer querying. A network-free, runnable reference adapter that implements `SessionStore` + `RunLedger` + `ProductionPersistenceStore` reads against in-memory tables lives at [`examples/external-app-db-backed.ts`](../examples/external-app-db-backed.ts) — lift its contract shapes into your own SQL/NoSQL adapter. The example also calls `assertSessionStoreConforms(..., { exerciseReadBranchPath: true })`, so adapter authors have an executable baseline before adding database-specific tests.
19
+
20
+ ## Inputs / request
21
+
22
+ Import the contracts from the root package:
23
+
24
+ ```ts
25
+ import type {
26
+ ProductionPersistenceStore,
27
+ PersistencePage,
28
+ PersistenceQuery,
29
+ SessionRecord,
30
+ SessionQuery,
31
+ BranchRecord,
32
+ BranchQuery,
33
+ SessionEntryQuery,
34
+ SessionBranchRead,
35
+ RunRecord,
36
+ RunQuery,
37
+ AgentEventRecord,
38
+ AgentEventQuery,
39
+ ToolCallRecord,
40
+ ToolCallQuery,
41
+ UsageRecord,
42
+ UsageQuery,
43
+ AgentDefinitionRecord,
44
+ AgentDefinitionQuery,
45
+ RetentionPolicy,
46
+ RetentionPolicyQuery,
47
+ MigrationRecord,
48
+ MigrationQuery,
49
+ OwnershipScope,
50
+ } from "@arnilo/prism";
51
+ ```
52
+
53
+ Important shapes:
54
+
55
+ | Contract | Purpose |
56
+ | --- | --- |
57
+ | `ProductionPersistenceStore` | Adapter-facing interface with `query*` methods plus optional `readBranchPath(query)`, all returning `PersistencePage<T>`. No SQL/ORM/host file storage/network dependency is required. |
58
+ | `PersistencePage<T>` | `{ items; nextCursor?; total? }` cursor page. |
59
+ | `PersistenceQuery` | `{ cursor?; limit?; order?: "asc" \| "desc" }`. |
60
+ | `OwnershipScope` | `{ tenantId?; accountId?; userId? }` included in records and queries for multi-tenant isolation. |
61
+ | `SessionRecord` | Stored session with ids, timestamps, optional parent session, agent-definition reference, retention policy, and ownership scope. |
62
+ | `BranchRecord` | Branch handle / leaf pointer with `sessionId`, optional `name`, `rootEntryId`, `parentBranchId`, and `leafEntryId`. |
63
+ | `SessionEntryQuery` | Cursor query for entry ranges by session/run/parent/leaf/kind/time. |
64
+ | `SessionBranchRead` | `{ sessionId, leafId?, cursor?, limit? }` request for one branch's ancestor chain. Used by `readBranchPath` so runtime branch reads avoid `list(sessionId)`. |
65
+ | `RunRecord` | Stored run with `sessionId`, `branchId`, status (`queued` \| `running` \| `succeeded` \| `failed` \| `aborted`), `model`, `provider`, `idempotencyKey`, `abortReason`, and `error`. |
66
+ | `AgentEventRecord` | Event ledger row with `event: AgentEvent` and a `redacted` flag. Hosts redact before storage. |
67
+ | `ToolCallRecord` | Tool-call row with `arguments`, optional `result: ToolResult`, `reason`, `progress` snapshots, status, and a `redacted` flag. |
68
+ | `UsageRecord` | Usage row wrapping `Usage` with session/run/entry linkage. |
69
+ | `AgentDefinitionRecord` | Versioned agent definition snapshot. Only stores `AgentDefinition` data; never provider credentials/resolvers/instances. |
70
+ | `RetentionPolicy` | Policy with `maxAgeDays`, `maxEntriesPerSession`, `maxTotalBytes`, `archiveStore`, and `appliedKinds`. |
71
+ | `MigrationRecord` | Applied migration with name, version, timestamp, checksum, and applied-by. |
72
+
73
+ ## Outputs / response / events
74
+
75
+ Each `query*` method returns a `PersistencePage<T>`:
76
+
77
+ ```ts
78
+ {
79
+ items: readonly T[];
80
+ nextCursor?: string;
81
+ total?: number;
82
+ }
83
+ ```
84
+
85
+ `nextCursor` is opaque to Prism; hosts encode whatever cursor they need. Absent `nextCursor` means the end of the result set. `total` is optional because exact counts can be expensive on some stores.
86
+
87
+ ## Reference relational schema
88
+
89
+ This schema is a reference, not generated DDL. Hosts map these tables/columns to their chosen database. Names snake_case here map to the camelCase TypeScript contracts in `src/contracts.ts`.
90
+
91
+ ### Multi-tenant ownership (host-managed)
92
+
93
+ Hosts that need tenant/account/user isolation can use these optional tables. The persistence contracts only require `tenantId`/`accountId`/`userId` strings on records.
94
+
95
+ | Table | Key columns |
96
+ | --- | --- |
97
+ | `prism_tenants` | `id`, `name`, `created_at`, `metadata` |
98
+ | `prism_accounts` | `id`, `tenant_id`, `name`, `created_at`, `metadata` |
99
+ | `prism_users` | `id`, `tenant_id`, `account_id`, `name`, `created_at`, `metadata` |
100
+
101
+ ### Agent definitions
102
+
103
+ | Table | Key columns |
104
+ | --- | --- |
105
+ | `prism_agent_definitions` | `id` PK, `name`, `version`, `source`, `agent_definition` JSONB, `tenant_id`, `account_id`, `user_id`, `created_at`, `created_by`, `metadata` JSONB |
106
+
107
+ The `agent_definition` column stores the `AgentDefinition` shape: name, description, model, tools, skills, context, system prompt, instructions, loop, and metadata. It never stores provider credentials, resolvers, or provider instances.
108
+
109
+ ### Sessions
110
+
111
+ | Table | Key columns |
112
+ | --- | --- |
113
+ | `prism_sessions` | `id` PK, `tenant_id`, `account_id`, `user_id`, `parent_session_id`, `agent_definition_id`, `agent_definition_version`, `created_at`, `updated_at`, `expires_at`, `retention_policy_id`, `metadata` JSONB |
114
+
115
+ ### Branches
116
+
117
+ | Table | Key columns |
118
+ | --- | --- |
119
+ | `prism_branches` | `id` PK, `session_id` FK, `name`, `root_entry_id`, `parent_branch_id`, `leaf_entry_id`, `created_at`, `metadata` JSONB |
120
+
121
+ A branch leaf (`leaf_entry_id`) is the current entry id for that branch. Rebuild logic walks `parent_id` from the leaf back to the root.
122
+
123
+ ### Session entries
124
+
125
+ | Table | Key columns |
126
+ | --- | --- |
127
+ | `prism_session_entries` | `id` PK, `session_id` FK, `parent_id`, `run_id`, `timestamp`, `kind`, `schema_version`, `message` JSONB, `event` JSONB, `model` JSONB, `previous_model` JSONB, `label`, `summary`, `data` JSONB, `metadata` JSONB |
128
+
129
+ Maps directly to `SessionEntry`. `kind` is one of the `SessionEntryKind` values. `schema_version` defaults to `1`. `parent_id` may be null for the root entry of a session.
130
+
131
+ `SessionAppendOptions.idempotencyKey` is not part of `SessionEntry`; store it in an adapter-owned side table when you need durable retry detection:
132
+
133
+ | Table | Key columns |
134
+ | --- | --- |
135
+ | `prism_session_append_idempotency` | `session_id`, `expected_parent_id`, `idempotency_key`, `entry_id`, `created_at`, `tenant_id`, `account_id`, `user_id` |
136
+
137
+ Use a unique key on `(session_id, expected_parent_id, idempotency_key)` (plus tenant/account columns when scoped). That matches the runtime retry shape: the same run may append several entries with one run-level key, but each append has a different `expectedParentId` as the leaf advances.
138
+
139
+ ### Runs
140
+
141
+ | Table | Key columns |
142
+ | --- | --- |
143
+ | `prism_runs` | `id` PK, `session_id` FK, `branch_id`, `agent_definition_id`, `agent_definition_version`, `status`, `started_at`, `finished_at`, `model` JSONB, `provider`, `idempotency_key`, `abort_reason`, `error` JSONB, `tenant_id`, `account_id`, `user_id`, `metadata` JSONB |
144
+
145
+ `status` values: `queued`, `running`, `succeeded`, `failed`, `aborted`. Hosts that do not queue runs will only see `running`, `succeeded`, `failed`, and `aborted` from the runtime.
146
+
147
+ ### Agent event ledger
148
+
149
+ | Table | Key columns |
150
+ | --- | --- |
151
+ | `prism_agent_events` | `id` PK, `session_id` FK, `run_id`, `entry_id`, `sequence`, `type`, `timestamp`, `event` JSONB, `redacted` boolean, `tenant_id`, `account_id`, `user_id`, `metadata` JSONB |
152
+
153
+ The `event` JSONB stores a redacted `AgentEvent`. The `sequence` column is an implementation aid for stable ordering when timestamps collide. Set `redacted = true` after applying a `SecretRedactor`.
154
+
155
+ ### Tool calls
156
+
157
+ | Table | Key columns |
158
+ | --- | --- |
159
+ | `prism_tool_calls` | `id` PK, `session_id` FK, `run_id`, `entry_id`, `tool_call_id`, `name`, `arguments` JSONB, `result` JSONB, `status`, `reason`, `progress` JSONB, `progress_metadata` JSONB, `progress_at`, `started_at`, `finished_at`, `redacted` boolean, `tenant_id`, `account_id`, `user_id`, `metadata` JSONB |
160
+
161
+ `status` values: `started`, `finished`, `error`, `blocked`. Progress snapshots are stored with status `started` and the `progress`/`progress_metadata`/`progress_at` columns populated. `arguments` and `result` must be redacted before storage when they contain secrets.
162
+
163
+ ### Usage
164
+
165
+ | Table | Key columns |
166
+ | --- | --- |
167
+ | `prism_usage` | `id` PK, `session_id` FK, `run_id`, `entry_id`, `usage` JSONB, `recorded_at`, `tenant_id`, `account_id`, `user_id`, `metadata` JSONB |
168
+
169
+ The `usage` JSONB stores the `Usage` shape: input/output/total/cache tokens, cost, and currency.
170
+
171
+ ### Retention policies
172
+
173
+ | Table | Key columns |
174
+ | --- | --- |
175
+ | `prism_retention_policies` | `id` PK, `tenant_id`, `account_id`, `user_id`, `name`, `max_age_days`, `max_entries_per_session`, `max_total_bytes`, `archive_store`, `applied_kinds` JSONB, `created_at`, `metadata` JSONB |
176
+
177
+ `applied_kinds` is a JSON array of `SessionEntryKind` values; null means all kinds.
178
+
179
+ ### Migrations
180
+
181
+ | Table | Key columns |
182
+ | --- | --- |
183
+ | `prism_migrations` | `id` PK, `name`, `version`, `applied_at`, `applied_by`, `checksum`, `metadata` JSONB |
184
+
185
+ Prism does not run migrations; hosts own migration tooling and use this table to record applied changes.
186
+
187
+ ## Adapter readiness checklist
188
+
189
+ Before using a host database adapter in production:
190
+
191
+ - Implement `SessionStore.append()` transactionally with duplicate-id rejection, `expectedParentId` existence validation, and `(session_id, expected_parent_id, idempotency_key)` retry deduplication.
192
+ - Implement `readBranchPath(query)` for large sessions and run `assertSessionStoreConforms(adapter, { exerciseReadBranchPath: true })` from `@arnilo/prism/testing/session-store-conformance` in adapter tests.
193
+ - Implement `RunLedger` writes for runs, events, tool calls, and usage if the host needs audit replay, billing, or observability; query those rows through `ProductionPersistenceStore` or host-specific read APIs.
194
+ - Prove secrets stay out of durable rows: no provider credentials, provider instances, credential resolvers, API keys, raw provider clients, or unredacted payloads in session, branch, ledger, usage, idempotency, migration, or definition records.
195
+ - Keep Prism core dependency-free: no ORM, migrations, connection pool, or database driver belongs in `@arnilo/prism`.
196
+
197
+ ## Adapter performance guidance
198
+
199
+ Database adapters should keep Prism reads and writes cursor-shaped and indexed. Do not add an ORM or adapter dependency to Prism core; implement this in host code.
200
+
201
+ Minimum production guidance:
202
+
203
+ - **Branch context:** implement `SessionStore.readBranchPath(query)` with an ancestor query / recursive CTE. Treat `SessionStore.list(sessionId)` as an O(n) development fallback only.
204
+ - **Cursor pagination:** every `query*` method should honor `cursor`, `limit`, and `order`. Encode cursors from indexed columns such as `(timestamp, id)`, `(started_at, id)`, `(recorded_at, id)`, or `(run_id, sequence)`; never use offset pagination for long sessions.
205
+ - **Batch appends:** `SessionStore.append()` is single-entry because the runtime advances one branch leaf at a time. Hosts may batch inside their DB/ledger adapters for `RunLedger` rows, but the adapter must preserve per-run event order and must not acknowledge writes before durable enqueue/commit.
206
+ - **Event sequence allocation:** allocate a monotonic `sequence` per `run_id` when inserting `prism_agent_events`. Use it with `run_id` for stable event timeline pagination when timestamps collide.
207
+ - **Run/event/usage query shapes:** runs page by `(session_id, started_at, id)` or `(branch_id, started_at, id)`; events page by `(run_id, sequence)` or `(session_id, timestamp, id)`; usage pages by `(run_id, recorded_at, id)` or `(session_id, recorded_at, id)`.
208
+ - **Host-owned sizing:** hosts own connection pools, transaction timeouts, page-size caps, queue/batch size, retention jobs, partitioning, and tenant/account/user isolation. Prism does not guess production limits.
209
+ - **Security:** persist redacted `SessionEntry`, `AgentEventRecord`, `ToolCallRecord`, and `UsageRecord` data only. Never store provider objects, credential resolvers, API keys, or raw provider clients.
210
+
211
+ ## Indexes
212
+
213
+ Recommended indexes for the reference schema. Hosts should add DB-specific partial or expression indexes as needed.
214
+
215
+ | Table | Index | Supports |
216
+ | --- | --- | --- |
217
+ | `prism_sessions` | `(tenant_id, account_id, user_id, created_at)` | tenant-scoped session listing |
218
+ | `prism_sessions` | `(expires_at)` | retention expiry scans |
219
+ | `prism_sessions` | `(agent_definition_id, agent_definition_version)` | definition-version usage |
220
+ | `prism_branches` | `(session_id, name)` | named branch lookup |
221
+ | `prism_branches` | `(leaf_entry_id)` | leaf-to-branch resolution |
222
+ | `prism_session_entries` | `(session_id, parent_id)` | parent existence checks and child lookups |
223
+ | `prism_session_entries` | `(session_id, kind, timestamp)` | kind-filtered entry listing |
224
+ | `prism_session_entries` | `(session_id, run_id, timestamp)` | run-scoped entry listing |
225
+ | `prism_session_entries` | `(session_id, timestamp, id)` | cursor pagination |
226
+ | `prism_session_entries` | `(session_id, id)` | append parent validation and recursive branch reads |
227
+ | `prism_session_append_idempotency` | unique `(session_id, expected_parent_id, idempotency_key)` | append retry deduplication |
228
+ | `prism_runs` | `(session_id, started_at)` | run history |
229
+ | `prism_runs` | `(branch_id, started_at)` | branch-scoped runs |
230
+ | `prism_runs` | `(status, finished_at)` | retention/completion scans |
231
+ | `prism_agent_events` | `(session_id, timestamp, id)` | event stream pagination |
232
+ | `prism_agent_events` | `(run_id, timestamp, id)` | run event stream |
233
+ | `prism_agent_events` | `(run_id, sequence)` | stable per-run event timeline pagination |
234
+ | `prism_agent_events` | `(session_id, type, timestamp)` | event-type filtering |
235
+ | `prism_agent_events` | `(entry_id)` | entry-to-event lookup |
236
+ | `prism_tool_calls` | `(session_id, name, started_at)` | tool usage by name |
237
+ | `prism_tool_calls` | `(run_id, started_at)` | run tool-call listing |
238
+ | `prism_tool_calls` | `(tool_call_id)` | deduplication / replay |
239
+ | `prism_usage` | `(session_id, recorded_at)` | usage aggregation |
240
+ | `prism_usage` | `(run_id, recorded_at)` | run usage |
241
+ | `prism_agent_definitions` | `(name, version)` | definition lookup |
242
+ | `prism_retention_policies` | `(tenant_id, account_id, user_id)` | policy listing |
243
+ | `prism_migrations` | `(name, version)` | applied-migration uniqueness |
244
+
245
+ Run idempotency keys are written by the runtime into `RunRecord.idempotencyKey` and the `prism_runs.idempotency_key` column. Hosts should add a unique index on `(tenant_id, idempotency_key)` or `(account_id, idempotency_key)` in `prism_runs` for run-level deduplication. Append idempotency uses the separate `prism_session_append_idempotency` unique key above because `SessionEntry` itself does not carry `idempotencyKey`.
246
+
247
+ ## Conditional append transaction pattern
248
+
249
+ Implement `SessionStore.append(entry, options)` in one DB transaction:
250
+
251
+ 1. If `options.idempotencyKey` exists, insert `(session_id, expected_parent_id, idempotency_key, entry_id)` into `prism_session_append_idempotency`. A unique-key hit means an exact retry; raise/return a `SessionAppendConflictError` with `idempotencyDuplicate: true` (or no-op if your adapter deliberately chooses idempotent success).
252
+ 2. If `options.expectedParentId` exists, verify that `(session_id, id)` exists in `prism_session_entries`. If missing, rollback and raise `SessionAppendConflictError` with `expectedParentId`.
253
+ 3. Insert the `prism_session_entries` row. A duplicate entry id should fail the transaction.
254
+ 4. Optionally update a `prism_branches.leaf_entry_id` row with a compare-and-swap if the host wants one-writer linear branches. Prism's built-in stores use existence-validation so checkout/fork can intentionally create two children of the same existing parent.
255
+
256
+ This keeps append guards O(1) with indexes, prevents dangling parent links, and deduplicates exact retries without forcing every branch to be linear.
257
+
258
+ ## Run, event, and usage query shapes
259
+
260
+ Use cursor columns that match query filters:
261
+
262
+ ```sql
263
+ -- Run history for one session.
264
+ CREATE INDEX prism_runs_session_started_idx ON prism_runs (session_id, started_at, id);
265
+
266
+ -- Stable event pagination within one run.
267
+ CREATE INDEX prism_agent_events_run_sequence_idx ON prism_agent_events (run_id, sequence);
268
+
269
+ -- Usage totals / billing reads by run.
270
+ CREATE INDEX prism_usage_run_recorded_idx ON prism_usage (run_id, recorded_at, id);
271
+ ```
272
+
273
+ For NoSQL stores, use equivalent partition/sort keys: partition by `session_id` or `run_id`; sort by `started_at`, `recorded_at`, or event `sequence`. Keep page sizes capped by host policy. `total` is optional because counting large partitions can be expensive.
274
+
275
+ ## Branch reads: no full-session scan
276
+
277
+ Implement `readBranchPath(query: SessionBranchRead): Promise<PersistencePage<SessionEntry>>` on database-backed stores. It should return the selected leaf's ancestor chain (any order is allowed; Prism's helper re-walks and orders it). Use one recursive CTE / ancestor query, for example:
278
+
279
+ ```sql
280
+ WITH RECURSIVE branch AS (
281
+ SELECT * FROM prism_session_entries
282
+ WHERE session_id = $1
283
+ AND id = COALESCE($2, (SELECT leaf_entry_id FROM prism_branches WHERE session_id = $1 LIMIT 1))
284
+ UNION ALL
285
+ SELECT parent.*
286
+ FROM prism_session_entries parent
287
+ JOIN branch child ON child.parent_id = parent.id
288
+ WHERE parent.session_id = $1
289
+ )
290
+ SELECT * FROM branch;
291
+ ```
292
+
293
+ Use `cursor`/`limit` when an adapter pages very long branches. Do not implement common runtime reads by `list(sessionId)` followed by an in-memory parent walk for large production sessions; that is the development fallback only.
294
+
295
+ ## Retention policies
296
+
297
+ A retention policy is a host-managed rule attached to sessions via `retention_policy_id`. Enforcement is host-owned and typically runs as a background job:
298
+
299
+ 1. Select policies whose `max_age_days`, `max_entries_per_session`, or `max_total_bytes` thresholds are exceeded.
300
+ 2. For each affected session, delete or archive entries older than the policy age, beyond the entry count, or over the byte budget.
301
+ 3. Respect `applied_kinds` — only delete kinds listed in the policy (null means all kinds).
302
+ 4. Compact or soft-delete sessions whose `expires_at` has passed.
303
+ 5. Write audit metadata to the migration or host audit log; do not delete the policy row unless explicitly requested.
304
+
305
+ Retention jobs should not run inside the agent/session runtime. They are a host concern.
306
+
307
+ ## Migrations
308
+
309
+ Hosts own schema migrations. Prism publishes only the TypeScript contracts; no DDL is generated or executed by the core library. Recommended migration practices:
310
+
311
+ - Use a sequential or timestamped migration naming convention.
312
+ - Store applied migrations in `prism_migrations` with `name`, `version`, `applied_at`, `applied_by`, and `checksum`.
313
+ - Make entry-kind and schema-version changes additive when possible; new kinds and versions fail closed in the JSONL parser, so DB schemas should accept the same additive expansion.
314
+ - Index new query columns before deploying code that uses them.
315
+ - Back-fill redacted flags and ownership columns before enforcing tenant isolation.
316
+
317
+ ## NoSQL mapping notes
318
+
319
+ For document or wide-column stores, map the relational tables above to the store's native partitioning model:
320
+
321
+ - **Partition key:** `session_id` is usually the best partition key. For multi-tenant workloads, use a composite partition key (`tenant_id`, `session_id`) or a synthetic `tenant_session_id`.
322
+ - **Sort/range key:** Use `timestamp` + `id` for entries and events; use `started_at` + `id` for runs and tool calls. This supports cursor pagination and branch rebuild.
323
+ - **Global secondary indexes / collections:** Duplicate run, kind, type, and name dimensions into GSIs or secondary collections so queries by `run_id`, `kind`, `type`, or `tool_call_id` remain efficient without full scans.
324
+ - **JSON payloads:** Store `AgentEvent`, `ToolResult`, `Message`, `Usage`, `AgentDefinition`, and `data`/`metadata` values as nested documents or serialized JSONB. Redact sensitive fields before writing.
325
+ - **Branches:** In document stores, a branch can be a lightweight document keyed by `leaf_entry_id` that points to the session and root. Rebuild still walks `parent_id` links in entries.
326
+ - **Retention:** Use TTL columns or scheduled map-reduce/streaming jobs. TTL on `expires_at` or entry timestamps is the simplest NoSQL implementation.
327
+
328
+ The Node JSONL session store is a single-process development adapter. It has no cross-process locking, no migrations, no retention enforcement, and no tenant isolation. Do not use it as a production multi-writer store.
329
+
330
+ ## Request/response example
331
+
332
+ ```json
333
+ {
334
+ "sessionId": "session-1",
335
+ "runId": "run-7",
336
+ "kind": ["message", "event"],
337
+ "fromTimestamp": "2024-01-01T00:00:00Z",
338
+ "toTimestamp": "2024-12-31T23:59:59Z",
339
+ "tenantId": "tenant-a",
340
+ "limit": 50,
341
+ "order": "desc"
342
+ }
343
+ ```
344
+
345
+ Example page:
346
+
347
+ ```json
348
+ {
349
+ "items": [
350
+ { "id": "e3", "sessionId": "session-1", "kind": "message", "timestamp": "2024-06-15T10:00:00Z" }
351
+ ],
352
+ "nextCursor": "eyJpZCI6ImUzIn0=",
353
+ "total": 128
354
+ }
355
+ ```
356
+
357
+ ## Implementation example
358
+
359
+ ```ts
360
+ import type {
361
+ ProductionPersistenceStore,
362
+ SessionEntryQuery,
363
+ SessionBranchRead,
364
+ PersistencePage,
365
+ SessionEntry,
366
+ } from "@arnilo/prism";
367
+
368
+ const dbStore: ProductionPersistenceStore = {
369
+ name: "host-postgres-store",
370
+ async queryEntries(query: SessionEntryQuery): Promise<PersistencePage<SessionEntry>> {
371
+ // Host owns SQL/NoSQL implementation.
372
+ return { items: [], nextCursor: undefined };
373
+ },
374
+ async readBranchPath(query: SessionBranchRead): Promise<PersistencePage<SessionEntry>> {
375
+ // Use one recursive/ancestor query, not list(sessionId) + in-memory scan.
376
+ return { items: [], nextCursor: undefined };
377
+ },
378
+ // ...other query methods
379
+ };
380
+ ```
381
+
382
+ ## Extension and configuration notes
383
+
384
+ - `ProductionPersistenceStore` is an optional extension point. The runtime does not require it.
385
+ - Hosts choose the database, schema, transaction, and indexing strategy. The contract only specifies query shapes.
386
+ - `SessionStore` (`append`/`list`/`get`/optional `readBranchPath`) can be implemented on top of `ProductionPersistenceStore` or kept separate.
387
+ - Cursor values and idempotency keys are host-defined and opaque to Prism.
388
+
389
+ ## Security and performance notes
390
+
391
+ - **No credentials in storage.** The contracts never include `CredentialResolver`, `AIProvider`, `ProviderResolver`, provider API keys, or credential values.
392
+ - **Redact before storage.** Runtime session entries are redacted before `SessionStore.append`; `AgentEventRecord.event` and `ToolCallRecord.result` may contain secrets, so hosts must redact them (for example with `redactAgentEvent()` and a `SecretRedactor`) before writing to durable storage and set `redacted: true`.
393
+ - **Tenant isolation.** `OwnershipScope` fields are available on records and queries, but enforcement is the host's responsibility.
394
+ - **Pagination and branch reads.** Every query supports `cursor`/`limit`/`order` so hosts can avoid full-table or full-session scans. Loading an entire large session into memory to serve a provider context is an anti-pattern; implement `readBranchPath` and use branch-relevant filters / recursive ancestor queries.
395
+ - **Indexes.** Production schemas should index `sessionId`, `runId`, `parentId`, `leafId`, timestamps, tenant/account/user, event type, and entry kind. See the reference indexes above.
396
+
397
+ ## Related APIs
398
+
399
+ - [Session store conformance](session-store-conformance.md): executable adapter baseline for append/idempotency/conflict/branch invariants.
400
+ - [Migration guide](migration.md): before/after shapes for moving from in-memory/JSONL to this contract.
401
+ - [Performance limits](performance.md): production sizing, subscriber queues, branch-read limits, and database adapter guidance.
402
+ - [Session stores and branching](session-stores-and-branching.md): `SessionStore`, `SessionEntry`, branch helpers, and runtime branch semantics.
403
+ - [Node JSONL session store](node-jsonl-session-store.md): development-only file adapter; not for production multi-writer storage.
404
+ - [Agent/session runtime](agent-session-runtime.md): sessions, runs, and event emission.
405
+ - [Agent events](agent-events.md): `AgentEvent` variants and redaction.
406
+ - [Tools](tools.md): `ToolResult`, `ToolCallContent`, and tool execution events.
407
+ - [Public contracts](public-contracts.md): full public contract inventory.