@arnilo/prism 0.3.1 → 0.4.0

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 (123) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +34 -57
  3. package/dist/agent-definitions.js +4 -1
  4. package/dist/agent-run-lifecycle.js +4 -0
  5. package/dist/agent-run-state.d.ts +4 -0
  6. package/dist/agent-run-state.js +18 -5
  7. package/dist/agent-session/session.d.ts +7 -0
  8. package/dist/agent-session/session.js +59 -2
  9. package/dist/cli-dev.d.ts +29 -0
  10. package/dist/cli-dev.js +52 -0
  11. package/dist/cli-init.d.ts +17 -2
  12. package/dist/cli-init.js +194 -21
  13. package/dist/cli-runner.d.ts +5 -1
  14. package/dist/cli-runner.js +12 -1
  15. package/dist/contracts-core/agent.d.ts +29 -2
  16. package/dist/contracts-protocol.d.ts +18 -0
  17. package/dist/contracts-run-state.d.ts +1 -2
  18. package/dist/index.d.ts +3 -1
  19. package/dist/index.js +2 -1
  20. package/dist/input.d.ts +8 -0
  21. package/dist/input.js +4 -0
  22. package/dist/rpc.d.ts +4 -1
  23. package/dist/rpc.js +5 -1
  24. package/dist/testing/persistence-schema.d.ts +1 -1
  25. package/dist/testing/persistence-schema.js +32 -28
  26. package/dist/testing/tool-conformance.d.ts +25 -0
  27. package/dist/testing/tool-conformance.js +128 -1
  28. package/dist/tool-search.d.ts +76 -0
  29. package/dist/tool-search.js +199 -0
  30. package/docs/0.1.0-readiness.md +2 -2
  31. package/docs/acp-agent.md +1 -1
  32. package/docs/agent-definitions.md +1 -1
  33. package/docs/antigravity-agent.md +1 -1
  34. package/docs/browser-automation.md +5 -5
  35. package/docs/caveman.md +2 -2
  36. package/docs/cli-rpc.md +26 -3
  37. package/docs/coding-agent-tools.md +7 -1
  38. package/docs/coding-security.md +1 -1
  39. package/docs/coding-tools.md +82 -0
  40. package/docs/compaction-and-retry.md +5 -2
  41. package/docs/compaction-llm.md +4 -4
  42. package/docs/compaction-observational-memory.md +49 -2
  43. package/docs/context-and-skills.md +2 -0
  44. package/docs/core.md +85 -0
  45. package/docs/credential-storage.md +1 -1
  46. package/docs/database-persistence.md +4 -0
  47. package/docs/dev-inspector.md +103 -0
  48. package/docs/diagrams.md +247 -0
  49. package/docs/documents.md +213 -0
  50. package/docs/evaluations.md +35 -1
  51. package/docs/extension-authoring.md +42 -0
  52. package/docs/graft.md +3 -3
  53. package/docs/guardrails.md +1 -1
  54. package/docs/host-security.md +4 -3
  55. package/docs/impeccable.md +2 -2
  56. package/docs/index.md +34 -23
  57. package/docs/mcp-tools.md +1 -1
  58. package/docs/migrate-to-0.4.md +312 -0
  59. package/docs/migration.md +22 -0
  60. package/docs/model-routing.md +1 -1
  61. package/docs/multi-agent-patterns.md +177 -0
  62. package/docs/multimodal-content.md +1 -1
  63. package/docs/obscura.md +10 -10
  64. package/docs/openapi-tools.md +1 -1
  65. package/docs/performance.md +23 -3
  66. package/docs/persistence-credentials-multimodality-primitives.md +1 -1
  67. package/docs/policy-and-audit.md +1 -1
  68. package/docs/ponytail.md +2 -2
  69. package/docs/prompt-registry.md +106 -0
  70. package/docs/provider-caching.md +32 -32
  71. package/docs/provider-conformance.md +1 -1
  72. package/docs/provider-packages.md +19 -19
  73. package/docs/provider-primitives.md +4 -4
  74. package/docs/providers/ai-sdk.md +3 -3
  75. package/docs/providers/alibaba.md +5 -5
  76. package/docs/providers/anthropic.md +6 -6
  77. package/docs/providers/azure.md +3 -3
  78. package/docs/providers/bedrock.md +3 -3
  79. package/docs/providers/clinepass.md +3 -3
  80. package/docs/providers/deepseek.md +3 -3
  81. package/docs/providers/google.md +4 -4
  82. package/docs/providers/kimi.md +3 -3
  83. package/docs/providers/neuralwatt.md +8 -8
  84. package/docs/providers/ollama.md +3 -3
  85. package/docs/providers/openai-compatible.md +1 -1
  86. package/docs/providers/openai.md +5 -5
  87. package/docs/providers/opencode-go.md +4 -4
  88. package/docs/providers/openrouter.md +3 -3
  89. package/docs/providers/vertex.md +5 -5
  90. package/docs/providers/xai.md +3 -3
  91. package/docs/providers/zai.md +3 -3
  92. package/docs/public-contracts.md +1 -1
  93. package/docs/rag.md +5 -5
  94. package/docs/release-and-install.md +116 -50
  95. package/docs/runs-and-usage.md +14 -1
  96. package/docs/server.md +90 -1
  97. package/docs/sheets.md +229 -0
  98. package/docs/supervisors.md +9 -1
  99. package/docs/thinking-and-reasoning.md +10 -10
  100. package/docs/tool-conformance.md +27 -2
  101. package/docs/tools.md +29 -2
  102. package/docs/web-tools.md +2 -2
  103. package/docs/wiki.md +24 -10
  104. package/docs/workflow-orchestration-primitives.md +24 -0
  105. package/docs/workflows.md +102 -8
  106. package/docs/working-and-semantic-memory.md +53 -5
  107. package/package.json +10 -30
  108. package/templates/README.md +23 -0
  109. package/templates/deep-research/README.md.tmpl +47 -0
  110. package/templates/deep-research/env.example.tmpl +12 -0
  111. package/templates/deep-research/gitignore.tmpl +7 -0
  112. package/templates/deep-research/manifest.json +12 -0
  113. package/templates/deep-research/package.json.tmpl +23 -0
  114. package/templates/deep-research/src/agent.ts.tmpl +81 -0
  115. package/templates/deep-research/src/index.ts.tmpl +53 -0
  116. package/templates/deep-research/src/tests/research.test.ts.tmpl +114 -0
  117. package/templates/deep-research/src/tools.ts.tmpl +86 -0
  118. package/templates/deep-research/src/types.ts.tmpl +45 -0
  119. package/templates/deep-research/src/workflow.ts.tmpl +156 -0
  120. package/templates/deep-research/tsconfig.json.tmpl +15 -0
  121. package/templates/init/manifest.json +5 -0
  122. package/templates/init/package.json.tmpl +2 -1
  123. package/templates/init/providers.json +16 -16
package/docs/workflows.md CHANGED
@@ -2,14 +2,14 @@
2
2
 
3
3
  ## What it does
4
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, can coordinate queued runs across multiple host processes using durable leases and fencing, and can run bounded linear sagas with durable compensation.
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/loop nodes; the package runs a Kahn-style scheduler with a bounded worker pool, emits package-local `WorkflowEvent`s, checkpoints progress, can coordinate queued runs across multiple host processes using durable leases and fencing, and can run bounded linear sagas with durable compensation.
6
6
 
7
7
  Primary exports:
8
8
 
9
9
  | Export | Purpose |
10
10
  | --- | --- |
11
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 |
12
+ | `agentNode`, `functionNode`, `loopNode`, `toolNode`, `conditionalNode`, `fanOutNode`, `joinNode`, `workflowNode` | Typed node factories, including bounded iterative refinement and composition through the same runner |
13
13
  | `runWorkflow` / `resumeWorkflow` / `suspend` / `replayWorkflow` | Execute, durably suspend, exactly-once resume, or create an immutable-lineage replay from a succeeded node |
14
14
  | `createMemoryWorkflowCheckpoints` | In-process `WorkflowCheckpointAdapter` over core `createMemoryCheckpointStore()` |
15
15
  | `createWorkflowCheckpoints` | Adapt core `CheckpointStore` (including SQLite/PostgreSQL persistence capabilities) to workflow checkpoint shapes |
@@ -50,8 +50,22 @@ Use `defineSaga`/`runSaga` for a linear business sequence whose remote effects n
50
50
  | `limits.maxStateBytes` / hard cap | 64 KiB / 512 KiB |
51
51
  | `limits.maxStateHistory` / hard cap | 32 / 128 state snapshots; updates stop before evidence would be discarded |
52
52
  | `limits.maxReplayDepth` / hard cap | 8 / 32 lineage generations |
53
+ | loop `maxIterations` | Required per loop / hard cap 64 |
53
54
  | `state.initial` / `state.schema` | Initial shared JSON object and optional host-validated schema |
54
55
 
56
+ ### Node kinds
57
+
58
+ | Kind | Factory | Behavior |
59
+ | --- | --- | --- |
60
+ | `agent` | `agentNode` | Runs `AgentSession` from `agentFactory` |
61
+ | `function` | `functionNode` | Runs one host async function |
62
+ | `loop` | `loopNode` | Runs one bounded inline or function/tool body repeatedly until `until(ctx)` is true |
63
+ | `tool` | `toolNode` | Dispatches one registered tool, optionally behind durable approval |
64
+ | `conditional` | `conditionalNode` | Evaluates a predicate and skips configured successors |
65
+ | `fan_out` | `fanOutNode` | Maps a bounded list with workflow concurrency |
66
+ | `join` | `joinNode` | Reduces an upstream array |
67
+ | `workflow` | `workflowNode` | Runs a nested workflow with inherited capabilities |
68
+
55
69
  All workflow limits and runtime `concurrency` reject non-safe integers, zero, negatives, NaN, `Infinity`, and values above the named hard cap. Node retries allow 0–100; an explicit node timeout allows 1–86,400,000 ms. Omitting `timeoutMs` remains an explicit host choice.
56
70
 
57
71
  `runWorkflow(workflow, input, options?)`:
@@ -76,6 +90,16 @@ All workflow limits and runtime `concurrency` reject non-safe integers, zero, ne
76
90
 
77
91
  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.
78
92
 
93
+ > **Contract — resume-aware nodes.** After an approved resume, the **same** node's `execute` is re-invoked with `ctx.resume`. Returning `suspend(...)` unconditionally re-suspends silently; downstream nodes never run. Branch on `ctx.resume`:
94
+ >
95
+ > ```ts
96
+ > execute: async (ctx) => ctx.resume
97
+ > ? handle(ctx.resume)
98
+ > : suspendAskUserDecision({ ... }),
99
+ > ```
100
+ >
101
+ > Live demo: [`examples/autonomous-coding-loop.ts`](../examples/autonomous-coding-loop.ts) (`gate` node).
102
+
79
103
  Coding-agent ask-user glue (opt-in, no Goal DB): `suspendAskUserDecision(request)` wraps `suspend` with durable question/options/`selectionMode`/`allowCustom` data + resume schema; resume with `createAskUserDecisionResumeValidator()` or `validateAskUserDecisionResume`. Goal→verify: `runCodingGoalVerify` / `createCodingGoalVerifyWorkflow` compose plan Markdown → named checks → approve suspend → bounded handoff over the same primitives (`examples/coding-goal-verify.ts`). When a workflow node wraps a durable agent run, that run's shared pending-decision batch (Task 2) is the approval authority — workflow `suspend`/`resume` stay workflow-scoped and do not mint a parallel decision store.
80
104
 
81
105
  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. A rejected state or checkpoint write stays rejected (nothing committed) and recovers the per-run chain so a later valid write can run. `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.
@@ -120,7 +144,7 @@ Saga `onEvent` callbacks receive metadata-only `saga_transition` events with ten
120
144
 
121
145
  Schedule `onEvent` receives bounded-attribution `schedule_fired` or metadata-only `schedule_failed`; schedule input is never copied into these events.
122
146
 
123
- 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)`.
147
+ Package-local `WorkflowEvent` types: `workflow_started`, `workflow_suspended`, `workflow_resumed`, `workflow_finished`, `node_started`, `node_finished`, `node_iteration_started`, `node_iteration_finished`, `node_failed`, `node_skipped`, `checkpoint_saved`, `agent_event` (wraps a redacted `AgentEvent`), `workflow_event_overflow`. Loop iteration-finished events carry bounded/redacted output and stable `iterationId`. Sequences are monotonic; drain/order is deterministic by `(sequence, nodeId)`.
124
148
 
125
149
  ## Request/response example
126
150
 
@@ -154,6 +178,7 @@ import {
154
178
  runWorkflow,
155
179
  resumeWorkflow,
156
180
  functionNode,
181
+ loopNode,
157
182
  agentNode,
158
183
  createWorkflowCheckpoints,
159
184
  createWorkflowCommands,
@@ -300,11 +325,79 @@ runRpcServer({
300
325
  });
301
326
  ```
302
327
 
328
+ ## Iterative refinement (`loopNode`)
329
+
330
+ `loopNode` keeps the workflow graph acyclic while executing one body repeatedly. `ctx.iteration` is zero-based, `ctx.iterationId` is a stable compensation key (tenant-prefixed when ownership is supplied), and the body receives the prior body output as `ctx.previousOutput`. `until(ctx)` receives the current body output through that same property. Body and predicate can use `ctx.updateState()` for durable accumulation.
331
+
332
+ Use inline `execute` for pure refinement, or `body` for one interior function/tool sub-step. A tool body uses the normal durable approval gate; approval suspends before its side effect and an approved resume re-enters the same iteration. Completed prior iterations remain in the checkpoint ledger and are not re-executed.
333
+
334
+ `maxIterations` is required and capped at 64 (`HARD_MAX_LOOP_ITERATIONS`). The scheduler enforces the cap even when `until` never passes. Exhaustion throws `WorkflowLoopLimitError` with code `ERR_PRISM_WORKFLOW_LOOP_LIMIT`, `iterations`, and a bounded/redacted `lastOutput`. Each completed iteration stores a versioned, bounded/redacted output record before the next body starts; `maxNodeOutputBytes` applies to every body output.
335
+
336
+ ### Frozen budget accounting
337
+
338
+ `maxNodes` counts declared DAG nodes once. `maxIterations` independently caps loop body executions; iterations never consume `maxNodes`. Both limits are validated before execution and fail closed at their hard caps.
339
+
340
+ `node_iteration_started` and `node_iteration_finished` events expose `iteration` and `iterationId`; finished events also expose `done` and bounded/redacted output. A replay started from a completed loop re-runs its body and emits the same iteration sequence for the new run. Hosts that persist events should treat `iterationId` as the idempotency key.
341
+
342
+ ```ts
343
+ const refine = loopNode({
344
+ execute: async (ctx) => ({
345
+ iteration: ctx.iteration,
346
+ draft: improve((ctx.previousOutput as { draft?: string } | undefined)?.draft),
347
+ }),
348
+ until: (ctx) => (ctx.previousOutput as { passed?: boolean } | undefined)?.passed === true,
349
+ maxIterations: 5,
350
+ });
351
+
352
+ const approvedRefine = loopNode({
353
+ body: toolNode({
354
+ tool: publishDraft,
355
+ args: () => ({ action: "refine" }),
356
+ approval: { reason: "approve refinement side effect" },
357
+ }),
358
+ until: (ctx) => ctx.previousOutput === "accepted",
359
+ maxIterations: 3,
360
+ });
361
+
362
+ const workflow = defineWorkflow({
363
+ id: "refine-draft",
364
+ revision: "1",
365
+ nodes: { refine, approvedRefine },
366
+ });
367
+ ```
368
+
369
+ ### Saga compensation boundary
370
+
371
+ A loop remains one DAG node and one host saga step. Persist the loop's `iterations` as that step's aggregate output, and register external compensation under each record's `iterationId`; compensate records in reverse iteration order. The workflow runner does not invoke saga handlers implicitly, so the host retains ownership of side-effect policy and audit records while replay/resume stay deterministic.
372
+
373
+ ## Bounded iterate-until-done (host-loop pattern)
374
+
375
+ Workflows can now use `loopNode` for bounded in-graph refinement. A host `for`/`while` over `runWorkflow` remains useful when each iteration must be a separate run id, use a different workflow definition, or run on versions before this node kind. Runnable proof: [`examples/autonomous-coding-loop.ts`](../examples/autonomous-coding-loop.ts) (N runs, mid-loop human gate with simulated restart, typed budget exhaustion).
376
+
377
+ 1. Keep the DAG acyclic (roadmap → execute → validate → gate → compact).
378
+ 2. Pass `{ goal, iteration }` as `runWorkflow` input — never a back-edge.
379
+ 3. Bound the host loop (`MAX_ITERATIONS`). Per-child tool/token caps stay on `supervisor.delegate` / `RunOptions`.
380
+ 4. Explicit predicate (`passed(outputs)`). Exhaustion throws a typed error — fail-closed, never hang.
381
+ 5. Human gate is ordinary `suspend` / `resumeWorkflow` (CAS `expectedVersion`). Restart = new runtime, same checkpoint store.
382
+ 6. Audit **each iteration** with `replayWorkflow({ sourceRunId, fromNodeId })`. The host loop is N run ids — `listWorkflowRuns` lists them; `replayWorkflow` does not replay the `for`.
383
+
384
+ ```ts
385
+ for (let i = 0; i < MAX_ITERATIONS; i++) {
386
+ const run = await runWorkflow(phase, { goal, iteration: i }, { checkpoints, ownership });
387
+ if (run.status === "suspended") break; // resumeWorkflow later with expectedVersion
388
+ if (run.status !== "succeeded") throw new Error(run.status);
389
+ if (passed(run.outputs)) break;
390
+ }
391
+ if (!passed(last.outputs)) throw new BudgetExhaustedError(MAX_ITERATIONS);
392
+ ```
393
+
394
+ For a single bounded refinement, prefer `loopNode`. Keep this host-loop pattern when separate run ids, per-run checkpoints, or a new workflow definition are part of the contract.
395
+
303
396
  ## Extension and configuration notes
304
397
 
305
398
  - Workflow semantics stay in this optional package; generic checkpoint persistence and bounded event fan-in live in core.
306
399
  - `ProductionPersistenceStore.checkpoints` and `.leases` are optional generic capabilities. First-party SQLite/PostgreSQL adapters own `prism_checkpoints` / `prism_leases`; workflows only adapt them. Sagas use the same `WorkflowCheckpointAdapter` and `LeaseStore`; they add no SQL table or scheduler.
307
- - `createWorkflowEventBus()` delegates queueing, source fan-in, overflow, abort, and close behavior to core `createEventMultiplexer()`, including its single-consumer contract: a second concurrent `subscribe()` is rejected with `EventMultiplexerError` (`ERR_PRISM_EVENT_MULTIPLEXER_SINGLE_CONSUMER`) instead of silently splitting the stream. Graceful `close()` stops new emits/sources and drains already-queued events (in `(sequence, nodeId)` order) before the subscriber completes; overflow `close` still emits one `workflow_event_overflow` notice and terminates.
400
+ - `createWorkflowEventBus()` delegates queueing, source fan-in, overflow, abort, and close behavior to core `createEventMultiplexer()`, including its single-consumer contract: a second concurrent `subscribe()` is rejected with `EventMultiplexerError` (`ERR_PRISM_EVENT_MULTIPLEXER_SINGLE_CONSUMER`) instead of silently splitting the stream. Graceful `close()` stops new emits/sources and drains already-queued events (in `(sequence, nodeId)` order) before the subscriber completes; overflow `close` still emits one `workflow_event_overflow` notice and terminates. Loop iteration events carry bounded/redacted output and stable `iterationId` values for durable sinks.
308
401
  - The in-process active-run registry (`registerActiveWorkflowRun` / `getActiveWorkflowRun` / `abortActiveWorkflowRun`) is **non-durable, in-process only — it does not survive restart**; durable active-run recovery is a later milestone. It is bounded: every register sweeps aborted/leaked entries (runs whose promise never settled) and the registry fails closed at `MAX_ACTIVE_WORKFLOW_RUNS` (512) rather than evicting a live run; `sweepActiveWorkflowRuns()` is available for hosts. Cross-tenant lookups stay ownership-isolated.
309
402
  - `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.
310
403
  - Hosts may bridge `WorkflowEvent` into OpenTelemetry or custom sinks; there is no built-in TUI.
@@ -313,7 +406,8 @@ runRpcServer({
313
406
 
314
407
  ## Security and performance notes
315
408
 
316
- - Definitions require a non-empty host-authored `revision` and fail closed on cycles, unknown edges, self-edges, invalid limits, and `maxNodes` overflow. Revision and every nested revision enter the deterministic definition hash; hosts must bump revision when function/tool behavior changes.
409
+ - Definitions require a non-empty host-authored `revision` and fail closed on cycles, unknown edges, self-edges, invalid limits, and `maxNodes` overflow. Revision and every nested revision enter the deterministic definition hash; hosts must bump revision when function/tool behavior changes. Loop `maxIterations` is required and capped at 64.
410
+ - Loop bodies run serially inside one scheduler node; every body output and durable iteration record is bounded/redacted with `maxNodeOutputBytes`, and the scheduler persists the completed-iteration cursor before advancing. Approved durable resumes re-enter only the incomplete iteration.
317
411
  - Fan-out length is bounded by `maxFanOut`. Independent `map` items run in a local worker pool capped by the resolved workflow `maxConcurrency` (and `options.concurrency`); output stays in input order. Abort or the first map failure stops further items. There is no extra global admission service.
318
412
  - Node outputs, shared state/history, schedule input/records, and checkpoints are byte/count/depth bounded. Checkpoint size remains the final aggregate ceiling.
319
413
  - Event buses use a bounded buffer (default 2048) with `close` / `drop_oldest` / `drop_newest` overflow.
@@ -325,19 +419,19 @@ runRpcServer({
325
419
  - Active registry identity includes workflow ID, run ID, and exact ownership. Exact duplicates fail instead of overwriting; distinct owners remain isolated in lookup/list/cancel/unregister.
326
420
  - Tool nodes attach `workflowId` / `nodeId` on `ExecutionAction.metadata` for approval/audit context.
327
421
  - 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.
328
- - 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.
422
+ - 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. Replaying from a completed loop starts a fresh loop cursor and emits its per-iteration records; it never mutates source evidence.
329
423
  - 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.
330
424
  - Proactive schedules require an explicit capability grant. Revocation pauses the schedule (never fired by `pollOnce`) and `assertActive` fails closed on missing/revoked/expired tokens; enable/revoke/deny events carry redacted actor refs for the host policy ledger. Capability TTL is capped (default 24h / hard 31d) and the token record is byte-bounded (≤ 16 KiB); tokens are ownership-scoped, so foreign access fails closed rather than leaking existence.
331
425
  - Scheduler stores O(nodes + active outputs + bounded state history); ready-node work uses indegree maps, not repeated full scans.
332
426
  - 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.
333
427
  - Saga runs require `tenantId`; checkpoint keys and leases include tenant ownership. Every transition uses checkpoint CAS plus the current lease fence. Forward/compensation retries are capped at 3 by default / 10 hard; ambiguous outcomes require reconciliation and unresolved state becomes `manual_intervention`.
334
- - Saga input, step outputs, and error text are byte-bounded and passed through the configured `SecretRedactor` before persistence or compensation. Manual resolution requires an active verified actor for the tenant, exact checkpoint version, bounded reason, and a non-empty host audit reference; Prism does not pretend to verify the external audit record.
428
+ - Saga input, step outputs, and error text are byte-bounded and passed through the configured `SecretRedactor` before persistence or compensation. A loop used as one saga step remains one aggregate compensation record; hosts register and compensate its durable iteration IDs in reverse order. Manual resolution requires an active verified actor for the tenant, exact checkpoint version, bounded reason, and a non-empty host audit reference; Prism does not pretend to verify the external audit record.
335
429
 
336
430
  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.
337
431
 
338
432
  ## Related APIs
339
433
 
340
- - 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.
434
+ - 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`, `examples/autonomous-coding-loop.ts` (host-loop iterate-until-done) — offline runnable demos; PostgreSQL safely skips unless `PRISM_TEST_POSTGRES_URL` is set.
341
435
  - [Workflow orchestration primitives](workflow-orchestration-primitives.md): Task 0–1 inventory and locked adapter contracts
342
436
  - [Agent/session runtime](agent-session-runtime.md): `AgentSession.run()`/`stream()`, abort, subscribe
343
437
  - [Guardrails](guardrails.md): `RunWorkflowOptions.guardrails` routes tool nodes through core dispatch before policy and side effects.
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## What it does
4
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.
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 the `@arnilo/prism-memory/rag` subpath, plus an in-memory reference path and one PostgreSQL/pgvector production adapter.
6
6
 
7
7
  ## When to use it
8
8
 
@@ -26,6 +26,7 @@ Ordinary Prism sessions do not require this package or any vector backend.
26
26
  | `limits` | no | top-K, adjacent range, batch, payload, injected-token, export, and rebuild caps |
27
27
  | `redactor` / `secrets` | no | Redact text/metadata before persist/inject |
28
28
  | `requireConsent` | no | Strict mode: recall/injection excludes entries lacking explicit consent |
29
+ | `importanceFrom` | no | Host-owned hook deriving importance from a redacted reflection payload (write time only; no default, no LLM) |
29
30
 
30
31
  Semantic indexing (entries carry `MemoryConsent` source/visibility; unset defaults to `{ source: "user", scope: "thread", visible: true }`):
31
32
 
@@ -37,13 +38,60 @@ Semantic indexing (entries carry `MemoryConsent` source/visibility; unset defaul
37
38
  | `grantedAt` / `revokedAt` | Optional host/audit timestamps; a revocation excludes the record. |
38
39
 
39
40
  ```ts
40
- await memory.remember({ entries: [{ id, text, metadata?, consent?, sequence? }] }, { wait?: boolean })
41
+ await memory.remember({ entries: [{ id, text, metadata?, consent?, sequence?, importance?, reflection? }] }, { wait?: boolean })
41
42
  ```
42
43
 
43
44
  Semantic recall (honors consent/visibility at assembly time):
44
45
 
45
46
  ```ts
46
- await memory.recall(query, { topK?, messageRange?, requireConsent?, signal? })
47
+ await memory.recall(query, { topK?, messageRange?, requireConsent?, scoring?, signal? })
48
+ ```
49
+
50
+ #### Composite recall scoring (opt-in)
51
+
52
+ Default recall is pure similarity + lexical scoring and stays unchanged. Hosts opt into blending recency and importance at recall time via `scoring`:
53
+
54
+ | `RecallScoringOptions` field | Meaning |
55
+ | --- | --- |
56
+ | `recencyWeight` | Weight in `[0,1]` for timestamp half-life decay; requires `halfLifeMs` |
57
+ | `importanceWeight` | Weight in `[0,1]` for the stored record `importance` (neutral `1.0` when absent) |
58
+ | `halfLifeMs` | Positive finite recency half-life in milliseconds |
59
+
60
+ The resolver validates weights (finite, in `[0,1]`, no extra dependencies) and sum-normalizes: similarity keeps the remainder of `1`; weights overshooting `1` normalize down (similarity → `0`). Hit order becomes the blended score with the same deterministic tie-break (`score` desc, `sequence` asc, `id` asc), and hits expose the `similarity`, `recency`, `importance`, and `score` components. Both adapters converge on one shared pure re-rank — candidates are fetched at `topK × 4`, blended, then cut to `topK` — so pgvector ordering matches the in-memory adapter by construction.
61
+
62
+ ```ts
63
+ const recalled = await memory.recall("preferred response format", {
64
+ topK: 8,
65
+ scoring: { recencyWeight: 0.3, importanceWeight: 0.2, halfLifeMs: 7 * 24 * 3600 * 1000 },
66
+ });
67
+ // hits[0]: { text, score, similarity, recency, importance, ... }
68
+ ```
69
+
70
+ Security/performance: importance is host-trusted data clamped to `[0,1]` at write and scoring time; scoring is per-hit arithmetic with no extra queries or LLM calls; recall without `scoring` returns today's ordering and hit shape unchanged.
71
+
72
+ #### Importance at write (derivation from existing signals)
73
+
74
+ Stored `importance` never comes from an LLM analysis pass over writes — it derives from existing signals only:
75
+
76
+ - Direct: pass `importance` on a `remember()` entry (clamped to `[0,1]` at write; wins over derivation).
77
+ - Derived: set `importanceFrom` on `createMemory()` and pass a `reflection` object on the entry. The hook runs once at write time over the reflection **after secret redaction**, and its output is clamped to `[0,1]`; a non-finite output fails the write. Entries without `importance`/`reflection` (or without a hook) score at the neutral `1.0`. The hook is never invoked at recall, and the reflection payload itself is not persisted.
78
+
79
+ For observational-memory reflections (`@arnilo/prism-memory/compaction/observational-memory`, `MemoryReflection`), spread the record into the entry. The recipe below is an example heuristic — hosts own the real heuristic, and none ships as a default:
80
+
81
+ ```ts
82
+ const memory = createMemory({
83
+ // ...scope + embedder + stores
84
+ importanceFrom: (reflection) => {
85
+ // frequency/prominence recipe example: normalized mention count, no LLM call
86
+ const mentions = Number(reflection.mentions ?? reflection.supportingObservationIds?.length ?? 1);
87
+ return Number.isFinite(mentions) ? mentions / 10 : 1;
88
+ },
89
+ });
90
+
91
+ await memory.remember(
92
+ { entries: [{ id: reflection.id, text: reflection.content, reflection: { ...reflection } }] },
93
+ { wait: true },
94
+ );
47
95
  ```
48
96
 
49
97
  Consent + lifecycle (real grant/correct/delete/retention on stored entries):
@@ -171,14 +219,14 @@ const store = await createPostgresVectorStore({
171
219
  // getCurrentGeneration/setCurrentGeneration. close() ends adapter-owned pools.
172
220
  ```
173
221
 
174
- `createPostgresVectorStore()` is the production counterpart to `createMemoryVectorStore()` used by `@arnilo/prism-rag`; `createPostgresMemoryStores()` reuses the same vector implementation internally.
222
+ `createPostgresVectorStore()` is the production counterpart to `createMemoryVectorStore()` used by the `rag` subpath; `createPostgresMemoryStores()` reuses the same vector implementation internally.
175
223
 
176
224
  ## Extension and configuration notes
177
225
 
178
226
  - Hosts wire the context provider into `AgentConfig.context` or `resolveContextProviders()`.
179
227
  - The working-memory processor is opt-in and host-invoked; middleware is not required.
180
228
  - `createHashEmbedder()` is for tests/demos only; production hosts supply a real `Embedder`.
181
- - Observational memory (`@arnilo/prism-compaction-observational-memory`) remains unchanged and composable.
229
+ - Observational memory (`/compaction/observational-memory`) remains unchanged and composable.
182
230
  - Consent is enforced at the single `recall()` gate, so both direct recall and `createContextProvider()` injection honor it; `visible: false` (or a revoked grant) keeps an entry out of prompts, events, exports, and telemetry. `setConsent`/`correct` re-upsert in place (consent change does not re-embed); `forget`/`applyRetention` are real deletes, not tombstones. Retention uses indexed oldest-first pages plus a scoped count, deleting one default-500/hard-5000 batch without reading a corpus into memory. The PostgreSQL adapter persists consent in a `consent JSONB` column added by `buildMemoryDdl`.
183
231
  - The PostgreSQL vector path owns its DDL in Prism (`buildMemoryDdl`/`buildVectorSearchDdl` exported): the `<table>_rag_scope_generations` per-scope generation pointer table, `text_tsv` tsvector column + GIN index for the lexical RAG leg, and an HNSW index when the embedding dimension is pinned. DDL runs against the host's **knowledge database** — the host names `schema`/`table` (defaults `prism_memory`/`semantic_memory`), owns backup/retention of that database, and can run migrations manually with `skipMigrations: true`. Identifiers are validated/quoted; values stay parameterized.
184
232
  - `createPostgresVectorStore({ dimension })` pins the embedding column width before building indexes: pgvector can only build HNSW over `vector(N)` columns, and dimension mismatch fails closed instead of drifting.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arnilo/prism",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Agent harness for AI providers, agents, sessions, and tools.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -122,34 +122,14 @@
122
122
  "CHANGELOG.md"
123
123
  ],
124
124
  "workspaces": [
125
- "packages/provider-*",
126
- "packages/memory",
127
- "packages/rag",
128
- "packages/compaction-*",
129
- "packages/observability-*",
130
- "packages/tool-validator-*",
131
- "packages/session-store-*",
132
- "packages/credentials-node",
133
- "packages/mcp",
134
- "packages/evals",
135
- "packages/workflows",
136
- "packages/coding-agent",
137
- "packages/coding-security",
138
- "packages/server",
139
- "packages/supervisor",
140
- "packages/web-tools",
141
- "packages/work-tools",
142
- "packages/policy",
143
- "packages/model-router",
144
- "packages/enterprise-postgres",
145
- "packages/browser",
146
- "packages/obscura",
147
- "packages/ag-ui",
148
125
  "packages/acp-agent",
149
- "packages/computer-use-linux",
150
- "packages/document-reader",
126
+ "packages/ag-ui",
151
127
  "packages/antigravity-agent",
152
- "packages/prism-*"
128
+ "packages/mcp",
129
+ "packages/memory",
130
+ "packages/office",
131
+ "packages/prism-*",
132
+ "packages/web-tools"
153
133
  ],
154
134
  "scripts": {
155
135
  "build:core": "node scripts/with-build-lock.mjs tsc",
@@ -157,21 +137,21 @@
157
137
  "build": "npm run build:core && npm run build --workspaces --if-present",
158
138
  "typecheck": "npm run build && npm run typecheck --workspaces --if-present && tsc -p examples --noEmit",
159
139
  "sweep:unused": "node scripts/sweep-unused.mjs --json",
160
- "test": "npm run build && node scripts/with-build-lock.mjs node --test dist/__tests__/*.test.js && node scripts/with-build-lock.mjs node --test scripts/release-gate.test.mjs scripts/tooling-gate.test.mjs scripts/budget-gate.test.mjs scripts/phase8-conformance.test.mjs scripts/phase9-conformance.test.mjs scripts/phase10-conformance.test.mjs scripts/phase11-conformance.test.mjs scripts/phase11-freeze.test.mjs scripts/phase12-freeze.test.mjs scripts/phase13-freeze.test.mjs scripts/phase14-freeze.test.mjs scripts/phase15-freeze.test.mjs scripts/phase16-freeze.test.mjs scripts/phase17-freeze.test.mjs scripts/phase18-freeze.test.mjs scripts/phase19-freeze.test.mjs scripts/phase20-freeze.test.mjs scripts/phase21-freeze.test.mjs scripts/benchmark-0.1.0.test.mjs scripts/benchmark-multi-agent.test.mjs scripts/sweep-unused.test.mjs scripts/e2e-enterprise-journey.test.mjs scripts/e2e-coding-journey.test.mjs scripts/phase23-quality-gates.test.mjs scripts/phase24-truth.test.mjs scripts/phase25-bounded-accumulation.test.mjs scripts/phase26-freeze.test.mjs scripts/phase27-freeze.test.mjs scripts/phase27-ha.test.mjs scripts/phase27-erp-journey.test.mjs scripts/phase27-release.test.mjs scripts/phase29-freeze.test.mjs scripts/phase30-freeze.test.mjs scripts/phase30-release.test.mjs scripts/phase34-freeze.test.mjs scripts/phase37-provider-matrix.test.mjs scripts/phase26-index-benchmark.test.mjs scripts/obscura-host-conformance.test.mjs && node --test scripts/phase23-build-race.test.mjs && npm run test --workspaces --if-present",
140
+ "test": "npm run build && node scripts/with-build-lock.mjs node --test dist/__tests__/*.test.js && node scripts/with-build-lock.mjs node --test scripts/release-gate.test.mjs scripts/tooling-gate.test.mjs scripts/budget-gate.test.mjs scripts/phase8-conformance.test.mjs scripts/phase9-conformance.test.mjs scripts/phase10-conformance.test.mjs scripts/phase11-conformance.test.mjs scripts/phase11-freeze.test.mjs scripts/phase12-freeze.test.mjs scripts/phase13-freeze.test.mjs scripts/phase14-freeze.test.mjs scripts/phase15-freeze.test.mjs scripts/phase16-freeze.test.mjs scripts/phase17-freeze.test.mjs scripts/phase18-freeze.test.mjs scripts/phase19-freeze.test.mjs scripts/phase20-freeze.test.mjs scripts/phase21-freeze.test.mjs scripts/benchmark-0.1.0.test.mjs scripts/benchmark-multi-agent.test.mjs scripts/benchmark-tool-search.test.mjs scripts/benchmark-workflow-loop.test.mjs scripts/sweep-unused.test.mjs scripts/e2e-enterprise-journey.test.mjs scripts/e2e-coding-journey.test.mjs scripts/phase23-quality-gates.test.mjs scripts/phase24-truth.test.mjs scripts/phase25-bounded-accumulation.test.mjs scripts/phase26-freeze.test.mjs scripts/phase27-freeze.test.mjs scripts/phase27-ha.test.mjs scripts/phase27-erp-journey.test.mjs scripts/phase27-release.test.mjs scripts/phase29-freeze.test.mjs scripts/phase30-freeze.test.mjs scripts/phase30-release.test.mjs scripts/phase34-freeze.test.mjs scripts/phase37-provider-matrix.test.mjs scripts/phase26-index-benchmark.test.mjs scripts/obscura-host-conformance.test.mjs scripts/phase54-package-map.test.mjs scripts/phase54-legacy-registry.test.mjs && node --test scripts/phase23-build-race.test.mjs && npm run test --workspaces --if-present",
161
141
  "test:coverage": "node scripts/with-build-lock.mjs node --test --experimental-test-coverage --test-coverage-lines=60 --test-coverage-functions=70 --test-coverage-branches=75 --test-coverage-exclude='**/__tests__/**' --test-coverage-exclude='**/node_modules/**' --test-coverage-exclude='**/scripts/**' --test-coverage-exclude='**/packages/**' --test-coverage-exclude='**/examples/**' dist/__tests__/*.test.js && node scripts/with-build-lock.mjs node scripts/coverage-summary.mjs && node --test scripts/phase23-coverage.test.mjs && node --test scripts/phase23-skip-manifest.test.mjs",
162
142
  "coverage:summary": "node scripts/with-build-lock.mjs node scripts/coverage-summary.mjs",
163
143
  "lint": "biome lint . --reporter=sarif --reporter-file=scripts/lint-report.sarif",
164
144
  "format": "biome format --write .",
165
145
  "format:check": "biome format .",
166
146
  "pack:dry-run": "npm pack --dry-run && npm run pack:dry-run --workspaces --if-present",
167
- "test:postgres": "node scripts/require-postgres-url.mjs && npm run test:postgres --workspace @arnilo/prism-session-store-postgres && npm run test:postgres --workspace @arnilo/prism-memory && npm run test:postgres --workspace @arnilo/prism-enterprise-postgres && node --test scripts/phase7-conformance.test.mjs scripts/phase12-restart-recovery.test.mjs scripts/phase22-conformance.test.mjs",
147
+ "test:postgres": "node scripts/require-postgres-url.mjs && npm run test:postgres --workspace @arnilo/prism-core --if-present && npm run test:postgres --workspace @arnilo/prism-memory && node --test scripts/phase7-conformance.test.mjs scripts/phase12-restart-recovery.test.mjs scripts/phase22-conformance.test.mjs",
168
148
  "release:dry-run": "npm run sdk:ready",
169
149
  "release:check": "node scripts/release.mjs check",
170
150
  "release:publish": "node scripts/release.mjs publish",
171
151
  "release:evidence": "node scripts/release-skip-manifest.mjs",
172
152
  "sdk:ready": "npm run typecheck && npm run lint && npm run format:check && npm test && npm run test:coverage && npm run pack:dry-run && npm run release:gate",
173
153
  "release:gate": "node scripts/release-skip-manifest.mjs && node scripts/check-client-neutrality.mjs && node scripts/release.mjs gate",
174
- "security:threat-suites": "node --test scripts/phase8-conformance.test.mjs scripts/phase9-conformance.test.mjs scripts/phase10-conformance.test.mjs scripts/phase11-conformance.test.mjs scripts/phase20-security.test.mjs scripts/phase21-security.test.mjs scripts/phase22-security.test.mjs scripts/phase23-security.test.mjs scripts/phase38-codeql-regression.test.mjs"
154
+ "security:threat-suites": "node --test scripts/phase8-conformance.test.mjs scripts/phase9-conformance.test.mjs scripts/phase10-conformance.test.mjs scripts/phase11-conformance.test.mjs scripts/phase20-security.test.mjs scripts/phase21-security.test.mjs scripts/phase22-security.test.mjs scripts/phase23-security.test.mjs scripts/phase38-codeql-regression.test.mjs scripts/phase40-security.test.mjs scripts/phase46-webhooks-security.test.mjs dist/__tests__/pinned-fetch.test.js packages/prism-core/dist/runtime/server/__tests__/webhooks.test.js"
175
155
  },
176
156
  "devDependencies": {
177
157
  "@biomejs/biome": "^2.5.5",
@@ -0,0 +1,23 @@
1
+ # Prism Template Gallery
2
+
3
+ Ready-to-run project templates for `prism init --template <name>`.
4
+
5
+ ## Available Templates
6
+
7
+ | Template | Description | Included Packages |
8
+ | --- | --- | --- |
9
+ | `init` | Minimal starter Prism agent with one selected provider and offline mock test | `@arnilo/prism` |
10
+ | `deep-research` | Flagship deep research agent: plan -> search -> extract -> refine loop -> citations -> HITL clarify | `@arnilo/prism`, `@arnilo/prism-web-tools`, `@arnilo/prism-memory`, `@arnilo/prism-workflows` |
11
+
12
+ ## Usage
13
+
14
+ ```bash
15
+ # Scaffold the flagship deep-research template
16
+ prism init my-research --template deep-research
17
+
18
+ # List all available templates
19
+ prism init --list-templates
20
+
21
+ # Scaffold the standard minimal agent
22
+ prism init my-agent
23
+ ```
@@ -0,0 +1,47 @@
1
+ # __PROJECT_NAME__ — Deep Research Agent
2
+
3
+ A production-grade, modular deep research agent built on **Prism**.
4
+
5
+ The agent executes an end-to-end research pipeline:
6
+ 1. **Plan:** Generates a structured research plan with targeted queries.
7
+ 2. **Search & Extract:** Fetches search results and content via `@arnilo/prism-web-tools`.
8
+ 3. **Refine (Bounded Loop):** Evaluates coverage and refines queries across bounded iterations.
9
+ 4. **Cite & Synthesize:** Synthesizes findings with verifiable, traceable source citations.
10
+ 5. **HITL Clarification:** Proposes structured clarification choices when research topics are ambiguous.
11
+
12
+ ## Quick Start
13
+
14
+ ```bash
15
+ # 1. Install dependencies
16
+ npm install
17
+
18
+ # 2. Run offline tests (100% offline with mock provider)
19
+ npm test
20
+
21
+ # 3. Start the research agent
22
+ npm start
23
+
24
+ # 4. Launch local dev inspector
25
+ npm run dev
26
+ ```
27
+
28
+ ## Architecture & Component Mapping
29
+
30
+ | Stage / Seam | Component | Documentation |
31
+ | --- | --- | --- |
32
+ | **Orchestration** | `@arnilo/prism-workflows` DAG with bounded refine loop & checkpoints | [`docs/workflows.md`](https://github.com/arniloy/prism/blob/main/docs/workflows.md) |
33
+ | **Search & Fetch** | `@arnilo/prism-web-tools` Brave/Exa search & Firecrawl fetch | [`docs/web-tools.md`](https://github.com/arniloy/prism/blob/main/docs/web-tools.md) |
34
+ | **Citations & RAG** | Attribution via `@arnilo/prism-web-tools` & `@arnilo/prism-memory` | [`docs/rag.md`](https://github.com/arniloy/prism/blob/main/docs/rag.md) |
35
+ | **HITL Clarification** | Structured decision tool with durable suspend/resume | [`docs/coding-agent-tools.md`](https://github.com/arniloy/prism/blob/main/docs/coding-agent-tools.md) |
36
+ | **Security & Trust** | Untrusted content boundaries for search & web data | [`docs/host-security.md`](https://github.com/arniloy/prism/blob/main/docs/host-security.md) |
37
+ | **Inspector** | Local loopback playground via `prism dev` | [`docs/cli-rpc.md`](https://github.com/arniloy/prism/blob/main/docs/cli-rpc.md) |
38
+
39
+ ## Live Configuration (Opt-in)
40
+
41
+ To connect real LLM providers and live web search APIs:
42
+ 1. Copy `.env.example` to `.env`:
43
+ ```bash
44
+ cp .env.example .env
45
+ ```
46
+ 2. Set your provider key (e.g. `OPENAI_API_KEY`) and search key (e.g. `BRAVE_API_KEY`).
47
+ 3. Update `src/tools.ts` or `src/agent.ts` to instantiate live providers.
@@ -0,0 +1,12 @@
1
+ # Deep Research Agent Environment Configuration
2
+ # Copy this file to .env to configure live providers and search tools.
3
+ # By default, tests and starter runs use the offline mock provider (no keys required).
4
+
5
+ # --- Model Providers (Optional) ---
6
+ # OPENAI_API_KEY=sk-...
7
+ # ANTHROPIC_API_KEY=sk-ant-...
8
+
9
+ # --- Web Search & Extraction (Optional) ---
10
+ # BRAVE_API_KEY=...
11
+ # FIRECRAWL_API_KEY=...
12
+ # EXA_API_KEY=...
@@ -0,0 +1,7 @@
1
+ node_modules/
2
+ dist/
3
+ .env
4
+ .env.local
5
+ .prism/
6
+ coverage/
7
+ *.log
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "deep-research",
3
+ "description": "Deep research agent: plan -> search -> extract -> refine loop -> citations -> HITL clarify",
4
+ "version": "0.1.0",
5
+ "tags": ["research", "workflows", "web-tools", "rag", "hitl"],
6
+ "packages": [
7
+ "@arnilo/prism",
8
+ "@arnilo/prism-web-tools",
9
+ "@arnilo/prism-memory",
10
+ "@arnilo/prism-workflows"
11
+ ]
12
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "__PROJECT_NAME__",
3
+ "version": "0.1.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__/research.test.js",
10
+ "start": "npm run build && node dist/index.js",
11
+ "dev": "prism dev"
12
+ },
13
+ "dependencies": {
14
+ __DEPENDENCIES__
15
+ },
16
+ "devDependencies": {
17
+ "@types/node": "^22.0.0",
18
+ "typescript": "^5.7.0"
19
+ },
20
+ "engines": {
21
+ "node": ">=20"
22
+ }
23
+ }
@@ -0,0 +1,81 @@
1
+ import {
2
+ type Agent,
3
+ createAgent,
4
+ createMockProvider,
5
+ providerDone,
6
+ providerTextDelta,
7
+ } from "@arnilo/prism";
8
+ import { createResearchSearchAdapter, createResearchWebTools } from "./tools.js";
9
+
10
+ /**
11
+ * Creates the planning agent responsible for breaking down topics into search queries.
12
+ */
13
+ export function createPlannerAgent(options?: {
14
+ readonly model?: { provider: string; model: string };
15
+ readonly provider?: import("@arnilo/prism").AIProvider;
16
+ }): Agent {
17
+ const provider =
18
+ options?.provider ??
19
+ createMockProvider([
20
+ providerTextDelta(
21
+ JSON.stringify({
22
+ topic: "Prism Architecture",
23
+ queries: [
24
+ { query: "Prism workflows orchestration", rationale: "Understand DAG execution", aspect: "workflows" },
25
+ { query: "Prism web tools citations", rationale: "Check search and citation seam", aspect: "web-tools" },
26
+ ],
27
+ }),
28
+ ),
29
+ providerDone(),
30
+ ]);
31
+
32
+ return createAgent({
33
+ model: options?.model ?? { provider: "mock", model: "demo" },
34
+ provider,
35
+ instructions:
36
+ "You are a research planning agent. Given a research topic, produce a structured research plan breaking down the topic into targeted search queries.",
37
+ });
38
+ }
39
+
40
+ /**
41
+ * Creates the synthesis agent responsible for compiling findings into a cited report.
42
+ */
43
+ export function createSynthesizerAgent(options?: {
44
+ readonly model?: { provider: string; model: string };
45
+ readonly provider?: import("@arnilo/prism").AIProvider;
46
+ }): Agent {
47
+ const provider =
48
+ options?.provider ??
49
+ createMockProvider([
50
+ providerTextDelta(
51
+ "### Executive Summary\nPrism provides modular agent runtimes with durable workflows and verifiable web-tool citations.\n\n### Findings\n- Workflows execute deterministic DAG pipelines.\n- Web tools generate verifiable cryptographic citations.",
52
+ ),
53
+ providerDone(),
54
+ ]);
55
+
56
+ return createAgent({
57
+ model: options?.model ?? { provider: "mock", model: "demo" },
58
+ provider,
59
+ instructions:
60
+ "You are a research synthesis agent. Review the gathered research findings and compose a detailed, factual summary referencing the attributable citations.",
61
+ });
62
+ }
63
+
64
+ /**
65
+ * Default app agent export for `prism dev` and CLI inspector.
66
+ */
67
+ export function createAppAgent(): Agent {
68
+ const adapter = createResearchSearchAdapter();
69
+ const tools = createResearchWebTools(adapter);
70
+
71
+ return createAgent({
72
+ model: { provider: "mock", model: "demo" },
73
+ provider: createMockProvider([
74
+ providerTextDelta("Deep Research Agent ready. Ask me to research any topic."),
75
+ providerDone(),
76
+ ]),
77
+ tools,
78
+ instructions:
79
+ "You are a Deep Research Assistant. You break down topics, search the web, refine evidence, and produce cited reports.",
80
+ });
81
+ }