@warlock.js/ai 4.2.10 → 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.
Files changed (427) hide show
  1. package/CHANGELOG.md +61 -1
  2. package/cjs/index.cjs +10151 -4625
  3. package/cjs/index.cjs.map +1 -1
  4. package/cjs/magic-string.es-BtxW4VqG.cjs +1015 -0
  5. package/cjs/magic-string.es-BtxW4VqG.cjs.map +1 -0
  6. package/cjs/matcher-logic-SBnzYohQ.cjs +217 -0
  7. package/cjs/matcher-logic-SBnzYohQ.cjs.map +1 -0
  8. package/cjs/matchers-BBh3gyB-.cjs +13739 -0
  9. package/cjs/matchers-BBh3gyB-.cjs.map +1 -0
  10. package/esm/agent/agent-config.type.d.mts +19 -6
  11. package/esm/agent/agent-config.type.d.mts.map +1 -1
  12. package/esm/agent/agent.d.mts.map +1 -1
  13. package/esm/agent/agent.mjs +17 -6
  14. package/esm/agent/agent.mjs.map +1 -1
  15. package/esm/agent/index.d.mts +2 -1
  16. package/esm/agent/index.mjs +1 -0
  17. package/esm/agent/spawn-sub-agent.d.mts +87 -0
  18. package/esm/agent/spawn-sub-agent.d.mts.map +1 -0
  19. package/esm/agent/spawn-sub-agent.mjs +68 -0
  20. package/esm/agent/spawn-sub-agent.mjs.map +1 -0
  21. package/esm/ai.d.mts +58 -3
  22. package/esm/ai.d.mts.map +1 -1
  23. package/esm/ai.mjs +58 -3
  24. package/esm/ai.mjs.map +1 -1
  25. package/esm/batch/batch.d.mts +43 -0
  26. package/esm/batch/batch.d.mts.map +1 -0
  27. package/esm/batch/batch.mjs +179 -0
  28. package/esm/batch/batch.mjs.map +1 -0
  29. package/esm/batch/batch.type.d.mts +144 -0
  30. package/esm/batch/batch.type.d.mts.map +1 -0
  31. package/esm/batch/index.mjs +3 -0
  32. package/esm/batch/run-batch-item.mjs +100 -0
  33. package/esm/batch/run-batch-item.mjs.map +1 -0
  34. package/esm/batch/run-with-concurrency.mjs +39 -0
  35. package/esm/batch/run-with-concurrency.mjs.map +1 -0
  36. package/esm/checkpoint/index.d.mts +3 -0
  37. package/esm/checkpoint/memory.d.mts +21 -0
  38. package/esm/checkpoint/memory.d.mts.map +1 -0
  39. package/esm/checkpoint/memory.mjs +0 -0
  40. package/esm/checkpoint/memory.mjs.map +1 -0
  41. package/esm/checkpoint/pg.d.mts +37 -0
  42. package/esm/checkpoint/pg.d.mts.map +1 -0
  43. package/esm/checkpoint/pg.mjs +265 -0
  44. package/esm/checkpoint/pg.mjs.map +1 -0
  45. package/esm/checkpoint/redis.d.mts +39 -0
  46. package/esm/checkpoint/redis.d.mts.map +1 -0
  47. package/esm/checkpoint/redis.mjs +200 -0
  48. package/esm/checkpoint/redis.mjs.map +1 -0
  49. package/esm/config.d.mts +61 -14
  50. package/esm/config.d.mts.map +1 -1
  51. package/esm/config.mjs +25 -6
  52. package/esm/config.mjs.map +1 -1
  53. package/esm/contracts/agent/agent.contract.d.mts +43 -0
  54. package/esm/contracts/agent/agent.contract.d.mts.map +1 -1
  55. package/esm/contracts/agent/eval.type.d.mts +143 -0
  56. package/esm/contracts/agent/eval.type.d.mts.map +1 -0
  57. package/esm/contracts/agent/index.d.mts +1 -0
  58. package/esm/contracts/events/supervisor-events.type.d.mts +3 -3
  59. package/esm/contracts/fallback-model.contract.d.mts +65 -0
  60. package/esm/contracts/fallback-model.contract.d.mts.map +1 -0
  61. package/esm/contracts/index.d.mts +32 -12
  62. package/esm/contracts/memory/index.d.mts +4 -0
  63. package/esm/contracts/memory/memory-config.type.d.mts +150 -0
  64. package/esm/contracts/memory/memory-config.type.d.mts.map +1 -0
  65. package/esm/contracts/memory/memory-item.type.d.mts +64 -0
  66. package/esm/contracts/memory/memory-item.type.d.mts.map +1 -0
  67. package/esm/contracts/memory/memory.contract.d.mts +87 -0
  68. package/esm/contracts/memory/memory.contract.d.mts.map +1 -0
  69. package/esm/contracts/memory/recall-options.type.d.mts +33 -0
  70. package/esm/contracts/memory/recall-options.type.d.mts.map +1 -0
  71. package/esm/contracts/middleware/index.d.mts +2 -2
  72. package/esm/contracts/middleware/middleware-context.type.d.mts +42 -2
  73. package/esm/contracts/middleware/middleware-context.type.d.mts.map +1 -1
  74. package/esm/contracts/middleware/middleware.contract.d.mts +46 -2
  75. package/esm/contracts/middleware/middleware.contract.d.mts.map +1 -1
  76. package/esm/contracts/model.contract.d.mts +63 -2
  77. package/esm/contracts/model.contract.d.mts.map +1 -1
  78. package/esm/contracts/orchestrator/checkpoint-store.contract.d.mts +91 -0
  79. package/esm/contracts/orchestrator/checkpoint-store.contract.d.mts.map +1 -0
  80. package/esm/contracts/orchestrator/index.d.mts +8 -0
  81. package/esm/contracts/orchestrator/orchestrator-commands.type.d.mts +43 -0
  82. package/esm/contracts/orchestrator/orchestrator-commands.type.d.mts.map +1 -0
  83. package/esm/contracts/orchestrator/orchestrator-config.type.d.mts +170 -0
  84. package/esm/contracts/orchestrator/orchestrator-config.type.d.mts.map +1 -0
  85. package/esm/contracts/orchestrator/orchestrator-event.type.d.mts +118 -0
  86. package/esm/contracts/orchestrator/orchestrator-event.type.d.mts.map +1 -0
  87. package/esm/contracts/orchestrator/orchestrator-execute-options.type.d.mts +44 -0
  88. package/esm/contracts/orchestrator/orchestrator-execute-options.type.d.mts.map +1 -0
  89. package/esm/contracts/orchestrator/orchestrator.contract.d.mts +129 -0
  90. package/esm/contracts/orchestrator/orchestrator.contract.d.mts.map +1 -0
  91. package/esm/contracts/orchestrator/session.contract.d.mts +26 -0
  92. package/esm/contracts/orchestrator/session.contract.d.mts.map +1 -0
  93. package/esm/contracts/orchestrator/snapshot-store.contract.d.mts +89 -0
  94. package/esm/contracts/orchestrator/snapshot-store.contract.d.mts.map +1 -0
  95. package/esm/contracts/planner/index.d.mts +6 -0
  96. package/esm/contracts/planner/planner-capability.type.d.mts +39 -0
  97. package/esm/contracts/planner/planner-capability.type.d.mts.map +1 -0
  98. package/esm/contracts/planner/planner-config.type.d.mts +78 -0
  99. package/esm/contracts/planner/planner-config.type.d.mts.map +1 -0
  100. package/esm/contracts/planner/planner-execute-options.type.d.mts +43 -0
  101. package/esm/contracts/planner/planner-execute-options.type.d.mts.map +1 -0
  102. package/esm/contracts/planner/planner-plan.type.d.mts +48 -0
  103. package/esm/contracts/planner/planner-plan.type.d.mts.map +1 -0
  104. package/esm/contracts/planner/planner-result.type.d.mts +82 -0
  105. package/esm/contracts/planner/planner-result.type.d.mts.map +1 -0
  106. package/esm/contracts/planner/planner.contract.d.mts +60 -0
  107. package/esm/contracts/planner/planner.contract.d.mts.map +1 -0
  108. package/esm/contracts/result/base-report.type.d.mts +7 -2
  109. package/esm/contracts/result/base-report.type.d.mts.map +1 -1
  110. package/esm/contracts/result/base-report.type.mjs.map +1 -1
  111. package/esm/contracts/result/index.d.mts +2 -1
  112. package/esm/contracts/result/model-pricing.type.d.mts +10 -0
  113. package/esm/contracts/result/model-pricing.type.d.mts.map +1 -1
  114. package/esm/contracts/result/orchestrator-result.type.d.mts +139 -0
  115. package/esm/contracts/result/orchestrator-result.type.d.mts.map +1 -0
  116. package/esm/contracts/result/session-send-result.type.d.mts +12 -3
  117. package/esm/contracts/result/session-send-result.type.d.mts.map +1 -1
  118. package/esm/contracts/result/supervisor-result.type.d.mts +1 -1
  119. package/esm/contracts/result/usage.type.d.mts +24 -0
  120. package/esm/contracts/result/usage.type.d.mts.map +1 -1
  121. package/esm/contracts/sdk-adapter.contract.d.mts +1 -1
  122. package/esm/contracts/supervisor/dispatch-context.type.d.mts +3 -3
  123. package/esm/contracts/supervisor/evaluate-context.type.d.mts +1 -1
  124. package/esm/contracts/supervisor/index.d.mts +5 -5
  125. package/esm/contracts/supervisor/route-context.type.d.mts +2 -2
  126. package/esm/contracts/supervisor/supervisor-config.type.d.mts +55 -13
  127. package/esm/contracts/supervisor/supervisor-config.type.d.mts.map +1 -1
  128. package/esm/contracts/supervisor/supervisor-snapshot.type.d.mts +1 -1
  129. package/esm/contracts/supervisor/supervisor.contract.d.mts +9 -2
  130. package/esm/contracts/supervisor/supervisor.contract.d.mts.map +1 -1
  131. package/esm/contracts/workflow/index.d.mts +2 -2
  132. package/esm/contracts/workflow/workflow.contract.d.mts +28 -7
  133. package/esm/contracts/workflow/workflow.contract.d.mts.map +1 -1
  134. package/esm/errors/error-code.type.d.mts +1 -1
  135. package/esm/errors/index.d.mts +7 -0
  136. package/esm/errors/index.mjs +7 -0
  137. package/esm/errors/orchestrator-cancelled-error.d.mts +32 -0
  138. package/esm/errors/orchestrator-cancelled-error.d.mts.map +1 -0
  139. package/esm/errors/orchestrator-cancelled-error.mjs +31 -0
  140. package/esm/errors/orchestrator-cancelled-error.mjs.map +1 -0
  141. package/esm/errors/orchestrator-config-error.d.mts +26 -0
  142. package/esm/errors/orchestrator-config-error.d.mts.map +1 -0
  143. package/esm/errors/orchestrator-config-error.mjs +30 -0
  144. package/esm/errors/orchestrator-config-error.mjs.map +1 -0
  145. package/esm/errors/orchestrator-drift-error.d.mts +38 -0
  146. package/esm/errors/orchestrator-drift-error.d.mts.map +1 -0
  147. package/esm/errors/orchestrator-drift-error.mjs +37 -0
  148. package/esm/errors/orchestrator-drift-error.mjs.map +1 -0
  149. package/esm/errors/orchestrator-failed-error.d.mts +33 -0
  150. package/esm/errors/orchestrator-failed-error.d.mts.map +1 -0
  151. package/esm/errors/orchestrator-failed-error.mjs +36 -0
  152. package/esm/errors/orchestrator-failed-error.mjs.map +1 -0
  153. package/esm/errors/planner-cancelled-error.d.mts +33 -0
  154. package/esm/errors/planner-cancelled-error.d.mts.map +1 -0
  155. package/esm/errors/planner-cancelled-error.mjs +29 -0
  156. package/esm/errors/planner-cancelled-error.mjs.map +1 -0
  157. package/esm/errors/planner-failed-error.d.mts +40 -0
  158. package/esm/errors/planner-failed-error.d.mts.map +1 -0
  159. package/esm/errors/planner-failed-error.mjs +37 -0
  160. package/esm/errors/planner-failed-error.mjs.map +1 -0
  161. package/esm/errors/planner-plan-invalid-error.d.mts +21 -0
  162. package/esm/errors/planner-plan-invalid-error.d.mts.map +1 -0
  163. package/esm/errors/planner-plan-invalid-error.mjs +25 -0
  164. package/esm/errors/planner-plan-invalid-error.mjs.map +1 -0
  165. package/esm/eval/eval-runner.d.mts +17 -0
  166. package/esm/eval/eval-runner.d.mts.map +1 -0
  167. package/esm/eval/eval-runner.mjs +121 -0
  168. package/esm/eval/eval-runner.mjs.map +1 -0
  169. package/esm/eval/index.d.mts +29 -0
  170. package/esm/eval/index.d.mts.map +1 -0
  171. package/esm/eval/index.mjs +30 -0
  172. package/esm/eval/index.mjs.map +1 -0
  173. package/esm/eval/judge-scorer.d.mts +21 -0
  174. package/esm/eval/judge-scorer.d.mts.map +1 -0
  175. package/esm/eval/judge-scorer.mjs +87 -0
  176. package/esm/eval/judge-scorer.mjs.map +1 -0
  177. package/esm/eval/scorers.d.mts +50 -0
  178. package/esm/eval/scorers.d.mts.map +1 -0
  179. package/esm/eval/scorers.mjs +101 -0
  180. package/esm/eval/scorers.mjs.map +1 -0
  181. package/esm/index.d.mts +95 -30
  182. package/esm/index.mjs +66 -22
  183. package/esm/memory/derive-id.mjs +24 -0
  184. package/esm/memory/derive-id.mjs.map +1 -0
  185. package/esm/memory/episodic-memory.mjs +106 -0
  186. package/esm/memory/episodic-memory.mjs.map +1 -0
  187. package/esm/memory/index.d.mts +5 -0
  188. package/esm/memory/memory.d.mts +42 -0
  189. package/esm/memory/memory.d.mts.map +1 -0
  190. package/esm/memory/memory.mjs +166 -0
  191. package/esm/memory/memory.mjs.map +1 -0
  192. package/esm/memory/procedural-memory.mjs +103 -0
  193. package/esm/memory/procedural-memory.mjs.map +1 -0
  194. package/esm/memory/semantic-memory.mjs +80 -0
  195. package/esm/memory/semantic-memory.mjs.map +1 -0
  196. package/esm/memory/working-memory.mjs +62 -0
  197. package/esm/memory/working-memory.mjs.map +1 -0
  198. package/esm/middleware/builtins/budget-contract.type.d.mts +126 -0
  199. package/esm/middleware/builtins/budget-contract.type.d.mts.map +1 -0
  200. package/esm/middleware/builtins/budget.d.mts +71 -1
  201. package/esm/middleware/builtins/budget.d.mts.map +1 -1
  202. package/esm/middleware/builtins/budget.mjs +119 -4
  203. package/esm/middleware/builtins/budget.mjs.map +1 -1
  204. package/esm/middleware/builtins/semantic-cache.d.mts +1 -1
  205. package/esm/middleware/index.d.mts +2 -1
  206. package/esm/middleware/index.mjs +1 -1
  207. package/esm/middleware/pipeline.d.mts +9 -6
  208. package/esm/middleware/pipeline.d.mts.map +1 -1
  209. package/esm/middleware/pipeline.mjs.map +1 -1
  210. package/esm/mock/index.d.mts +1 -0
  211. package/esm/mock/index.mjs +1 -0
  212. package/esm/mock/mock-router.d.mts +63 -0
  213. package/esm/mock/mock-router.d.mts.map +1 -0
  214. package/esm/mock/mock-router.mjs +58 -0
  215. package/esm/mock/mock-router.mjs.map +1 -0
  216. package/esm/model/fallback-model.d.mts +45 -0
  217. package/esm/model/fallback-model.d.mts.map +1 -0
  218. package/esm/model/fallback-model.mjs +218 -0
  219. package/esm/model/fallback-model.mjs.map +1 -0
  220. package/esm/model/index.d.mts +2 -0
  221. package/esm/model/index.mjs +3 -0
  222. package/esm/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.mjs +78 -0
  223. package/esm/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.mjs.map +1 -0
  224. package/esm/node_modules/@vitest/expect/dist/index.mjs +1473 -0
  225. package/esm/node_modules/@vitest/expect/dist/index.mjs.map +1 -0
  226. package/esm/node_modules/@vitest/pretty-format/dist/index.mjs +888 -0
  227. package/esm/node_modules/@vitest/pretty-format/dist/index.mjs.map +1 -0
  228. package/esm/node_modules/@vitest/runner/dist/chunk-artifact.mjs +1533 -0
  229. package/esm/node_modules/@vitest/runner/dist/chunk-artifact.mjs.map +1 -0
  230. package/esm/node_modules/@vitest/runner/dist/index.mjs +3 -0
  231. package/esm/node_modules/@vitest/runner/dist/utils.mjs +3 -0
  232. package/esm/node_modules/@vitest/snapshot/dist/index.mjs +922 -0
  233. package/esm/node_modules/@vitest/snapshot/dist/index.mjs.map +1 -0
  234. package/esm/node_modules/@vitest/spy/dist/index.mjs +386 -0
  235. package/esm/node_modules/@vitest/spy/dist/index.mjs.map +1 -0
  236. package/esm/node_modules/@vitest/utils/dist/chunk-pathe.M-eThtNZ.mjs +82 -0
  237. package/esm/node_modules/@vitest/utils/dist/chunk-pathe.M-eThtNZ.mjs.map +1 -0
  238. package/esm/node_modules/@vitest/utils/dist/diff.mjs +1357 -0
  239. package/esm/node_modules/@vitest/utils/dist/diff.mjs.map +1 -0
  240. package/esm/node_modules/@vitest/utils/dist/display.mjs +559 -0
  241. package/esm/node_modules/@vitest/utils/dist/display.mjs.map +1 -0
  242. package/esm/node_modules/@vitest/utils/dist/error.mjs +38 -0
  243. package/esm/node_modules/@vitest/utils/dist/error.mjs.map +1 -0
  244. package/esm/node_modules/@vitest/utils/dist/helpers.mjs +181 -0
  245. package/esm/node_modules/@vitest/utils/dist/helpers.mjs.map +1 -0
  246. package/esm/node_modules/@vitest/utils/dist/offset.mjs +27 -0
  247. package/esm/node_modules/@vitest/utils/dist/offset.mjs.map +1 -0
  248. package/esm/node_modules/@vitest/utils/dist/serialize.mjs +77 -0
  249. package/esm/node_modules/@vitest/utils/dist/serialize.mjs.map +1 -0
  250. package/esm/node_modules/@vitest/utils/dist/source-map.mjs +374 -0
  251. package/esm/node_modules/@vitest/utils/dist/source-map.mjs.map +1 -0
  252. package/esm/node_modules/@vitest/utils/dist/timers.mjs +37 -0
  253. package/esm/node_modules/@vitest/utils/dist/timers.mjs.map +1 -0
  254. package/esm/node_modules/chai/index.mjs +2973 -0
  255. package/esm/node_modules/chai/index.mjs.map +1 -0
  256. package/esm/node_modules/magic-string/dist/magic-string.es.mjs +940 -0
  257. package/esm/node_modules/magic-string/dist/magic-string.es.mjs.map +1 -0
  258. package/esm/node_modules/tinyrainbow/dist/index.mjs +87 -0
  259. package/esm/node_modules/tinyrainbow/dist/index.mjs.map +1 -0
  260. package/esm/node_modules/vitest/dist/chunks/_commonjsHelpers.D26ty3Ew.mjs +6 -0
  261. package/esm/node_modules/vitest/dist/chunks/_commonjsHelpers.D26ty3Ew.mjs.map +1 -0
  262. package/esm/node_modules/vitest/dist/chunks/rpc.MzXet3jl.mjs +52 -0
  263. package/esm/node_modules/vitest/dist/chunks/rpc.MzXet3jl.mjs.map +1 -0
  264. package/esm/node_modules/vitest/dist/chunks/test.DNmyFkvJ.mjs +2697 -0
  265. package/esm/node_modules/vitest/dist/chunks/test.DNmyFkvJ.mjs.map +1 -0
  266. package/esm/node_modules/vitest/dist/chunks/utils.BX5Fg8C4.mjs +45 -0
  267. package/esm/node_modules/vitest/dist/chunks/utils.BX5Fg8C4.mjs.map +1 -0
  268. package/esm/orchestrator/as-tool.d.mts +42 -0
  269. package/esm/orchestrator/as-tool.d.mts.map +1 -0
  270. package/esm/orchestrator/as-tool.mjs +98 -0
  271. package/esm/orchestrator/as-tool.mjs.map +1 -0
  272. package/esm/orchestrator/checkpoint.mjs +75 -0
  273. package/esm/orchestrator/checkpoint.mjs.map +1 -0
  274. package/esm/orchestrator/commands.d.mts +38 -0
  275. package/esm/orchestrator/commands.d.mts.map +1 -0
  276. package/esm/orchestrator/commands.mjs +34 -0
  277. package/esm/orchestrator/commands.mjs.map +1 -0
  278. package/esm/orchestrator/compaction.mjs +206 -0
  279. package/esm/orchestrator/compaction.mjs.map +1 -0
  280. package/esm/orchestrator/dispatch.mjs +171 -0
  281. package/esm/orchestrator/dispatch.mjs.map +1 -0
  282. package/esm/orchestrator/emitter-port.type.d.mts +31 -0
  283. package/esm/orchestrator/emitter-port.type.d.mts.map +1 -0
  284. package/esm/orchestrator/emitter.d.mts +56 -0
  285. package/esm/orchestrator/emitter.d.mts.map +1 -0
  286. package/esm/orchestrator/emitter.mjs +85 -0
  287. package/esm/orchestrator/emitter.mjs.map +1 -0
  288. package/esm/orchestrator/engine-context.type.d.mts +56 -0
  289. package/esm/orchestrator/engine-context.type.d.mts.map +1 -0
  290. package/esm/orchestrator/execution.d.mts +116 -0
  291. package/esm/orchestrator/execution.d.mts.map +1 -0
  292. package/esm/orchestrator/execution.mjs +406 -0
  293. package/esm/orchestrator/execution.mjs.map +1 -0
  294. package/esm/orchestrator/index.d.mts +8 -0
  295. package/esm/orchestrator/index.mjs +10 -0
  296. package/esm/orchestrator/load.mjs +49 -0
  297. package/esm/orchestrator/load.mjs.map +1 -0
  298. package/esm/orchestrator/lock.mjs +75 -0
  299. package/esm/orchestrator/lock.mjs.map +1 -0
  300. package/esm/orchestrator/memory.d.mts +84 -0
  301. package/esm/orchestrator/memory.d.mts.map +1 -0
  302. package/esm/orchestrator/memory.mjs +141 -0
  303. package/esm/orchestrator/memory.mjs.map +1 -0
  304. package/esm/orchestrator/orchestrator-stream.d.mts +42 -0
  305. package/esm/orchestrator/orchestrator-stream.d.mts.map +1 -0
  306. package/esm/orchestrator/orchestrator-stream.mjs +98 -0
  307. package/esm/orchestrator/orchestrator-stream.mjs.map +1 -0
  308. package/esm/orchestrator/orchestrator.d.mts +38 -0
  309. package/esm/orchestrator/orchestrator.d.mts.map +1 -0
  310. package/esm/orchestrator/orchestrator.mjs +173 -0
  311. package/esm/orchestrator/orchestrator.mjs.map +1 -0
  312. package/esm/orchestrator/resume.mjs +74 -0
  313. package/esm/orchestrator/resume.mjs.map +1 -0
  314. package/esm/orchestrator/signature.d.mts +40 -0
  315. package/esm/orchestrator/signature.d.mts.map +1 -0
  316. package/esm/orchestrator/signature.mjs +120 -0
  317. package/esm/orchestrator/signature.mjs.map +1 -0
  318. package/esm/orchestrator/window.mjs +56 -0
  319. package/esm/orchestrator/window.mjs.map +1 -0
  320. package/esm/planner/index.d.mts +5 -0
  321. package/esm/planner/index.mjs +6 -0
  322. package/esm/planner/plan-prompt.d.mts +17 -0
  323. package/esm/planner/plan-prompt.d.mts.map +1 -0
  324. package/esm/planner/plan-prompt.mjs +30 -0
  325. package/esm/planner/plan-prompt.mjs.map +1 -0
  326. package/esm/planner/plan-schema.d.mts +27 -0
  327. package/esm/planner/plan-schema.d.mts.map +1 -0
  328. package/esm/planner/plan-schema.mjs +117 -0
  329. package/esm/planner/plan-schema.mjs.map +1 -0
  330. package/esm/planner/planner-run.d.mts +23 -0
  331. package/esm/planner/planner-run.d.mts.map +1 -0
  332. package/esm/planner/planner-run.mjs +344 -0
  333. package/esm/planner/planner-run.mjs.map +1 -0
  334. package/esm/planner/planner.d.mts +37 -0
  335. package/esm/planner/planner.d.mts.map +1 -0
  336. package/esm/planner/planner.mjs +120 -0
  337. package/esm/planner/planner.mjs.map +1 -0
  338. package/esm/planner/signature.d.mts +18 -0
  339. package/esm/planner/signature.d.mts.map +1 -0
  340. package/esm/planner/signature.mjs +27 -0
  341. package/esm/planner/signature.mjs.map +1 -0
  342. package/esm/snapshot/index.d.mts +3 -0
  343. package/esm/snapshot/memory.d.mts +26 -0
  344. package/esm/snapshot/memory.d.mts.map +1 -0
  345. package/esm/snapshot/memory.mjs +81 -0
  346. package/esm/snapshot/memory.mjs.map +1 -0
  347. package/esm/snapshot/pg.d.mts +41 -0
  348. package/esm/snapshot/pg.d.mts.map +1 -0
  349. package/esm/snapshot/pg.mjs +146 -0
  350. package/esm/snapshot/pg.mjs.map +1 -0
  351. package/esm/snapshot/redis.d.mts +42 -0
  352. package/esm/snapshot/redis.d.mts.map +1 -0
  353. package/esm/snapshot/redis.mjs +101 -0
  354. package/esm/snapshot/redis.mjs.map +1 -0
  355. package/esm/supervisor/as-tool.d.mts +0 -6
  356. package/esm/supervisor/as-tool.d.mts.map +1 -1
  357. package/esm/supervisor/as-tool.mjs +0 -6
  358. package/esm/supervisor/as-tool.mjs.map +1 -1
  359. package/esm/supervisor/execution.d.mts +43 -8
  360. package/esm/supervisor/execution.d.mts.map +1 -1
  361. package/esm/supervisor/execution.mjs +66 -16
  362. package/esm/supervisor/execution.mjs.map +1 -1
  363. package/esm/supervisor/fan-out.d.mts +65 -0
  364. package/esm/supervisor/fan-out.d.mts.map +1 -0
  365. package/esm/supervisor/fan-out.mjs +65 -0
  366. package/esm/supervisor/fan-out.mjs.map +1 -0
  367. package/esm/supervisor/index.d.mts +5 -3
  368. package/esm/supervisor/index.mjs +3 -1
  369. package/esm/supervisor/router-factory.d.mts +110 -0
  370. package/esm/supervisor/router-factory.d.mts.map +1 -0
  371. package/esm/supervisor/router-factory.mjs +141 -0
  372. package/esm/supervisor/router-factory.mjs.map +1 -0
  373. package/esm/supervisor/router-prompt.d.mts +1 -1
  374. package/esm/supervisor/snapshot.d.mts +4 -10
  375. package/esm/supervisor/snapshot.d.mts.map +1 -1
  376. package/esm/supervisor/snapshot.mjs +8 -16
  377. package/esm/supervisor/snapshot.mjs.map +1 -1
  378. package/esm/supervisor/supervisor.mjs +1 -0
  379. package/esm/supervisor/supervisor.mjs.map +1 -1
  380. package/esm/system-prompt/index.mjs +6 -0
  381. package/esm/system-prompt/system-prompt.d.mts +51 -3
  382. package/esm/system-prompt/system-prompt.d.mts.map +1 -1
  383. package/esm/system-prompt/system-prompt.mjs +52 -6
  384. package/esm/system-prompt/system-prompt.mjs.map +1 -1
  385. package/esm/testing/matcher-logic.d.mts +76 -0
  386. package/esm/testing/matcher-logic.d.mts.map +1 -0
  387. package/esm/testing/matcher-logic.mjs +144 -0
  388. package/esm/testing/matcher-logic.mjs.map +1 -0
  389. package/esm/testing/matchers.d.mts +48 -0
  390. package/esm/testing/matchers.d.mts.map +1 -0
  391. package/esm/testing/matchers.mjs +37 -0
  392. package/esm/testing/matchers.mjs.map +1 -0
  393. package/esm/testing/register-lazy.d.mts +20 -0
  394. package/esm/testing/register-lazy.d.mts.map +1 -0
  395. package/esm/testing/register-lazy.mjs +24 -0
  396. package/esm/testing/register-lazy.mjs.map +1 -0
  397. package/esm/tool/executable-as-tool.d.mts +87 -0
  398. package/esm/tool/executable-as-tool.d.mts.map +1 -0
  399. package/esm/tool/executable-as-tool.mjs +81 -0
  400. package/esm/tool/executable-as-tool.mjs.map +1 -0
  401. package/esm/tool/index.d.mts +2 -1
  402. package/esm/tool/index.mjs +1 -0
  403. package/esm/workflow/as-tool.mjs +0 -6
  404. package/esm/workflow/as-tool.mjs.map +1 -1
  405. package/esm/workflow/engine.mjs +2 -2
  406. package/esm/workflow/snapshot.mjs +13 -7
  407. package/esm/workflow/snapshot.mjs.map +1 -1
  408. package/esm/workflow/step-runner.mjs +1 -1
  409. package/esm/workflow/workflow.mjs +1 -0
  410. package/esm/workflow/workflow.mjs.map +1 -1
  411. package/llms-full.txt +947 -42
  412. package/llms.txt +13 -8
  413. package/package.json +3 -3
  414. package/skills/README.md +25 -5
  415. package/skills/ai-basics/SKILL.md +18 -7
  416. package/skills/ai-dx-helpers/SKILL.md +180 -0
  417. package/skills/attach-ai-middleware/SKILL.md +32 -3
  418. package/skills/handle-ai-errors/SKILL.md +20 -6
  419. package/skills/manage-ai-stores/SKILL.md +127 -0
  420. package/skills/persist-ai-data/SKILL.md +21 -10
  421. package/skills/pick-ai-provider/SKILL.md +46 -12
  422. package/skills/run-ai-agent/SKILL.md +51 -2
  423. package/skills/run-orchestrator/SKILL.md +198 -0
  424. package/skills/run-planner/SKILL.md +68 -0
  425. package/skills/run-supervisor/SKILL.md +47 -2
  426. package/skills/use-ai-memory/SKILL.md +124 -0
  427. 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 v2). Every primitive returns {data, error, usage, report}. Triggers: `ai.agent`, `ai.tool`, `ai.workflow`, `ai.supervisor`, `ai.systemPrompt`, `ExecuteResult`, `BaseReport`, `AIError`; ''which AI primitive do I use'', ''what is warlock ai'', ''pick an AI skill''; 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`.'
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() → statefulowns session/history/ctx (v2)
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 supervisors expose `.asTool()` so an agent can call them inside its tool loop; compose freely.
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
- | Choosing a provider adapter (OpenAI / OpenRouter / Anthropic / Bedrock / Ollama) | [`@warlock.js/ai/pick-ai-provider/SKILL.md`](@warlock.js/ai/pick-ai-provider/SKILL.md) |
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
- ## Workflow + middleware — what works today (v1)
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 / supervisor-level middleware does NOT exist yet.
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 default via ai.config({defaultStore}). Covers drift detection + three recovery paths. Triggers: `ai.config`, `defaultStore`, `snapshotStore`, `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: cache driver catalog — `@warlock.js/cache/cache-basics/SKILL.md`; competing libs `temporal`, `inngest`.'
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
- options.store ?? ai.config({ defaultStore }) ?? undefined
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 neither is set:
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
- ### Wiring
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
- ai.config({ defaultStore: cache.driver("redis", { client }) });
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 defaultStore
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 driver
1016
- snapshotStore: cache.driver("pg", { client: pgPool, table: "support_runs" }),
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 — two flags today, both optional (absent = treat as `false`):
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
- ## Pricingper-channel cost breakdown
1635
+ ## Cost truth pricing + token channels
1252
1636
 
1253
- Configure `pricing` on the model (or via the SDK registry) and every report carries `Usage.cost`:
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
- const openai = new OpenAISDK({
1257
- apiKey,
1258
- pricing: { "gpt-4o-mini": { input: 0.15, output: 0.6, cachedInput: 0.075 } },
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
- // { input: 0.0000045, output: 0.000192, cachedInput: 0.000009 } — USD breakdown
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
- Shape mirrors `ModelPricing` `{ input, output, cachedInput?, cachedOutput? }`. Per-model `pricing` wins over the SDK-level registry. `undefined` when no pricing configured honest absence over false zero.
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?: ToolContract<any, any>[],
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` (v2)** — when the *session* matters: long-running conversations.
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.