@warlock.js/ai 4.2.11 → 4.3.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.
- package/CHANGELOG.md +61 -1
- package/cjs/index.cjs +10151 -4625
- package/cjs/index.cjs.map +1 -1
- package/cjs/magic-string.es-BtxW4VqG.cjs +1015 -0
- package/cjs/magic-string.es-BtxW4VqG.cjs.map +1 -0
- package/cjs/matcher-logic-SBnzYohQ.cjs +217 -0
- package/cjs/matcher-logic-SBnzYohQ.cjs.map +1 -0
- package/cjs/matchers-BBh3gyB-.cjs +13739 -0
- package/cjs/matchers-BBh3gyB-.cjs.map +1 -0
- package/esm/agent/agent-config.type.d.mts +19 -6
- package/esm/agent/agent-config.type.d.mts.map +1 -1
- package/esm/agent/agent.d.mts.map +1 -1
- package/esm/agent/agent.mjs +17 -6
- package/esm/agent/agent.mjs.map +1 -1
- package/esm/agent/index.d.mts +2 -1
- package/esm/agent/index.mjs +1 -0
- package/esm/agent/spawn-sub-agent.d.mts +87 -0
- package/esm/agent/spawn-sub-agent.d.mts.map +1 -0
- package/esm/agent/spawn-sub-agent.mjs +68 -0
- package/esm/agent/spawn-sub-agent.mjs.map +1 -0
- package/esm/ai.d.mts +58 -3
- package/esm/ai.d.mts.map +1 -1
- package/esm/ai.mjs +58 -3
- package/esm/ai.mjs.map +1 -1
- package/esm/batch/batch.d.mts +43 -0
- package/esm/batch/batch.d.mts.map +1 -0
- package/esm/batch/batch.mjs +179 -0
- package/esm/batch/batch.mjs.map +1 -0
- package/esm/batch/batch.type.d.mts +144 -0
- package/esm/batch/batch.type.d.mts.map +1 -0
- package/esm/batch/index.mjs +3 -0
- package/esm/batch/run-batch-item.mjs +100 -0
- package/esm/batch/run-batch-item.mjs.map +1 -0
- package/esm/batch/run-with-concurrency.mjs +39 -0
- package/esm/batch/run-with-concurrency.mjs.map +1 -0
- package/esm/checkpoint/index.d.mts +3 -0
- package/esm/checkpoint/memory.d.mts +21 -0
- package/esm/checkpoint/memory.d.mts.map +1 -0
- package/esm/checkpoint/memory.mjs +0 -0
- package/esm/checkpoint/memory.mjs.map +1 -0
- package/esm/checkpoint/pg.d.mts +37 -0
- package/esm/checkpoint/pg.d.mts.map +1 -0
- package/esm/checkpoint/pg.mjs +265 -0
- package/esm/checkpoint/pg.mjs.map +1 -0
- package/esm/checkpoint/redis.d.mts +39 -0
- package/esm/checkpoint/redis.d.mts.map +1 -0
- package/esm/checkpoint/redis.mjs +200 -0
- package/esm/checkpoint/redis.mjs.map +1 -0
- package/esm/config.d.mts +61 -14
- package/esm/config.d.mts.map +1 -1
- package/esm/config.mjs +25 -6
- package/esm/config.mjs.map +1 -1
- package/esm/contracts/agent/agent.contract.d.mts +43 -0
- package/esm/contracts/agent/agent.contract.d.mts.map +1 -1
- package/esm/contracts/agent/eval.type.d.mts +143 -0
- package/esm/contracts/agent/eval.type.d.mts.map +1 -0
- package/esm/contracts/agent/index.d.mts +1 -0
- package/esm/contracts/events/supervisor-events.type.d.mts +3 -3
- package/esm/contracts/fallback-model.contract.d.mts +65 -0
- package/esm/contracts/fallback-model.contract.d.mts.map +1 -0
- package/esm/contracts/index.d.mts +32 -12
- package/esm/contracts/memory/index.d.mts +4 -0
- package/esm/contracts/memory/memory-config.type.d.mts +150 -0
- package/esm/contracts/memory/memory-config.type.d.mts.map +1 -0
- package/esm/contracts/memory/memory-item.type.d.mts +64 -0
- package/esm/contracts/memory/memory-item.type.d.mts.map +1 -0
- package/esm/contracts/memory/memory.contract.d.mts +87 -0
- package/esm/contracts/memory/memory.contract.d.mts.map +1 -0
- package/esm/contracts/memory/recall-options.type.d.mts +33 -0
- package/esm/contracts/memory/recall-options.type.d.mts.map +1 -0
- package/esm/contracts/middleware/index.d.mts +2 -2
- package/esm/contracts/middleware/middleware-context.type.d.mts +42 -2
- package/esm/contracts/middleware/middleware-context.type.d.mts.map +1 -1
- package/esm/contracts/middleware/middleware.contract.d.mts +46 -2
- package/esm/contracts/middleware/middleware.contract.d.mts.map +1 -1
- package/esm/contracts/model.contract.d.mts +63 -2
- package/esm/contracts/model.contract.d.mts.map +1 -1
- package/esm/contracts/orchestrator/checkpoint-store.contract.d.mts +91 -0
- package/esm/contracts/orchestrator/checkpoint-store.contract.d.mts.map +1 -0
- package/esm/contracts/orchestrator/index.d.mts +8 -0
- package/esm/contracts/orchestrator/orchestrator-commands.type.d.mts +43 -0
- package/esm/contracts/orchestrator/orchestrator-commands.type.d.mts.map +1 -0
- package/esm/contracts/orchestrator/orchestrator-config.type.d.mts +170 -0
- package/esm/contracts/orchestrator/orchestrator-config.type.d.mts.map +1 -0
- package/esm/contracts/orchestrator/orchestrator-event.type.d.mts +118 -0
- package/esm/contracts/orchestrator/orchestrator-event.type.d.mts.map +1 -0
- package/esm/contracts/orchestrator/orchestrator-execute-options.type.d.mts +44 -0
- package/esm/contracts/orchestrator/orchestrator-execute-options.type.d.mts.map +1 -0
- package/esm/contracts/orchestrator/orchestrator.contract.d.mts +129 -0
- package/esm/contracts/orchestrator/orchestrator.contract.d.mts.map +1 -0
- package/esm/contracts/orchestrator/session.contract.d.mts +26 -0
- package/esm/contracts/orchestrator/session.contract.d.mts.map +1 -0
- package/esm/contracts/orchestrator/snapshot-store.contract.d.mts +89 -0
- package/esm/contracts/orchestrator/snapshot-store.contract.d.mts.map +1 -0
- package/esm/contracts/planner/index.d.mts +6 -0
- package/esm/contracts/planner/planner-capability.type.d.mts +39 -0
- package/esm/contracts/planner/planner-capability.type.d.mts.map +1 -0
- package/esm/contracts/planner/planner-config.type.d.mts +78 -0
- package/esm/contracts/planner/planner-config.type.d.mts.map +1 -0
- package/esm/contracts/planner/planner-execute-options.type.d.mts +43 -0
- package/esm/contracts/planner/planner-execute-options.type.d.mts.map +1 -0
- package/esm/contracts/planner/planner-plan.type.d.mts +48 -0
- package/esm/contracts/planner/planner-plan.type.d.mts.map +1 -0
- package/esm/contracts/planner/planner-result.type.d.mts +82 -0
- package/esm/contracts/planner/planner-result.type.d.mts.map +1 -0
- package/esm/contracts/planner/planner.contract.d.mts +60 -0
- package/esm/contracts/planner/planner.contract.d.mts.map +1 -0
- package/esm/contracts/result/base-report.type.d.mts +7 -2
- package/esm/contracts/result/base-report.type.d.mts.map +1 -1
- package/esm/contracts/result/base-report.type.mjs.map +1 -1
- package/esm/contracts/result/index.d.mts +2 -1
- package/esm/contracts/result/model-pricing.type.d.mts +10 -0
- package/esm/contracts/result/model-pricing.type.d.mts.map +1 -1
- package/esm/contracts/result/orchestrator-result.type.d.mts +139 -0
- package/esm/contracts/result/orchestrator-result.type.d.mts.map +1 -0
- package/esm/contracts/result/session-send-result.type.d.mts +12 -3
- package/esm/contracts/result/session-send-result.type.d.mts.map +1 -1
- package/esm/contracts/result/supervisor-result.type.d.mts +1 -1
- package/esm/contracts/result/usage.type.d.mts +24 -0
- package/esm/contracts/result/usage.type.d.mts.map +1 -1
- package/esm/contracts/sdk-adapter.contract.d.mts +1 -1
- package/esm/contracts/supervisor/dispatch-context.type.d.mts +3 -3
- package/esm/contracts/supervisor/evaluate-context.type.d.mts +1 -1
- package/esm/contracts/supervisor/index.d.mts +5 -5
- package/esm/contracts/supervisor/route-context.type.d.mts +2 -2
- package/esm/contracts/supervisor/supervisor-config.type.d.mts +55 -13
- package/esm/contracts/supervisor/supervisor-config.type.d.mts.map +1 -1
- package/esm/contracts/supervisor/supervisor-snapshot.type.d.mts +1 -1
- package/esm/contracts/supervisor/supervisor.contract.d.mts +9 -2
- package/esm/contracts/supervisor/supervisor.contract.d.mts.map +1 -1
- package/esm/contracts/workflow/index.d.mts +2 -2
- package/esm/contracts/workflow/workflow.contract.d.mts +28 -7
- package/esm/contracts/workflow/workflow.contract.d.mts.map +1 -1
- package/esm/errors/error-code.type.d.mts +1 -1
- package/esm/errors/index.d.mts +7 -0
- package/esm/errors/index.mjs +7 -0
- package/esm/errors/orchestrator-cancelled-error.d.mts +32 -0
- package/esm/errors/orchestrator-cancelled-error.d.mts.map +1 -0
- package/esm/errors/orchestrator-cancelled-error.mjs +31 -0
- package/esm/errors/orchestrator-cancelled-error.mjs.map +1 -0
- package/esm/errors/orchestrator-config-error.d.mts +26 -0
- package/esm/errors/orchestrator-config-error.d.mts.map +1 -0
- package/esm/errors/orchestrator-config-error.mjs +30 -0
- package/esm/errors/orchestrator-config-error.mjs.map +1 -0
- package/esm/errors/orchestrator-drift-error.d.mts +38 -0
- package/esm/errors/orchestrator-drift-error.d.mts.map +1 -0
- package/esm/errors/orchestrator-drift-error.mjs +37 -0
- package/esm/errors/orchestrator-drift-error.mjs.map +1 -0
- package/esm/errors/orchestrator-failed-error.d.mts +33 -0
- package/esm/errors/orchestrator-failed-error.d.mts.map +1 -0
- package/esm/errors/orchestrator-failed-error.mjs +36 -0
- package/esm/errors/orchestrator-failed-error.mjs.map +1 -0
- package/esm/errors/planner-cancelled-error.d.mts +33 -0
- package/esm/errors/planner-cancelled-error.d.mts.map +1 -0
- package/esm/errors/planner-cancelled-error.mjs +29 -0
- package/esm/errors/planner-cancelled-error.mjs.map +1 -0
- package/esm/errors/planner-failed-error.d.mts +40 -0
- package/esm/errors/planner-failed-error.d.mts.map +1 -0
- package/esm/errors/planner-failed-error.mjs +37 -0
- package/esm/errors/planner-failed-error.mjs.map +1 -0
- package/esm/errors/planner-plan-invalid-error.d.mts +21 -0
- package/esm/errors/planner-plan-invalid-error.d.mts.map +1 -0
- package/esm/errors/planner-plan-invalid-error.mjs +25 -0
- package/esm/errors/planner-plan-invalid-error.mjs.map +1 -0
- package/esm/eval/eval-runner.d.mts +17 -0
- package/esm/eval/eval-runner.d.mts.map +1 -0
- package/esm/eval/eval-runner.mjs +121 -0
- package/esm/eval/eval-runner.mjs.map +1 -0
- package/esm/eval/index.d.mts +29 -0
- package/esm/eval/index.d.mts.map +1 -0
- package/esm/eval/index.mjs +30 -0
- package/esm/eval/index.mjs.map +1 -0
- package/esm/eval/judge-scorer.d.mts +21 -0
- package/esm/eval/judge-scorer.d.mts.map +1 -0
- package/esm/eval/judge-scorer.mjs +87 -0
- package/esm/eval/judge-scorer.mjs.map +1 -0
- package/esm/eval/scorers.d.mts +50 -0
- package/esm/eval/scorers.d.mts.map +1 -0
- package/esm/eval/scorers.mjs +101 -0
- package/esm/eval/scorers.mjs.map +1 -0
- package/esm/index.d.mts +95 -30
- package/esm/index.mjs +66 -22
- package/esm/memory/derive-id.mjs +24 -0
- package/esm/memory/derive-id.mjs.map +1 -0
- package/esm/memory/episodic-memory.mjs +106 -0
- package/esm/memory/episodic-memory.mjs.map +1 -0
- package/esm/memory/index.d.mts +5 -0
- package/esm/memory/memory.d.mts +42 -0
- package/esm/memory/memory.d.mts.map +1 -0
- package/esm/memory/memory.mjs +166 -0
- package/esm/memory/memory.mjs.map +1 -0
- package/esm/memory/procedural-memory.mjs +103 -0
- package/esm/memory/procedural-memory.mjs.map +1 -0
- package/esm/memory/semantic-memory.mjs +80 -0
- package/esm/memory/semantic-memory.mjs.map +1 -0
- package/esm/memory/working-memory.mjs +62 -0
- package/esm/memory/working-memory.mjs.map +1 -0
- package/esm/middleware/builtins/budget-contract.type.d.mts +126 -0
- package/esm/middleware/builtins/budget-contract.type.d.mts.map +1 -0
- package/esm/middleware/builtins/budget.d.mts +71 -1
- package/esm/middleware/builtins/budget.d.mts.map +1 -1
- package/esm/middleware/builtins/budget.mjs +119 -4
- package/esm/middleware/builtins/budget.mjs.map +1 -1
- package/esm/middleware/builtins/semantic-cache.d.mts +1 -1
- package/esm/middleware/index.d.mts +2 -1
- package/esm/middleware/index.mjs +1 -1
- package/esm/middleware/pipeline.d.mts +9 -6
- package/esm/middleware/pipeline.d.mts.map +1 -1
- package/esm/middleware/pipeline.mjs.map +1 -1
- package/esm/mock/index.d.mts +1 -0
- package/esm/mock/index.mjs +1 -0
- package/esm/mock/mock-router.d.mts +63 -0
- package/esm/mock/mock-router.d.mts.map +1 -0
- package/esm/mock/mock-router.mjs +58 -0
- package/esm/mock/mock-router.mjs.map +1 -0
- package/esm/model/fallback-model.d.mts +45 -0
- package/esm/model/fallback-model.d.mts.map +1 -0
- package/esm/model/fallback-model.mjs +218 -0
- package/esm/model/fallback-model.mjs.map +1 -0
- package/esm/model/index.d.mts +2 -0
- package/esm/model/index.mjs +3 -0
- package/esm/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.mjs +78 -0
- package/esm/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.mjs.map +1 -0
- package/esm/node_modules/@vitest/expect/dist/index.mjs +1473 -0
- package/esm/node_modules/@vitest/expect/dist/index.mjs.map +1 -0
- package/esm/node_modules/@vitest/pretty-format/dist/index.mjs +888 -0
- package/esm/node_modules/@vitest/pretty-format/dist/index.mjs.map +1 -0
- package/esm/node_modules/@vitest/runner/dist/chunk-artifact.mjs +1533 -0
- package/esm/node_modules/@vitest/runner/dist/chunk-artifact.mjs.map +1 -0
- package/esm/node_modules/@vitest/runner/dist/index.mjs +3 -0
- package/esm/node_modules/@vitest/runner/dist/utils.mjs +3 -0
- package/esm/node_modules/@vitest/snapshot/dist/index.mjs +922 -0
- package/esm/node_modules/@vitest/snapshot/dist/index.mjs.map +1 -0
- package/esm/node_modules/@vitest/spy/dist/index.mjs +386 -0
- package/esm/node_modules/@vitest/spy/dist/index.mjs.map +1 -0
- package/esm/node_modules/@vitest/utils/dist/chunk-pathe.M-eThtNZ.mjs +82 -0
- package/esm/node_modules/@vitest/utils/dist/chunk-pathe.M-eThtNZ.mjs.map +1 -0
- package/esm/node_modules/@vitest/utils/dist/diff.mjs +1357 -0
- package/esm/node_modules/@vitest/utils/dist/diff.mjs.map +1 -0
- package/esm/node_modules/@vitest/utils/dist/display.mjs +559 -0
- package/esm/node_modules/@vitest/utils/dist/display.mjs.map +1 -0
- package/esm/node_modules/@vitest/utils/dist/error.mjs +38 -0
- package/esm/node_modules/@vitest/utils/dist/error.mjs.map +1 -0
- package/esm/node_modules/@vitest/utils/dist/helpers.mjs +181 -0
- package/esm/node_modules/@vitest/utils/dist/helpers.mjs.map +1 -0
- package/esm/node_modules/@vitest/utils/dist/offset.mjs +27 -0
- package/esm/node_modules/@vitest/utils/dist/offset.mjs.map +1 -0
- package/esm/node_modules/@vitest/utils/dist/serialize.mjs +77 -0
- package/esm/node_modules/@vitest/utils/dist/serialize.mjs.map +1 -0
- package/esm/node_modules/@vitest/utils/dist/source-map.mjs +374 -0
- package/esm/node_modules/@vitest/utils/dist/source-map.mjs.map +1 -0
- package/esm/node_modules/@vitest/utils/dist/timers.mjs +37 -0
- package/esm/node_modules/@vitest/utils/dist/timers.mjs.map +1 -0
- package/esm/node_modules/chai/index.mjs +2973 -0
- package/esm/node_modules/chai/index.mjs.map +1 -0
- package/esm/node_modules/magic-string/dist/magic-string.es.mjs +940 -0
- package/esm/node_modules/magic-string/dist/magic-string.es.mjs.map +1 -0
- package/esm/node_modules/tinyrainbow/dist/index.mjs +87 -0
- package/esm/node_modules/tinyrainbow/dist/index.mjs.map +1 -0
- package/esm/node_modules/vitest/dist/chunks/_commonjsHelpers.D26ty3Ew.mjs +6 -0
- package/esm/node_modules/vitest/dist/chunks/_commonjsHelpers.D26ty3Ew.mjs.map +1 -0
- package/esm/node_modules/vitest/dist/chunks/rpc.MzXet3jl.mjs +52 -0
- package/esm/node_modules/vitest/dist/chunks/rpc.MzXet3jl.mjs.map +1 -0
- package/esm/node_modules/vitest/dist/chunks/test.DNmyFkvJ.mjs +2697 -0
- package/esm/node_modules/vitest/dist/chunks/test.DNmyFkvJ.mjs.map +1 -0
- package/esm/node_modules/vitest/dist/chunks/utils.BX5Fg8C4.mjs +45 -0
- package/esm/node_modules/vitest/dist/chunks/utils.BX5Fg8C4.mjs.map +1 -0
- package/esm/orchestrator/as-tool.d.mts +42 -0
- package/esm/orchestrator/as-tool.d.mts.map +1 -0
- package/esm/orchestrator/as-tool.mjs +98 -0
- package/esm/orchestrator/as-tool.mjs.map +1 -0
- package/esm/orchestrator/checkpoint.mjs +75 -0
- package/esm/orchestrator/checkpoint.mjs.map +1 -0
- package/esm/orchestrator/commands.d.mts +38 -0
- package/esm/orchestrator/commands.d.mts.map +1 -0
- package/esm/orchestrator/commands.mjs +34 -0
- package/esm/orchestrator/commands.mjs.map +1 -0
- package/esm/orchestrator/compaction.mjs +206 -0
- package/esm/orchestrator/compaction.mjs.map +1 -0
- package/esm/orchestrator/dispatch.mjs +171 -0
- package/esm/orchestrator/dispatch.mjs.map +1 -0
- package/esm/orchestrator/emitter-port.type.d.mts +31 -0
- package/esm/orchestrator/emitter-port.type.d.mts.map +1 -0
- package/esm/orchestrator/emitter.d.mts +56 -0
- package/esm/orchestrator/emitter.d.mts.map +1 -0
- package/esm/orchestrator/emitter.mjs +85 -0
- package/esm/orchestrator/emitter.mjs.map +1 -0
- package/esm/orchestrator/engine-context.type.d.mts +56 -0
- package/esm/orchestrator/engine-context.type.d.mts.map +1 -0
- package/esm/orchestrator/execution.d.mts +116 -0
- package/esm/orchestrator/execution.d.mts.map +1 -0
- package/esm/orchestrator/execution.mjs +406 -0
- package/esm/orchestrator/execution.mjs.map +1 -0
- package/esm/orchestrator/index.d.mts +8 -0
- package/esm/orchestrator/index.mjs +10 -0
- package/esm/orchestrator/load.mjs +49 -0
- package/esm/orchestrator/load.mjs.map +1 -0
- package/esm/orchestrator/lock.mjs +75 -0
- package/esm/orchestrator/lock.mjs.map +1 -0
- package/esm/orchestrator/memory.d.mts +84 -0
- package/esm/orchestrator/memory.d.mts.map +1 -0
- package/esm/orchestrator/memory.mjs +141 -0
- package/esm/orchestrator/memory.mjs.map +1 -0
- package/esm/orchestrator/orchestrator-stream.d.mts +42 -0
- package/esm/orchestrator/orchestrator-stream.d.mts.map +1 -0
- package/esm/orchestrator/orchestrator-stream.mjs +98 -0
- package/esm/orchestrator/orchestrator-stream.mjs.map +1 -0
- package/esm/orchestrator/orchestrator.d.mts +38 -0
- package/esm/orchestrator/orchestrator.d.mts.map +1 -0
- package/esm/orchestrator/orchestrator.mjs +173 -0
- package/esm/orchestrator/orchestrator.mjs.map +1 -0
- package/esm/orchestrator/resume.mjs +74 -0
- package/esm/orchestrator/resume.mjs.map +1 -0
- package/esm/orchestrator/signature.d.mts +40 -0
- package/esm/orchestrator/signature.d.mts.map +1 -0
- package/esm/orchestrator/signature.mjs +120 -0
- package/esm/orchestrator/signature.mjs.map +1 -0
- package/esm/orchestrator/window.mjs +56 -0
- package/esm/orchestrator/window.mjs.map +1 -0
- package/esm/planner/index.d.mts +5 -0
- package/esm/planner/index.mjs +6 -0
- package/esm/planner/plan-prompt.d.mts +17 -0
- package/esm/planner/plan-prompt.d.mts.map +1 -0
- package/esm/planner/plan-prompt.mjs +30 -0
- package/esm/planner/plan-prompt.mjs.map +1 -0
- package/esm/planner/plan-schema.d.mts +27 -0
- package/esm/planner/plan-schema.d.mts.map +1 -0
- package/esm/planner/plan-schema.mjs +117 -0
- package/esm/planner/plan-schema.mjs.map +1 -0
- package/esm/planner/planner-run.d.mts +23 -0
- package/esm/planner/planner-run.d.mts.map +1 -0
- package/esm/planner/planner-run.mjs +344 -0
- package/esm/planner/planner-run.mjs.map +1 -0
- package/esm/planner/planner.d.mts +37 -0
- package/esm/planner/planner.d.mts.map +1 -0
- package/esm/planner/planner.mjs +120 -0
- package/esm/planner/planner.mjs.map +1 -0
- package/esm/planner/signature.d.mts +18 -0
- package/esm/planner/signature.d.mts.map +1 -0
- package/esm/planner/signature.mjs +27 -0
- package/esm/planner/signature.mjs.map +1 -0
- package/esm/snapshot/index.d.mts +3 -0
- package/esm/snapshot/memory.d.mts +26 -0
- package/esm/snapshot/memory.d.mts.map +1 -0
- package/esm/snapshot/memory.mjs +81 -0
- package/esm/snapshot/memory.mjs.map +1 -0
- package/esm/snapshot/pg.d.mts +41 -0
- package/esm/snapshot/pg.d.mts.map +1 -0
- package/esm/snapshot/pg.mjs +146 -0
- package/esm/snapshot/pg.mjs.map +1 -0
- package/esm/snapshot/redis.d.mts +42 -0
- package/esm/snapshot/redis.d.mts.map +1 -0
- package/esm/snapshot/redis.mjs +101 -0
- package/esm/snapshot/redis.mjs.map +1 -0
- package/esm/supervisor/as-tool.d.mts +0 -6
- package/esm/supervisor/as-tool.d.mts.map +1 -1
- package/esm/supervisor/as-tool.mjs +0 -6
- package/esm/supervisor/as-tool.mjs.map +1 -1
- package/esm/supervisor/execution.d.mts +43 -8
- package/esm/supervisor/execution.d.mts.map +1 -1
- package/esm/supervisor/execution.mjs +66 -16
- package/esm/supervisor/execution.mjs.map +1 -1
- package/esm/supervisor/fan-out.d.mts +65 -0
- package/esm/supervisor/fan-out.d.mts.map +1 -0
- package/esm/supervisor/fan-out.mjs +65 -0
- package/esm/supervisor/fan-out.mjs.map +1 -0
- package/esm/supervisor/index.d.mts +5 -3
- package/esm/supervisor/index.mjs +3 -1
- package/esm/supervisor/router-factory.d.mts +110 -0
- package/esm/supervisor/router-factory.d.mts.map +1 -0
- package/esm/supervisor/router-factory.mjs +141 -0
- package/esm/supervisor/router-factory.mjs.map +1 -0
- package/esm/supervisor/router-prompt.d.mts +1 -1
- package/esm/supervisor/snapshot.d.mts +4 -10
- package/esm/supervisor/snapshot.d.mts.map +1 -1
- package/esm/supervisor/snapshot.mjs +8 -16
- package/esm/supervisor/snapshot.mjs.map +1 -1
- package/esm/supervisor/supervisor.mjs +1 -0
- package/esm/supervisor/supervisor.mjs.map +1 -1
- package/esm/system-prompt/index.mjs +6 -0
- package/esm/system-prompt/system-prompt.d.mts +51 -3
- package/esm/system-prompt/system-prompt.d.mts.map +1 -1
- package/esm/system-prompt/system-prompt.mjs +52 -6
- package/esm/system-prompt/system-prompt.mjs.map +1 -1
- package/esm/testing/matcher-logic.d.mts +76 -0
- package/esm/testing/matcher-logic.d.mts.map +1 -0
- package/esm/testing/matcher-logic.mjs +144 -0
- package/esm/testing/matcher-logic.mjs.map +1 -0
- package/esm/testing/matchers.d.mts +48 -0
- package/esm/testing/matchers.d.mts.map +1 -0
- package/esm/testing/matchers.mjs +37 -0
- package/esm/testing/matchers.mjs.map +1 -0
- package/esm/testing/register-lazy.d.mts +20 -0
- package/esm/testing/register-lazy.d.mts.map +1 -0
- package/esm/testing/register-lazy.mjs +24 -0
- package/esm/testing/register-lazy.mjs.map +1 -0
- package/esm/tool/executable-as-tool.d.mts +87 -0
- package/esm/tool/executable-as-tool.d.mts.map +1 -0
- package/esm/tool/executable-as-tool.mjs +81 -0
- package/esm/tool/executable-as-tool.mjs.map +1 -0
- package/esm/tool/index.d.mts +2 -1
- package/esm/tool/index.mjs +1 -0
- package/esm/workflow/as-tool.mjs +0 -6
- package/esm/workflow/as-tool.mjs.map +1 -1
- package/esm/workflow/engine.mjs +2 -2
- package/esm/workflow/snapshot.mjs +13 -7
- package/esm/workflow/snapshot.mjs.map +1 -1
- package/esm/workflow/step-runner.mjs +1 -1
- package/esm/workflow/workflow.mjs +1 -0
- package/esm/workflow/workflow.mjs.map +1 -1
- package/llms-full.txt +947 -42
- package/llms.txt +13 -8
- package/package.json +3 -3
- package/skills/README.md +25 -5
- package/skills/ai-basics/SKILL.md +18 -7
- package/skills/ai-dx-helpers/SKILL.md +180 -0
- package/skills/attach-ai-middleware/SKILL.md +32 -3
- package/skills/handle-ai-errors/SKILL.md +20 -6
- package/skills/manage-ai-stores/SKILL.md +127 -0
- package/skills/persist-ai-data/SKILL.md +21 -10
- package/skills/pick-ai-provider/SKILL.md +46 -12
- package/skills/run-ai-agent/SKILL.md +51 -2
- package/skills/run-orchestrator/SKILL.md +198 -0
- package/skills/run-planner/SKILL.md +68 -0
- package/skills/run-supervisor/SKILL.md +47 -2
- package/skills/use-ai-memory/SKILL.md +124 -0
- package/skills/write-system-prompt/SKILL.md +14 -1
package/llms-full.txt
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
name: ai-basics
|
|
11
|
-
description: 'Start with @warlock.js/ai — provider-agnostic core for agents / tools / workflows / supervisors. 4-primitive ladder (agent → workflow → supervisor → orchestrator
|
|
11
|
+
description: 'Start with @warlock.js/ai — provider-agnostic core for agents / tools / workflows / supervisors / orchestrators. 4-primitive ladder (agent → workflow → supervisor → orchestrator, all shipped) plus planner, memory, stores, DX helpers, and the optional @warlock.js/ai-panoptic observability sidecar. Every primitive returns {data, error, usage, report}. Triggers: `ai.agent`, `ai.tool`, `ai.workflow`, `ai.supervisor`, `ai.orchestrator`, `ai.planner`, `ai.memory`, `ai.systemPrompt`, `ExecuteResult`, `BaseReport`, `AIError`, `panoptic`; ''which AI primitive do I use'', ''what is warlock ai'', ''pick an AI skill'', ''how do I observe / trace AI runs''; typical import `import { ai } from "@warlock.js/ai"`. Skip: agent details — `@warlock.js/ai/run-ai-agent/SKILL.md`; competing libs `langchain`, `llamaindex`, `ai` (Vercel SDK); raw `openai` / `@anthropic-ai/sdk`.'
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
# AI foundations
|
|
@@ -23,10 +23,12 @@ Provider-agnostic core for building AI primitives in TypeScript. Adapters live i
|
|
|
23
23
|
ai.agent() → single task, stateless [shipped]
|
|
24
24
|
ai.workflow() → static predefined steps, resumable [shipped]
|
|
25
25
|
ai.supervisor() → multi-agent dynamic routing, resumable [shipped]
|
|
26
|
-
ai.orchestrator() →
|
|
26
|
+
ai.orchestrator() → durable session — state/history/resume [shipped]
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
Each primitive is an escape hatch to the next level of complexity. Users start low, graduate upward only when needed. Every primitive returns the same result envelope — canonical destructure `{ data, error, usage, report }` (the shared `BaseResult` guarantees `usage` + optional `error`; each primitive adds `data` + `report`). Workflows and
|
|
29
|
+
Each primitive is an escape hatch to the next level of complexity. Users start low, graduate upward only when needed. Every primitive returns the same result envelope — canonical destructure `{ data, error, usage, report }` (the shared `BaseResult` guarantees `usage` + optional `error`; each primitive adds `data` + `report`). Workflows, supervisors, and orchestrators expose `.asTool()` so an agent can call them inside its tool loop; raw executables also auto-adapt when dropped into an agent's `tools: []`. Compose freely.
|
|
30
|
+
|
|
31
|
+
Beyond the ladder: `ai.planner()` (LLM-generated plans), `ai.memory()` (working + semantic + episodic + procedural recall), `ai.batch()` / `ai.fallbackModel()` / `ai.router()` / `ai.fanOut()` (DX helpers), `agent.eval()` (scoring), and the `ai.checkpoint.*` / `ai.snapshot.*` orchestrator stores.
|
|
30
32
|
|
|
31
33
|
## Foundations
|
|
32
34
|
|
|
@@ -69,12 +71,18 @@ console.log(text, usage.total, report.duration);
|
|
|
69
71
|
| `ai.systemPrompt()` / `ai.persona()` / `ai.instruction()` — composable prompts with placeholders | [`@warlock.js/ai/write-system-prompt/SKILL.md`](@warlock.js/ai/write-system-prompt/SKILL.md) |
|
|
70
72
|
| `ai.workflow({...})` — durable resumable pipelines with steps, routing, retry | [`@warlock.js/ai/run-ai-workflow/SKILL.md`](@warlock.js/ai/run-ai-workflow/SKILL.md) |
|
|
71
73
|
| `ai.supervisor({...})` — multi-intent routing, fan-out, evaluate loops | [`@warlock.js/ai/run-supervisor/SKILL.md`](@warlock.js/ai/run-supervisor/SKILL.md) |
|
|
74
|
+
| `ai.orchestrator({...})` — durable stateful sessions, drift, compaction, resume | [`@warlock.js/ai/run-orchestrator/SKILL.md`](@warlock.js/ai/run-orchestrator/SKILL.md) |
|
|
75
|
+
| `ai.planner({...})` — LLM-generated plans over registered capabilities | [`@warlock.js/ai/run-planner/SKILL.md`](@warlock.js/ai/run-planner/SKILL.md) |
|
|
76
|
+
| `ai.memory({...})` — working + semantic + episodic + procedural recall for agents / sessions | [`@warlock.js/ai/use-ai-memory/SKILL.md`](@warlock.js/ai/use-ai-memory/SKILL.md) |
|
|
77
|
+
| `ai.checkpoint.*` / `ai.snapshot.*` — orchestrator session + run stores | [`@warlock.js/ai/manage-ai-stores/SKILL.md`](@warlock.js/ai/manage-ai-stores/SKILL.md) |
|
|
78
|
+
| DX helpers — `batch` / `fallbackModel` / `eval` + matchers / SLO contracts / `fromFile` | [`@warlock.js/ai/ai-dx-helpers/SKILL.md`](@warlock.js/ai/ai-dx-helpers/SKILL.md) |
|
|
72
79
|
| `sdk.embedder({...})` — text-to-vector for RAG tools, vector ingest | [`@warlock.js/ai/embed-text/SKILL.md`](@warlock.js/ai/embed-text/SKILL.md) |
|
|
73
|
-
| Agent middleware — `budget` / `guardrail` / `semanticCache` + custom hooks | [`@warlock.js/ai/attach-ai-middleware/SKILL.md`](@warlock.js/ai/attach-ai-middleware/SKILL.md) |
|
|
80
|
+
| Agent + supervisor middleware — `budget` / `guardrail` / `semanticCache` + custom hooks | [`@warlock.js/ai/attach-ai-middleware/SKILL.md`](@warlock.js/ai/attach-ai-middleware/SKILL.md) |
|
|
74
81
|
| Snapshot resume + semantic cache via `@warlock.js/cache` | [`@warlock.js/ai/persist-ai-data/SKILL.md`](@warlock.js/ai/persist-ai-data/SKILL.md) |
|
|
75
82
|
| Configuring framework logging | [`@warlock.js/ai/log-ai-calls/SKILL.md`](@warlock.js/ai/log-ai-calls/SKILL.md) |
|
|
76
|
-
| `AIError` hierarchy, `error.code` / `error.category`, retry patterns | [`@warlock.js/ai/handle-ai-errors/SKILL.md`](@warlock.js/ai/handle-ai-errors/SKILL.md) |
|
|
77
|
-
|
|
|
83
|
+
| `AIError` hierarchy, `error.code` / `error.category`, retry patterns (incl. `ORCHESTRATOR_*` / `PLANNER_*` families) | [`@warlock.js/ai/handle-ai-errors/SKILL.md`](@warlock.js/ai/handle-ai-errors/SKILL.md) |
|
|
84
|
+
| Provider adapters + cost truth (pricing / cache + reasoning tokens / capabilities) | [`@warlock.js/ai/pick-ai-provider/SKILL.md`](@warlock.js/ai/pick-ai-provider/SKILL.md) |
|
|
85
|
+
| Observability — `panoptic()` subscriber, queryable trace store, OTEL / Langfuse / console / file exporters | [`@warlock.js/ai-panoptic/observe-with-panoptic/SKILL.md`](@warlock.js/ai-panoptic/observe-with-panoptic/SKILL.md) |
|
|
78
86
|
|
|
79
87
|
## Package layout
|
|
80
88
|
|
|
@@ -85,8 +93,11 @@ console.log(text, usage.total, report.duration);
|
|
|
85
93
|
@warlock.js/ai-bedrock — AWS Bedrock adapter (Converse API + Titan embeddings)
|
|
86
94
|
@warlock.js/ai-google — Google / Gemini adapter (@google/genai + batch embeddings)
|
|
87
95
|
@warlock.js/ai-ollama — Ollama adapter for local models
|
|
96
|
+
@warlock.js/ai-panoptic — observability sidecar: panoptic() subscriber → collector → queryable trace store + console / file / OTEL / Langfuse exporters
|
|
88
97
|
```
|
|
89
98
|
|
|
99
|
+
The observability sidecar is OPTIONAL and lives in its own package — it subscribes to the report tree every primitive already emits, so you wire `panoptic(...)` once and never touch primitive code. Load [`@warlock.js/ai-panoptic/observe-with-panoptic/SKILL.md`](@warlock.js/ai-panoptic/observe-with-panoptic/SKILL.md) for collecting / querying traces and [`@warlock.js/ai-panoptic/export-traces/SKILL.md`](@warlock.js/ai-panoptic/export-traces/SKILL.md) for OTEL / Langfuse / console / file exporters.
|
|
100
|
+
|
|
90
101
|
Runtime deps: `@warlock.js/cache` (persistence), `@warlock.js/logger` (logging), `@warlock.js/seal` (recommended schema lib).
|
|
91
102
|
|
|
92
103
|
## When NOT to use this skill
|
|
@@ -103,11 +114,195 @@ Runtime deps: `@warlock.js/cache` (persistence), `@warlock.js/logger` (logging),
|
|
|
103
114
|
- `domains/ai/conventions/errors.md` — framework-vs-consumer-app error split
|
|
104
115
|
|
|
105
116
|
|
|
117
|
+
## ai-dx-helpers `@warlock.js/ai/ai-dx-helpers/SKILL.md`
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
name: ai-dx-helpers
|
|
121
|
+
description: 'Developer-experience helpers across @warlock.js/ai — ai.batch (fan-out an executable over a dataset w/ concurrency + per-item retry), ai.fallbackModel (ordered model failover), agent.eval + ai.eval scorers + Vitest matchers (registerAiMatchers / toRouteTo / toConverge / toPassStep / toOutputShape) + ai.mockRouter, SLO/cost budget contracts (ai.middleware.budget({contract}) + readBudgetFallbackSignal), supervisor-level middleware, ai.systemPrompt.fromFile, and auto-adapt executables in tools:[]. Triggers: `ai.batch`, `BatchResult`, `ai.fallbackModel`, `FallbackModelContract`, `agent.eval`, `ai.eval`, `EvalReport`, `EvalScorer`, `ai.eval.judge`, `registerAiMatchers`, `toRouteTo`, `toConverge`, `toPassStep`, `toOutputShape`, `ai.mockRouter`, `MockSDK`, `mockAgent`, `budget({contract})`, `BudgetContract`, `maxLatencyMs`, `onViolation`, `readBudgetFallbackSignal`, `supervisor middleware`, `systemPrompt.fromFile`; ''run an agent over a list'', ''fail over to a backup model'', ''evaluate / score an agent'', ''SLO budget'', ''test a supervisor without an LLM'', ''prompt from a file''; typical import `import { ai } from "@warlock.js/ai"`. Skip: core agent lifecycle — `@warlock.js/ai/run-ai-agent/SKILL.md`; the budget/guardrail/semanticCache basics — `@warlock.js/ai/attach-ai-middleware/SKILL.md`; competing libs `promptfoo`, `langsmith`.'
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
# DX helpers — batch, fallback, eval, SLO, supervisor middleware
|
|
125
|
+
|
|
126
|
+
A grab-bag of additive 4.3.0 helpers. Each is independent — load the section you need.
|
|
127
|
+
|
|
128
|
+
## `ai.batch(executable, items, options?)` — fan-out a dataset
|
|
129
|
+
|
|
130
|
+
Runs the SAME executable (agent / workflow / supervisor / tool — anything `ExecutableContract`) N times, once per item, with bounded concurrency and per-item retry. Aggregates into the unified `ExecuteResult` envelope so a batch slots into cost dashboards exactly like a single run.
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
const result = await ai.batch(summarizer, articles, {
|
|
134
|
+
concurrency: 4, // default = items.length (all at once); <=0 → serial
|
|
135
|
+
retry: { attempts: 3, backoff: "exponential" }, // workflow RetryConfig, applied per item
|
|
136
|
+
onItem: (item) => log.info("batch", "item", "settled", { index: item.index }),
|
|
137
|
+
signal: AbortSignal.timeout(120_000),
|
|
138
|
+
sessionId: "ingest-2026-06-19", // lineage onto every child report
|
|
139
|
+
name: "summarize-articles",
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
console.log(`${result.report.succeeded}/${result.report.total} ok`);
|
|
143
|
+
console.log(`${result.usage.total} tokens total`);
|
|
144
|
+
|
|
145
|
+
for (const item of result.items) {
|
|
146
|
+
if (item.status === "completed") console.log(item.index, item.result?.data);
|
|
147
|
+
else console.warn(item.index, item.error?.code, "after", item.attempts, "attempts");
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Isolation.** Items are independent — one item's failure (after its retries) never cancels a sibling, and **the batch never rejects as a whole** (`result.error` stays undefined). Failures live on each `BatchItemResult` (`status: "completed" | "failed" | "cancelled"`, `error`, `attempts`). `result.data` is the positional array of successful items' `.data` with `undefined` in failed/cancelled slots. Usage rolls up bottom-up (batch has zero own cost); each item's report attaches under `report.children[]` in original order. An `onItem` throw is swallowed — a progress hook never breaks the batch.
|
|
152
|
+
|
|
153
|
+
## `ai.fallbackModel(models, options?)` — ordered model failover
|
|
154
|
+
|
|
155
|
+
A drop-in `ModelContract` that wraps an ordered list and advances to the next model only on a **transient** provider error.
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
const model = ai.fallbackModel([
|
|
159
|
+
ai.openai.model({ name: "gpt-4o" }),
|
|
160
|
+
ai.anthropic.model({ name: "claude-3-5-sonnet" }),
|
|
161
|
+
]);
|
|
162
|
+
|
|
163
|
+
const agent = ai.agent({ model }); // hand it anywhere a model goes
|
|
164
|
+
|
|
165
|
+
// custom retry predicate or code list:
|
|
166
|
+
ai.fallbackModel([primary, backup], { retryOn: ["PROVIDER_RATE_LIMIT", "PROVIDER_TIMEOUT"] });
|
|
167
|
+
ai.fallbackModel([primary, backup], { retryOn: (error) => error instanceof ProviderError });
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Default retryable codes: `PROVIDER_RATE_LIMIT`, `PROVIDER_TIMEOUT`, `PROVIDER_ERROR`. Auth / invalid-request / context-length / content-filter re-throw immediately (they'd fail identically downstream — retrying only burns budget). Identity/capabilities/pricing front the primary model. Usage aggregates across attempted models. Inspect `model.lastAttempts` for the failed models of the most recent call.
|
|
171
|
+
|
|
172
|
+
**Streaming caveat:** `stream()` can only fail over while no chunk has been emitted yet — once the first `delta` / `tool-call` reaches the consumer, a mid-stream failure propagates instead of restarting. It advances *instantly* (no backoff) — pair with a backoff middleware if you want delay.
|
|
173
|
+
|
|
174
|
+
## `agent.eval(options)` + `ai.eval.*` scorers — evaluate an agent
|
|
175
|
+
|
|
176
|
+
Run a suite of cases through `agent.execute()` and score each.
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
const report = await myAgent.eval({
|
|
180
|
+
cases: [
|
|
181
|
+
{ name: "capital", input: "Capital of Egypt?", expected: "Cairo" },
|
|
182
|
+
{ name: "tone", input: "Comfort an upset user." }, // judge-scored
|
|
183
|
+
],
|
|
184
|
+
scorers: [ai.eval.contains()], // default scorers for cases w/o their own
|
|
185
|
+
judge: { agent: judgeAgent, rubric: "Score 1.0 only if empathetic." }, // LLM-as-judge fallback
|
|
186
|
+
passThreshold: 0.5, // default
|
|
187
|
+
onFailure: (caseResult) => snapshot(caseResult),
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
expect(report.passed).toBe(true); // true only when EVERY case passed
|
|
191
|
+
report.passRate; report.meanScore; report.cases; // drill-down
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Built-in scorers on `ai.eval.*`: `exact()` (trimmed, case-insensitive; structured compared by canonical JSON), `contains()` (substring), `predicate(fn)` (arbitrary boolean assertion), `judge(config)` (LLM-as-judge). Scorer precedence per case: the case's own `scorers` → suite `scorers` → synthesized judge. A case with NONE throws at author time. A case passes only when the agent did not error AND every scorer passed.
|
|
195
|
+
|
|
196
|
+
## Vitest matchers + `ai.mockRouter` — test report trees
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
import { registerAiMatchers } from "@warlock.js/ai";
|
|
200
|
+
registerAiMatchers(); // once per test file (idempotent)
|
|
201
|
+
|
|
202
|
+
expect(await supervisor.execute(input)).toRouteTo("critic"); // dispatched the named intent
|
|
203
|
+
expect(await supervisor.execute(input)).toConverge(); // terminated cleanly on own decision
|
|
204
|
+
expect(await workflow.execute(input)).toPassStep("draft"); // named step completed
|
|
205
|
+
expect(await agent.execute(input, { output: schema })).toOutputShape(schema); // data validates
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
The pure verdict functions (`matchConverge`, `matchOutputShape`, `matchPassStep`, `matchRouteTo`) and `AiMatchers` ship eagerly with no `vitest` coupling; only `registerAiMatchers` lazily imports `vitest` (a devDependency), so importing `@warlock.js/ai` in production never pulls in `vitest`.
|
|
209
|
+
|
|
210
|
+
`ai.mockRouter(decisions, options?)` builds a deterministic `route` callback that replays a canned sequence — one decision per supervisor iteration — for testing supervisors without an LLM router:
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
import { END } from "@warlock.js/ai";
|
|
214
|
+
|
|
215
|
+
ai.supervisor({
|
|
216
|
+
name: "draft-then-review",
|
|
217
|
+
intents: { writer, critic },
|
|
218
|
+
route: ai.mockRouter(["writer", "critic", END]),
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// branch on state, repeat the last decision until done:
|
|
222
|
+
ai.mockRouter(["research", (ctx) => (ctx.state.summary ? END : "research")], { onExhausted: "repeat" });
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
A decision is a literal `Next` (intent name / fan-out array / `END`) or a predicate over the live `RouteContext`. On exhaustion: `"end"` (default — terminate), `"throw"` (test failure), `"repeat"` (replay last). For a scripted LLM, use `MockSDK` (script the model output) and `mockAgent({ name, responses })` for fixed-response capabilities.
|
|
226
|
+
|
|
227
|
+
## SLO / cost budget contracts — `ai.middleware.budget({ contract })`
|
|
228
|
+
|
|
229
|
+
On top of the legacy `maxTokens` / `maxCostUSD` caps, declare a run-level SLO as data, with one global reaction:
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
const guard = ai.middleware.budget({
|
|
233
|
+
pricing: { "gpt-4o": { inputPer1K: 0.005, outputPer1K: 0.015 } },
|
|
234
|
+
contract: {
|
|
235
|
+
maxCostUSD: 0.05,
|
|
236
|
+
maxLatencyMs: 8_000, // wall-clock from first execute.before to each trip.after
|
|
237
|
+
maxTokens: 40_000,
|
|
238
|
+
onViolation: "fallback", // "abort" (default) hard-stops; "fallback" records a signal + continues
|
|
239
|
+
fallback: (violation) => routeToCheaperModel(violation.dimension),
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Every clause is optional (a contract with no caps is inert). `onViolation: "abort"` throws `BudgetExceededError` at the next trip boundary; `"fallback"` does NOT abort — it records a typed `BudgetContractViolation` and fires `fallback`, letting the run continue (the middleware can't itself swap models). A latency breach has no `BudgetUnit` — its numbers surface via the error's `context.dimension`. `maxCostUSD` still needs a `pricing` entry for the running model or it degrades silently.
|
|
245
|
+
|
|
246
|
+
Read a recorded fallback signal in an outer middleware's `execute.after`:
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
import { readBudgetFallbackSignal } from "@warlock.js/ai";
|
|
250
|
+
|
|
251
|
+
const signal = readBudgetFallbackSignal(ctx.state); // pass the middleware name as 2nd arg if non-default
|
|
252
|
+
if (signal?.dimension === "cost") await rerunOnCheaperModel();
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Supervisor-level middleware
|
|
256
|
+
|
|
257
|
+
The `middleware: AgentMiddleware[]` array on `ai.supervisor({...})` fires each middleware's optional `supervisor` hook map (`before` / `after` / `onError`) ONCE around the entire `execute()` / `stream()` / `resume()` run — the supervisor-level peer of an agent's `execute`-level middleware.
|
|
258
|
+
|
|
259
|
+
```ts
|
|
260
|
+
ai.supervisor({ name: "support", router, intents, middleware: [auditTrail] });
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Same onion semantics as the agent pipeline: `before` runs top-down (return a `SupervisorResult` to short-circuit, throw to abort), `after` / `onError` run bottom-up. A middleware without a `supervisor` hook map is skipped — so the same builtin objects (budget, guardrail, …) can be registered on agents AND on the supervisor, each declaring whichever level applies. Each needs a unique `name` (its `ctx.state` namespace).
|
|
264
|
+
|
|
265
|
+
## `ai.systemPrompt.fromFile(path)`
|
|
266
|
+
|
|
267
|
+
Build a system prompt by reading a file **once, synchronously, at construction** — the file's UTF-8 contents seed one instruction block, so placeholders inside resolve at `resolve()` time and the result forks with further `.persona()` / `.instruction()` calls.
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
const prompt = ai.systemPrompt.fromFile("./prompts/support-agent.md");
|
|
271
|
+
const localized = prompt.instruction("Respond in {{language|English}}.");
|
|
272
|
+
localized.resolve({ language: "Arabic" });
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
One-shot by design (never re-read on `resolve()`). Throws `InvalidRequestError` when the file can't be read — a typo in the path fails loudly at construction instead of producing an empty prompt. `SystemPrompt.fromFile(path)` and `ai.systemPrompt.fromFile(path)` are identical.
|
|
276
|
+
|
|
277
|
+
## Auto-adapt executables in `tools: []`
|
|
278
|
+
|
|
279
|
+
An agent's `tools` array accepts a raw executable primitive (`AgentContract` / `WorkflowInstance` / `SupervisorContract` / orchestrator) directly — it is auto-adapted into a `ToolContract` at factory time. The tool manifest is derived from the executable's `name` + `description` + (optional) `inputSchema`; dispatch flows through its `execute()`.
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
const support = ai.supervisor({ name: "support", inputSchema: v.object({ message: v.string() }), router, intents });
|
|
283
|
+
|
|
284
|
+
const concierge = ai.agent({
|
|
285
|
+
model,
|
|
286
|
+
tools: [support, billingWorkflow, lookupTool], // no .asTool() needed
|
|
287
|
+
});
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
`.asTool()` still works and takes precedence when you need a custom name / schema per use. For a supervisor/orchestrator, declaring `inputSchema` on the config is what lets it drop straight into `tools: []`.
|
|
291
|
+
|
|
292
|
+
## See also
|
|
293
|
+
|
|
294
|
+
- [`@warlock.js/ai/run-ai-agent/SKILL.md`](@warlock.js/ai/run-ai-agent/SKILL.md) — `agent.eval`, `tools: []`, the agent the helpers wrap
|
|
295
|
+
- [`@warlock.js/ai/run-supervisor/SKILL.md`](@warlock.js/ai/run-supervisor/SKILL.md) — `ai.router` / `ai.fanOut` / supervisor `middleware` / `mockRouter`
|
|
296
|
+
- [`@warlock.js/ai/attach-ai-middleware/SKILL.md`](@warlock.js/ai/attach-ai-middleware/SKILL.md) — budget / guardrail / semanticCache basics
|
|
297
|
+
- [`@warlock.js/ai/write-system-prompt/SKILL.md`](@warlock.js/ai/write-system-prompt/SKILL.md) — `systemPrompt.fromFile` in context
|
|
298
|
+
- [`@warlock.js/ai/pick-ai-provider/SKILL.md`](@warlock.js/ai/pick-ai-provider/SKILL.md) — `fallbackModel` wraps these adapters; cost-truth tokens
|
|
299
|
+
|
|
300
|
+
|
|
106
301
|
## attach-ai-middleware `@warlock.js/ai/attach-ai-middleware/SKILL.md`
|
|
107
302
|
|
|
108
303
|
---
|
|
109
304
|
name: attach-ai-middleware
|
|
110
|
-
description: 'Wire agent middleware — ai.middleware.budget (token / USD caps), ai.middleware.guardrail (pre / post content checks), ai.middleware.semanticCache (exact + vector cache), plus authoring custom hooks (execute / trip / tool). Triggers: `ai.middleware.budget`, `ai.middleware.guardrail`, `ai.middleware.semanticCache`, `ai.middleware.compose`, `ai.middleware.forTool`, `AgentMiddleware`, `BudgetExceededError`, `GuardrailViolationError`; ''cap token cost'', ''block pii in prompts'', ''semantic cache before LLM'', ''write custom hook''; typical import `import { ai } from "@warlock.js/ai"`. Skip: agent lifecycle — `@warlock.js/ai/run-ai-agent/SKILL.md`; cache drivers — `@warlock.js/ai/persist-ai-data/SKILL.md`; competing libs `langchain` callbacks.'
|
|
305
|
+
description: 'Wire agent middleware — ai.middleware.budget (token / USD caps + SLO/cost contract w/ maxLatencyMs + onViolation fallback), ai.middleware.guardrail (pre / post content checks), ai.middleware.semanticCache (exact + vector cache), supervisor-level middleware, plus authoring custom hooks (execute / trip / tool). Triggers: `ai.middleware.budget`, `ai.middleware.guardrail`, `ai.middleware.semanticCache`, `ai.middleware.compose`, `ai.middleware.forTool`, `AgentMiddleware`, `BudgetExceededError`, `GuardrailViolationError`, `BudgetContract`, `maxLatencyMs`, `onViolation`, `readBudgetFallbackSignal`, `supervisor middleware`; ''cap token cost'', ''SLO budget'', ''block pii in prompts'', ''semantic cache before LLM'', ''supervisor-level middleware'', ''write custom hook''; typical import `import { ai } from "@warlock.js/ai"`. Skip: agent lifecycle — `@warlock.js/ai/run-ai-agent/SKILL.md`; cache drivers — `@warlock.js/ai/persist-ai-data/SKILL.md`; competing libs `langchain` callbacks.'
|
|
111
306
|
---
|
|
112
307
|
|
|
113
308
|
# Middleware — agent-level pipeline
|
|
@@ -160,6 +355,25 @@ Breach → `BudgetExceededError` on `result.error`. Inspect `error.unit` (`"toke
|
|
|
160
355
|
|
|
161
356
|
USD only fires when both `maxCostUSD` AND a matching `pricing[modelName]` entry exist.
|
|
162
357
|
|
|
358
|
+
### SLO / cost contract — `budget({ contract })`
|
|
359
|
+
|
|
360
|
+
On top of the legacy caps, declare a run-level SLO as data with one global reaction. Adds a wall-clock `maxLatencyMs` dimension:
|
|
361
|
+
|
|
362
|
+
```ts
|
|
363
|
+
ai.middleware.budget({
|
|
364
|
+
pricing: { "gpt-4o": { inputPer1K: 0.005, outputPer1K: 0.015 } },
|
|
365
|
+
contract: {
|
|
366
|
+
maxTokens: 40_000,
|
|
367
|
+
maxCostUSD: 0.05,
|
|
368
|
+
maxLatencyMs: 8_000, // wall-clock, first execute.before → each trip.after
|
|
369
|
+
onViolation: "fallback", // "abort" (default) throws; "fallback" records a signal + continues
|
|
370
|
+
fallback: (violation) => routeToCheaperModel(violation.dimension),
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Every clause optional (no caps = inert). `"fallback"` can't itself swap models — it records a typed `BudgetContractViolation` and fires `fallback`; an outer layer reads it via `readBudgetFallbackSignal(ctx.state)` and degrades the next run. A latency breach has no `BudgetUnit` — read its numbers from the thrown error's `context.dimension`. Full coverage in [`@warlock.js/ai/ai-dx-helpers/SKILL.md`](@warlock.js/ai/ai-dx-helpers/SKILL.md).
|
|
376
|
+
|
|
163
377
|
## `ai.middleware.guardrail(options)`
|
|
164
378
|
|
|
165
379
|
Pre / post content checks.
|
|
@@ -278,11 +492,21 @@ const scoped = ai.middleware.forTool(["paid_api", "expensive_db"], toolRateLimit
|
|
|
278
492
|
- **`name` must be unique** across an agent's middleware array.
|
|
279
493
|
- **Middleware state does NOT cross `agent.execute()` boundaries.** One execute → one fresh `ctx.state`.
|
|
280
494
|
|
|
281
|
-
##
|
|
495
|
+
## Supervisor-level middleware
|
|
496
|
+
|
|
497
|
+
`ai.supervisor({ middleware: [...] })` fires each middleware's optional `supervisor` hook map (`before` / `after` / `onError`) ONCE around the whole `execute()` / `stream()` / `resume()` run — the supervisor-level peer of an agent's `execute`-level middleware:
|
|
498
|
+
|
|
499
|
+
```ts
|
|
500
|
+
ai.supervisor({ name: "support", router, intents, middleware: [auditTrail] });
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
Same onion semantics: `before` top-down (return a `SupervisorResult` to short-circuit, throw to abort), `after` / `onError` bottom-up. A middleware WITHOUT a `supervisor` hook map is skipped — so the SAME builtin objects (budget, guardrail, …) can be registered on agents AND on the supervisor, each declaring whichever level applies. Each needs a unique `name`. See [`@warlock.js/ai/ai-dx-helpers/SKILL.md`](@warlock.js/ai/ai-dx-helpers/SKILL.md).
|
|
504
|
+
|
|
505
|
+
## Workflow + middleware — what works today
|
|
282
506
|
|
|
283
507
|
- Inside a workflow step with `agent: myAgent` — the agent's own middleware fires normally.
|
|
284
508
|
- `workflow.asTool()` called from an agent — the calling agent's `tool`-level middleware wraps the workflow.
|
|
285
|
-
- Step-level / workflow-level
|
|
509
|
+
- Step-level / workflow-level middleware does NOT exist yet (supervisor-level DOES — see above).
|
|
286
510
|
|
|
287
511
|
## See also
|
|
288
512
|
|
|
@@ -594,7 +818,7 @@ No built-in vector store. Bring your own (pgvector / Qdrant / Pinecone / Chroma
|
|
|
594
818
|
|
|
595
819
|
---
|
|
596
820
|
name: handle-ai-errors
|
|
597
|
-
description: 'Typed AIError hierarchy with stable code strings + coarse category for retry-policy dispatch. execute() never throws — errors surface via result.error. Triggers: `AIError`, `ProviderRateLimitError`, `ProviderAuthError`, `ContextLengthExceededError`, `ContentFilterError`, `SchemaValidationError`, `ToolExecutionError`, `WorkflowDriftError`, `BudgetExceededError`, `GuardrailViolationError`, `error.code`, `error.category`; ''handle ai error'', ''retry on rate limit'', ''branch on error code'', ''build fallback ladder''; typical import `import { AIError } from "@warlock.js/ai"`. Skip: log surfacing — `@warlock.js/ai/log-ai-calls/SKILL.md`; native `try / catch` on raw `openai`.'
|
|
821
|
+
description: 'Typed AIError hierarchy with stable code strings + coarse category for retry-policy dispatch. execute() never throws — errors surface via result.error (the sole exception: OrchestratorConfigError throws at construction). Triggers: `AIError`, `ProviderRateLimitError`, `ProviderAuthError`, `ContextLengthExceededError`, `ContentFilterError`, `SchemaValidationError`, `ToolExecutionError`, `WorkflowDriftError`, `SupervisorDriftError`, `SupervisorFailedError`, `SupervisorRoutingError`, `OrchestratorFailedError`, `OrchestratorDriftError`, `OrchestratorConfigError`, `OrchestratorCancelledError`, `PlannerFailedError`, `PlannerPlanInvalidError`, `PlannerCancelledError`, `BudgetExceededError`, `GuardrailViolationError`, `error.code`, `error.category`; ''handle ai error'', ''retry on rate limit'', ''branch on error code'', ''ORCHESTRATOR_DRIFT'', ''PLANNER_PLAN_INVALID'', ''build fallback ladder''; typical import `import { AIError } from "@warlock.js/ai"`. Skip: log surfacing — `@warlock.js/ai/log-ai-calls/SKILL.md`; native `try / catch` on raw `openai`.'
|
|
598
822
|
---
|
|
599
823
|
|
|
600
824
|
# Typed errors — `AIError` hierarchy
|
|
@@ -685,9 +909,16 @@ AIError (base — code, category, message, cause?, context?)
|
|
|
685
909
|
│ └── RoutingError WORKFLOW_INVALID_GOTO { stepName, targetName }
|
|
686
910
|
├── SupervisorFailedError SUPERVISOR_FAILED (base + authoring/runtime)
|
|
687
911
|
│ ├── MaxIterationsError SUPERVISOR_MAX_ITERATIONS { maxIterations }
|
|
688
|
-
│ ├── SupervisorRoutingError SUPERVISOR_INVALID_ROUTE
|
|
912
|
+
│ ├── SupervisorRoutingError SUPERVISOR_INVALID_ROUTE { returned, availableKeys }
|
|
689
913
|
│ ├── SupervisorCancelledError SUPERVISOR_CANCELLED { cancelledAt, reason }
|
|
690
914
|
│ └── SupervisorDriftError SUPERVISOR_DRIFT { savedSignature, currentSignature, runId }
|
|
915
|
+
├── OrchestratorFailedError ORCHESTRATOR_FAILED (base — durable-session turn)
|
|
916
|
+
│ ├── OrchestratorConfigError ORCHESTRATOR_CONFIG authoring-time, THROWS (validation) — bad ai.orchestrator(config)
|
|
917
|
+
│ ├── OrchestratorDriftError ORCHESTRATOR_DRIFT { savedSignature, currentSignature, sessionId } (drift) — checkpoint ≠ definition
|
|
918
|
+
│ └── OrchestratorCancelledError ORCHESTRATOR_CANCELLED { cancelledAt, sessionId, reason } (cancelled) — mid-turn abort
|
|
919
|
+
├── PlannerFailedError PLANNER_FAILED (base — plan generation/execution)
|
|
920
|
+
│ ├── PlannerPlanInvalidError PLANNER_PLAN_INVALID (schema) — LLM plan unparseable or names an unregistered capability
|
|
921
|
+
│ └── PlannerCancelledError PLANNER_CANCELLED { cancelledAt, reason } (cancelled) — mid-plan abort
|
|
691
922
|
├── ProviderError PROVIDER_ERROR (base + catch-all)
|
|
692
923
|
│ ├── ProviderRateLimitError PROVIDER_RATE_LIMIT { retryAfter? } — transient
|
|
693
924
|
│ ├── QuotaExceededError PROVIDER_QUOTA_EXCEEDED — NOT retryable (billing cap)
|
|
@@ -700,7 +931,9 @@ AIError (base — code, category, message, cause?, context?)
|
|
|
700
931
|
└── GuardrailViolationError GUARDRAIL_VIOLATION { phase, reason } — from ai.middleware.guardrail
|
|
701
932
|
```
|
|
702
933
|
|
|
703
|
-
> `SupervisorFailedError` doubles as the base for the supervisor family **and** the authoring-time error for bad config (e.g. `route` + `router` both set). It carries extra `SUPERVISOR_INTENT_*` / `SUPERVISOR_DISPATCH_CYCLE` codes for specific intent-validation failures.
|
|
934
|
+
> `SupervisorFailedError` doubles as the base for the supervisor family **and** the authoring-time error for bad config (e.g. `route` + `router` both set). It carries extra `SUPERVISOR_INTENT_*` / `SUPERVISOR_DISPATCH_CYCLE` codes for specific intent-validation failures (`SUPERVISOR_INTENT_DESCRIPTION_REQUIRED`, `SUPERVISOR_INTENT_MIXED_DISPATCH`, `SUPERVISOR_INTENT_STREAM_AND_OUTPUT`, `SUPERVISOR_INTENT_STREAM_TO_REQUIRED`, `SUPERVISOR_INTENT_STREAM_ON_WORKFLOW`, `SUPERVISOR_DISPATCH_CYCLE`).
|
|
935
|
+
|
|
936
|
+
> **Orchestrator + planner families** anchor on `OrchestratorFailedError` / `PlannerFailedError` (the `ORCHESTRATOR_*` / `PLANNER_*` code families). Both follow the never-throw rule: `orchestrator.execute()` / `resume()` / `command()` and `planner.execute()` surface failures on `result.error` with `report.status` `"failed"` / `"cancelled"`. The **one exception** is `OrchestratorConfigError` (`ORCHESTRATOR_CONFIG`) — an authoring-time misconfiguration (`iterate: true` with no resolvable `snapshotStore`, no `checkpointStore`, both `route` and `router` set, `initialAgent` absent from `intents`) that **throws synchronously at construction** so a bad definition fails fast at boot. Child-execution errors (agent / tool / provider / supervisor / workflow) flow through both primitives **unchanged** — captured on the step / turn report and surfaced on `result.error` directly, never re-wrapped into a `PLANNER_*` / `ORCHESTRATOR_*` code. On an `iterate: true` mid-turn cancel the underlying `SupervisorCancelledError` rides on `OrchestratorCancelledError.cause`.
|
|
704
937
|
|
|
705
938
|
## Error fields
|
|
706
939
|
|
|
@@ -725,9 +958,11 @@ Typed fields (`retryAfter`, `toolName`, `issues`, `stepName`, …) are first-cla
|
|
|
725
958
|
| `ContentFilterError` | Usually **no** — the prompt itself is the issue |
|
|
726
959
|
| `SchemaValidationError` | Use agent `repair: { maxAttempts }` instead |
|
|
727
960
|
| `ToolExecutionError` | Depends on `cause` |
|
|
728
|
-
| `WorkflowDriftError` | **No** — manual migration or `force: true` |
|
|
729
|
-
| `WorkflowCancelledError` | **No** — caller-driven cancel |
|
|
730
|
-
| `MaxStepsExceededError` / `RoutingError` | **No** — programmer error |
|
|
961
|
+
| `WorkflowDriftError` / `SupervisorDriftError` / `OrchestratorDriftError` | **No** — manual migration or `force: true` |
|
|
962
|
+
| `WorkflowCancelledError` / `SupervisorCancelledError` / `OrchestratorCancelledError` / `PlannerCancelledError` | **No** — caller-driven cancel |
|
|
963
|
+
| `MaxStepsExceededError` / `RoutingError` / `SupervisorRoutingError` | **No** — programmer error |
|
|
964
|
+
| `OrchestratorConfigError` | **No** — authoring-time config bug; thrown at construction |
|
|
965
|
+
| `PlannerPlanInvalidError` | **No** — bad LLM plan / unregistered capability; re-prompt or fix the capability roster |
|
|
731
966
|
| `BudgetExceededError` | **No** — raise the cap, split the workload |
|
|
732
967
|
| `GuardrailViolationError` (`phase: "input"`) | **No** — block / sanitize at product layer |
|
|
733
968
|
| `GuardrailViolationError` (`phase: "output"`) | Sometimes — re-prompt with adjusted system message |
|
|
@@ -783,6 +1018,9 @@ async function runWithFallbacks(input: string) {
|
|
|
783
1018
|
|
|
784
1019
|
- [`@warlock.js/ai/run-ai-agent/SKILL.md`](@warlock.js/ai/run-ai-agent/SKILL.md) — `AgentResult.error`
|
|
785
1020
|
- [`@warlock.js/ai/run-ai-workflow/SKILL.md`](@warlock.js/ai/run-ai-workflow/SKILL.md) — `WorkflowError` subclasses
|
|
1021
|
+
- [`@warlock.js/ai/run-supervisor/SKILL.md`](@warlock.js/ai/run-supervisor/SKILL.md) — `SupervisorFailedError` family + intent-validation codes
|
|
1022
|
+
- [`@warlock.js/ai/run-orchestrator/SKILL.md`](@warlock.js/ai/run-orchestrator/SKILL.md) — `OrchestratorFailedError` family + `ORCHESTRATOR_CONFIG` boot-time throw
|
|
1023
|
+
- [`@warlock.js/ai/run-planner/SKILL.md`](@warlock.js/ai/run-planner/SKILL.md) — `PlannerFailedError` family + `PLANNER_PLAN_INVALID`
|
|
786
1024
|
- [`@warlock.js/ai/define-ai-tool/SKILL.md`](@warlock.js/ai/define-ai-tool/SKILL.md) — `ToolExecutionError` wrapping
|
|
787
1025
|
- [`@warlock.js/ai/log-ai-calls/SKILL.md`](@warlock.js/ai/log-ai-calls/SKILL.md) — error logging
|
|
788
1026
|
- `domains/ai/conventions/errors.md` — framework vs app error convention
|
|
@@ -933,11 +1171,142 @@ See [`@warlock.js/logger/test-logging-code/SKILL.md`](@warlock.js/logger/test-lo
|
|
|
933
1171
|
- [`@warlock.js/ai/handle-ai-errors/SKILL.md`](@warlock.js/ai/handle-ai-errors/SKILL.md) — what lands on the `error` channel
|
|
934
1172
|
|
|
935
1173
|
|
|
1174
|
+
## manage-ai-stores `@warlock.js/ai/manage-ai-stores/SKILL.md`
|
|
1175
|
+
|
|
1176
|
+
---
|
|
1177
|
+
name: manage-ai-stores
|
|
1178
|
+
description: 'Durable orchestrator stores — ai.checkpoint.{memory,pg,redis}() for cross-turn SESSION STATE and ai.snapshot.{memory,pg,redis}() for in-flight SUPERVISOR/WORKFLOW run state. Two distinct contracts (CheckpointStore vs SnapshotStore), dev-owned pg/redis clients (no peer dep), never-auto-migrated schema(), global defaults via ai.config({defaultCheckpointStore, defaultSnapshotStore}). Triggers: `ai.checkpoint`, `ai.snapshot`, `checkpointStore`, `snapshotStore`, `CheckpointStore`, `SnapshotStore`, `CheckpointRecord`, `checkpoint.pg`, `checkpoint.redis`, `snapshot.pg`, `snapshot.redis`, `store.schema()`, `keepSnapshots`, `defaultCheckpointStore`, `defaultSnapshotStore`, `PgClientLike`, `RedisClientLike`; ''persist orchestrator sessions'', ''wire a pg checkpoint store'', ''run the store DDL'', ''checkpoint vs snapshot''; typical import `import { ai } from "@warlock.js/ai"`. Skip: orchestrator lifecycle — `@warlock.js/ai/run-orchestrator/SKILL.md`; cache-backed snapshot resume / semanticCache store — `@warlock.js/ai/persist-ai-data/SKILL.md`; competing libs `temporal`, `inngest`.'
|
|
1179
|
+
---
|
|
1180
|
+
|
|
1181
|
+
# Orchestrator stores — checkpoint vs snapshot
|
|
1182
|
+
|
|
1183
|
+
`ai.orchestrator()` persists through **two distinct stores** with two distinct contracts. Confusing them is the #1 wiring mistake.
|
|
1184
|
+
|
|
1185
|
+
| Store | Contract | Persists | Keyed by | Factories |
|
|
1186
|
+
|---|---|---|---|---|
|
|
1187
|
+
| **checkpoint** | `CheckpointStore` | cross-turn SESSION STATE (one append-only row per settled turn) | `(orchestrator_name, session_id, turn_index)` | `ai.checkpoint.{memory,pg,redis}()` |
|
|
1188
|
+
| **snapshot** | `SnapshotStore` | in-flight internal SUPERVISOR run state (for `iterate: true` mid-turn resume) | `runId` | `ai.snapshot.{memory,pg,redis}()` |
|
|
1189
|
+
|
|
1190
|
+
- A **checkpoint** is what lets `execute()` rehydrate a session across calls — state, `turn_index`, drift `signature`, `version`, `last_route`, compaction progress, lock metadata.
|
|
1191
|
+
- A **snapshot** is what lets a crashed mid-turn `iterate: true` turn resume — it round-trips the existing `SupervisorSnapshot` envelope (the same shape the supervisor's own `snapshotStore` uses).
|
|
1192
|
+
|
|
1193
|
+
`iterate: false` orchestrators need only a `checkpointStore`. `iterate: true` needs **both**.
|
|
1194
|
+
|
|
1195
|
+
## Wiring
|
|
1196
|
+
|
|
1197
|
+
```ts
|
|
1198
|
+
import { ai } from "@warlock.js/ai";
|
|
1199
|
+
|
|
1200
|
+
const orch = ai.orchestrator({
|
|
1201
|
+
name: "support",
|
|
1202
|
+
intents,
|
|
1203
|
+
route,
|
|
1204
|
+
iterate: true,
|
|
1205
|
+
checkpointStore: ai.checkpoint.pg({ client: pgPool }),
|
|
1206
|
+
snapshotStore: ai.snapshot.pg({ client: pgPool }), // a single pg.Pool backs both
|
|
1207
|
+
});
|
|
1208
|
+
```
|
|
1209
|
+
|
|
1210
|
+
### Global defaults
|
|
1211
|
+
|
|
1212
|
+
```ts
|
|
1213
|
+
ai.config({
|
|
1214
|
+
defaultCheckpointStore: ai.checkpoint.memory(),
|
|
1215
|
+
defaultSnapshotStore: ai.snapshot.memory(),
|
|
1216
|
+
});
|
|
1217
|
+
```
|
|
1218
|
+
|
|
1219
|
+
Resolution: explicit `checkpointStore` / `snapshotStore` on the config wins, else the matching `ai.config({ default… })`, else undefined. `iterate: true` with no snapshot store resolvable throws `OrchestratorConfigError` at construction.
|
|
1220
|
+
|
|
1221
|
+
## The three drivers
|
|
1222
|
+
|
|
1223
|
+
| Driver | Client | Durable | Cross-process | Fits |
|
|
1224
|
+
|---|---|---|---|---|
|
|
1225
|
+
| `memory()` | none | ❌ | ❌ | dev / tests / single-process; no resume across restarts |
|
|
1226
|
+
| `pg({ client, table?, ttl? })` | dev-supplied `pg.Pool`/`Client` | ✅ | ✅ | production with Postgres |
|
|
1227
|
+
| `redis({ client, prefix?, ttl? })` | dev-supplied `redis` client | ✅ | ✅ | production with Redis |
|
|
1228
|
+
|
|
1229
|
+
`@warlock.js/ai` takes **NO peer dependency** on `pg` or `redis` — you install the client, build it, and pass it in via `{ client }` (anything matching `PgClientLike` / `RedisClientLike`). The store never opens or closes the connection. A single `pg.Pool` can back the cache, the checkpoint store, and the snapshot store at once.
|
|
1230
|
+
|
|
1231
|
+
```ts
|
|
1232
|
+
import { Pool } from "pg";
|
|
1233
|
+
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
1234
|
+
|
|
1235
|
+
ai.checkpoint.pg({ client: pool, table: "warlock_orchestrator_sessions", ttl: 86_400 });
|
|
1236
|
+
ai.snapshot.pg({ client: pool, table: "warlock_supervisor_snapshots" });
|
|
1237
|
+
|
|
1238
|
+
// redis
|
|
1239
|
+
ai.checkpoint.redis({ client: redisClient, prefix: "warlock:orchestrator", ttl: 86_400 });
|
|
1240
|
+
ai.snapshot.redis({ client: redisClient, prefix: "warlock:snapshot" });
|
|
1241
|
+
```
|
|
1242
|
+
|
|
1243
|
+
Table / prefix names must be safe SQL identifiers (`[A-Za-z_][A-Za-z0-9_]*`) — interpolated into DDL/DML, so anything outside that subset is rejected. Defaults: pg checkpoint table `warlock_orchestrator_sessions`, pg snapshot table `warlock_supervisor_snapshots`, redis prefix `warlock:orchestrator`.
|
|
1244
|
+
|
|
1245
|
+
## Schema is NEVER auto-migrated
|
|
1246
|
+
|
|
1247
|
+
The framework never creates or alters tables. Each pg store exposes `schema()` returning the reference DDL — run it through YOUR migration tool once before use:
|
|
1248
|
+
|
|
1249
|
+
```ts
|
|
1250
|
+
const store = ai.checkpoint.pg({ client: pool });
|
|
1251
|
+
await pool.query(store.schema()); // once, via your migration tooling
|
|
1252
|
+
```
|
|
1253
|
+
|
|
1254
|
+
The memory and redis drivers return an empty `schema()` string (no backing table), so callers can treat `schema()` uniformly.
|
|
1255
|
+
|
|
1256
|
+
## `CheckpointRecord` — the persisted row
|
|
1257
|
+
|
|
1258
|
+
```ts
|
|
1259
|
+
type CheckpointRecord = {
|
|
1260
|
+
orchestrator_name: string; // PK segment 1
|
|
1261
|
+
session_id: string; // PK segment 2
|
|
1262
|
+
turn_index: number; // PK segment 3 — highest is live
|
|
1263
|
+
state: unknown; // post-merge session accumulator (TState)
|
|
1264
|
+
last_route: string | string[] | null;
|
|
1265
|
+
signature: string; // drift fingerprint at write time
|
|
1266
|
+
version: string | null; // config.version tag — metadata only
|
|
1267
|
+
summarized_through: number | null; // exclusive turn index compaction reached
|
|
1268
|
+
lock_acquired_at: string | null; // compaction lock metadata
|
|
1269
|
+
lock_expires_at: string | null;
|
|
1270
|
+
saved_at: string; // ISO write timestamp
|
|
1271
|
+
};
|
|
1272
|
+
```
|
|
1273
|
+
|
|
1274
|
+
Append-only from v1 — `save()` never overwrites a prior `turn_index`. `load(name, sessionId)` returns the latest row (highest `turn_index`), or `undefined` for a session the store has never seen.
|
|
1275
|
+
|
|
1276
|
+
## Store contract methods
|
|
1277
|
+
|
|
1278
|
+
Both stores: `load(...)`, `save(...)`, `delete(...)`, optional `list(...)`, `schema()`.
|
|
1279
|
+
|
|
1280
|
+
- **`list(orchestratorName, prefix?)`** (checkpoint) / **`list(prefix?)`** (snapshot) — enumerate session/run ids for the production boot-drain loop. Optional: stores that can't enumerate omit it.
|
|
1281
|
+
- The orchestrator's **`keepSnapshots`** retention policy lives on the orchestrator config, NOT the store — the orchestrator calls the pg store's `prune()` after a successful `save` when `keepSnapshots` is a finite number; `"all"` skips pruning.
|
|
1282
|
+
|
|
1283
|
+
## Boot-drain pattern
|
|
1284
|
+
|
|
1285
|
+
On startup, resume any session whose last turn was interrupted mid-flight:
|
|
1286
|
+
|
|
1287
|
+
```ts
|
|
1288
|
+
const sessions = await checkpointStore.list?.(orch.name) ?? [];
|
|
1289
|
+
for (const sessionId of sessions) {
|
|
1290
|
+
await orch.resume(sessionId); // null when nothing in flight — harmless
|
|
1291
|
+
}
|
|
1292
|
+
```
|
|
1293
|
+
|
|
1294
|
+
## Distinct from `@warlock.js/cache` snapshot resume
|
|
1295
|
+
|
|
1296
|
+
A bare `ai.supervisor()` / `ai.workflow()` uses a `snapshotStore` for `resume(runId)`. That `SnapshotStore` was promoted from the historical `@warlock.js/cache` `CacheDriver` path. ⚠ The CacheDriver overload is deprecated for one minor — new code wires `ai.snapshot.*` stores. See [`@warlock.js/ai/persist-ai-data/SKILL.md`](@warlock.js/ai/persist-ai-data/SKILL.md) for the supervisor/workflow side and the cache-backed semantic cache.
|
|
1297
|
+
|
|
1298
|
+
## See also
|
|
1299
|
+
|
|
1300
|
+
- [`@warlock.js/ai/run-orchestrator/SKILL.md`](@warlock.js/ai/run-orchestrator/SKILL.md) — the consumer of these stores
|
|
1301
|
+
- [`@warlock.js/ai/persist-ai-data/SKILL.md`](@warlock.js/ai/persist-ai-data/SKILL.md) — supervisor/workflow snapshot resume + the SnapshotStore migration
|
|
1302
|
+
- [`@warlock.js/ai/handle-ai-errors/SKILL.md`](@warlock.js/ai/handle-ai-errors/SKILL.md) — `OrchestratorDriftError` / `OrchestratorConfigError`
|
|
1303
|
+
|
|
1304
|
+
|
|
936
1305
|
## persist-ai-data `@warlock.js/ai/persist-ai-data/SKILL.md`
|
|
937
1306
|
|
|
938
1307
|
---
|
|
939
1308
|
name: persist-ai-data
|
|
940
|
-
description: 'Persistence delegated to @warlock.js/cache — workflow + supervisor snapshot resume via snapshotStore, semantic cache via vector-capable CacheDriver, global
|
|
1309
|
+
description: 'Persistence delegated to @warlock.js/cache — workflow + supervisor snapshot resume via snapshotStore (4.3.0: now a SnapshotStore from ai.snapshot.*, ⚠ moved off raw CacheDriver), semantic cache + memory via vector-capable CacheDriver, global defaults via ai.config({defaultStore}) + ai.config({defaultSnapshotStore}). Covers drift detection + three recovery paths. Triggers: `ai.config`, `defaultStore`, `defaultSnapshotStore`, `snapshotStore`, `ai.snapshot`, `wf.resume`, `supervisor.resume`, `WorkflowSnapshot`, `SupervisorSnapshot`, `WorkflowDriftError`, `SupervisorDriftError`, `force: true`; ''resume a workflow run'', ''configure snapshot store'', ''handle signature drift'', ''wire pg vector cache''; typical import `import { ai } from "@warlock.js/ai"`. Skip: orchestrator checkpoint/snapshot store factories — `@warlock.js/ai/manage-ai-stores/SKILL.md`; cache driver catalog — `@warlock.js/cache/cache-basics/SKILL.md`; competing libs `temporal`, `inngest`.'
|
|
941
1310
|
---
|
|
942
1311
|
|
|
943
1312
|
# Persistence — `@warlock.js/cache` everywhere
|
|
@@ -960,15 +1329,19 @@ description: 'Persistence delegated to @warlock.js/cache — workflow + supervis
|
|
|
960
1329
|
└──────────────┘ └────────────────────┘ └──────────────────┘
|
|
961
1330
|
```
|
|
962
1331
|
|
|
963
|
-
## Resolution order
|
|
1332
|
+
## Resolution order — two separate defaults
|
|
964
1333
|
|
|
965
1334
|
```
|
|
966
|
-
|
|
1335
|
+
// semantic cache + memory (CacheDriver):
|
|
1336
|
+
options.store ?? ai.config({ defaultStore }) ?? undefined
|
|
1337
|
+
|
|
1338
|
+
// supervisor / workflow / orchestrator snapshots (SnapshotStore):
|
|
1339
|
+
options.snapshotStore ?? ai.config({ defaultSnapshotStore }) ?? undefined
|
|
967
1340
|
```
|
|
968
1341
|
|
|
969
|
-
When
|
|
1342
|
+
`defaultStore` (a `CacheDriver`) and `defaultSnapshotStore` (a `SnapshotStore`) are independent — set whichever the consumer needs. When the relevant one is unset:
|
|
970
1343
|
- **Snapshot consumers** silently skip writes and throw on `resume()`.
|
|
971
|
-
- **Semantic cache** throws at construction.
|
|
1344
|
+
- **Semantic cache / memory** throws at construction.
|
|
972
1345
|
|
|
973
1346
|
## `ai.config({ defaultStore })` — set once at boot
|
|
974
1347
|
|
|
@@ -997,26 +1370,32 @@ Brute-force memory drivers carry an `O(N)` similarity scan — fine up to a few
|
|
|
997
1370
|
|
|
998
1371
|
## Snapshot resume — workflow + supervisor
|
|
999
1372
|
|
|
1000
|
-
|
|
1373
|
+
> ⚠ **BREAKING (4.3.0): supervisor + workflow snapshot persistence moved `CacheDriver` → `SnapshotStore`.** A `snapshotStore` is now a `SnapshotStore` built with `ai.snapshot.{memory,pg,redis}()`, not a raw `cache.driver(...)`. The framework still ships a deprecated `CacheDriver` overload for ONE minor so existing wiring keeps working, but new code uses the dedicated store factories. The `defaultSnapshotStore` resolution is via `ai.config({ defaultSnapshotStore })` (a `SnapshotStore`), separate from `defaultStore` (a `CacheDriver`, still used for `semanticCache` + memory). See [`@warlock.js/ai/manage-ai-stores/SKILL.md`](@warlock.js/ai/manage-ai-stores/SKILL.md).
|
|
1374
|
+
|
|
1375
|
+
### Wiring (new)
|
|
1001
1376
|
|
|
1002
1377
|
```ts
|
|
1003
|
-
|
|
1378
|
+
import { ai } from "@warlock.js/ai";
|
|
1379
|
+
|
|
1380
|
+
ai.config({ defaultSnapshotStore: ai.snapshot.redis({ client }) });
|
|
1004
1381
|
|
|
1005
1382
|
const wf = ai.workflow({
|
|
1006
1383
|
name: "ticket-processor",
|
|
1007
1384
|
steps: [...],
|
|
1008
|
-
// snapshotStore optional — falls back to
|
|
1385
|
+
// snapshotStore optional — falls back to ai.config({ defaultSnapshotStore })
|
|
1009
1386
|
});
|
|
1010
1387
|
|
|
1011
1388
|
const sup = ai.supervisor({
|
|
1012
1389
|
name: "support-team",
|
|
1013
1390
|
router: routerAgent,
|
|
1014
1391
|
intents: { triage, billing, resolver },
|
|
1015
|
-
// explicit override when this primitive needs a different
|
|
1016
|
-
snapshotStore:
|
|
1392
|
+
// explicit override when this primitive needs a different store
|
|
1393
|
+
snapshotStore: ai.snapshot.pg({ client: pgPool, table: "support_runs" }),
|
|
1017
1394
|
});
|
|
1018
1395
|
```
|
|
1019
1396
|
|
|
1397
|
+
The `SnapshotStore` is generic over its snapshot shape — it defaults to `SupervisorSnapshot`, and the workflow engine parameterizes it with `WorkflowSnapshot`; the only structural requirement is a `runId` string. `ai.snapshot.memory()` for dev/tests, `ai.snapshot.{pg,redis}()` for production (dev-owned client, never-auto-migrated `schema()`).
|
|
1398
|
+
|
|
1020
1399
|
### Snapshot shapes
|
|
1021
1400
|
|
|
1022
1401
|
```ts
|
|
@@ -1137,6 +1516,7 @@ The driver must support `similar()`. Without similarity → `CacheUnsupportedErr
|
|
|
1137
1516
|
|
|
1138
1517
|
## See also
|
|
1139
1518
|
|
|
1519
|
+
- [`@warlock.js/ai/manage-ai-stores/SKILL.md`](@warlock.js/ai/manage-ai-stores/SKILL.md) — `ai.snapshot.*` + `ai.checkpoint.*` store factories, schema(), drivers
|
|
1140
1520
|
- [`@warlock.js/ai/run-ai-workflow/SKILL.md`](@warlock.js/ai/run-ai-workflow/SKILL.md) — `snapshotStore` + `resume()`
|
|
1141
1521
|
- [`@warlock.js/ai/run-supervisor/SKILL.md`](@warlock.js/ai/run-supervisor/SKILL.md) — same on supervisor
|
|
1142
1522
|
- [`@warlock.js/ai/attach-ai-middleware/SKILL.md`](@warlock.js/ai/attach-ai-middleware/SKILL.md) — `semanticCache` middleware
|
|
@@ -1148,7 +1528,7 @@ The driver must support `similar()`. Without similarity → `CacheUnsupportedErr
|
|
|
1148
1528
|
|
|
1149
1529
|
---
|
|
1150
1530
|
name: pick-ai-provider
|
|
1151
|
-
description: 'Choose an AI provider adapter — @warlock.js/ai-openai (shipped, also handles OpenRouter / Azure via baseURL), @warlock.js/ai-anthropic, @warlock.js/ai-bedrock, @warlock.js/ai-google, @warlock.js/ai-ollama. Triggers: `OpenAISDK`, `SDKAdapterContract`, `ModelContract`, `sdk.model`, `sdk.embedder`, `capabilities.vision`, `capabilities.structuredOutput`, `pricing`, `baseURL`, `provider: "openrouter"`; ''pick a provider'', ''openai vs openrouter'', ''does this model support vision'', ''configure pricing''; typical import `import { OpenAISDK } from "@warlock.js/ai-openai"`. Skip: agent factory — `@warlock.js/ai/run-ai-agent/SKILL.md`; competing libs raw `openai`, `@anthropic-ai/sdk`, `@aws-sdk/client-bedrock-runtime`.'
|
|
1531
|
+
description: 'Choose an AI provider adapter — @warlock.js/ai-openai (shipped, also handles OpenRouter / Azure via baseURL), @warlock.js/ai-anthropic, @warlock.js/ai-bedrock, @warlock.js/ai-google, @warlock.js/ai-ollama — plus cost truth: ModelPricing (per-1M tokens), Usage cost breakdown, the cachedTokens / cacheWriteTokens / reasoningTokens channels, and capability flags. Triggers: `OpenAISDK`, `SDKAdapterContract`, `ModelContract`, `ModelPricing`, `ModelCapabilities`, `sdk.model`, `sdk.embedder`, `capabilities.vision`, `capabilities.structuredOutput`, `capabilities.reasoning`, `capabilities.promptCaching`, `pricing`, `Usage.cost`, `cachedTokens`, `cacheWriteTokens`, `reasoningTokens`, `reasoning.effort`, `cacheControl`, `baseURL`, `provider: "openrouter"`; ''pick a provider'', ''openai vs openrouter'', ''does this model support vision/reasoning'', ''configure pricing'', ''how much did reasoning cost'', ''prompt cache tokens''; typical import `import { OpenAISDK } from "@warlock.js/ai-openai"`. Skip: agent factory — `@warlock.js/ai/run-ai-agent/SKILL.md`; competing libs raw `openai`, `@anthropic-ai/sdk`, `@aws-sdk/client-bedrock-runtime`.'
|
|
1152
1532
|
---
|
|
1153
1533
|
|
|
1154
1534
|
# Pick an AI provider adapter
|
|
@@ -1192,16 +1572,20 @@ Adapters are classes — `new OpenAISDK({ apiKey })`, `new AnthropicSDK({ apiKey
|
|
|
1192
1572
|
- `count(text, model?)` — provider-appropriate token count.
|
|
1193
1573
|
- `embedder({ name })` — text-to-vector. Optional; check `typeof sdk.embedder === "function"` before calling.
|
|
1194
1574
|
|
|
1195
|
-
The `ModelContract.capabilities` field declares what the model supports —
|
|
1575
|
+
The `ModelContract.capabilities` field declares what the model supports — all flags optional (absent = treat as `false`):
|
|
1196
1576
|
|
|
1197
1577
|
```ts
|
|
1198
1578
|
type ModelCapabilities = {
|
|
1199
1579
|
structuredOutput?: boolean; // native response_format: json_schema support?
|
|
1200
1580
|
vision?: boolean; // can accept image attachments?
|
|
1581
|
+
reasoning?: boolean; // forwards ModelCallOptions.reasoning (effort / thinking budget)?
|
|
1582
|
+
promptCaching?: boolean; // honors cacheControl breakpoints + reports cache token channels?
|
|
1583
|
+
audio?: boolean; // can accept audio ContentPart input?
|
|
1584
|
+
pdf?: boolean; // can accept PDF / document ContentPart input?
|
|
1201
1585
|
};
|
|
1202
1586
|
```
|
|
1203
1587
|
|
|
1204
|
-
The framework reads `capabilities` to fail loud upfront — e.g. passing `attachments: [...]` to a non-vision model throws at the boundary instead of failing mid-trip.
|
|
1588
|
+
The framework reads `capabilities` to fail loud upfront — e.g. passing `attachments: [...]` to a non-vision model throws at the boundary instead of failing mid-trip; reasoning / cacheControl options are silently skipped when the adapter doesn't declare support, rather than sent as unsupported params.
|
|
1205
1589
|
|
|
1206
1590
|
## OpenAI adapter — usage
|
|
1207
1591
|
|
|
@@ -1248,23 +1632,53 @@ const customModel = openai.model({
|
|
|
1248
1632
|
});
|
|
1249
1633
|
```
|
|
1250
1634
|
|
|
1251
|
-
##
|
|
1635
|
+
## Cost truth — pricing + token channels
|
|
1252
1636
|
|
|
1253
|
-
|
|
1637
|
+
`ModelPricing` is **USD per 1,000,000 tokens** (the industry-standard unit), declared at two optional sites — `SDK.pricing` (registry keyed by model name) and `model({ pricing })` (per-model override, wins). Resolution: per-model > SDK registry > undefined (no cost computed).
|
|
1254
1638
|
|
|
1255
1639
|
```ts
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1640
|
+
type ModelPricing = {
|
|
1641
|
+
input: number; // required — USD / 1M input tokens
|
|
1642
|
+
output: number; // required — USD / 1M output tokens
|
|
1643
|
+
cachedInput?: number; // prompt-cache READ rate; falls back to `input`
|
|
1644
|
+
cachedOutput?: number; // cache-WRITE rate (Anthropic premium); falls back to `output`
|
|
1645
|
+
reasoning?: number; // reasoning/thinking-token rate; falls back to `output`
|
|
1646
|
+
};
|
|
1647
|
+
```
|
|
1648
|
+
|
|
1649
|
+
Configure it and every report carries `Usage.cost` — a per-channel breakdown captured at emit time as a historical fact (stored reports stay accurate after the upstream table changes):
|
|
1260
1650
|
|
|
1651
|
+
```ts
|
|
1261
1652
|
const { usage } = await ai.agent({ model: openai.model({ name: "gpt-4o-mini" }) }).execute("hi");
|
|
1262
1653
|
|
|
1263
|
-
usage.cost;
|
|
1264
|
-
//
|
|
1654
|
+
usage.cost; // { input, output, cachedInput?, cachedOutput? } — USD per channel
|
|
1655
|
+
// single scalar total: sum the populated fields, treating undefined as 0.
|
|
1656
|
+
```
|
|
1657
|
+
|
|
1658
|
+
`usage.cost` is `undefined` when no pricing is available — honest absence over false zero. Aggregators merge only defined fields, so one unpriced child never erases a priced sibling's cost.
|
|
1659
|
+
|
|
1660
|
+
### Token channels (`Usage`) — what each adapter reports
|
|
1661
|
+
|
|
1662
|
+
Beyond `input` / `output` / `total`, `Usage` carries optional sub-channels (undefined when the provider doesn't meter them):
|
|
1663
|
+
|
|
1664
|
+
| Channel | Meaning | Provider source |
|
|
1665
|
+
|---|---|---|
|
|
1666
|
+
| `cachedTokens` | subset of `input` served from prompt cache (READ hit) | OpenAI `prompt_tokens_details.cached_tokens`, Anthropic `cache_read_input_tokens` |
|
|
1667
|
+
| `cacheWriteTokens` | input tokens WRITTEN to the cache this call | Anthropic `cache_creation_input_tokens` (OpenAI does not write-bill) |
|
|
1668
|
+
| `reasoningTokens` | subset of `output` for internal reasoning/thinking | OpenAI `completion_tokens_details.reasoning_tokens`, Anthropic extended-thinking |
|
|
1669
|
+
|
|
1670
|
+
### Driving cache + reasoning per call
|
|
1671
|
+
|
|
1672
|
+
`ModelCallOptions` exposes vendor-neutral controls the agent forwards only when `capabilities` allows:
|
|
1673
|
+
|
|
1674
|
+
```ts
|
|
1675
|
+
await model.complete(messages, {
|
|
1676
|
+
reasoning: { effort: "high", maxTokens: 8_000 }, // effort → OpenAI reasoning_effort; maxTokens → Anthropic thinking budget
|
|
1677
|
+
cacheControl: { breakpoints: 1 }, // WRITE breakpoint → Anthropic cache_control markers
|
|
1678
|
+
});
|
|
1265
1679
|
```
|
|
1266
1680
|
|
|
1267
|
-
|
|
1681
|
+
Read-side cache accounting (`Usage.cachedTokens`) works WITHOUT `cacheControl` — it only controls WRITE placement. Adapters whose `capabilities.reasoning` / `.promptCaching` is absent ignore these rather than forwarding unsupported params.
|
|
1268
1682
|
|
|
1269
1683
|
## Embeddings
|
|
1270
1684
|
|
|
@@ -1316,7 +1730,7 @@ If you switch providers mid-project (e.g. OpenAI → Anthropic):
|
|
|
1316
1730
|
|
|
1317
1731
|
---
|
|
1318
1732
|
name: run-ai-agent
|
|
1319
|
-
description: 'Build agents with ai.agent({...}) — the single-LLM-turn primitive. Covers execute / stream, attachments, structured output, placeholders, events. Triggers: `ai.agent`, `agent.execute`, `agent.stream`, `AgentResult`, `AgentReport`, `streamingToolGuard`, `attachments`, `repair`, `maxTrips`, `sessionId`; ''run an agent'', ''stream an agent response'', ''structured output schema'', ''pass image to agent'', ''cancel an agent run''; typical import `import { ai } from "@warlock.js/ai"`. Skip: tool definition — `@warlock.js/ai/define-ai-tool/SKILL.md`; workflows — `@warlock.js/ai/run-ai-workflow/SKILL.md`; competing libs `langchain`, `ai` (Vercel), raw `openai`.'
|
|
1733
|
+
description: 'Build agents with ai.agent({...}) — the single-LLM-turn primitive. Covers execute / stream, attachments, structured output, placeholders, events, agent.eval scoring, and auto-adapting raw executables in tools:[]. Triggers: `ai.agent`, `agent.execute`, `agent.stream`, `agent.eval`, `AgentResult`, `AgentReport`, `AgentToolEntry`, `streamingToolGuard`, `attachments`, `repair`, `maxTrips`, `sessionId`; ''run an agent'', ''stream an agent response'', ''structured output schema'', ''pass image to agent'', ''evaluate an agent'', ''put a supervisor in tools'', ''cancel an agent run''; typical import `import { ai } from "@warlock.js/ai"`. Skip: tool definition — `@warlock.js/ai/define-ai-tool/SKILL.md`; workflows — `@warlock.js/ai/run-ai-workflow/SKILL.md`; eval matchers / batch / fallback detail — `@warlock.js/ai/ai-dx-helpers/SKILL.md`; competing libs `langchain`, `ai` (Vercel), raw `openai`.'
|
|
1320
1734
|
---
|
|
1321
1735
|
|
|
1322
1736
|
# `ai.agent()` — single-turn primitive
|
|
@@ -1335,7 +1749,7 @@ ai.agent({
|
|
|
1335
1749
|
name?: string, // optional — anonymous gets a fingerprint
|
|
1336
1750
|
model: openai.model({ name: "gpt-4o-mini" }),
|
|
1337
1751
|
systemPrompt?: string | SystemPromptContract,
|
|
1338
|
-
tools?:
|
|
1752
|
+
tools?: AgentToolEntry<any, any>[], // ToolContract OR a raw executable (auto-adapted)
|
|
1339
1753
|
placeholders?: Record<string, unknown>,
|
|
1340
1754
|
maxTrips?: number, // default 10
|
|
1341
1755
|
modelOptions?: ModelCallOptions,
|
|
@@ -1571,6 +1985,37 @@ await myAgent.execute("go", {
|
|
|
1571
1985
|
|
|
1572
1986
|
Every event payload carries `runId` and `rootRunId`. Same identity fields ride on stream events.
|
|
1573
1987
|
|
|
1988
|
+
## `tools: []` — auto-adapt executables
|
|
1989
|
+
|
|
1990
|
+
Each `tools` entry is either a built `ToolContract` (from `ai.tool(...)` or an explicit `.asTool(...)`) OR a **raw executable primitive** (`AgentContract` / `WorkflowInstance` / `SupervisorContract` / orchestrator) — auto-adapted into a `ToolContract` at factory time. The manifest is derived from the executable's `name` + `description` + (optional) `inputSchema`; dispatch flows through its `execute()`.
|
|
1991
|
+
|
|
1992
|
+
```ts
|
|
1993
|
+
const concierge = ai.agent({
|
|
1994
|
+
model,
|
|
1995
|
+
tools: [billingWorkflow, supportSupervisor, lookupTool], // no .asTool() needed
|
|
1996
|
+
});
|
|
1997
|
+
```
|
|
1998
|
+
|
|
1999
|
+
`.asTool()` still works and takes precedence when you need a custom name / schema per use. A supervisor/orchestrator needs `inputSchema` on its config to drop straight into `tools: []`. See [`@warlock.js/ai/define-ai-tool/SKILL.md`](@warlock.js/ai/define-ai-tool/SKILL.md).
|
|
2000
|
+
|
|
2001
|
+
## `agent.eval(options)` — score the agent against a suite
|
|
2002
|
+
|
|
2003
|
+
```ts
|
|
2004
|
+
const report = await myAgent.eval({
|
|
2005
|
+
cases: [
|
|
2006
|
+
{ name: "capital", input: "Capital of Egypt?", expected: "Cairo" },
|
|
2007
|
+
{ name: "tone", input: "Comfort an upset user." }, // judge-scored
|
|
2008
|
+
],
|
|
2009
|
+
scorers: [ai.eval.contains()], // default for cases w/o their own
|
|
2010
|
+
judge: { agent: judgeAgent, rubric: "Score 1.0 only if empathetic." }, // LLM-as-judge fallback
|
|
2011
|
+
passThreshold: 0.5, // default
|
|
2012
|
+
});
|
|
2013
|
+
|
|
2014
|
+
expect(report.passed).toBe(true); // true only when EVERY case passed
|
|
2015
|
+
```
|
|
2016
|
+
|
|
2017
|
+
Each case runs through `execute(input)`; scorer precedence is per-case `scorers` → suite `scorers` → synthesized `judge` (throws at author time if a case resolves none). Built-in scorers on `ai.eval.*`: `exact()`, `contains()`, `predicate(fn)`, `judge(config)`. Full coverage — plus the Vitest matchers (`registerAiMatchers` / `toRouteTo` / `toConverge` / `toPassStep` / `toOutputShape`) — in [`@warlock.js/ai/ai-dx-helpers/SKILL.md`](@warlock.js/ai/ai-dx-helpers/SKILL.md).
|
|
2018
|
+
|
|
1574
2019
|
## When NOT to use this primitive
|
|
1575
2020
|
|
|
1576
2021
|
- Multi-step pipeline with a fixed shape → [`@warlock.js/ai/run-ai-workflow/SKILL.md`](@warlock.js/ai/run-ai-workflow/SKILL.md)
|
|
@@ -1845,11 +2290,285 @@ Every payload carries `runId` and `rootRunId`.
|
|
|
1845
2290
|
- [`@warlock.js/ai/handle-ai-errors/SKILL.md`](@warlock.js/ai/handle-ai-errors/SKILL.md) — `WorkflowError` subclasses
|
|
1846
2291
|
|
|
1847
2292
|
|
|
2293
|
+
## run-orchestrator `@warlock.js/ai/run-orchestrator/SKILL.md`
|
|
2294
|
+
|
|
2295
|
+
---
|
|
2296
|
+
name: run-orchestrator
|
|
2297
|
+
description: 'Durable stateful sessions with ai.orchestrator({...}) — the capstone of the 4-primitive ladder. Wraps a supervisor with cross-turn session state (checkpointStore), per-turn windowing, drift detection, post-turn compaction, mid-turn resume (iterate: true + snapshotStore), per-turn memory, typed commands, asTool, and a 3-tier event model. Triggers: `ai.orchestrator`, `orchestrator.execute`, `orchestrator.resume`, `orchestrator.command`, `orchestrator.stream`, `OrchestratorConfig`, `OrchestratorResult`, `OrchestratorReport`, `OrchestratorContract`, `CheckpointStore`, `OrchestratorDriftError`, `sessionId`, `iterate`, `historyWindow`, `summarize`, `keepSnapshots`, `awaiting-input`, `turns[]`, `TurnSnapshot`, `CompactionResult`, `initialAgent`, `checkpointStore`; ''multi-turn conversation that persists'', ''durable session across calls'', ''resume an interrupted turn'', ''compact session history'', ''per-session memory''; typical import `import { ai } from "@warlock.js/ai"`. Skip: a single routing turn with no session — `@warlock.js/ai/run-supervisor/SKILL.md`; a fixed pipeline — `@warlock.js/ai/run-ai-workflow/SKILL.md`; the store factories themselves — `@warlock.js/ai/manage-ai-stores/SKILL.md`; competing libs `langgraph`, `crewai`.'
|
|
2298
|
+
---
|
|
2299
|
+
|
|
2300
|
+
# `ai.orchestrator()` — durable stateful sessions
|
|
2301
|
+
|
|
2302
|
+
The capstone of the 4-primitive ladder. An orchestrator is a **session-state manager wrapped around a supervisor**: each `execute` / `stream` call is ONE turn against a named `sessionId`, with the session's accumulated state, drift signature, and compaction progress persisted in a `CheckpointStore` between calls. The "what runs" fields (`intents`, `route` / `router`, `evaluate`, `state`, `output`, `initialAgent`, `maxIterations`) are the supervisor's surface spread directly — the orchestrator builds the supervisor lazily per turn and delegates to it. You never see the supervisor object.
|
|
2303
|
+
|
|
2304
|
+
## When to reach for it
|
|
2305
|
+
|
|
2306
|
+
- **`supervisor`** — routes one input to a specialist each turn; stateless between runs unless you wire `snapshotStore`. No cross-turn session memory.
|
|
2307
|
+
- **`orchestrator`** — when the **session** matters: a long-running conversation where each turn must rehydrate the prior turn's state, history must be windowed/compacted, and an interrupted turn must resume after a crash.
|
|
2308
|
+
|
|
2309
|
+
## Shape
|
|
2310
|
+
|
|
2311
|
+
```ts
|
|
2312
|
+
import { ai } from "@warlock.js/ai";
|
|
2313
|
+
import { END } from "@warlock.js/ai";
|
|
2314
|
+
|
|
2315
|
+
type SessionState = { category?: string; order?: { id: string }; reply?: string };
|
|
2316
|
+
|
|
2317
|
+
const supportBot = ai.orchestrator<SessionState>({
|
|
2318
|
+
name: "refund-support",
|
|
2319
|
+
intents: { classify, lookup, process, compose },
|
|
2320
|
+
route: (ctx) => (ctx.iteration === 0 ? "classify" : END),
|
|
2321
|
+
iterate: true, // delegate each turn to a real supervisor
|
|
2322
|
+
historyWindow: { router: 5, agents: 20 },
|
|
2323
|
+
summarize: { afterTurns: 20, keep: 6 }, // auto-compaction policy
|
|
2324
|
+
keepSnapshots: 100, // turns retained per session
|
|
2325
|
+
checkpointStore: ai.checkpoint.pg({ client: pg }),
|
|
2326
|
+
snapshotStore: ai.snapshot.pg({ client: pg }), // required when iterate: true
|
|
2327
|
+
});
|
|
2328
|
+
|
|
2329
|
+
const result = await supportBot.execute(message, { sessionId: "sess_42", history });
|
|
2330
|
+
|
|
2331
|
+
if (result.report.status === "awaiting-input") {
|
|
2332
|
+
// session continues — wait for the next user turn
|
|
2333
|
+
}
|
|
2334
|
+
```
|
|
2335
|
+
|
|
2336
|
+
`route` XOR `router` is required (mutually exclusive). `initialAgent`, when set, must be a key in `intents` and dispatches on turn 0, skipping the first route/router call. All config-shape errors throw `OrchestratorConfigError` at construction (author-time), not on the first turn.
|
|
2337
|
+
|
|
2338
|
+
## The session is owned by `sessionId` — passed per call
|
|
2339
|
+
|
|
2340
|
+
There is no stateful session object and no implicit "current session" — every method names the session it acts on via `options.sessionId`. `history` is **required** on every `execute` call: the framework never persists raw messages (it owns session *state*, not the message log — that is the dev's store). `state` is a partial seed/patch shallow-merged into the loaded session state; `context` is the request-scoped bag, frozen at intake.
|
|
2341
|
+
|
|
2342
|
+
```ts
|
|
2343
|
+
await supportBot.execute(input, {
|
|
2344
|
+
sessionId: "sess_42", // required — names the session
|
|
2345
|
+
history: priorMessages, // required — the dev re-supplies prior turns each call
|
|
2346
|
+
state: { tier: "gold" }, // partial patch shallow-merged into loaded state
|
|
2347
|
+
context: { userId, db }, // request-scoped, never persisted
|
|
2348
|
+
signal: AbortSignal.timeout(60_000),
|
|
2349
|
+
on: { "orchestrator.turn.completed": (e) => log(e) }, // tier-3 per-call handlers
|
|
2350
|
+
force: false, // bypass drift check for this call
|
|
2351
|
+
});
|
|
2352
|
+
```
|
|
2353
|
+
|
|
2354
|
+
## The turn lifecycle (what each turn does)
|
|
2355
|
+
|
|
2356
|
+
1. **load** — read the latest checkpoint for `(name, sessionId)`; seed empty on first call (`orchestrator.session.loaded`).
|
|
2357
|
+
2. **drift check** — compare the loaded checkpoint's `signature` to the current definition (`orchestrator.drift.checked`). Mismatch throws `OrchestratorDriftError` unless `force: true`.
|
|
2358
|
+
3. **lock wait** — wait on the compaction lock if held (`orchestrator.lock.waiting`).
|
|
2359
|
+
4. **window** — slice history per `historyWindow.{router,agents}` (`orchestrator.history.windowed`).
|
|
2360
|
+
5. **dispatch** — `route`/`router` (or `initialAgent` on turn 0) picks the intent(s); the supervisor runs the turn (`orchestrator.turn.routed`, `orchestrator.turn.streaming`).
|
|
2361
|
+
6. **persist** — append a checkpoint row for the settled turn, then prune to `keepSnapshots` (`orchestrator.checkpoint.persisted`).
|
|
2362
|
+
7. **compaction** — fire the post-turn compaction trigger if configured (`orchestrator.compaction.suggested` / `.applied`).
|
|
2363
|
+
|
|
2364
|
+
Terminal events: `orchestrator.turn.completed` / `.failed` / `.cancelled`, plus the non-terminal `orchestrator.turn.awaiting-input`.
|
|
2365
|
+
|
|
2366
|
+
## `OrchestratorResult` — read the report
|
|
2367
|
+
|
|
2368
|
+
```ts
|
|
2369
|
+
const result = await supportBot.execute(message, { sessionId, history });
|
|
2370
|
+
|
|
2371
|
+
result.sessionId; // echoes the session this turn acted on
|
|
2372
|
+
result.turnIndex; // zero-indexed turn number
|
|
2373
|
+
result.data; // validated against `output`, if set
|
|
2374
|
+
result.error; // typed AIError — execute() never throws on runtime failure
|
|
2375
|
+
result.report.type; // "orchestrator"
|
|
2376
|
+
result.report.status; // ReportStatus | "awaiting-input"
|
|
2377
|
+
result.report.turns; // TurnSnapshot[] — current turn + prior, bounded by keepSnapshots
|
|
2378
|
+
result.compaction; // CompactionResult when a turn compacted (and no onCompact ran)
|
|
2379
|
+
```
|
|
2380
|
+
|
|
2381
|
+
`report.children[]` carries ONLY the current turn's dispatched primitive reports. Full session history lives on `report.turns[]` — a `children[]` walker will NOT reach prior turns (intentional). Child `supervisor.*` / `agent.*` events bubble up unmodified under their own identity.
|
|
2382
|
+
|
|
2383
|
+
**`awaiting-input` is the only non-terminal status across the unified result tree.** Code branching on `status === "completed"` MUST explicitly handle `"awaiting-input"` as a session-continues path, not a failure.
|
|
2384
|
+
|
|
2385
|
+
## `iterate` — single dispatch vs. internal supervisor
|
|
2386
|
+
|
|
2387
|
+
- **`iterate: false`** (default) — one dispatch per turn. No `snapshotStore` needed.
|
|
2388
|
+
- **`iterate: true`** — each turn delegates to a real internal supervisor that loops to `maxIterations` (default 10). **Requires** a `snapshotStore` (explicit or `ai.config({ defaultSnapshotStore })`) so a crashed mid-turn iteration can resume. Construction throws if you set `iterate: true` without one.
|
|
2389
|
+
|
|
2390
|
+
## `resume()` — drain an interrupted turn
|
|
2391
|
+
|
|
2392
|
+
```ts
|
|
2393
|
+
const result = await supportBot.resume("sess_42", { context: { db }, force: false });
|
|
2394
|
+
```
|
|
2395
|
+
|
|
2396
|
+
Resume continues an interrupted `iterate: true` turn from its persisted supervisor snapshot. Returns `null` when there is nothing in flight for the session (a no-op for `iterate: false` orchestrators). It re-supplies request-scoped `context` (NOT persisted) and rehydrates state from the checkpoint — there is no `history` field, since it continues an in-flight turn rather than opening a fresh one. Runs the same drift check as `execute()`; throws `OrchestratorDriftError` on mismatch unless `{ force: true }`. Use the boot-drain pattern: enumerate sessions via `checkpointStore.list(name)` and `resume()` each on startup.
|
|
2397
|
+
|
|
2398
|
+
## Compaction — `summarize`
|
|
2399
|
+
|
|
2400
|
+
Bounds session history growth. Two forms:
|
|
2401
|
+
|
|
2402
|
+
```ts
|
|
2403
|
+
// Object policy — count-based auto-fire after `afterTurns`, keep the most recent `keep`.
|
|
2404
|
+
summarize: {
|
|
2405
|
+
afterTurns: 20,
|
|
2406
|
+
keep: 6,
|
|
2407
|
+
summarizer: cheapModel, // defaults to the orchestrator's own model
|
|
2408
|
+
onCompact: async (compaction, ctx) => { // apply to the dev's message store
|
|
2409
|
+
await messages.applyCompaction(ctx.sessionId, compaction);
|
|
2410
|
+
},
|
|
2411
|
+
lock: { maxWait: 5_000 },
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
// Callback form — full control; NEVER auto-fires, driven only by command("compact").
|
|
2415
|
+
summarize: (history) => ({ summary, replacesFromIndex, replacesToIndex }),
|
|
2416
|
+
```
|
|
2417
|
+
|
|
2418
|
+
A `CompactionResult` is `{ summary: Message, replacesFromIndex, replacesToIndex }` — the replacement summary plus the inclusive index range it replaces in the dev's history array. When `onCompact` is supplied the orchestrator applies it for you and does NOT surface `result.compaction`; otherwise it surfaces `result.compaction` for you to apply manually.
|
|
2419
|
+
|
|
2420
|
+
## `command()` — typed built-ins
|
|
2421
|
+
|
|
2422
|
+
```ts
|
|
2423
|
+
const compaction = await supportBot.command("compact", { sessionId, history });
|
|
2424
|
+
// → { summary, replacesFromIndex, replacesToIndex }
|
|
2425
|
+
```
|
|
2426
|
+
|
|
2427
|
+
v1 ships exactly one built-in command, `compact` (manual compaction outside the auto-trigger; reuses the same compaction code path). User commands attach via module augmentation of `OrchestratorCommands` — declaring extra keys in your own `.d.ts` widens the typed `command<K>` surface without a framework release.
|
|
2428
|
+
|
|
2429
|
+
## Per-turn memory — `memory`
|
|
2430
|
+
|
|
2431
|
+
Wire an `ai.memory()` store so each turn recalls relevant memories before routing and remembers the settled outcome after:
|
|
2432
|
+
|
|
2433
|
+
```ts
|
|
2434
|
+
ai.orchestrator({
|
|
2435
|
+
name: "support",
|
|
2436
|
+
intents,
|
|
2437
|
+
route,
|
|
2438
|
+
memory: mem, // bare MemoryContract — recall + remember w/ defaults
|
|
2439
|
+
// or finer control:
|
|
2440
|
+
memory: {
|
|
2441
|
+
store: mem,
|
|
2442
|
+
recall: { k: 5, threshold: 0.7, tier: "semantic" }, // k: 0 = write-only memory
|
|
2443
|
+
remember: true, // false = read-only (recall, never write)
|
|
2444
|
+
rememberTier: "semantic",
|
|
2445
|
+
injectKey: "memories", // ctx.context[injectKey] holds RecalledMemory[]
|
|
2446
|
+
},
|
|
2447
|
+
});
|
|
2448
|
+
```
|
|
2449
|
+
|
|
2450
|
+
Recalled memories land in the per-turn `context` bag under `injectKey` (default `"memories"`) — every route / router / evaluate / dispatch callback reads them at `ctx.context.memories`. Memory never mutates the prompt itself; surfacing it stays explicit. Cancelled / failed turns never remember (they revert), regardless of `remember`. See [`@warlock.js/ai/use-ai-memory/SKILL.md`](@warlock.js/ai/use-ai-memory/SKILL.md).
|
|
2451
|
+
|
|
2452
|
+
## `asTool()` — orchestrator as a tool
|
|
2453
|
+
|
|
2454
|
+
```ts
|
|
2455
|
+
const supportTool = supportBot.asTool({
|
|
2456
|
+
name: "handle_refund",
|
|
2457
|
+
description: "Handle a refund conversation end-to-end.",
|
|
2458
|
+
inputSchema: v.object({ message: v.string() }),
|
|
2459
|
+
sessionScope: "fresh", // default — each call gets a brand-new sessionId
|
|
2460
|
+
});
|
|
2461
|
+
|
|
2462
|
+
const concierge = ai.agent({ model, tools: [supportTool] });
|
|
2463
|
+
```
|
|
2464
|
+
|
|
2465
|
+
The tool boundary is **opaque**: the parent's `signal` / `context` / events do NOT auto-forward — anything the wrapped orchestrator needs must ride on the `inputSchema` payload. `sessionScope`:
|
|
2466
|
+
- **`"fresh"`** (default) — each invocation gets a generated `sessionId` and empty history; no continuity across calls.
|
|
2467
|
+
- **`"shared"`** — the parent threads `sessionId` (and optionally `history`) through the validated payload; the orchestrator participates in that session. A missing/blank `sessionId` throws.
|
|
2468
|
+
|
|
2469
|
+
## Drift detection
|
|
2470
|
+
|
|
2471
|
+
The orchestrator signature fingerprints: name + intents map + route/router presence + evaluate presence + initialAgent + maxIterations + iterate flag + historyWindow shape. It does NOT aggregate the internal supervisor's signature — internal-supervisor drift surfaces only on `iterate: true` resume via the supervisor's own drift check. On mismatch, `OrchestratorDriftError` (`code: "ORCHESTRATOR_DRIFT"`, `category: "drift"`) is thrown synchronously — nothing dispatches. Recover by discarding the session, migrating the persisted checkpoint, or passing `{ force: true }`.
|
|
2472
|
+
|
|
2473
|
+
## 3-tier events
|
|
2474
|
+
|
|
2475
|
+
Handlers fire definition → instance → per-call, in that order, on every emission:
|
|
2476
|
+
|
|
2477
|
+
```ts
|
|
2478
|
+
const orch = ai.orchestrator({ ..., on: { "orchestrator.turn.failed": tier1 } }); // tier 1 — definition
|
|
2479
|
+
const off = orch.on("orchestrator.turn.completed", tier2); // tier 2 — instance
|
|
2480
|
+
await orch.execute(input, { sessionId, history, on: { "orchestrator.drift.checked": tier3 } }); // tier 3 — per-call
|
|
2481
|
+
```
|
|
2482
|
+
|
|
2483
|
+
## Stores
|
|
2484
|
+
|
|
2485
|
+
`checkpointStore` (cross-turn session state) and `snapshotStore` (internal-supervisor run state for `iterate: true`) are distinct contracts with distinct factories. See [`@warlock.js/ai/manage-ai-stores/SKILL.md`](@warlock.js/ai/manage-ai-stores/SKILL.md).
|
|
2486
|
+
|
|
2487
|
+
## See also
|
|
2488
|
+
|
|
2489
|
+
- [`@warlock.js/ai/run-supervisor/SKILL.md`](@warlock.js/ai/run-supervisor/SKILL.md) — the engine each turn delegates to
|
|
2490
|
+
- [`@warlock.js/ai/manage-ai-stores/SKILL.md`](@warlock.js/ai/manage-ai-stores/SKILL.md) — `ai.checkpoint.*` / `ai.snapshot.*`
|
|
2491
|
+
- [`@warlock.js/ai/use-ai-memory/SKILL.md`](@warlock.js/ai/use-ai-memory/SKILL.md) — the `memory` field
|
|
2492
|
+
- [`@warlock.js/ai/handle-ai-errors/SKILL.md`](@warlock.js/ai/handle-ai-errors/SKILL.md) — `OrchestratorDriftError` / `OrchestratorConfigError`
|
|
2493
|
+
|
|
2494
|
+
|
|
2495
|
+
## run-planner `@warlock.js/ai/run-planner/SKILL.md`
|
|
2496
|
+
|
|
2497
|
+
---
|
|
2498
|
+
name: run-planner
|
|
2499
|
+
description: 'Goal-driven planning with ai.planner({...}) — an LLM GENERATES an ordered execution plan over your registered capabilities (agents / workflows / supervisors / tools), then the planner EXECUTES that plan step-by-step, threading each step output into the next, and returns the unified {data, report, usage, error} envelope with report.type "planner". A plan step may delegate via ai.spawnSubAgent({...}) — a GENERAL one-shot-agent helper (a fresh agent + optional per-task budget), covered fully in `@warlock.js/ai/run-ai-agent/SKILL.md`; it is not planner-specific and the planner engine does not require it. Triggers: `ai.planner`, `planner.execute`, `spawnSubAgent`, `PlannerConfig`, `PlannerCapability`, `PlannerResult`, `PlannerReport`, `PlannerPlan`, `PlannerStep`, `maxSteps`, `report.plan`, `report.executedSteps`; ''let the model plan the steps'', ''dynamic plan from a goal'', ''decompose a goal into capability calls'', ''spawn a sub-agent for a subtask''; typical import `import { ai } from "@warlock.js/ai"`. Skip: a FIXED known pipeline — `@warlock.js/ai/run-ai-workflow/SKILL.md`; routing one input to a specialist each turn — `@warlock.js/ai/run-supervisor/SKILL.md`; a single model + tools call — `@warlock.js/ai/run-ai-agent/SKILL.md`; competing libs `langgraph`, `crewai`.'
|
|
2500
|
+
---
|
|
2501
|
+
|
|
2502
|
+
# `ai.planner()` — LLM-generated, then executed, plans
|
|
2503
|
+
|
|
2504
|
+
A planner turns a free-form **goal** into an ordered **plan** the LLM writes itself (referencing only the capabilities you registered), then runs that plan one step at a time through each capability's own `execute()`. Use it when you do NOT know the steps up front — the model decides the sequence.
|
|
2505
|
+
|
|
2506
|
+
## When to reach for it
|
|
2507
|
+
|
|
2508
|
+
- **`agent`** — one model + tools, single task. No multi-step decomposition.
|
|
2509
|
+
- **`workflow`** — a FIXED pipeline you author by hand (`steps: [...]`). The steps are known at design time.
|
|
2510
|
+
- **`supervisor`** — routes one input to the right specialist each turn; loops on a quality verdict.
|
|
2511
|
+
- **`planner`** — the steps are NOT known in advance. The LLM generates the ordered plan from the goal, then the planner executes it. Bounded v1: strictly sequential, no DAG scheduling, no mid-plan re-planning.
|
|
2512
|
+
|
|
2513
|
+
## Shape
|
|
2514
|
+
|
|
2515
|
+
```ts
|
|
2516
|
+
import { ai } from "@warlock.js/ai";
|
|
2517
|
+
|
|
2518
|
+
const research = ai.planner({
|
|
2519
|
+
name: "research-assistant",
|
|
2520
|
+
model: ai.openai.model({ name: "gpt-4o" }), // the plan-GENERATION brain
|
|
2521
|
+
capabilities: [
|
|
2522
|
+
{ name: "search", description: "Search the web for sources", executable: searchAgent },
|
|
2523
|
+
{ name: "summarize", description: "Summarize text into bullet points", executable: summarizer },
|
|
2524
|
+
{ name: "write", description: "Draft a final report", executable: writerAgent },
|
|
2525
|
+
],
|
|
2526
|
+
maxSteps: 6, // hard cap; steps beyond it are recorded as "skipped"
|
|
2527
|
+
});
|
|
2528
|
+
|
|
2529
|
+
const { data, report, usage, error } = await research.execute("Compare React vs Vue in 2026");
|
|
2530
|
+
|
|
2531
|
+
console.log(report.plan?.summary); // the LLM's one-line strategy
|
|
2532
|
+
for (const step of report.executedSteps) { // forensic, in execution order
|
|
2533
|
+
console.log(step.step.capability, step.status);
|
|
2534
|
+
}
|
|
2535
|
+
```
|
|
2536
|
+
|
|
2537
|
+
- `model` builds an internal planning agent with a generated plan-prompt baked on. **Mutually exclusive** with `planner`.
|
|
2538
|
+
- `planner` lets you bring your own fully-configured planning agent (custom prompt, middleware). The planner injects the plan schema as that agent's per-call `output`.
|
|
2539
|
+
- A `capability` is `{ name, description, executable }`. The `name` is what the LLM references per step; the `description` is what it reads to pick. `executable` is any `ExecutableContract` (agent / workflow / supervisor / tool).
|
|
2540
|
+
|
|
2541
|
+
## Execution model (bounded v1)
|
|
2542
|
+
|
|
2543
|
+
1. **Generate** — the planning agent is asked for a `{ steps, summary? }` plan via a generated schema whose `capability` field is an `enum` of your capability names.
|
|
2544
|
+
2. **Execute** — steps run **strictly in array order**. Each completed step's output is threaded into the next step's input as "Context from earlier steps". `dependsOn` on a step is advisory metadata only — recorded, not scheduled on.
|
|
2545
|
+
3. **Finalize** — when `output` is set (factory or per-call), the LAST completed step's structured output is validated into `result.data`. A capability that should feed typed output to the planner's `output` should declare its own `output` schema (the planner reads `data`, falling back to an agent's raw `text`).
|
|
2546
|
+
|
|
2547
|
+
`report.type === "planner"`; `report.children[]` carries every dispatched capability report (plus the planning trip), with usage rolled up. Lazy capability loading is **deferred** — every capability is fully constructed up front.
|
|
2548
|
+
|
|
2549
|
+
## Failure + cancellation
|
|
2550
|
+
|
|
2551
|
+
`execute()` never throws — failures surface on `result.error`:
|
|
2552
|
+
|
|
2553
|
+
- **`PlannerPlanInvalidError`** (`PLANNER_PLAN_INVALID`, category `schema`) — empty plan or a step naming an unknown capability; also a final-output validation failure.
|
|
2554
|
+
- **`PlannerCancelledError`** (`PLANNER_CANCELLED`, category `cancelled`) — the `AbortSignal` fired. `report.status === "cancelled"`, `report.cancelledAt` set; remaining steps are `skipped`.
|
|
2555
|
+
- A child capability's own error (agent / tool / provider) flows through unchanged on the failing step's snapshot and as `result.error`. The planner stops at the first failed step and marks the rest `skipped`.
|
|
2556
|
+
- **`PlannerFailedError`** is the base for the `PLANNER_*` family.
|
|
2557
|
+
|
|
2558
|
+
## Delegating a step with `ai.spawnSubAgent()`
|
|
2559
|
+
|
|
2560
|
+
A plan step can hand a bounded subtask to a fresh single-use agent with a hard spend cap via `ai.spawnSubAgent({...})`. It is **not** a planner feature — it's a general one-shot-agent helper (a fresh `ai.agent()` + an optional per-task `budget`, run once) that works identically inside a tool, a workflow step, a supervisor intent, or hand-rolled orchestration. The planner engine never calls it; it's simply a primitive a capability *you* write can reach for. Full coverage: [`@warlock.js/ai/run-ai-agent/SKILL.md`](@warlock.js/ai/run-ai-agent/SKILL.md).
|
|
2561
|
+
|
|
2562
|
+
## Testing
|
|
2563
|
+
|
|
2564
|
+
Use `MockSDK` for the planning model — script the plan as a JSON string matching `{ steps, summary? }`. Capabilities can be `mockAgent({ name, responses })`. See `src/planner/planner.spec.ts`.
|
|
2565
|
+
|
|
2566
|
+
|
|
1848
2567
|
## run-supervisor `@warlock.js/ai/run-supervisor/SKILL.md`
|
|
1849
2568
|
|
|
1850
2569
|
---
|
|
1851
2570
|
name: run-supervisor
|
|
1852
|
-
description: 'Multi-intent routing with ai.supervisor({...}) — classifier (iter-0 dispatch), router agent OR route callback, intents as agents / workflows / callbacks, fan-out, evaluate quality loop, ack receptionist. Triggers: `ai.supervisor`, `supervisor.execute`, `supervisor.resume`, `intents`, `router`, `route`, `classifier`, `evaluate`, `ack`, `artifactsSchema`, `END`, `ctx.intents.X.execute`; ''route one input across specialists'', ''multi-intent dispatch'', ''fan-out then evaluate'', ''classifier then router''; typical import `import { ai } from "@warlock.js/ai"`. Skip: fixed pipelines — `@warlock.js/ai/run-ai-workflow/SKILL.md`; single agent — `@warlock.js/ai/run-ai-agent/SKILL.md`; competing libs `langgraph`, `crewai`.'
|
|
2571
|
+
description: 'Multi-intent routing with ai.supervisor({...}) — classifier (iter-0 dispatch), router agent OR route callback, intents as agents / workflows / callbacks, fan-out, evaluate quality loop, ack receptionist, supervisor-level middleware. Triggers: `ai.supervisor`, `ai.router`, `ai.fanOut`, `supervisor.execute`, `supervisor.resume`, `intents`, `router`, `route`, `classifier`, `evaluate`, `ack`, `artifactsSchema`, `middleware`, `END`, `ctx.intents.X.execute`; ''route one input across specialists'', ''multi-intent dispatch'', ''fan-out then evaluate'', ''classifier then router'', ''supervisor middleware'', ''self-consistency / voting''; typical import `import { ai } from "@warlock.js/ai"`. Skip: durable multi-turn sessions — `@warlock.js/ai/run-orchestrator/SKILL.md`; fixed pipelines — `@warlock.js/ai/run-ai-workflow/SKILL.md`; single agent — `@warlock.js/ai/run-ai-agent/SKILL.md`; competing libs `langgraph`, `crewai`.'
|
|
1853
2572
|
---
|
|
1854
2573
|
|
|
1855
2574
|
# `ai.supervisor()` — multi-intent routing
|
|
@@ -1861,7 +2580,7 @@ A supervisor takes one input, picks which intent(s) handle it, runs them, option
|
|
|
1861
2580
|
- **`agent`** — one model + tools, single task. Doesn't fit when the right specialist depends on the input.
|
|
1862
2581
|
- **`workflow`** — fixed step order. Doesn't fit when routing decisions need an LLM or vary per request.
|
|
1863
2582
|
- **`supervisor`** — when the right specialist is decided per-call and you may iterate to a goal.
|
|
1864
|
-
- **`orchestrator
|
|
2583
|
+
- **`orchestrator`** — when the *session* matters: long-running conversations with durable cross-turn state, history windowing/compaction, and mid-turn resume. See [`@warlock.js/ai/run-orchestrator/SKILL.md`](@warlock.js/ai/run-orchestrator/SKILL.md).
|
|
1865
2584
|
|
|
1866
2585
|
## Three dispatch surfaces
|
|
1867
2586
|
|
|
@@ -1922,6 +2641,41 @@ The router agent's output MUST include `next: string | string[] | typeof END`; `
|
|
|
1922
2641
|
|
|
1923
2642
|
`evaluate` pairs with both `router` AND `route` — state-driven termination is useful in either dispatch mode.
|
|
1924
2643
|
|
|
2644
|
+
#### `ai.router()` — skip the boilerplate
|
|
2645
|
+
|
|
2646
|
+
`ai.router({ model, intents })` builds the routing agent for you: it generates the `{ next, reasoning }` output schema (with the intent names + `END` baked in as a JSON-Schema `enum`) and auto-writes the routing system prompt listing every intent + description. Pass the **same** `intents` object you pass to `ai.supervisor()`.
|
|
2647
|
+
|
|
2648
|
+
```ts
|
|
2649
|
+
const intents = { billing, shipping, returns, escalate };
|
|
2650
|
+
|
|
2651
|
+
const supportBot = ai.supervisor({
|
|
2652
|
+
router: ai.router({
|
|
2653
|
+
model,
|
|
2654
|
+
intents,
|
|
2655
|
+
systemPrompt: "You coordinate a customer-support team.", // optional framing on top
|
|
2656
|
+
}),
|
|
2657
|
+
intents,
|
|
2658
|
+
});
|
|
2659
|
+
```
|
|
2660
|
+
|
|
2661
|
+
The result is a plain `AgentContract` — usable standalone or as `router`. Hand-writing the agent (above) still works; `ai.router()` is the shortcut.
|
|
2662
|
+
|
|
2663
|
+
#### `ai.fanOut()` — voting / self-consistency
|
|
2664
|
+
|
|
2665
|
+
`ai.fanOut(unit, n)` spreads one agent/workflow into `n` distinctly-keyed intent entries (`writer1..writerN`) so the supervisor can dispatch them in parallel and a downstream intent can pick the best/majority answer. Spread it into `intents`:
|
|
2666
|
+
|
|
2667
|
+
```ts
|
|
2668
|
+
ai.supervisor({
|
|
2669
|
+
intents: {
|
|
2670
|
+
...ai.fanOut(writer, 3), // writer1, writer2, writer3
|
|
2671
|
+
vote: { run: pickMajority, description: "Choose the majority answer." },
|
|
2672
|
+
},
|
|
2673
|
+
route: (ctx) => (ctx.iteration === 0 ? ["writer1", "writer2", "writer3"] : "vote"),
|
|
2674
|
+
});
|
|
2675
|
+
```
|
|
2676
|
+
|
|
2677
|
+
Each key references the same underlying unit; the description defaults to the unit's. Override the key base with `{ keyPrefix }` and the per-entry text with `{ description }`.
|
|
2678
|
+
|
|
1925
2679
|
## The `intents` map — five accepted shapes
|
|
1926
2680
|
|
|
1927
2681
|
```ts
|
|
@@ -2095,6 +2849,16 @@ await supportBot.execute(message, {
|
|
|
2095
2849
|
|
|
2096
2850
|
`history` precedence: per-call → factory `config.history`. Slice with `historyWindow.{router,agents,ack}` (default ack = 0, router/agents = unbounded) or per-entry `history(ctx)` override.
|
|
2097
2851
|
|
|
2852
|
+
## Supervisor-level middleware
|
|
2853
|
+
|
|
2854
|
+
`middleware: AgentMiddleware[]` fires each middleware's optional `supervisor` hook map (`before` / `after` / `onError`) ONCE around the whole `execute()` / `stream()` / `resume()` run:
|
|
2855
|
+
|
|
2856
|
+
```ts
|
|
2857
|
+
ai.supervisor({ name: "support", router, intents, middleware: [auditTrail] });
|
|
2858
|
+
```
|
|
2859
|
+
|
|
2860
|
+
Same onion semantics as the agent pipeline: `before` top-down (return a `SupervisorResult` to short-circuit, throw to abort), `after` / `onError` bottom-up. A middleware without a `supervisor` hook map is skipped — the SAME builtin objects (budget, guardrail, …) can be registered on agents AND here, each declaring whichever level applies. Each needs a unique `name`. See [`@warlock.js/ai/attach-ai-middleware/SKILL.md`](@warlock.js/ai/attach-ai-middleware/SKILL.md).
|
|
2861
|
+
|
|
2098
2862
|
## Iteration model
|
|
2099
2863
|
|
|
2100
2864
|
1. Router/route picks `next` (or `END`).
|
|
@@ -2158,11 +2922,139 @@ const escalationAgent = ai.agent({ model, tools: [supportTool] });
|
|
|
2158
2922
|
- [`@warlock.js/ai/define-ai-tool/SKILL.md`](@warlock.js/ai/define-ai-tool/SKILL.md) — tool artifacts side-channel
|
|
2159
2923
|
|
|
2160
2924
|
|
|
2925
|
+
## use-ai-memory `@warlock.js/ai/use-ai-memory/SKILL.md`
|
|
2926
|
+
|
|
2927
|
+
---
|
|
2928
|
+
name: use-ai-memory
|
|
2929
|
+
description: 'Agent memory with ai.memory({...}) — a provider-neutral store with FOUR tiers: WORKING (in-run scratch, recalled by recency), SEMANTIC (durable facts by cosine similarity over a @warlock.js/cache vector driver via .similar()), EPISODIC (durable events, similarity blended with recency), and PROCEDURAL (durable how-tos, similarity blended with reinforcement). remember() / recall() / clear(); wire it into ai.orchestrator({ memory }). Triggers: `ai.memory`, `memory.remember`, `memory.recall`, `memory.clear`, `MemoryContract`, `MemoryConfig`, `MemoryItem`, `RecalledMemory`, `MemoryTier`, `SemanticMemoryConfig`, `EpisodicMemoryConfig`, `ProceduralMemoryConfig`, `working`, `semantic`, `episodic`, `procedural`, `defaultTier`, `threshold`, `recencyWeight`, `halfLifeMs`, `reinforcementWeight`, `injectKey`; ''give the agent memory'', ''remember user preferences'', ''semantic recall'', ''per-session working memory'', ''episodic / event memory'', ''procedural / how-to memory'', ''recency-weighted recall'', ''reinforce a procedure''; typical import `import { ai } from "@warlock.js/ai"`. Skip: orchestrator wiring of the memory — `@warlock.js/ai/run-orchestrator/SKILL.md`; the vector cache driver itself — `@warlock.js/cache/cache-basics/SKILL.md`; embeddings primitive — `@warlock.js/ai/embed-text/SKILL.md`; competing libs `mem0`, `langchain` memory.'
|
|
2930
|
+
---
|
|
2931
|
+
|
|
2932
|
+
# `ai.memory()` — agent memory store
|
|
2933
|
+
|
|
2934
|
+
A single provider-neutral store that holds and retrieves what an agent / orchestrator should remember across turns. Four tiers ship in 4.3.0:
|
|
2935
|
+
|
|
2936
|
+
- **working** — in-run scratch threaded across turns of one session. Volatile, unscored, recalled in insertion order (recency). On by default.
|
|
2937
|
+
- **semantic** — durable *facts* stored as embeddings in a `@warlock.js/cache` driver, retrieved by cosine similarity via the driver's native `.similar()` — the same delegation the `semanticCache` middleware uses. Activates only when you pass `semantic` config.
|
|
2938
|
+
- **episodic** — durable *events*: a timestamped log retrieved by similarity **blended with recency** (recent episodes rank higher). Embedder-backed like semantic; tune with `recencyWeight` + `halfLifeMs`.
|
|
2939
|
+
- **procedural** — durable *how-tos*: learned procedures retrieved by similarity **blended with reinforcement** — re-remembering a procedure increments its use count so well-worn procedures rank higher. Tune with `reinforcementWeight`.
|
|
2940
|
+
|
|
2941
|
+
> **Still deferred** — decay / forgetting (TTL falloff, eviction). The four tiers above are the full 4.3.0 surface; the `MemoryTier` union widened from `"working" | "semantic"` to add `"episodic" | "procedural"` (a non-breaking change).
|
|
2942
|
+
|
|
2943
|
+
## Shape
|
|
2944
|
+
|
|
2945
|
+
```ts
|
|
2946
|
+
import { ai } from "@warlock.js/ai";
|
|
2947
|
+
import { MemoryCacheDriver } from "@warlock.js/cache";
|
|
2948
|
+
import { OpenAISDK } from "@warlock.js/ai-openai";
|
|
2949
|
+
|
|
2950
|
+
const openai = new OpenAISDK({ apiKey: process.env.OPENAI_API_KEY! });
|
|
2951
|
+
|
|
2952
|
+
const store = new MemoryCacheDriver();
|
|
2953
|
+
store.setOptions({});
|
|
2954
|
+
|
|
2955
|
+
const mem = ai.memory({
|
|
2956
|
+
semantic: {
|
|
2957
|
+
embedder: openai.embedder({ name: "text-embedding-3-small" }),
|
|
2958
|
+
store, // vector-capable CacheDriver
|
|
2959
|
+
namespace: "ai.memory", // key prefix; default "ai.memory"
|
|
2960
|
+
},
|
|
2961
|
+
defaultTier: "semantic", // tier a remember() item lands in without its own `tier`
|
|
2962
|
+
k: 5, // default recall count
|
|
2963
|
+
threshold: 0.7, // default semantic similarity floor [0,1]
|
|
2964
|
+
});
|
|
2965
|
+
|
|
2966
|
+
await mem.remember({ text: "User prefers concise answers." });
|
|
2967
|
+
const hits = await mem.recall("how should I respond?", { k: 3 });
|
|
2968
|
+
```
|
|
2969
|
+
|
|
2970
|
+
## Configuration rules (loud at construction)
|
|
2971
|
+
|
|
2972
|
+
- **At least one tier must be enabled** — `working` defaults to `true`; `semantic` / `episodic` / `procedural` each activate only when you pass their config. Enabling neither throws (`a memory with no tiers can't store or recall`).
|
|
2973
|
+
- **A vector tier with no store throws now** — pass the tier's `store`, or set `ai.config({ defaultStore })` at boot. Applies to `semantic`, `episodic`, and `procedural`. Resolution happens once at construction, not silently on first use (the same loud-now contract `semanticCache` follows).
|
|
2974
|
+
- **`defaultTier` must reference an enabled tier** — defaults to `"working"`.
|
|
2975
|
+
- Set `working: false` for a durable-only memory (then set `defaultTier` to an enabled vector tier).
|
|
2976
|
+
|
|
2977
|
+
## Episodic & procedural tiers
|
|
2978
|
+
|
|
2979
|
+
Both are durable, embedder-backed tiers wired like `semantic` (`{ embedder, store? }`), but they re-rank by *time* and *use*:
|
|
2980
|
+
|
|
2981
|
+
```ts
|
|
2982
|
+
const mem = ai.memory({
|
|
2983
|
+
episodic: { embedder, store, recencyWeight: 0.3, halfLifeMs: 7 * 24 * 60 * 60 * 1000 },
|
|
2984
|
+
procedural: { embedder, store, reinforcementWeight: 0.3 },
|
|
2985
|
+
defaultTier: "episodic",
|
|
2986
|
+
});
|
|
2987
|
+
|
|
2988
|
+
await mem.remember({ text: "Refunded order 5821 after a cracked-item complaint.", tier: "episodic" });
|
|
2989
|
+
await mem.remember({ id: "esc", text: "Escalate refunds over $500 to a human.", tier: "procedural" });
|
|
2990
|
+
await mem.remember({ id: "esc", text: "Escalate refunds over $500 to a human.", tier: "procedural" }); // reinforce → uses 1→2
|
|
2991
|
+
```
|
|
2992
|
+
|
|
2993
|
+
- **episodic** — stamps each entry with the remember time and decays its recency on an exponential half-life; at equal similarity a recent episode wins. `recencyWeight: 0` → pure similarity. The similarity `threshold` still gates relevance (recency never surfaces an irrelevant-but-recent episode). `now` is injectable for deterministic tests.
|
|
2994
|
+
- **procedural** — keeps a per-procedure use count; re-remembering (same `id`, or same text → same derived id) **reinforces** it with diminishing returns. Recall is side-effect-free.
|
|
2995
|
+
- Each vector tier defaults to its own namespace (`ai.memory.semantic` / `.episodic` / `.procedural`) so they don't collide on a shared driver; override with `namespace`.
|
|
2996
|
+
|
|
2997
|
+
## The three methods
|
|
2998
|
+
|
|
2999
|
+
### `remember(items)`
|
|
3000
|
+
|
|
3001
|
+
```ts
|
|
3002
|
+
await mem.remember({ text: "User is on the Enterprise plan.", tier: "semantic", metadata: { source: "crm" } });
|
|
3003
|
+
await mem.remember([{ text: "a" }, { text: "b", tier: "working" }]); // batch
|
|
3004
|
+
```
|
|
3005
|
+
|
|
3006
|
+
A `MemoryItem` is `{ text, tier?, id?, metadata? }`. `text` is the only required field — it's what gets embedded (semantic) and surfaced back on recall. `tier` defaults to the factory `defaultTier`. Semantic items are embedded + indexed; working items append to the in-run buffer. **Re-remembering an item whose id (explicit or text-derived) already exists overwrites in place rather than duplicating.** `metadata` is an opaque bag round-tripped verbatim onto the recalled memory.
|
|
3007
|
+
|
|
3008
|
+
### `recall(query, options?)`
|
|
3009
|
+
|
|
3010
|
+
```ts
|
|
3011
|
+
const hits = await mem.recall("which plan is the user on?", {
|
|
3012
|
+
k: 5, // cap result count (defaults to factory k)
|
|
3013
|
+
threshold: 0.75, // raise the semantic floor for this call
|
|
3014
|
+
tier: "semantic", // restrict to one tier; omit to query every enabled tier
|
|
3015
|
+
});
|
|
3016
|
+
|
|
3017
|
+
for (const hit of hits) {
|
|
3018
|
+
hit.id; hit.text; hit.tier; hit.score; hit.metadata;
|
|
3019
|
+
}
|
|
3020
|
+
```
|
|
3021
|
+
|
|
3022
|
+
Returns `RecalledMemory[]` scored and ordered by descending relevance. By default queries every enabled tier and merges. `score` is in `[0,1]` for **every** tier — cosine similarity (semantic), a recency proxy (working, most-recent = 1), similarity×recency (episodic), or similarity×reinforcement (procedural) — so a mixed recall set sorts on one field without special-casing the tier. Returns `[]` when nothing clears the threshold — never throws on "no hits".
|
|
3023
|
+
|
|
3024
|
+
**Memory never mutates the prompt.** `recall()` hands you scored entries; surfacing the recalled text (system prefix, a synthesized "what you remember" block, …) is YOUR call so the injection point stays explicit.
|
|
3025
|
+
|
|
3026
|
+
### `clear(tier?)`
|
|
3027
|
+
|
|
3028
|
+
```ts
|
|
3029
|
+
await mem.clear(); // every tier
|
|
3030
|
+
await mem.clear("working"); // just working — e.g. at session end, keeping durable recall
|
|
3031
|
+
```
|
|
3032
|
+
|
|
3033
|
+
## Wiring into an orchestrator
|
|
3034
|
+
|
|
3035
|
+
Pass the store as `ai.orchestrator({ memory })` to recall before each turn's dispatch and remember the settled outcome after. Recalled memories land in `ctx.context[injectKey]` (default `"memories"`). See [`@warlock.js/ai/run-orchestrator/SKILL.md`](@warlock.js/ai/run-orchestrator/SKILL.md) for the per-turn `memory` field, `recall.k: 0` (write-only), `remember: false` (read-only), and `rememberTier`.
|
|
3036
|
+
|
|
3037
|
+
## Picking a vector driver
|
|
3038
|
+
|
|
3039
|
+
The semantic tier delegates similarity entirely to the `CacheDriver`:
|
|
3040
|
+
- **Dev / tests** — `new MemoryCacheDriver()` (zero config, O(N) scan; fine up to a few thousand entries).
|
|
3041
|
+
- **Production** — a driver with a real ANN index: `pg` with pgvector, `redis` with RediSearch.
|
|
3042
|
+
|
|
3043
|
+
Drivers without similarity support throw `CacheUnsupportedError` from `set({ vector })` / `similar()`. See [`@warlock.js/cache/cache-basics/SKILL.md`](@warlock.js/cache/cache-basics/SKILL.md).
|
|
3044
|
+
|
|
3045
|
+
## See also
|
|
3046
|
+
|
|
3047
|
+
- [`@warlock.js/ai/run-orchestrator/SKILL.md`](@warlock.js/ai/run-orchestrator/SKILL.md) — the `memory` field on a session
|
|
3048
|
+
- [`@warlock.js/ai/embed-text/SKILL.md`](@warlock.js/ai/embed-text/SKILL.md) — the embedder the semantic tier needs
|
|
3049
|
+
- [`@warlock.js/ai/attach-ai-middleware/SKILL.md`](@warlock.js/ai/attach-ai-middleware/SKILL.md) — `semanticCache`, the sibling `.similar()` consumer
|
|
3050
|
+
- [`@warlock.js/cache/cache-basics/SKILL.md`](@warlock.js/cache/cache-basics/SKILL.md) — vector driver catalog
|
|
3051
|
+
|
|
3052
|
+
|
|
2161
3053
|
## write-system-prompt `@warlock.js/ai/write-system-prompt/SKILL.md`
|
|
2162
3054
|
|
|
2163
3055
|
---
|
|
2164
3056
|
name: write-system-prompt
|
|
2165
|
-
description: 'Compose system prompts via ai.systemPrompt() / ai.persona() / ai.instruction() — immutable builders with {{placeholder}} substitution. Triggers: `ai.systemPrompt`, `ai.persona`, `ai.instruction`, `SystemPromptBlockContract`, `PersonaContract`, `InstructionContract`, `placeholders`, `{{placeholder|default}}`; ''write a system prompt'', ''compose persona + instructions'', ''per-call prompt override'', ''mustache placeholder''; typical import `import { ai } from "@warlock.js/ai"`. Skip: agent factory wiring — `@warlock.js/ai/run-ai-agent/SKILL.md`; competing libs `langchain` `PromptTemplate`, raw f-strings.'
|
|
3057
|
+
description: 'Compose system prompts via ai.systemPrompt() / ai.persona() / ai.instruction() — immutable builders with {{placeholder}} substitution, plus ai.systemPrompt.fromFile(path) to seed from a file read once at construction. Triggers: `ai.systemPrompt`, `ai.systemPrompt.fromFile`, `ai.persona`, `ai.instruction`, `SystemPromptBlockContract`, `PersonaContract`, `InstructionContract`, `placeholders`, `{{placeholder|default}}`, `InvalidRequestError`; ''write a system prompt'', ''compose persona + instructions'', ''prompt from a file'', ''per-call prompt override'', ''mustache placeholder''; typical import `import { ai } from "@warlock.js/ai"`. Skip: agent factory wiring — `@warlock.js/ai/run-ai-agent/SKILL.md`; competing libs `langchain` `PromptTemplate`, raw f-strings.'
|
|
2166
3058
|
---
|
|
2167
3059
|
|
|
2168
3060
|
# System prompts — immutable builders
|
|
@@ -2177,6 +3069,7 @@ import { ai } from "@warlock.js/ai";
|
|
|
2177
3069
|
ai.systemPrompt(); // empty — chain .persona(), .instruction() onto it
|
|
2178
3070
|
ai.systemPrompt("literal text"); // one-shot string form
|
|
2179
3071
|
ai.systemPrompt([block1, block2]); // array form — blocks render in declaration order
|
|
3072
|
+
ai.systemPrompt.fromFile(path); // seed from a file read once at construction
|
|
2180
3073
|
|
|
2181
3074
|
ai.persona(text); // PersonaContract block
|
|
2182
3075
|
ai.instruction(text); // InstructionContract block
|
|
@@ -2213,6 +3106,18 @@ ai.systemPrompt([
|
|
|
2213
3106
|
]);
|
|
2214
3107
|
```
|
|
2215
3108
|
|
|
3109
|
+
### From a file — `ai.systemPrompt.fromFile(path)`
|
|
3110
|
+
|
|
3111
|
+
Read a prompt template from disk ONCE, synchronously, at construction. The file's UTF-8 contents seed one instruction block — so `{{placeholders}}` inside the file resolve at `resolve()` time and the result forks with further `.persona()` / `.instruction()` calls:
|
|
3112
|
+
|
|
3113
|
+
```ts
|
|
3114
|
+
const prompt = ai.systemPrompt.fromFile("./prompts/support-agent.md");
|
|
3115
|
+
const localized = prompt.instruction("Respond in {{language|English}}.");
|
|
3116
|
+
localized.resolve({ language: "Arabic" });
|
|
3117
|
+
```
|
|
3118
|
+
|
|
3119
|
+
One-shot by design (never re-read on `resolve()`). Throws `InvalidRequestError` when the file can't be read — a path typo fails loudly at construction instead of silently producing an empty prompt. `ai.systemPrompt.fromFile(path)` === `SystemPrompt.fromFile(path)`.
|
|
3120
|
+
|
|
2216
3121
|
## Block ordering
|
|
2217
3122
|
|
|
2218
3123
|
`SystemPrompt` stores `blocks: readonly SystemPromptBlockContract[]` — not separate persona + instructions fields. Rendering honors insertion order.
|