@arnilo/prism 0.0.3 → 0.0.5

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 (131) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +62 -26
  3. package/dist/agent-loops.d.ts +8 -1
  4. package/dist/agent-loops.js +57 -11
  5. package/dist/agents.js +212 -32
  6. package/dist/checkpoints.d.ts +11 -0
  7. package/dist/checkpoints.js +144 -0
  8. package/dist/cli-init.d.ts +41 -0
  9. package/dist/cli-init.js +390 -0
  10. package/dist/cli-runner.d.ts +7 -1
  11. package/dist/cli-runner.js +13 -1
  12. package/dist/compaction.js +9 -1
  13. package/dist/content.d.ts +121 -0
  14. package/dist/content.js +538 -0
  15. package/dist/contracts.d.ts +236 -11
  16. package/dist/contracts.js +8 -0
  17. package/dist/event-multiplexer.d.ts +23 -0
  18. package/dist/event-multiplexer.js +136 -0
  19. package/dist/execution-policy.d.ts +28 -0
  20. package/dist/execution-policy.js +24 -0
  21. package/dist/feedback.d.ts +48 -0
  22. package/dist/feedback.js +230 -0
  23. package/dist/index.d.ts +20 -6
  24. package/dist/index.js +13 -5
  25. package/dist/input.js +11 -1
  26. package/dist/leases.d.ts +8 -0
  27. package/dist/leases.js +111 -0
  28. package/dist/node/agent-definitions.js +3 -5
  29. package/dist/node/config.d.ts +1 -0
  30. package/dist/node/config.js +5 -3
  31. package/dist/node/contribution-discovery.js +5 -8
  32. package/dist/node/session-store-jsonl.js +8 -5
  33. package/dist/node/settings.js +2 -2
  34. package/dist/node/trust.js +2 -4
  35. package/dist/observability.d.ts +3 -0
  36. package/dist/observability.js +18 -0
  37. package/dist/providers/media.d.ts +44 -0
  38. package/dist/providers/media.js +126 -0
  39. package/dist/providers/openai-compatible.js +18 -119
  40. package/dist/providers/openai-primitives.d.ts +9 -0
  41. package/dist/providers/openai-primitives.js +129 -0
  42. package/dist/providers/transport.d.ts +40 -0
  43. package/dist/providers/transport.js +221 -0
  44. package/dist/redaction.js +40 -13
  45. package/dist/resources.d.ts +5 -0
  46. package/dist/resources.js +4 -0
  47. package/dist/structured-output.d.ts +11 -0
  48. package/dist/structured-output.js +59 -0
  49. package/dist/testing/feedback.d.ts +6 -0
  50. package/dist/testing/feedback.js +37 -0
  51. package/dist/testing/persistence-schema.d.ts +102 -0
  52. package/dist/testing/persistence-schema.js +487 -0
  53. package/dist/testing/provider-conformance.js +10 -1
  54. package/dist/testing/run-ledger-conformance.d.ts +33 -0
  55. package/dist/testing/run-ledger-conformance.js +178 -0
  56. package/dist/testing/session-store-conformance.d.ts +16 -0
  57. package/dist/testing/session-store-conformance.js +73 -0
  58. package/dist/tools.d.ts +17 -0
  59. package/dist/tools.js +29 -2
  60. package/docs/a2a.md +73 -0
  61. package/docs/agent-events.md +17 -10
  62. package/docs/agent-loops.md +11 -5
  63. package/docs/agent-session-runtime.md +15 -16
  64. package/docs/cli-rpc.md +36 -5
  65. package/docs/coding-agent-tools.md +43 -9
  66. package/docs/coding-security.md +88 -0
  67. package/docs/compaction-observational-memory.md +2 -0
  68. package/docs/context-and-skills.md +1 -0
  69. package/docs/credential-storage.md +177 -0
  70. package/docs/credentials-and-redaction.md +4 -3
  71. package/docs/database-persistence.md +52 -7
  72. package/docs/evaluations.md +122 -0
  73. package/docs/extensions.md +2 -2
  74. package/docs/host-security.md +33 -2
  75. package/docs/index.md +46 -18
  76. package/docs/input-and-prompt-assembly.md +6 -5
  77. package/docs/mcp-tools.md +184 -0
  78. package/docs/middleware-hooks.md +2 -0
  79. package/docs/migration.md +51 -28
  80. package/docs/model-registry.md +5 -3
  81. package/docs/multimodal-content.md +156 -0
  82. package/docs/observability.md +171 -0
  83. package/docs/performance.md +249 -1
  84. package/docs/persistence-credentials-multimodality-primitives.md +303 -0
  85. package/docs/postgres-persistence.md +143 -0
  86. package/docs/provider-conformance.md +18 -0
  87. package/docs/provider-layer.md +1 -1
  88. package/docs/provider-packages.md +2 -0
  89. package/docs/provider-primitives.md +281 -0
  90. package/docs/providers/ai-sdk.md +113 -0
  91. package/docs/providers/kimi.md +1 -0
  92. package/docs/providers/neuralwatt.md +1 -0
  93. package/docs/providers/openai-compatible.md +2 -1
  94. package/docs/providers/openai.md +8 -1
  95. package/docs/providers/opencode-go.md +1 -0
  96. package/docs/providers/openrouter.md +1 -0
  97. package/docs/providers/zai.md +1 -0
  98. package/docs/public-contracts.md +13 -5
  99. package/docs/rag.md +113 -0
  100. package/docs/release-and-install.md +237 -30
  101. package/docs/resource-loading.md +14 -4
  102. package/docs/review-coverage-2026-07-14.md +260 -0
  103. package/docs/review-coverage-2026-07-15.md +193 -0
  104. package/docs/run-ledger-conformance.md +96 -0
  105. package/docs/runs-and-usage.md +43 -4
  106. package/docs/server.md +139 -0
  107. package/docs/session-store-conformance.md +16 -0
  108. package/docs/session-stores-and-branching.md +1 -0
  109. package/docs/settings-auth-trust-security.md +6 -5
  110. package/docs/sqlite-persistence.md +123 -0
  111. package/docs/structured-output.md +9 -0
  112. package/docs/supervisors.md +71 -0
  113. package/docs/tool-conformance.md +1 -0
  114. package/docs/tool-execution-primitives.md +374 -0
  115. package/docs/tools.md +39 -1
  116. package/docs/workflow-orchestration-primitives.md +581 -0
  117. package/docs/workflow-tui-primitives.md +5 -0
  118. package/docs/workflows.md +293 -0
  119. package/docs/working-and-semantic-memory.md +169 -0
  120. package/package.json +43 -5
  121. package/templates/init/README.md.tmpl +28 -0
  122. package/templates/init/env.example.tmpl +1 -0
  123. package/templates/init/gitignore.tmpl +11 -0
  124. package/templates/init/optional/evals-example.ts.tmpl +17 -0
  125. package/templates/init/optional/workflows-example.ts.tmpl +27 -0
  126. package/templates/init/package.json.tmpl +22 -0
  127. package/templates/init/providers.json +76 -0
  128. package/templates/init/src/agent.ts.tmpl +10 -0
  129. package/templates/init/src/index.ts.tmpl +12 -0
  130. package/templates/init/src/tests/agent.test.ts.tmpl +24 -0
  131. package/templates/init/tsconfig.json.tmpl +15 -0
@@ -0,0 +1,293 @@
1
+ # Workflows
2
+
3
+ ## What it does
4
+
5
+ `@arnilo/prism-workflows` is an optional package for typed, bounded DAG orchestration over Prism sessions, tools, events, and persistence seams. Hosts define acyclic workflows with agent/function/tool/conditional/fan-out/join/nested-workflow nodes; the package runs a Kahn-style scheduler with a bounded worker pool, emits package-local `WorkflowEvent`s, checkpoints progress, and can coordinate queued runs across multiple host processes using durable leases and fencing.
6
+
7
+ Primary exports:
8
+
9
+ | Export | Purpose |
10
+ | --- | --- |
11
+ | `defineWorkflow` / `buildGraph` | Validate definitions (acyclicity, edge refs, limits) and build deterministic successor/indegree maps |
12
+ | `agentNode`, `functionNode`, `toolNode`, `conditionalNode`, `fanOutNode`, `joinNode`, `workflowNode` | Typed node factories, including composition through the same runner |
13
+ | `runWorkflow` / `resumeWorkflow` / `suspend` / `replayWorkflow` | Execute, durably suspend, exactly-once resume, or create an immutable-lineage replay from a succeeded node |
14
+ | `createMemoryWorkflowCheckpoints` | In-process `WorkflowCheckpointAdapter` over core `createMemoryCheckpointStore()` |
15
+ | `createWorkflowCheckpoints` | Adapt core `CheckpointStore` (including SQLite/PostgreSQL persistence capabilities) to workflow checkpoint shapes |
16
+ | `createWorkflowEventBus` | Bounded pub/sub for `WorkflowEvent` with overflow policy |
17
+ | `getWorkflowRun` / `listWorkflowRuns` / `cancelWorkflowRun` | Status, paginated list, and cancel helpers |
18
+ | `createWorkflowCommands` | Optional `CommandDefinition[]` for direct/background/replay/status/list/cancel/resume and, when selected, schedule control |
19
+ | `enqueueWorkflow` / `startWorkflowBackground` / `createWorkflowCoordinator` | Persist queued work and atomically claim/renew/execute it across processes using `LeaseStore` |
20
+ | `createWorkflowSchedules` | Explicit ownership-scoped one-time/interval/host-calculated schedules over existing checkpoint/lease stores |
21
+
22
+ Included through `@arnilo/prism-sdk` and `@arnilo/prism-all`; installing either profile does not start workflows. Interactive TUI is out of scope (C-012 deferred).
23
+
24
+ ## When to use it
25
+
26
+ Use this package when a host needs multi-node dependency scheduling, conditionals, bounded fan-out/join, retries/timeouts, workflow events, or checkpoint/resume — without putting graph vocabulary into core.
27
+
28
+ Use `createWorkflowCoordinator()` when multiple processes share SQLite/PostgreSQL persistence and must claim queued work exclusively. It is a database-backed coordinator, not a separate broker, DSL parser, provider abstraction, or terminal UI. Agent nodes call public `AgentSession.run()` only; tool nodes go through ordinary `ToolDefinition` dispatch and optional `ExecutionPolicy`.
29
+
30
+ ## Inputs / request
31
+
32
+ `defineWorkflow({ id, nodes, edges, limits? })`:
33
+
34
+ | Field | Notes |
35
+ | --- | --- |
36
+ | `id` | Stable workflow id (required) |
37
+ | `nodes` | Record of node definitions (`kind` + typed fields) |
38
+ | `edges` | `[from, to]` pairs; must be acyclic; unknown ids rejected |
39
+ | `limits.maxNodes` | Default 1000 |
40
+ | `limits.maxFanOut` | Default 64 |
41
+ | `limits.maxConcurrency` | Default 8 |
42
+ | `limits.maxNodeOutputBytes` | Default 4 MiB |
43
+ | `limits.maxCheckpointBytes` | Default 1 MiB |
44
+ | `limits.maxNestedDepth` / hard cap | 8 / 32; inherited by child workflows |
45
+ | `limits.maxStateBytes` / hard cap | 64 KiB / 512 KiB |
46
+ | `limits.maxStateHistory` / hard cap | 32 / 128 state snapshots; updates stop before evidence would be discarded |
47
+ | `limits.maxReplayDepth` / hard cap | 8 / 32 lineage generations |
48
+ | `state.initial` / `state.schema` | Initial shared JSON object and optional host-validated schema |
49
+
50
+ `runWorkflow(workflow, input, options?)`:
51
+
52
+ | Option | Notes |
53
+ | --- | --- |
54
+ | `concurrency` | Worker pool size (capped by workflow/global limits) |
55
+ | `checkpoints` | `WorkflowCheckpointAdapter` for save/load/list |
56
+ | `agentFactory` | `(agentName) => AgentSession` for agent nodes |
57
+ | `tools` | Tool registry/lookup for tool nodes |
58
+ | `executionPolicy` | Optional `ExecutionPolicy`; tool actions include `workflowId`/`nodeId` metadata |
59
+ | `runLedger` | Optional `RunLedger` for agent-event bridging |
60
+ | `ownership` | Tenant/account/user scope copied into checkpoints |
61
+ | `redactor` / `secrets` | Redaction before checkpoint persistence and event emission |
62
+ | `signal` | Cancels the run and in-flight agent sessions |
63
+ | `onEvent` | Synchronous `WorkflowEvent` sink |
64
+ | `runId` | Caller-supplied id; otherwise generated (`wfr_…`) |
65
+ | `resume` | For suspended runs: `{ decision: "approve" | "deny", input?, expectedVersion }`; version is mandatory for an exact-once CAS claim |
66
+ | `validateResume` | Host validator for resume input; required when `suspend()` declares `resumeSchema` |
67
+ | `validateState` | Host validator for every initial/restored/updated state; required when workflow declares `state.schema` |
68
+ | `initialState` | Optional host initial state override; nested workflows receive parent state automatically |
69
+
70
+ A function node returns `suspend({ reason, data?, resumeSchema? })` to persist `status: "suspended"`. Its next invocation receives `ctx.resume` only after an approved resume. `resumeWorkflow(workflow, { runId }, options)` validates schema/version/ownership/`definitionHash`, claims the checkpoint before node execution, and continues the suspended node. Denial persists terminal `denied` status without invoking it. Existing failed/aborted checkpoint resume remains available without a human decision.
71
+
72
+ Every node receives bounded `ctx.state`, `ctx.stateVersion`, and async `ctx.updateState(patch, { mode: "merge" | "replace" })`. Updates serialize, validate, redact, and snapshot before checkpoint save. `workflowNode({ workflow })` runs its child with the same ownership, agent/tool registries, execution policy, redactor, signal, checkpoints, and event bus; child state replaces parent state after success.
73
+
74
+ `replayWorkflow(workflow, { sourceRunId, fromNodeId, runId? }, options)` requires a succeeded source/node, creates a new checkpoint, copies terminal evidence outside the selected node's downstream closure, restores selected-node pre-state, and records `{ sourceRunId, fromNodeId, rootRunId, depth }`. Source evidence is untouched. Copying any prior nested/tool approval is rejected; replay from that approval node or earlier so Phase 8 approval executes again.
75
+
76
+ `createWorkflowCoordinator({ coordinatorId, workflows, checkpoints, leases, ... })` polls queued/running checkpoints with bounded pages, atomically claims each run, renews its lease, and aborts/fences work after lease loss. Key controls: `leaseTtlMs` (default 30s), `renewalIntervalMs` (default TTL/3), `pollIntervalMs` (default 1s), `maxConcurrentRuns` (default 4), and `pageSize` (default 100, maximum 500).
77
+
78
+ `createWorkflowSchedules({ store, leases, checkpoints, workflows, ownership, ownerId, calculators? })` is inert until its host calls `pollOnce()` or `run({ signal })`. Ownership requires `tenantId` plus `accountId` or `userId`. Methods are `create`, `get`, `list`, `pause`, `resume`, `trigger`, `delete`, `pollOnce`, and `run`. A record has one required `nextRunAt`, optional fixed `intervalMs` or registered `calculatorId` (never both), bounded input/metadata, status, version, and last-fire attribution. Manual trigger requires an idempotency key. Scheduled run IDs derive from schedule ID plus fire timestamp, so retry after enqueue-before-advance finds the same queued checkpoint instead of duplicating it. Defaults: page 100/hard 500, due claims 16/hard 256, input 256 KiB/hard 1 MiB, poll 1s, fire lease 30s.
79
+
80
+ ## Outputs / response / events
81
+
82
+ `runWorkflow` / `resumeWorkflow` resolve to `WorkflowRunResult`:
83
+
84
+ | Field | Notes |
85
+ | --- | --- |
86
+ | `runId`, `workflowId`, `status` | `queued` / `running` / `suspended` / `succeeded` / `failed` / `denied` / `aborted` |
87
+ | `outputs` | Map of succeeded node outputs |
88
+ | `state` | Final/current bounded shared JSON state |
89
+ | `lineage` | Replay source/root/node/depth record when this is a replay |
90
+ | `suspension` | Current/persisted `{ nodeId, reason, data?, resumeSchema?, requestedAt }` |
91
+ | `resume` | Attributable resume decision/input/version/time record |
92
+ | `version` | Checkpoint CAS identity shown to reviewers and required on suspended resume |
93
+
94
+ Schedule `onEvent` receives bounded-attribution `schedule_fired` or metadata-only `schedule_failed`; schedule input is never copied into these events.
95
+
96
+ Package-local `WorkflowEvent` types: `workflow_started`, `workflow_suspended`, `workflow_resumed`, `workflow_finished`, `node_started`, `node_finished`, `node_failed`, `node_skipped`, `checkpoint_saved`, `agent_event` (wraps a redacted `AgentEvent`), `workflow_event_overflow`. Sequences are monotonic; drain/order is deterministic by `(sequence, nodeId)`.
97
+
98
+ ## Request/response example
99
+
100
+ ```json
101
+ {
102
+ "id": "research-draft",
103
+ "nodes": ["research", "draft"],
104
+ "edges": [["research", "draft"]],
105
+ "limits": { "maxNodes": 256, "maxFanOut": 32, "maxConcurrency": 4 }
106
+ }
107
+ ```
108
+
109
+ Successful run shape:
110
+
111
+ ```json
112
+ {
113
+ "runId": "wfr_01HZX…",
114
+ "workflowId": "research-draft",
115
+ "status": "succeeded",
116
+ "outputs": { "research": "…", "draft": "…" },
117
+ "version": 3
118
+ }
119
+ ```
120
+
121
+ ## Implementation example
122
+
123
+ ```ts
124
+ import {
125
+ defineWorkflow,
126
+ runWorkflow,
127
+ resumeWorkflow,
128
+ functionNode,
129
+ agentNode,
130
+ createWorkflowCheckpoints,
131
+ createWorkflowCommands,
132
+ cancelWorkflowRun,
133
+ enqueueWorkflow,
134
+ createWorkflowCoordinator,
135
+ createWorkflowSchedules,
136
+ replayWorkflow,
137
+ workflowNode,
138
+ suspend,
139
+ } from "@arnilo/prism-workflows";
140
+ import { runRpcServer } from "@arnilo/prism";
141
+ import { createSqlitePersistence } from "@arnilo/prism-session-store-sqlite";
142
+
143
+ const research = agentNode({
144
+ agent: "researcher",
145
+ input: (ctx) => ctx.workflowInput,
146
+ });
147
+ const draft = functionNode({
148
+ execute: async (ctx) => `Draft from ${String(ctx.upstream.research)}`,
149
+ });
150
+ const publish = functionNode({
151
+ execute: async (ctx) => ctx.resume
152
+ ? publishDraft(ctx.upstream.draft, ctx.resume.input)
153
+ : suspend({
154
+ reason: "publish",
155
+ data: { draft: ctx.upstream.draft },
156
+ resumeSchema: { type: "object", required: ["reviewer"] },
157
+ }),
158
+ });
159
+
160
+ const workflow = defineWorkflow({
161
+ id: "research-draft",
162
+ nodes: { research, draft, publish },
163
+ edges: [["research", "draft"], ["draft", "publish"]],
164
+ limits: { maxNodes: 256, maxFanOut: 32, maxConcurrency: 4 },
165
+ });
166
+
167
+ const persistence = createSqlitePersistence({ filename: "prism.db" });
168
+ const checkpoints = createWorkflowCheckpoints({ store: persistence.checkpoints });
169
+
170
+ const queued = await enqueueWorkflow(workflow, { topic: "hooks" }, {
171
+ checkpoints,
172
+ ownership: { tenantId: "t1" },
173
+ });
174
+ const coordinator = createWorkflowCoordinator({
175
+ coordinatorId: process.env.HOSTNAME ?? "worker-1",
176
+ workflows: { [workflow.id]: workflow },
177
+ checkpoints,
178
+ leases: persistence.leases,
179
+ ownership: { tenantId: "t1" },
180
+ runOptions: { agentFactory: (name) => agents.resolve(name).createSession() },
181
+ maxConcurrentRuns: 4,
182
+ });
183
+ await coordinator.run({ signal: shutdownSignal });
184
+
185
+ // Direct single-process execution remains available:
186
+ const result = await runWorkflow(workflow, { topic: "hooks" }, {
187
+ agentFactory: (name) => agents.resolve(name).createSession(),
188
+ checkpoints,
189
+ ownership: { tenantId: "t1" },
190
+ signal: AbortSignal.timeout(60_000),
191
+ onEvent: (event) => sink.push(event),
192
+ });
193
+
194
+ // Durable human resume after process restart. Use result.version shown to reviewer.
195
+ if (result.status === "suspended") {
196
+ await resumeWorkflow(workflow, { runId: result.runId }, {
197
+ checkpoints,
198
+ agentFactory: (name) => agents.resolve(name).createSession(),
199
+ ownership: { tenantId: "t1" },
200
+ resume: {
201
+ decision: "approve",
202
+ input: { reviewer: "Ada" },
203
+ expectedVersion: result.version,
204
+ },
205
+ validateResume: ({ value }) => validateResumePayload(value),
206
+ });
207
+ }
208
+
209
+ await cancelWorkflowRun({
210
+ workflowId: workflow.id,
211
+ runId: result.runId,
212
+ checkpoints,
213
+ ownership: { tenantId: "t1" },
214
+ });
215
+
216
+ // Optional host control via existing CLI/RPC CommandDefinition seam:
217
+ const schedules = createWorkflowSchedules({
218
+ store: persistence.checkpoints,
219
+ leases: persistence.leases,
220
+ checkpoints,
221
+ workflows: { [workflow.id]: workflow },
222
+ ownership: { tenantId: "t1", userId: "ops" },
223
+ ownerId: process.env.HOSTNAME ?? "scheduler-1",
224
+ });
225
+ await schedules.create({
226
+ id: "daily-research",
227
+ workflowId: workflow.id,
228
+ nextRunAt: "2026-07-17T00:00:00.000Z",
229
+ intervalMs: 86_400_000,
230
+ input: { topic: "hooks" },
231
+ });
232
+ // Host calls schedules.pollOnce() from an existing timer, or explicitly starts schedules.run({ signal }).
233
+
234
+ const replay = await replayWorkflow(workflow, {
235
+ sourceRunId: result.runId,
236
+ fromNodeId: "draft",
237
+ }, { checkpoints, ownership: { tenantId: "t1" }, agentFactory });
238
+
239
+ runRpcServer({
240
+ createSession,
241
+ commands: createWorkflowCommands({
242
+ workflows: { [workflow.id]: workflow },
243
+ checkpoints,
244
+ schedules,
245
+ runOptions: { ownership: { tenantId: "t1" }, agentFactory },
246
+ }),
247
+ });
248
+ ```
249
+
250
+ ## Extension and configuration notes
251
+
252
+ - Workflow semantics stay in this optional package; generic checkpoint persistence and bounded event fan-in live in core.
253
+ - `ProductionPersistenceStore.checkpoints` and `.leases` are optional generic capabilities. First-party SQLite/PostgreSQL adapters own `prism_checkpoints` / `prism_leases`; workflows only adapt them.
254
+ - `createWorkflowEventBus()` delegates queueing, source fan-in, overflow, abort, and close behavior to core `createEventMultiplexer()`.
255
+ - `createWorkflowCommands()` is optional; hosts can drive `workflow.start` / `enqueue` / `replay` / `status` / `list` / `cancel` / `resume`. The six `schedule.*` commands appear only when a scoped `schedules` service is supplied.
256
+ - Hosts may bridge `WorkflowEvent` into OpenTelemetry or custom sinks; there is no built-in TUI.
257
+ - Agent exclusivity is per session: one active `run()` at a time, same as core.
258
+
259
+ ## Security and performance notes
260
+
261
+ - Definitions fail closed on cycles, unknown edges, self-edges, and `maxNodes` overflow.
262
+ - Fan-out length is bounded by `maxFanOut`; concurrency by `maxConcurrency`.
263
+ - Node outputs, shared state/history, schedule input/records, and checkpoints are byte/count/depth bounded. Checkpoint size remains the final aggregate ceiling.
264
+ - Event buses use a bounded buffer (default 2048) with `close` / `drop_oldest` / `drop_newest` overflow.
265
+ - Checkpoints redact suspension/resume payloads via `SecretRedactor` / `secrets` before save; resume rejects tenant, schema, definition-hash, and expected-version mismatch.
266
+ - Suspension requires a checkpoint adapter, consumes no worker/polling slot, and is ignored by distributed coordinators until explicit resume.
267
+ - Concurrent resumes race on checkpoint CAS before node execution; one wins and stale/duplicate reviewers fail closed. Approved tool nodes then re-run current `ExecutionPolicy`, so durable approval cannot grant stale permissions.
268
+ - `toolNode({ approval: { reason, data?, resumeSchema? } })` suspends before tool execution. Denial is terminal `denied`; no tool side effect occurs.
269
+ - `cancelWorkflowRun` aborts local runs immediately and writes a durable cancellation request for a remotely leased run; workers check it during lease renewal.
270
+ - Tool nodes attach `workflowId` / `nodeId` on `ExecutionAction.metadata` for approval/audit context.
271
+ - Nested workflows inherit host registries/policies and cannot inject broader tools, agents, ownership, or credentials. Nested depth is inherited; child suspension bubbles to the parent review cursor.
272
+ - Replay source ownership/hash/status/node eligibility are checked before a new checkpoint is created. Source records are immutable, lineage is bounded, and copied approval-bearing paths are rejected.
273
+ - Schedule services are ownership-scoped and explicitly started. Per-fire leases plus deterministic run IDs/CAS prevent duplicate enqueue across coordinators and crash retry. Host calculator IDs resolve only from the supplied map; no callback or cron expression is persisted.
274
+ - Scheduler stores O(nodes + active outputs + bounded state history); ready-node work uses indegree maps, not repeated full scans.
275
+ - Lease acquisition is atomic; opaque tokens protect renew/release; monotonically increasing fencing tokens plus checkpoint compare-and-swap prevent expired workers from committing after takeover. Node functions must honor `ctx.signal` for prompt cooperative cancellation.
276
+
277
+ Use workflows for known, durable, replayable graphs. Use optional supervisor delegation only when child selection must be dynamic at runtime; do not replace deterministic nodes with model routing without a concrete need.
278
+
279
+ ## Related APIs
280
+
281
+ - Examples: `examples/workflow-research-and-review.ts`, `examples/workflow-parallel-research.ts`, `examples/workflow-tool-approval.ts`, `examples/workflow-multimodal-document.ts`, `examples/workflow-sqlite-resume.ts`, `examples/workflow-postgres-resume.ts`, `examples/workflow-event-sink.ts`, `examples/workflow-rpc-cancel.ts`, `examples/workflow-distributed-coordinator.ts` — offline runnable demos; PostgreSQL safely skips unless `PRISM_TEST_POSTGRES_URL` is set.
282
+ - [Workflow orchestration primitives](workflow-orchestration-primitives.md): Task 0–1 inventory and locked adapter contracts
283
+ - [Agent/session runtime](agent-session-runtime.md): `AgentSession.run()`/`stream()`, abort, subscribe
284
+ - [Supervisor delegation](supervisors.md): bounded dynamic child selection.
285
+ - [Agent events](agent-events.md): core `AgentEvent` wrapped by `agent_event`
286
+ - [Session stores and branching](session-stores-and-branching.md): session `leafId` reuse on resume
287
+ - [CLI/RPC](cli-rpc.md): host control seam; wire `createWorkflowCommands()` into `runRpcServer`
288
+ - [Database persistence](database-persistence.md): generic `CheckpointStore` and `LeaseStore` capabilities
289
+ - [SQLite persistence](sqlite-persistence.md): durable `persistence.checkpoints`
290
+ - [PostgreSQL persistence](postgres-persistence.md): durable `persistence.checkpoints`
291
+ - [Observability](observability.md): exporting workflow/agent events
292
+ - [Coding execution approval and sandboxing](coding-security.md): `ExecutionPolicy` for tool nodes
293
+ - [Release and install](release-and-install.md): atomic and profile installs
@@ -0,0 +1,169 @@
1
+ # Working and semantic memory
2
+
3
+ ## What it does
4
+
5
+ `@arnilo/prism-memory` is an optional package for schema/template-backed working memory and embedding-based semantic recall. It owns narrow `Embedder` and `VectorStore` contracts reused by `@arnilo/prism-rag`, plus an in-memory reference path and one PostgreSQL/pgvector production adapter.
6
+
7
+ ## When to use it
8
+
9
+ Use it when a host needs durable per-tenant profile/state (working memory) or top-K semantic retrieval over prior thread entries. Do not use it as a replacement for observational memory compaction: observational memory compresses source-backed observations; semantic memory retrieves embeddings; working memory stores the current structured profile.
10
+
11
+ Ordinary Prism sessions do not require this package or any vector backend.
12
+
13
+ ## Inputs / request
14
+
15
+ `createMemory(options)`:
16
+
17
+ | Field | Required | Meaning |
18
+ | --- | --- | --- |
19
+ | `tenantId` | yes | Tenant isolation key |
20
+ | `resourceId` | yes | Resource/user isolation key |
21
+ | `threadId` | for semantic ops | Thread isolation; optional for resource-scoped working memory |
22
+ | `embedder` | yes | Host-owned or package hash embedder |
23
+ | `vectorStore` / `workingStore` | no | Defaults to in-memory adapters |
24
+ | `schema` / `validateWorkingMemory` | no | Working-memory shape checks (JSON Schema subset or host hook) |
25
+ | `workingMemoryTemplate` | no | `{{path}}` template for context injection |
26
+ | `limits` | no | top-K, adjacent range, batch, payload, injected-token caps |
27
+ | `redactor` / `secrets` | no | Redact text/metadata before persist/inject |
28
+
29
+ Semantic indexing:
30
+
31
+ ```ts
32
+ await memory.remember({ entries: [{ id, text, metadata?, sequence? }] }, { wait?: boolean })
33
+ ```
34
+
35
+ Semantic recall:
36
+
37
+ ```ts
38
+ await memory.recall(query, { topK?, messageRange?, signal? })
39
+ ```
40
+
41
+ ## Outputs / response / events
42
+
43
+ | API | Result |
44
+ | --- | --- |
45
+ | `updateWorking` / `getWorking` | Versioned `WorkingMemoryRecord` |
46
+ | `remember` | `{ accepted, pending, done }` — default `wait: false` indexes asynchronously |
47
+ | `recall` | `{ hits, adjacent }` tenant/thread scoped |
48
+ | `createContextProvider()` | Inert `ContextProvider` blocks for working and/or semantic text |
49
+ | `createWorkingMemoryProcessor({ extract })` | Explicit host-invoked updater; never auto-runs |
50
+
51
+ No package-owned agent events are emitted. Injection uses existing context assembly only.
52
+
53
+ ## Request/response example
54
+
55
+ ```json
56
+ {
57
+ "tenantId": "t1",
58
+ "resourceId": "user-ada",
59
+ "threadId": "thread-1",
60
+ "working": { "name": "Ada", "preferences": { "format": "concise" } },
61
+ "recall": {
62
+ "query": "preferred response format",
63
+ "topK": 5,
64
+ "messageRange": 1
65
+ }
66
+ }
67
+ ```
68
+
69
+ ## Implementation example
70
+
71
+ ```ts
72
+ import { createAgent, createMockProvider, providerDone, providerTextDelta } from "@arnilo/prism";
73
+ import { createHashEmbedder, createMemory } from "@arnilo/prism-memory";
74
+
75
+ const memory = createMemory({
76
+ tenantId: "t1",
77
+ resourceId: "user-ada",
78
+ threadId: "thread-1",
79
+ embedder: createHashEmbedder(),
80
+ workingMemoryTemplate: "Name: {{name}}; Format: {{preferences.format}}",
81
+ schema: {
82
+ type: "object",
83
+ properties: {
84
+ name: { type: "string" },
85
+ preferences: {
86
+ type: "object",
87
+ properties: { format: { type: "string" } },
88
+ required: ["format"],
89
+ additionalProperties: false,
90
+ },
91
+ },
92
+ required: ["name"],
93
+ additionalProperties: false,
94
+ },
95
+ });
96
+
97
+ await memory.updateWorking({ name: "Ada", preferences: { format: "concise" } });
98
+ await memory.remember({ entries: [{ id: "m1", text: "Prefers concise answers" }] });
99
+
100
+ const agent = createAgent({
101
+ model: { provider: "mock", model: "demo" },
102
+ provider: createMockProvider([providerTextDelta("Got it."), providerDone()]),
103
+ context: [memory.createContextProvider()],
104
+ });
105
+ ```
106
+
107
+ PostgreSQL/pgvector:
108
+
109
+ ```ts
110
+ import { createPostgresMemoryStores, createMemory, createHashEmbedder } from "@arnilo/prism-memory";
111
+
112
+ const stores = await createPostgresMemoryStores({
113
+ connectionString: process.env.DATABASE_URL!,
114
+ schema: "prism_memory",
115
+ dimensions: 32,
116
+ });
117
+
118
+ const memory = createMemory({
119
+ tenantId: "t1",
120
+ resourceId: "user-ada",
121
+ threadId: "thread-1",
122
+ embedder: createHashEmbedder({ dimensions: 32 }),
123
+ workingStore: stores.workingStore,
124
+ vectorStore: stores.vectorStore,
125
+ });
126
+ ```
127
+
128
+ ## Extension and configuration notes
129
+
130
+ - Hosts wire the context provider into `AgentConfig.context` or `resolveContextProviders()`.
131
+ - The working-memory processor is opt-in and host-invoked; middleware is not required.
132
+ - `createHashEmbedder()` is for tests/demos only; production hosts supply a real `Embedder`.
133
+ - Observational memory (`@arnilo/prism-compaction-observational-memory`) remains unchanged and composable.
134
+ - Profile bundles do not include this package yet.
135
+
136
+ Shared conformance:
137
+
138
+ ```ts
139
+ import { runMemoryConformance, createHashEmbedder, createMemoryVectorStore, createMemoryWorkingStore } from "@arnilo/prism-memory";
140
+
141
+ await runMemoryConformance(() => ({
142
+ embedder: createHashEmbedder(),
143
+ vectorStore: createMemoryVectorStore(),
144
+ workingStore: createMemoryWorkingStore(),
145
+ }));
146
+ ```
147
+
148
+ ## Security and performance notes
149
+
150
+ - Every write/query/delete requires `tenantId` + `resourceId`; semantic paths also require `threadId`.
151
+ - Cross-tenant and cross-thread access is denied.
152
+ - Configure `secrets` / `redactor` so memory text and metadata cannot persist or inject raw canaries.
153
+ - Injected context is inert text — it cannot grant tools or permissions.
154
+ - Hard caps: top-K ≤ 32, messageRange ≤ 4, embed batch ≤ 128, injected tokens ≤ 8000, payload/working-memory byte limits enforced.
155
+ - Default `remember()` does not block agent completion; pass `{ wait: true }` when indexing must finish first.
156
+ - PostgreSQL live suite is gated by `PRISM_TEST_POSTGRES_URL` and requires the `vector` extension.
157
+
158
+ ## Delegated-agent isolation
159
+
160
+ Supervisor child factories receive unique derived `resourceId` and `threadId` values. Construct each child's `createMemory()` facade from those exact values; never reuse parent memory scope or let model-supplied IDs select another resource.
161
+
162
+ ## Related APIs
163
+
164
+ - [Supervisor delegation](supervisors.md): package-derived child resource/thread scope.
165
+ - [Retrieval-augmented generation](rag.md): bounded document chunks reuse this package's embed/vector contracts.
166
+ - [Context and skills](context-and-skills.md): `ContextProvider` injection seam.
167
+ - [Observational memory compaction package](compaction-observational-memory.md): source-backed observation/reflection memory distinction.
168
+ - [PostgreSQL persistence](postgres-persistence.md): session/run persistence; memory vectors live in this optional package instead.
169
+ - [Middleware hooks](middleware-hooks.md): reuse existing `context` hook if hosts transform injected blocks.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arnilo/prism",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "Agent harness for AI providers, agents, sessions, and tools.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -14,6 +14,18 @@
14
14
  "types": "./dist/providers/openai-compatible.d.ts",
15
15
  "default": "./dist/providers/openai-compatible.js"
16
16
  },
17
+ "./providers/transport": {
18
+ "types": "./dist/providers/transport.d.ts",
19
+ "default": "./dist/providers/transport.js"
20
+ },
21
+ "./providers/openai": {
22
+ "types": "./dist/providers/openai-primitives.d.ts",
23
+ "default": "./dist/providers/openai-primitives.js"
24
+ },
25
+ "./providers/media": {
26
+ "types": "./dist/providers/media.d.ts",
27
+ "default": "./dist/providers/media.js"
28
+ },
17
29
  "./testing/provider-conformance": {
18
30
  "types": "./dist/testing/provider-conformance.d.ts",
19
31
  "default": "./dist/testing/provider-conformance.js"
@@ -34,6 +46,18 @@
34
46
  "types": "./dist/testing/extension-conformance.d.ts",
35
47
  "default": "./dist/testing/extension-conformance.js"
36
48
  },
49
+ "./testing/persistence-schema": {
50
+ "types": "./dist/testing/persistence-schema.d.ts",
51
+ "default": "./dist/testing/persistence-schema.js"
52
+ },
53
+ "./testing/run-ledger-conformance": {
54
+ "types": "./dist/testing/run-ledger-conformance.d.ts",
55
+ "default": "./dist/testing/run-ledger-conformance.js"
56
+ },
57
+ "./testing/feedback": {
58
+ "types": "./dist/testing/feedback.d.ts",
59
+ "default": "./dist/testing/feedback.js"
60
+ },
37
61
  "./node/config": {
38
62
  "types": "./dist/node/config.d.ts",
39
63
  "default": "./dist/node/config.js"
@@ -75,23 +99,37 @@
75
99
  "!dist/__tests__",
76
100
  "!dist/**/*.map",
77
101
  "docs",
102
+ "templates",
78
103
  "CHANGELOG.md"
79
104
  ],
80
105
  "workspaces": [
81
106
  "packages/provider-*",
82
107
  "packages/compaction-*",
108
+ "packages/observability-*",
109
+ "packages/tool-validator-*",
110
+ "packages/session-store-*",
111
+ "packages/credentials-node",
112
+ "packages/mcp",
83
113
  "packages/coding-agent",
84
- "packages/prism-providers",
85
- "packages/prism-compaction",
86
- "packages/prism-all"
114
+ "packages/coding-security",
115
+ "packages/workflows",
116
+ "packages/evals",
117
+ "packages/memory",
118
+ "packages/rag",
119
+ "packages/server",
120
+ "packages/supervisor",
121
+ "packages/prism-*"
87
122
  ],
88
123
  "scripts": {
89
124
  "build:core": "tsc",
90
125
  "build": "npm run build:core && npm run build --workspaces --if-present",
91
- "typecheck": "npm run build:core && npm run typecheck --workspaces --if-present && tsc -p examples --noEmit",
126
+ "typecheck": "npm run build && npm run typecheck --workspaces --if-present && tsc -p examples --noEmit",
92
127
  "test": "npm run build && node --test dist/__tests__/*.test.js && npm run test --workspaces --if-present",
93
128
  "pack:dry-run": "npm pack --dry-run && npm run pack:dry-run --workspaces --if-present",
129
+ "test:postgres": "npm run test:postgres --workspace @arnilo/prism-session-store-postgres && npm run test:postgres --workspace @arnilo/prism-memory",
94
130
  "release:dry-run": "npm run sdk:ready",
131
+ "release:check": "node scripts/release.mjs check",
132
+ "release:publish": "node scripts/release.mjs publish",
95
133
  "sdk:ready": "npm run typecheck && npm test && npm run pack:dry-run"
96
134
  },
97
135
  "devDependencies": {
@@ -0,0 +1,28 @@
1
+ # __PROJECT_NAME__
2
+
3
+ Minimal Prism agent scaffold generated by `prism init`.
4
+
5
+ __PROVIDER_README_NOTE__
6
+
7
+ ## Setup
8
+
9
+ ```bash
10
+ npm install
11
+ npm test
12
+ ```
13
+
14
+ `npm test` always uses the built-in mock provider (no network, no credentials).
15
+
16
+ __NEXT_STEPS_LIVE__
17
+
18
+ ## Layout
19
+
20
+ - `src/agent.ts` — creates the agent with the selected provider.
21
+ - `src/index.ts` — runs one prompt and prints `AgentRunResult.text`.
22
+ - `src/__tests__/agent.test.ts` — offline mock smoke test.
23
+ __OPTIONAL_DOCS__
24
+ ## Notes
25
+
26
+ - Prism does not auto-load credentials, databases, telemetry, or tools.
27
+ - Keep real secrets out of git; `.gitignore` excludes `.env` and local stores.
28
+ - Docs: https://github.com/ashiqrniloy/prism
@@ -0,0 +1 @@
1
+ __ENV_EXAMPLE__
@@ -0,0 +1,11 @@
1
+ node_modules/
2
+ dist/
3
+ .env
4
+ .env.local
5
+ *.db
6
+ *.sqlite
7
+ *.sqlite3
8
+ .prism/
9
+ coverage/
10
+ .DS_Store
11
+ *.log
@@ -0,0 +1,17 @@
1
+ import type { AgentRunResult } from "@arnilo/prism";
2
+ import { defineScorer, scoreRun } from "@arnilo/prism-evals";
3
+
4
+ /**
5
+ * Deterministic scorer example. Not wired into `npm start` — call after a run.
6
+ */
7
+ export const greetsScorer = defineScorer({
8
+ id: "greets",
9
+ score: ({ result }) => ({
10
+ score: /\bhello\b/i.test(result.text) ? 1 : 0,
11
+ reason: "checks for a hello greeting",
12
+ }),
13
+ });
14
+
15
+ export async function scoreGreeting(result: AgentRunResult) {
16
+ return scoreRun({ result, scorers: [greetsScorer] });
17
+ }
@@ -0,0 +1,27 @@
1
+ import {
2
+ createMemoryWorkflowCheckpoints,
3
+ defineWorkflow,
4
+ functionNode,
5
+ runWorkflow,
6
+ } from "@arnilo/prism-workflows";
7
+
8
+ /**
9
+ * Tiny workflow example. Not wired into `npm start` — import and call from your host.
10
+ */
11
+ export async function runHelloWorkflow(name: string) {
12
+ const workflow = defineWorkflow({
13
+ id: "hello",
14
+ nodes: {
15
+ greet: functionNode({
16
+ execute: async (ctx) => {
17
+ const input = ctx.workflowInput as { name?: string };
18
+ return { message: `Hello, ${input.name ?? name}` };
19
+ },
20
+ }),
21
+ },
22
+ });
23
+
24
+ return runWorkflow(workflow, { name }, {
25
+ checkpoints: createMemoryWorkflowCheckpoints(),
26
+ });
27
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "__PROJECT_NAME__",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "build": "tsc -p tsconfig.json",
8
+ "typecheck": "tsc -p tsconfig.json --noEmit",
9
+ "test": "npm run build && node --test dist/__tests__/agent.test.js",
10
+ "start": "npm run build && node dist/index.js"
11
+ },
12
+ "dependencies": {
13
+ __DEPENDENCIES__
14
+ },
15
+ "devDependencies": {
16
+ "@types/node": "^22.0.0",
17
+ "typescript": "^5.7.0"
18
+ },
19
+ "engines": {
20
+ "node": ">=20"
21
+ }
22
+ }