@warlock.js/ai 4.1.1

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 (490) hide show
  1. package/README.md +72 -0
  2. package/cjs/index.cjs +8625 -0
  3. package/cjs/index.cjs.map +1 -0
  4. package/esm/agent/agent-config.type.d.mts +160 -0
  5. package/esm/agent/agent-config.type.d.mts.map +1 -0
  6. package/esm/agent/agent-input-builder.mjs +111 -0
  7. package/esm/agent/agent-input-builder.mjs.map +1 -0
  8. package/esm/agent/agent-log-event.mjs +101 -0
  9. package/esm/agent/agent-log-event.mjs.map +1 -0
  10. package/esm/agent/agent-stream.d.mts +47 -0
  11. package/esm/agent/agent-stream.d.mts.map +1 -0
  12. package/esm/agent/agent-stream.mjs +82 -0
  13. package/esm/agent/agent-stream.mjs.map +1 -0
  14. package/esm/agent/agent-to-stream-event.mjs +86 -0
  15. package/esm/agent/agent-to-stream-event.mjs.map +1 -0
  16. package/esm/agent/agent.d.mts +38 -0
  17. package/esm/agent/agent.d.mts.map +1 -0
  18. package/esm/agent/agent.mjs +974 -0
  19. package/esm/agent/agent.mjs.map +1 -0
  20. package/esm/agent/index.d.mts +3 -0
  21. package/esm/agent/index.mjs +4 -0
  22. package/esm/agent/json-stream-guard.mjs +400 -0
  23. package/esm/agent/json-stream-guard.mjs.map +1 -0
  24. package/esm/ai.d.mts +64 -0
  25. package/esm/ai.d.mts.map +1 -0
  26. package/esm/ai.mjs +65 -0
  27. package/esm/ai.mjs.map +1 -0
  28. package/esm/config.d.mts +79 -0
  29. package/esm/config.d.mts.map +1 -0
  30. package/esm/config.mjs +43 -0
  31. package/esm/config.mjs.map +1 -0
  32. package/esm/contracts/agent/agent-options.type.d.mts +132 -0
  33. package/esm/contracts/agent/agent-options.type.d.mts.map +1 -0
  34. package/esm/contracts/agent/agent.contract.d.mts +92 -0
  35. package/esm/contracts/agent/agent.contract.d.mts.map +1 -0
  36. package/esm/contracts/agent/index.d.mts +2 -0
  37. package/esm/contracts/attachment.type.d.mts +100 -0
  38. package/esm/contracts/attachment.type.d.mts.map +1 -0
  39. package/esm/contracts/content-part.type.d.mts +55 -0
  40. package/esm/contracts/content-part.type.d.mts.map +1 -0
  41. package/esm/contracts/conversation-message.type.d.mts +51 -0
  42. package/esm/contracts/conversation-message.type.d.mts.map +1 -0
  43. package/esm/contracts/embedder.contract.d.mts +69 -0
  44. package/esm/contracts/embedder.contract.d.mts.map +1 -0
  45. package/esm/contracts/end.type.d.mts +53 -0
  46. package/esm/contracts/end.type.d.mts.map +1 -0
  47. package/esm/contracts/end.type.mjs +47 -0
  48. package/esm/contracts/end.type.mjs.map +1 -0
  49. package/esm/contracts/events/agent-events.type.d.mts +71 -0
  50. package/esm/contracts/events/agent-events.type.d.mts.map +1 -0
  51. package/esm/contracts/events/complete-event.type.d.mts +33 -0
  52. package/esm/contracts/events/complete-event.type.d.mts.map +1 -0
  53. package/esm/contracts/events/event-identity.type.d.mts +30 -0
  54. package/esm/contracts/events/event-identity.type.d.mts.map +1 -0
  55. package/esm/contracts/events/event-map.type.d.mts +80 -0
  56. package/esm/contracts/events/event-map.type.d.mts.map +1 -0
  57. package/esm/contracts/events/index.d.mts +7 -0
  58. package/esm/contracts/events/supervisor-events.type.d.mts +162 -0
  59. package/esm/contracts/events/supervisor-events.type.d.mts.map +1 -0
  60. package/esm/contracts/events/usage-event.type.d.mts +39 -0
  61. package/esm/contracts/events/usage-event.type.d.mts.map +1 -0
  62. package/esm/contracts/events/workflow-events.type.d.mts +51 -0
  63. package/esm/contracts/events/workflow-events.type.d.mts.map +1 -0
  64. package/esm/contracts/executable.contract.d.mts +25 -0
  65. package/esm/contracts/executable.contract.d.mts.map +1 -0
  66. package/esm/contracts/finish-reason.type.d.mts +17 -0
  67. package/esm/contracts/finish-reason.type.d.mts.map +1 -0
  68. package/esm/contracts/index.d.mts +63 -0
  69. package/esm/contracts/middleware/index.d.mts +3 -0
  70. package/esm/contracts/middleware/middleware-context.type.d.mts +99 -0
  71. package/esm/contracts/middleware/middleware-context.type.d.mts.map +1 -0
  72. package/esm/contracts/middleware/middleware-state.type.d.mts +34 -0
  73. package/esm/contracts/middleware/middleware-state.type.d.mts.map +1 -0
  74. package/esm/contracts/middleware/middleware.contract.d.mts +140 -0
  75. package/esm/contracts/middleware/middleware.contract.d.mts.map +1 -0
  76. package/esm/contracts/model-tool-call-request.type.d.mts +58 -0
  77. package/esm/contracts/model-tool-call-request.type.d.mts.map +1 -0
  78. package/esm/contracts/model.contract.d.mts +164 -0
  79. package/esm/contracts/model.contract.d.mts.map +1 -0
  80. package/esm/contracts/placeholders.type.d.mts +23 -0
  81. package/esm/contracts/placeholders.type.d.mts.map +1 -0
  82. package/esm/contracts/result/agent-result.type.d.mts +33 -0
  83. package/esm/contracts/result/agent-result.type.d.mts.map +1 -0
  84. package/esm/contracts/result/attempt-entry.type.d.mts +40 -0
  85. package/esm/contracts/result/attempt-entry.type.d.mts.map +1 -0
  86. package/esm/contracts/result/base-report.type.d.mts +137 -0
  87. package/esm/contracts/result/base-report.type.d.mts.map +1 -0
  88. package/esm/contracts/result/base-report.type.mjs +17 -0
  89. package/esm/contracts/result/base-report.type.mjs.map +1 -0
  90. package/esm/contracts/result/base-result.type.d.mts +26 -0
  91. package/esm/contracts/result/base-result.type.d.mts.map +1 -0
  92. package/esm/contracts/result/execute-result.type.d.mts +40 -0
  93. package/esm/contracts/result/execute-result.type.d.mts.map +1 -0
  94. package/esm/contracts/result/execution-report.type.d.mts +50 -0
  95. package/esm/contracts/result/execution-report.type.d.mts.map +1 -0
  96. package/esm/contracts/result/index.d.mts +14 -0
  97. package/esm/contracts/result/llm-trip.type.d.mts +36 -0
  98. package/esm/contracts/result/llm-trip.type.d.mts.map +1 -0
  99. package/esm/contracts/result/model-pricing.type.d.mts +51 -0
  100. package/esm/contracts/result/model-pricing.type.d.mts.map +1 -0
  101. package/esm/contracts/result/session-send-result.type.d.mts +29 -0
  102. package/esm/contracts/result/session-send-result.type.d.mts.map +1 -0
  103. package/esm/contracts/result/step-result.type.d.mts +43 -0
  104. package/esm/contracts/result/step-result.type.d.mts.map +1 -0
  105. package/esm/contracts/result/supervisor-result.type.d.mts +86 -0
  106. package/esm/contracts/result/supervisor-result.type.d.mts.map +1 -0
  107. package/esm/contracts/result/tool-call.type.d.mts +44 -0
  108. package/esm/contracts/result/tool-call.type.d.mts.map +1 -0
  109. package/esm/contracts/result/usage.type.d.mts +57 -0
  110. package/esm/contracts/result/usage.type.d.mts.map +1 -0
  111. package/esm/contracts/result/workflow-result.type.d.mts +45 -0
  112. package/esm/contracts/result/workflow-result.type.d.mts.map +1 -0
  113. package/esm/contracts/sdk-adapter.contract.d.mts +77 -0
  114. package/esm/contracts/sdk-adapter.contract.d.mts.map +1 -0
  115. package/esm/contracts/stream/stream-event.type.d.mts +68 -0
  116. package/esm/contracts/stream/stream-event.type.d.mts.map +1 -0
  117. package/esm/contracts/stream/stream.contract.d.mts +61 -0
  118. package/esm/contracts/stream/stream.contract.d.mts.map +1 -0
  119. package/esm/contracts/streaming-tool-guard-config.type.d.mts +78 -0
  120. package/esm/contracts/streaming-tool-guard-config.type.d.mts.map +1 -0
  121. package/esm/contracts/supervisor/ack-entry.type.d.mts +97 -0
  122. package/esm/contracts/supervisor/ack-entry.type.d.mts.map +1 -0
  123. package/esm/contracts/supervisor/classifier-context.type.d.mts +189 -0
  124. package/esm/contracts/supervisor/classifier-context.type.d.mts.map +1 -0
  125. package/esm/contracts/supervisor/dispatch-context.type.d.mts +235 -0
  126. package/esm/contracts/supervisor/dispatch-context.type.d.mts.map +1 -0
  127. package/esm/contracts/supervisor/evaluate-context.type.d.mts +124 -0
  128. package/esm/contracts/supervisor/evaluate-context.type.d.mts.map +1 -0
  129. package/esm/contracts/supervisor/index.d.mts +16 -0
  130. package/esm/contracts/supervisor/intent-entry.type.d.mts +254 -0
  131. package/esm/contracts/supervisor/intent-entry.type.d.mts.map +1 -0
  132. package/esm/contracts/supervisor/iteration-snapshot.type.d.mts +117 -0
  133. package/esm/contracts/supervisor/iteration-snapshot.type.d.mts.map +1 -0
  134. package/esm/contracts/supervisor/next.type.d.mts +25 -0
  135. package/esm/contracts/supervisor/next.type.d.mts.map +1 -0
  136. package/esm/contracts/supervisor/route-context.type.d.mts +91 -0
  137. package/esm/contracts/supervisor/route-context.type.d.mts.map +1 -0
  138. package/esm/contracts/supervisor/router-entry.type.d.mts +67 -0
  139. package/esm/contracts/supervisor/router-entry.type.d.mts.map +1 -0
  140. package/esm/contracts/supervisor/supervisor-config.type.d.mts +356 -0
  141. package/esm/contracts/supervisor/supervisor-config.type.d.mts.map +1 -0
  142. package/esm/contracts/supervisor/supervisor-execute-options.type.d.mts +90 -0
  143. package/esm/contracts/supervisor/supervisor-execute-options.type.d.mts.map +1 -0
  144. package/esm/contracts/supervisor/supervisor-input.type.d.mts +24 -0
  145. package/esm/contracts/supervisor/supervisor-input.type.d.mts.map +1 -0
  146. package/esm/contracts/supervisor/supervisor-snapshot.type.d.mts +49 -0
  147. package/esm/contracts/supervisor/supervisor-snapshot.type.d.mts.map +1 -0
  148. package/esm/contracts/supervisor/supervisor-stream-event.type.d.mts +16 -0
  149. package/esm/contracts/supervisor/supervisor-stream-event.type.d.mts.map +1 -0
  150. package/esm/contracts/supervisor/supervisor.contract.d.mts +126 -0
  151. package/esm/contracts/supervisor/supervisor.contract.d.mts.map +1 -0
  152. package/esm/contracts/system-prompt.contract.d.mts +118 -0
  153. package/esm/contracts/system-prompt.contract.d.mts.map +1 -0
  154. package/esm/contracts/tool.contract.d.mts +161 -0
  155. package/esm/contracts/tool.contract.d.mts.map +1 -0
  156. package/esm/contracts/workflow/index.d.mts +6 -0
  157. package/esm/contracts/workflow/next-step-result.type.d.mts +17 -0
  158. package/esm/contracts/workflow/next-step-result.type.d.mts.map +1 -0
  159. package/esm/contracts/workflow/retry-config.type.d.mts +19 -0
  160. package/esm/contracts/workflow/retry-config.type.d.mts.map +1 -0
  161. package/esm/contracts/workflow/step.contract.d.mts +189 -0
  162. package/esm/contracts/workflow/step.contract.d.mts.map +1 -0
  163. package/esm/contracts/workflow/workflow-context.type.d.mts +45 -0
  164. package/esm/contracts/workflow/workflow-context.type.d.mts.map +1 -0
  165. package/esm/contracts/workflow/workflow-snapshot.type.d.mts +24 -0
  166. package/esm/contracts/workflow/workflow-snapshot.type.d.mts.map +1 -0
  167. package/esm/contracts/workflow/workflow.contract.d.mts +142 -0
  168. package/esm/contracts/workflow/workflow.contract.d.mts.map +1 -0
  169. package/esm/errors/agent-cancelled-error.d.mts +49 -0
  170. package/esm/errors/agent-cancelled-error.d.mts.map +1 -0
  171. package/esm/errors/agent-cancelled-error.mjs +43 -0
  172. package/esm/errors/agent-cancelled-error.mjs.map +1 -0
  173. package/esm/errors/agent-execution-error.d.mts +32 -0
  174. package/esm/errors/agent-execution-error.d.mts.map +1 -0
  175. package/esm/errors/agent-execution-error.mjs +35 -0
  176. package/esm/errors/agent-execution-error.mjs.map +1 -0
  177. package/esm/errors/agent-max-trips-error.d.mts +45 -0
  178. package/esm/errors/agent-max-trips-error.d.mts.map +1 -0
  179. package/esm/errors/agent-max-trips-error.mjs +41 -0
  180. package/esm/errors/agent-max-trips-error.mjs.map +1 -0
  181. package/esm/errors/ai-error.d.mts +94 -0
  182. package/esm/errors/ai-error.d.mts.map +1 -0
  183. package/esm/errors/ai-error.mjs +68 -0
  184. package/esm/errors/ai-error.mjs.map +1 -0
  185. package/esm/errors/budget-exceeded-error.d.mts +41 -0
  186. package/esm/errors/budget-exceeded-error.d.mts.map +1 -0
  187. package/esm/errors/budget-exceeded-error.mjs +31 -0
  188. package/esm/errors/budget-exceeded-error.mjs.map +1 -0
  189. package/esm/errors/content-filter-error.d.mts +33 -0
  190. package/esm/errors/content-filter-error.d.mts.map +1 -0
  191. package/esm/errors/content-filter-error.mjs +28 -0
  192. package/esm/errors/content-filter-error.mjs.map +1 -0
  193. package/esm/errors/context-length-exceeded-error.d.mts +38 -0
  194. package/esm/errors/context-length-exceeded-error.d.mts.map +1 -0
  195. package/esm/errors/context-length-exceeded-error.mjs +31 -0
  196. package/esm/errors/context-length-exceeded-error.mjs.map +1 -0
  197. package/esm/errors/error-category.type.d.mts +30 -0
  198. package/esm/errors/error-category.type.d.mts.map +1 -0
  199. package/esm/errors/error-code.type.d.mts +19 -0
  200. package/esm/errors/error-code.type.d.mts.map +1 -0
  201. package/esm/errors/guardrail-violation-error.d.mts +55 -0
  202. package/esm/errors/guardrail-violation-error.d.mts.map +1 -0
  203. package/esm/errors/guardrail-violation-error.mjs +41 -0
  204. package/esm/errors/guardrail-violation-error.mjs.map +1 -0
  205. package/esm/errors/index.d.mts +29 -0
  206. package/esm/errors/index.mjs +29 -0
  207. package/esm/errors/invalid-request-error.d.mts +27 -0
  208. package/esm/errors/invalid-request-error.d.mts.map +1 -0
  209. package/esm/errors/invalid-request-error.mjs +31 -0
  210. package/esm/errors/invalid-request-error.mjs.map +1 -0
  211. package/esm/errors/max-iterations-error.d.mts +31 -0
  212. package/esm/errors/max-iterations-error.d.mts.map +1 -0
  213. package/esm/errors/max-iterations-error.mjs +32 -0
  214. package/esm/errors/max-iterations-error.mjs.map +1 -0
  215. package/esm/errors/max-steps-exceeded-error.d.mts +20 -0
  216. package/esm/errors/max-steps-exceeded-error.d.mts.map +1 -0
  217. package/esm/errors/max-steps-exceeded-error.mjs +21 -0
  218. package/esm/errors/max-steps-exceeded-error.mjs.map +1 -0
  219. package/esm/errors/provider-auth-error.d.mts +22 -0
  220. package/esm/errors/provider-auth-error.d.mts.map +1 -0
  221. package/esm/errors/provider-auth-error.mjs +26 -0
  222. package/esm/errors/provider-auth-error.mjs.map +1 -0
  223. package/esm/errors/provider-error.d.mts +38 -0
  224. package/esm/errors/provider-error.d.mts.map +1 -0
  225. package/esm/errors/provider-error.mjs +42 -0
  226. package/esm/errors/provider-error.mjs.map +1 -0
  227. package/esm/errors/provider-rate-limit-error.d.mts +34 -0
  228. package/esm/errors/provider-rate-limit-error.d.mts.map +1 -0
  229. package/esm/errors/provider-rate-limit-error.mjs +31 -0
  230. package/esm/errors/provider-rate-limit-error.mjs.map +1 -0
  231. package/esm/errors/provider-timeout-error.d.mts +22 -0
  232. package/esm/errors/provider-timeout-error.d.mts.map +1 -0
  233. package/esm/errors/provider-timeout-error.mjs +26 -0
  234. package/esm/errors/provider-timeout-error.mjs.map +1 -0
  235. package/esm/errors/quota-exceeded-error.d.mts +36 -0
  236. package/esm/errors/quota-exceeded-error.d.mts.map +1 -0
  237. package/esm/errors/quota-exceeded-error.mjs +40 -0
  238. package/esm/errors/quota-exceeded-error.mjs.map +1 -0
  239. package/esm/errors/routing-error.d.mts +23 -0
  240. package/esm/errors/routing-error.d.mts.map +1 -0
  241. package/esm/errors/routing-error.mjs +23 -0
  242. package/esm/errors/routing-error.mjs.map +1 -0
  243. package/esm/errors/schema-validation-error.d.mts +40 -0
  244. package/esm/errors/schema-validation-error.d.mts.map +1 -0
  245. package/esm/errors/schema-validation-error.mjs +37 -0
  246. package/esm/errors/schema-validation-error.mjs.map +1 -0
  247. package/esm/errors/step-failed-error.d.mts +20 -0
  248. package/esm/errors/step-failed-error.d.mts.map +1 -0
  249. package/esm/errors/step-failed-error.mjs +19 -0
  250. package/esm/errors/step-failed-error.mjs.map +1 -0
  251. package/esm/errors/supervisor-cancelled-error.d.mts +29 -0
  252. package/esm/errors/supervisor-cancelled-error.d.mts.map +1 -0
  253. package/esm/errors/supervisor-cancelled-error.mjs +29 -0
  254. package/esm/errors/supervisor-cancelled-error.mjs.map +1 -0
  255. package/esm/errors/supervisor-drift-error.d.mts +31 -0
  256. package/esm/errors/supervisor-drift-error.d.mts.map +1 -0
  257. package/esm/errors/supervisor-drift-error.mjs +30 -0
  258. package/esm/errors/supervisor-drift-error.mjs.map +1 -0
  259. package/esm/errors/supervisor-failed-error.d.mts +40 -0
  260. package/esm/errors/supervisor-failed-error.d.mts.map +1 -0
  261. package/esm/errors/supervisor-failed-error.mjs +43 -0
  262. package/esm/errors/supervisor-failed-error.mjs.map +1 -0
  263. package/esm/errors/supervisor-routing-error.d.mts +44 -0
  264. package/esm/errors/supervisor-routing-error.d.mts.map +1 -0
  265. package/esm/errors/supervisor-routing-error.mjs +39 -0
  266. package/esm/errors/supervisor-routing-error.mjs.map +1 -0
  267. package/esm/errors/tool-execution-error.d.mts +35 -0
  268. package/esm/errors/tool-execution-error.d.mts.map +1 -0
  269. package/esm/errors/tool-execution-error.mjs +32 -0
  270. package/esm/errors/tool-execution-error.mjs.map +1 -0
  271. package/esm/errors/workflow-cancelled-error.d.mts +23 -0
  272. package/esm/errors/workflow-cancelled-error.d.mts.map +1 -0
  273. package/esm/errors/workflow-cancelled-error.mjs +23 -0
  274. package/esm/errors/workflow-cancelled-error.mjs.map +1 -0
  275. package/esm/errors/workflow-drift-error.d.mts +25 -0
  276. package/esm/errors/workflow-drift-error.d.mts.map +1 -0
  277. package/esm/errors/workflow-drift-error.mjs +24 -0
  278. package/esm/errors/workflow-drift-error.mjs.map +1 -0
  279. package/esm/errors/workflow-error.d.mts +15 -0
  280. package/esm/errors/workflow-error.d.mts.map +1 -0
  281. package/esm/errors/workflow-error.mjs +18 -0
  282. package/esm/errors/workflow-error.mjs.map +1 -0
  283. package/esm/index.d.mts +137 -0
  284. package/esm/index.mjs +81 -0
  285. package/esm/middleware/builtins/budget.d.mts +103 -0
  286. package/esm/middleware/builtins/budget.d.mts.map +1 -0
  287. package/esm/middleware/builtins/budget.mjs +100 -0
  288. package/esm/middleware/builtins/budget.mjs.map +1 -0
  289. package/esm/middleware/builtins/guardrail.d.mts +92 -0
  290. package/esm/middleware/builtins/guardrail.d.mts.map +1 -0
  291. package/esm/middleware/builtins/guardrail.mjs +76 -0
  292. package/esm/middleware/builtins/guardrail.mjs.map +1 -0
  293. package/esm/middleware/builtins/semantic-cache.d.mts +106 -0
  294. package/esm/middleware/builtins/semantic-cache.d.mts.map +1 -0
  295. package/esm/middleware/builtins/semantic-cache.mjs +149 -0
  296. package/esm/middleware/builtins/semantic-cache.mjs.map +1 -0
  297. package/esm/middleware/helpers/compose.d.mts +42 -0
  298. package/esm/middleware/helpers/compose.d.mts.map +1 -0
  299. package/esm/middleware/helpers/compose.mjs +52 -0
  300. package/esm/middleware/helpers/compose.mjs.map +1 -0
  301. package/esm/middleware/helpers/for-tool.d.mts +55 -0
  302. package/esm/middleware/helpers/for-tool.d.mts.map +1 -0
  303. package/esm/middleware/helpers/for-tool.mjs +80 -0
  304. package/esm/middleware/helpers/for-tool.mjs.map +1 -0
  305. package/esm/middleware/helpers/index.d.mts +2 -0
  306. package/esm/middleware/index.d.mts +8 -0
  307. package/esm/middleware/index.mjs +10 -0
  308. package/esm/middleware/pipeline.d.mts +70 -0
  309. package/esm/middleware/pipeline.d.mts.map +1 -0
  310. package/esm/middleware/pipeline.mjs +100 -0
  311. package/esm/middleware/pipeline.mjs.map +1 -0
  312. package/esm/middleware/utils/extract-user-text.d.mts +33 -0
  313. package/esm/middleware/utils/extract-user-text.d.mts.map +1 -0
  314. package/esm/middleware/utils/extract-user-text.mjs +40 -0
  315. package/esm/middleware/utils/extract-user-text.mjs.map +1 -0
  316. package/esm/middleware/utils/index.d.mts +2 -0
  317. package/esm/middleware/utils/namespaced-state.d.mts +60 -0
  318. package/esm/middleware/utils/namespaced-state.d.mts.map +1 -0
  319. package/esm/middleware/utils/namespaced-state.mjs +60 -0
  320. package/esm/middleware/utils/namespaced-state.mjs.map +1 -0
  321. package/esm/mock/index.d.mts +4 -0
  322. package/esm/mock/index.mjs +5 -0
  323. package/esm/mock/mock-agent.d.mts +41 -0
  324. package/esm/mock/mock-agent.d.mts.map +1 -0
  325. package/esm/mock/mock-agent.mjs +39 -0
  326. package/esm/mock/mock-agent.mjs.map +1 -0
  327. package/esm/mock/mock-config.type.d.mts +31 -0
  328. package/esm/mock/mock-config.type.d.mts.map +1 -0
  329. package/esm/mock/mock-model.d.mts +94 -0
  330. package/esm/mock/mock-model.d.mts.map +1 -0
  331. package/esm/mock/mock-model.mjs +150 -0
  332. package/esm/mock/mock-model.mjs.map +1 -0
  333. package/esm/mock/mock-sdk.d.mts +25 -0
  334. package/esm/mock/mock-sdk.d.mts.map +1 -0
  335. package/esm/mock/mock-sdk.mjs +37 -0
  336. package/esm/mock/mock-sdk.mjs.map +1 -0
  337. package/esm/supervisor/as-tool.d.mts +45 -0
  338. package/esm/supervisor/as-tool.d.mts.map +1 -0
  339. package/esm/supervisor/as-tool.mjs +80 -0
  340. package/esm/supervisor/as-tool.mjs.map +1 -0
  341. package/esm/supervisor/cancellation.d.mts +12 -0
  342. package/esm/supervisor/cancellation.d.mts.map +1 -0
  343. package/esm/supervisor/cancellation.mjs +22 -0
  344. package/esm/supervisor/cancellation.mjs.map +1 -0
  345. package/esm/supervisor/decide.mjs +272 -0
  346. package/esm/supervisor/decide.mjs.map +1 -0
  347. package/esm/supervisor/emitter.d.mts +27 -0
  348. package/esm/supervisor/emitter.d.mts.map +1 -0
  349. package/esm/supervisor/emitter.mjs +47 -0
  350. package/esm/supervisor/emitter.mjs.map +1 -0
  351. package/esm/supervisor/entries.d.mts +141 -0
  352. package/esm/supervisor/entries.d.mts.map +1 -0
  353. package/esm/supervisor/entries.mjs +206 -0
  354. package/esm/supervisor/entries.mjs.map +1 -0
  355. package/esm/supervisor/execution.d.mts +637 -0
  356. package/esm/supervisor/execution.d.mts.map +1 -0
  357. package/esm/supervisor/execution.mjs +1868 -0
  358. package/esm/supervisor/execution.mjs.map +1 -0
  359. package/esm/supervisor/index.d.mts +11 -0
  360. package/esm/supervisor/index.mjs +12 -0
  361. package/esm/supervisor/router-prompt.d.mts +56 -0
  362. package/esm/supervisor/router-prompt.d.mts.map +1 -0
  363. package/esm/supervisor/router-prompt.mjs +52 -0
  364. package/esm/supervisor/router-prompt.mjs.map +1 -0
  365. package/esm/supervisor/signature.d.mts +28 -0
  366. package/esm/supervisor/signature.d.mts.map +1 -0
  367. package/esm/supervisor/signature.mjs +89 -0
  368. package/esm/supervisor/signature.mjs.map +1 -0
  369. package/esm/supervisor/snapshot.d.mts +54 -0
  370. package/esm/supervisor/snapshot.d.mts.map +1 -0
  371. package/esm/supervisor/snapshot.mjs +77 -0
  372. package/esm/supervisor/snapshot.mjs.map +1 -0
  373. package/esm/supervisor/supervisor-stream.d.mts +29 -0
  374. package/esm/supervisor/supervisor-stream.d.mts.map +1 -0
  375. package/esm/supervisor/supervisor-stream.mjs +89 -0
  376. package/esm/supervisor/supervisor-stream.mjs.map +1 -0
  377. package/esm/supervisor/supervisor.d.mts +27 -0
  378. package/esm/supervisor/supervisor.d.mts.map +1 -0
  379. package/esm/supervisor/supervisor.mjs +138 -0
  380. package/esm/supervisor/supervisor.mjs.map +1 -0
  381. package/esm/system-prompt/instruction.d.mts +52 -0
  382. package/esm/system-prompt/instruction.d.mts.map +1 -0
  383. package/esm/system-prompt/instruction.mjs +57 -0
  384. package/esm/system-prompt/instruction.mjs.map +1 -0
  385. package/esm/system-prompt/persona.d.mts +52 -0
  386. package/esm/system-prompt/persona.d.mts.map +1 -0
  387. package/esm/system-prompt/persona.mjs +57 -0
  388. package/esm/system-prompt/persona.mjs.map +1 -0
  389. package/esm/system-prompt/render-placeholders.d.mts +31 -0
  390. package/esm/system-prompt/render-placeholders.d.mts.map +1 -0
  391. package/esm/system-prompt/render-placeholders.mjs +58 -0
  392. package/esm/system-prompt/render-placeholders.mjs.map +1 -0
  393. package/esm/system-prompt/system-prompt.d.mts +117 -0
  394. package/esm/system-prompt/system-prompt.d.mts.map +1 -0
  395. package/esm/system-prompt/system-prompt.mjs +137 -0
  396. package/esm/system-prompt/system-prompt.mjs.map +1 -0
  397. package/esm/tool/index.d.mts +1 -0
  398. package/esm/tool/index.mjs +3 -0
  399. package/esm/tool/tool.d.mts +64 -0
  400. package/esm/tool/tool.d.mts.map +1 -0
  401. package/esm/tool/tool.mjs +184 -0
  402. package/esm/tool/tool.mjs.map +1 -0
  403. package/esm/utils/compute-cost.d.mts +58 -0
  404. package/esm/utils/compute-cost.d.mts.map +1 -0
  405. package/esm/utils/compute-cost.mjs +87 -0
  406. package/esm/utils/compute-cost.mjs.map +1 -0
  407. package/esm/utils/extract-json-payload.d.mts +33 -0
  408. package/esm/utils/extract-json-payload.d.mts.map +1 -0
  409. package/esm/utils/extract-json-payload.mjs +39 -0
  410. package/esm/utils/extract-json-payload.mjs.map +1 -0
  411. package/esm/utils/generate-run-id.d.mts +20 -0
  412. package/esm/utils/generate-run-id.d.mts.map +1 -0
  413. package/esm/utils/generate-run-id.mjs +23 -0
  414. package/esm/utils/generate-run-id.mjs.map +1 -0
  415. package/esm/utils/index.d.mts +9 -0
  416. package/esm/utils/index.mjs +11 -0
  417. package/esm/utils/json-schema.d.mts +57 -0
  418. package/esm/utils/json-schema.d.mts.map +1 -0
  419. package/esm/utils/json-schema.mjs +89 -0
  420. package/esm/utils/json-schema.mjs.map +1 -0
  421. package/esm/utils/prepare-attachment-part.d.mts +36 -0
  422. package/esm/utils/prepare-attachment-part.d.mts.map +1 -0
  423. package/esm/utils/prepare-attachment-part.mjs +140 -0
  424. package/esm/utils/prepare-attachment-part.mjs.map +1 -0
  425. package/esm/utils/resolve-attachment.d.mts +33 -0
  426. package/esm/utils/resolve-attachment.d.mts.map +1 -0
  427. package/esm/utils/resolve-attachment.mjs +73 -0
  428. package/esm/utils/resolve-attachment.mjs.map +1 -0
  429. package/esm/utils/safe-json-parse.d.mts +14 -0
  430. package/esm/utils/safe-json-parse.d.mts.map +1 -0
  431. package/esm/utils/safe-json-parse.mjs +22 -0
  432. package/esm/utils/safe-json-parse.mjs.map +1 -0
  433. package/esm/utils/stamp-report-lineage.d.mts +61 -0
  434. package/esm/utils/stamp-report-lineage.d.mts.map +1 -0
  435. package/esm/utils/stamp-report-lineage.mjs +47 -0
  436. package/esm/utils/stamp-report-lineage.mjs.map +1 -0
  437. package/esm/utils/token-count.d.mts +15 -0
  438. package/esm/utils/token-count.d.mts.map +1 -0
  439. package/esm/utils/token-count.mjs +18 -0
  440. package/esm/utils/token-count.mjs.map +1 -0
  441. package/esm/workflow/as-tool.mjs +55 -0
  442. package/esm/workflow/as-tool.mjs.map +1 -0
  443. package/esm/workflow/cancellation.mjs +44 -0
  444. package/esm/workflow/cancellation.mjs.map +1 -0
  445. package/esm/workflow/emitter.mjs +40 -0
  446. package/esm/workflow/emitter.mjs.map +1 -0
  447. package/esm/workflow/engine.mjs +362 -0
  448. package/esm/workflow/engine.mjs.map +1 -0
  449. package/esm/workflow/index.d.mts +3 -0
  450. package/esm/workflow/index.mjs +5 -0
  451. package/esm/workflow/retry.mjs +32 -0
  452. package/esm/workflow/retry.mjs.map +1 -0
  453. package/esm/workflow/router.mjs +56 -0
  454. package/esm/workflow/router.mjs.map +1 -0
  455. package/esm/workflow/run-scoped-emitter.mjs +32 -0
  456. package/esm/workflow/run-scoped-emitter.mjs.map +1 -0
  457. package/esm/workflow/signature.d.mts +7 -0
  458. package/esm/workflow/signature.d.mts.map +1 -0
  459. package/esm/workflow/signature.mjs +34 -0
  460. package/esm/workflow/signature.mjs.map +1 -0
  461. package/esm/workflow/snapshot.mjs +71 -0
  462. package/esm/workflow/snapshot.mjs.map +1 -0
  463. package/esm/workflow/state.mjs +37 -0
  464. package/esm/workflow/state.mjs.map +1 -0
  465. package/esm/workflow/step-runner.mjs +371 -0
  466. package/esm/workflow/step-runner.mjs.map +1 -0
  467. package/esm/workflow/step.d.mts +19 -0
  468. package/esm/workflow/step.d.mts.map +1 -0
  469. package/esm/workflow/step.mjs +37 -0
  470. package/esm/workflow/step.mjs.map +1 -0
  471. package/esm/workflow/workflow.d.mts +12 -0
  472. package/esm/workflow/workflow.d.mts.map +1 -0
  473. package/esm/workflow/workflow.mjs +116 -0
  474. package/esm/workflow/workflow.mjs.map +1 -0
  475. package/llms-full.txt +2293 -0
  476. package/llms.txt +20 -0
  477. package/package.json +37 -0
  478. package/skills/README.md +53 -0
  479. package/skills/ai-basics/SKILL.md +95 -0
  480. package/skills/attach-ai-middleware/SKILL.md +184 -0
  481. package/skills/define-ai-tool/SKILL.md +186 -0
  482. package/skills/embed-text/SKILL.md +105 -0
  483. package/skills/handle-ai-errors/SKILL.md +194 -0
  484. package/skills/log-ai-calls/SKILL.md +141 -0
  485. package/skills/persist-ai-data/SKILL.md +207 -0
  486. package/skills/pick-ai-provider/SKILL.md +164 -0
  487. package/skills/run-ai-agent/SKILL.md +267 -0
  488. package/skills/run-ai-workflow/SKILL.md +258 -0
  489. package/skills/run-supervisor/SKILL.md +309 -0
  490. package/skills/write-system-prompt/SKILL.md +129 -0
package/llms-full.txt ADDED
@@ -0,0 +1,2293 @@
1
+ # Warlock AI — full skills
2
+
3
+ > Package: `@warlock.js/ai`
4
+
5
+ > Generated artifact. Concatenates every SKILL.md and reference file under `@warlock.js/ai/skills/`. Re-run `node scripts/generate-llms.mjs` after any change.
6
+
7
+ ## ai-basics `@warlock.js/ai/ai-basics/SKILL.md`
8
+
9
+ ---
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`.'
12
+ ---
13
+
14
+ # AI foundations
15
+
16
+ Provider-agnostic core for building AI primitives in TypeScript. Adapters live in sibling packages — all five first-party adapters ship today: `@warlock.js/ai-openai`, `-anthropic`, `-bedrock`, `-google`, `-ollama`.
17
+
18
+ > This skill is the AI **map** — read it first, then load the specific skill for the task.
19
+
20
+ ## The 4-primitive ladder
21
+
22
+ ```
23
+ ai.agent() → single task, stateless [shipped]
24
+ ai.workflow() → static predefined steps, resumable [shipped]
25
+ ai.supervisor() → multi-agent dynamic routing, resumable [shipped]
26
+ ai.orchestrator() → stateful — owns session/history/ctx (v2)
27
+ ```
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.
30
+
31
+ ## Foundations
32
+
33
+ 1. **Public API is functional factories.** Use `ai.agent({...})`, `ai.tool({...})`, `ai.workflow({...})`, `ai.step({...})`, `ai.supervisor({...})`, `ai.systemPrompt()`, `ai.persona()`, `ai.instruction()`. Never `new Agent()`.
34
+ 2. **Adapter entry points are classes.** `new OpenAISDK({ apiKey })` from [`@warlock.js/ai-openai/setup-openai/SKILL.md`](@warlock.js/ai-openai/setup-openai/SKILL.md).
35
+ 3. **Schemas everywhere are `StandardSchemaV1<T>`.** Recommended: [`@warlock.js/seal`](@warlock.js/seal/seal-basics/SKILL.md) — `v.object({...})`. Zod, Valibot, hand-rolled all interop.
36
+ 4. **`execute()` never throws.** Errors funnel into `result.error` as a typed `AIError` subclass. Same for `stream.result`, `workflow.execute()` / `resume()`, `supervisor.execute()` / `resume()`. See [`@warlock.js/ai/handle-ai-errors/SKILL.md`](@warlock.js/ai/handle-ai-errors/SKILL.md).
37
+ 5. **Each `execute()` call is isolated.** Fresh internal execution instance per call.
38
+ 6. **Every error is an `AIError`.** Plain `Error` never leaks. Branch on `error.code` (stable string), `error.category` (coarse), or `instanceof`.
39
+ 7. **Result shape is uniform.** `{ data, error, usage, report }` across every primitive. `report` is a recursive `BaseReport` tree.
40
+ 8. **Persistence is delegated** to `@warlock.js/cache`. See [`@warlock.js/ai/persist-ai-data/SKILL.md`](@warlock.js/ai/persist-ai-data/SKILL.md).
41
+ 9. **Logging is delegated** to `@warlock.js/logger`. See [`@warlock.js/ai/log-ai-calls/SKILL.md`](@warlock.js/ai/log-ai-calls/SKILL.md).
42
+ 10. **`name` on agents is optional.** Anonymous agents get a deterministic `anon_<provider>_<model>` fingerprint.
43
+ 11. **Every report carries lineage** — `rootRunId` + `parentRunId` + `reportSchemaVersion: 1`.
44
+ 12. **`version` is dev-curated, `sessionId` is caller-supplied** — both propagate through nested reports.
45
+ 13. **Cost is computed at emit time as a per-channel breakdown.** Set `pricing` on the model adapter; `Usage.cost` carries `{ input, output, cachedInput?, cachedOutput? }` per trip, rolled up bottom-up.
46
+ 14. **Every `AIError` carries a coarse `category`** for retry-policy dispatch (`rate-limit`, `auth`, `content-filter`, `schema`, etc.).
47
+
48
+ ## 30-second example
49
+
50
+ ```ts
51
+ import { ai } from "@warlock.js/ai";
52
+ import { OpenAISDK } from "@warlock.js/ai-openai";
53
+
54
+ const openai = new OpenAISDK({ apiKey: process.env.OPENAI_API_KEY! });
55
+ const myAgent = ai.agent({ model: openai.model({ name: "gpt-4o-mini" }) });
56
+
57
+ const { data, text, report, usage, error } = await myAgent.execute("Hello");
58
+
59
+ if (error) /* typed AIError */ ;
60
+ console.log(text, usage.total, report.duration);
61
+ ```
62
+
63
+ ## Pick a skill
64
+
65
+ | If the task is about… | Load |
66
+ | --- | --- |
67
+ | `ai.agent({...})` — single-LLM-turn primitive, structured output, streaming, attachments | [`@warlock.js/ai/run-ai-agent/SKILL.md`](@warlock.js/ai/run-ai-agent/SKILL.md) |
68
+ | `ai.tool({...})` — typed validated functions the model can call | [`@warlock.js/ai/define-ai-tool/SKILL.md`](@warlock.js/ai/define-ai-tool/SKILL.md) |
69
+ | `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
+ | `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
+ | `ai.supervisor({...})` — multi-intent routing, fan-out, evaluate loops | [`@warlock.js/ai/run-supervisor/SKILL.md`](@warlock.js/ai/run-supervisor/SKILL.md) |
72
+ | `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) |
74
+ | 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
+ | 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) |
78
+
79
+ ## Package layout
80
+
81
+ ```
82
+ @warlock.js/ai — agent, tool, workflow, supervisor, system-prompt, errors, middleware
83
+ @warlock.js/ai-openai — OpenAI SDK adapter (model + embedder); also OpenRouter / Azure via baseURL
84
+ @warlock.js/ai-anthropic — Anthropic / Claude adapter (Messages API)
85
+ @warlock.js/ai-bedrock — AWS Bedrock adapter (Converse API + Titan embeddings)
86
+ @warlock.js/ai-google — Google / Gemini adapter (@google/genai + batch embeddings)
87
+ @warlock.js/ai-ollama — Ollama adapter for local models
88
+ ```
89
+
90
+ Runtime deps: `@warlock.js/cache` (persistence), `@warlock.js/logger` (logging), `@warlock.js/seal` (recommended schema lib).
91
+
92
+ ## When NOT to use this skill
93
+
94
+ - Code importing `openai` / `@anthropic-ai/sdk` directly without going through `@warlock.js/ai` — those are raw provider SDKs.
95
+ - Generic JS/TS questions unrelated to agent / tool / workflow / supervisor wiring.
96
+
97
+ ## Design references
98
+
99
+ - `domains/ai/design/decisions.md` — locked architectural decisions with rationale
100
+ - `domains/ai/design/workflow.md` — workflow spec
101
+ - `domains/ai/design/supervisor.md` — supervisor spec
102
+ - `domains/ai/design/execution-result.md` — unified `ExecuteResult` + recursive `BaseReport` tree
103
+ - `domains/ai/conventions/errors.md` — framework-vs-consumer-app error split
104
+
105
+
106
+ ## attach-ai-middleware `@warlock.js/ai/attach-ai-middleware/SKILL.md`
107
+
108
+ ---
109
+ 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.'
111
+ ---
112
+
113
+ # Middleware — agent-level pipeline
114
+
115
+ Cross-cutting concerns wrapped around an agent run at three granularities: `execute`, `trip`, `tool`. One middleware = one object. Ships with `budget`, `guardrail`, and `semanticCache` built-ins.
116
+
117
+ ## Install order at a glance
118
+
119
+ ```ts
120
+ import { ai } from "@warlock.js/ai";
121
+ import { OpenAISDK } from "@warlock.js/ai-openai";
122
+ import { cache } from "@warlock.js/cache";
123
+
124
+ const openai = new OpenAISDK({ apiKey: process.env.OPENAI_API_KEY! });
125
+
126
+ ai.config({ defaultStore: cache.driver("redis", { client: redisClient }) });
127
+
128
+ const myAgent = ai.agent({
129
+ model: openai.model({ name: "gpt-4o" }),
130
+ middleware: [
131
+ ai.middleware.semanticCache({
132
+ embedder: openai.embedder({ name: "text-embedding-3-small" }),
133
+ threshold: 0.95,
134
+ }),
135
+ ai.middleware.budget({ maxTokens: 50_000 }),
136
+ ai.middleware.guardrail({
137
+ inputCheck: async (text) =>
138
+ text.match(/\bSSN\b/) ? { ok: false, reason: "pii" } : { ok: true },
139
+ }),
140
+ ],
141
+ });
142
+ ```
143
+
144
+ **Canonical order: `[cache, budget, guardrail, observability]`** — see "Ordering invariants" below.
145
+
146
+ ## `ai.middleware.budget(options)`
147
+
148
+ Cumulative token / USD cap across all trips of one execution.
149
+
150
+ ```ts
151
+ ai.middleware.budget({
152
+ maxTokens: 50_000,
153
+ maxCostUSD: 0.5,
154
+ pricing: { "gpt-4o": { inputPer1K: 0.005, outputPer1K: 0.015 } },
155
+ onExceeded: "abort", // or "warn"
156
+ });
157
+ ```
158
+
159
+ Breach → `BudgetExceededError` on `result.error`. Inspect `error.unit` (`"tokens" | "usd"`), `error.limit`, `error.actual`. Warn mode logs and continues — useful for measuring before enforcing.
160
+
161
+ USD only fires when both `maxCostUSD` AND a matching `pricing[modelName]` entry exist.
162
+
163
+ ## `ai.middleware.guardrail(options)`
164
+
165
+ Pre / post content checks.
166
+
167
+ ```ts
168
+ ai.middleware.guardrail({
169
+ inputCheck: async (text, ctx) =>
170
+ text.includes("forbidden") ? { ok: false, reason: "policy-1" } : { ok: true },
171
+ outputCheck: async (text) =>
172
+ text.length > 10_000 ? { ok: false, reason: "too-long" } : { ok: true },
173
+ name: "pii-guardrail",
174
+ });
175
+ ```
176
+
177
+ Rejection → `GuardrailViolationError` with `phase: "input" | "output"` and the configured `reason`. Output checks fire BEFORE tool dispatch — a rejected response means the tools it requested are never invoked.
178
+
179
+ Checks run on every trip (including tool follow-ups and repair attempts). Gate only the first trip via `ctx.tripIndex === 0`.
180
+
181
+ ## `ai.middleware.semanticCache(options)`
182
+
183
+ Two-tier cache — exact-match key first, vector similarity second. Delegates to any vector-capable `CacheDriver`.
184
+
185
+ ```ts
186
+ ai.middleware.semanticCache({
187
+ embedder: openai.embedder({ name: "text-embedding-3-small" }),
188
+ // store optional — falls back to ai.config({ defaultStore })
189
+ store: cache.driver("pg", {
190
+ client: pgPool,
191
+ vector: { dimensions: 1536, index: "hnsw" },
192
+ }),
193
+ threshold: 0.95,
194
+ ttlMs: 60 * 60 * 1000,
195
+ namespace: "support-faq",
196
+ });
197
+ ```
198
+
199
+ **Driver requirements.** Must support `similar()` — `pg` (with `vector` config), `redis` (with RediSearch), or memory drivers for dev. Without similarity → `CacheUnsupportedError` at first vector op.
200
+
201
+ **How it works.**
202
+ - **Exact-match** — FNV hash over the message list. `store.get(hash)` returns an instant hit.
203
+ - **Vector-match** — embeds the prompt, calls `store.similar(vector, { topK: 1, threshold })`. Driver uses its native ANN index.
204
+ - **Hits** return a synthetic `ModelResponse` with `usage: { input: 0, output: 0, total: 0 }`.
205
+ - **Writes** happen at `trip.after` on miss.
206
+ - **Trip-zero only** — only first-trip responses are cached. Tool-using loops never serve cached tool-call responses (would infinite-loop).
207
+ - **Never use memory drivers in production** — linear scan per query.
208
+
209
+ ## Writing your own middleware
210
+
211
+ One object. Any subset of three hook maps.
212
+
213
+ ```ts
214
+ import type { AgentMiddleware } from "@warlock.js/ai";
215
+
216
+ const latencyLogger: AgentMiddleware = {
217
+ name: "latency-logger",
218
+ execute: {
219
+ before(ctx) {
220
+ ctx.state.set("latency.start", performance.now());
221
+ },
222
+ after(ctx, result) {
223
+ const start = ctx.state.get("latency.start") as number;
224
+ console.log(`agent ${ctx.agent.name} finished in ${performance.now() - start}ms`);
225
+ },
226
+ },
227
+ trip: {
228
+ before(ctx) {
229
+ ctx.state.set(`latency.trip.${ctx.tripIndex}.start`, performance.now());
230
+ },
231
+ after(ctx) {
232
+ const start = ctx.state.get(`latency.trip.${ctx.tripIndex}.start`) as number;
233
+ console.log(` trip ${ctx.tripIndex}: ${performance.now() - start}ms`);
234
+ },
235
+ },
236
+ };
237
+ ```
238
+
239
+ ### Rules
240
+
241
+ - **Never close over mutable state.** Use `ctx.state` — fresh per `execute()` call.
242
+ - **Abort with a typed `AIError` subclass.** Never `throw new Error(...)`.
243
+ - **Short-circuit by returning from `before`.** Return the level's result type — the pipeline skips the real work and outer `after` hooks still run on your synthetic value.
244
+ - **`onError` is opt-in recovery.** Return a value to recover; return `void` to let the error propagate.
245
+ - **`log: false`** suppresses framework debug emission for that middleware (the middleware itself still runs).
246
+
247
+ ## Ordering invariants — read before shipping
248
+
249
+ 1. **Cache MUST be outermost when guardrails are present.** Guardrail `trip.after` throws to reject bad output — but `after` hooks run bottom-up. If guardrail is outside the cache, rejection fires AFTER the cache has written the bad response. Canonical order `[cache, budget, guardrail]` keeps rejected outputs out of the cache.
250
+ 2. **Budget before guardrails.** Guardrails may call classifiers with their own token costs.
251
+ 3. **Observability last.** It should see the final decision every other middleware made.
252
+
253
+ ## Helpers
254
+
255
+ ### `ai.middleware.compose(...sources)`
256
+
257
+ Flatten multiple sources into one ordered array. No sorting, no dedup.
258
+
259
+ ```ts
260
+ ai.agent({
261
+ model,
262
+ middleware: ai.middleware.compose(standardStack, toolRules, auditMiddleware),
263
+ });
264
+ ```
265
+
266
+ ### `ai.middleware.forTool(name | names, middleware)`
267
+
268
+ Scope `tool.*` hooks to specific tool names. `execute` and `trip` hooks pass through.
269
+
270
+ ```ts
271
+ const scoped = ai.middleware.forTool(["paid_api", "expensive_db"], toolRateLimit({ maxCalls: 5 }));
272
+ ```
273
+
274
+ ## Caveats
275
+
276
+ - **`tool.onError` is almost-never-useful.** `ToolContract.invoke()` never throws — errors are captured into `result.error`. `tool.onError` only fires when another middleware's `tool.before`/`tool.after` itself throws. For "the tool itself failed," branch on `result.error` in a `tool.after`.
277
+ - **Middleware does NOT observe unregistered tool calls.** When the model asks for a tool the agent wasn't configured with, the pipeline is bypassed and a failed `ToolCall` is recorded directly.
278
+ - **`name` must be unique** across an agent's middleware array.
279
+ - **Middleware state does NOT cross `agent.execute()` boundaries.** One execute → one fresh `ctx.state`.
280
+
281
+ ## Workflow + middleware — what works today (v1)
282
+
283
+ - Inside a workflow step with `agent: myAgent` — the agent's own middleware fires normally.
284
+ - `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.
286
+
287
+ ## See also
288
+
289
+ - [`@warlock.js/ai/run-ai-agent/SKILL.md`](@warlock.js/ai/run-ai-agent/SKILL.md) — agent lifecycle the middleware wraps
290
+ - [`@warlock.js/ai/persist-ai-data/SKILL.md`](@warlock.js/ai/persist-ai-data/SKILL.md) — `defaultStore` for semantic cache
291
+ - [`@warlock.js/ai/handle-ai-errors/SKILL.md`](@warlock.js/ai/handle-ai-errors/SKILL.md) — `BudgetExceededError` / `GuardrailViolationError`
292
+
293
+
294
+ ## define-ai-tool `@warlock.js/ai/define-ai-tool/SKILL.md`
295
+
296
+ ---
297
+ name: define-ai-tool
298
+ description: 'Define tools with ai.tool({...}) — typed validated async functions the model can call. Covers name / description / action / mode (feedback / silent) / input / execute, `ctx.artifacts` side-channel, `ToolExecutionError`. Triggers: `ai.tool`, `ToolContract`, `ToolContext`, `ToolCall`, `ToolExecutionError`, `artifactsSchema`, `mode: "silent"`, `workflow.asTool`; ''define a tool'', ''wire tool into agent'', ''tool input validation'', ''side-channel artifacts''; typical import `import { ai } from "@warlock.js/ai"`. Skip: agent loop — `@warlock.js/ai/run-ai-agent/SKILL.md`; supervisor artifacts — `@warlock.js/ai/run-supervisor/SKILL.md`; competing libs `langchain` tools, raw `openai` function-calling.'
299
+ ---
300
+
301
+ # `ai.tool()` — typed tool factory
302
+
303
+ Tools are async functions the model can call by name during a trip loop. Define one with `ai.tool()`, pass it in `agent({ tools: [...] })`, and the agent handles dispatch, input validation, and error surfacing automatically.
304
+
305
+ ## Factory shape
306
+
307
+ ```ts
308
+ ai.tool({
309
+ name: string, // stable identifier
310
+ description: string, // sent to the model
311
+ version?: string, // mirrored onto tool reports
312
+ action?: string | ((input: TInput) => string), // UI label for streaming UX
313
+ mode?: "feedback" | "silent", // result feedback control
314
+ input: StandardSchemaV1<TInput>, // validated before execute
315
+ execute: (input: TInput, ctx?: ToolContext) => Promise<unknown>,
316
+ });
317
+ ```
318
+
319
+ Returns a `ToolContract<TInput, TOutput>`. One tool can be attached to many agents.
320
+
321
+ ## `description` vs `action`
322
+
323
+ Two roles, two fields:
324
+
325
+ - **`description`** — what the LLM reads when deciding whether to call this tool.
326
+ - **`action`** — present-progressive UI string surfaced to humans on `agent.tool.calling` / `agent.tool.called` events.
327
+
328
+ ```ts
329
+ ai.tool({
330
+ name: "search_catalog",
331
+ description: "Search the product catalog. Returns matching products with SKU, name, price.",
332
+ action: ({ query }) => `Searching the catalog for "${query}"`,
333
+ input: v.object({ query: v.string() }),
334
+ execute: async ({ query }) => searchProducts(query),
335
+ });
336
+ ```
337
+
338
+ Two forms supported: static string or function. Function form runs after input validation; throws are swallowed (UI strings aren't worth aborting LLM dispatch over).
339
+
340
+ ## Schema via Standard Schema V1
341
+
342
+ Input is typed as `StandardSchemaV1<T>`. Recommended: `@warlock.js/seal`. Zod / Valibot / hand-rolled all interop.
343
+
344
+ ```ts
345
+ import { v } from "@warlock.js/seal";
346
+
347
+ const searchTool = ai.tool({
348
+ name: "search",
349
+ description: "Search the docs index",
350
+ input: v.object({
351
+ query: v.string(),
352
+ limit: v.number().optional(),
353
+ }),
354
+ execute: async ({ query, limit }) => fetchDocs(query, limit ?? 10),
355
+ });
356
+ ```
357
+
358
+ ## Input validation is automatic
359
+
360
+ The agent calls `input["~standard"].validate(rawArgs)` before invoking `execute`. Validation failures **do not throw** — the failure is recorded on the trip's `ToolCall.error` and fed back to the model on the next trip as a tool error message. The model gets a chance to correct and retry within the bounded `maxTrips` loop.
361
+
362
+ ## What gets returned to the model
363
+
364
+ Whatever your `execute` resolves with is `JSON.stringify`'d and sent back as the next trip's `tool` message. Strings pass through unchanged. Throw (or return a rejected promise) to signal failure — the agent records the error on `ToolCall.error` and tells the model.
365
+
366
+ ## `mode` — feedback vs silent
367
+
368
+ Default `"feedback"`.
369
+
370
+ - **`mode: "feedback"`** (default) — standard round-trip. Result feeds back into next trip; the model reads it and replies. Use for tools whose output the model needs to narrate: `search_catalog`, `search_knowledge_base`, `ask_questions`.
371
+ - **`mode: "silent"`** — fire-and-forget. Result NOT fed back to the model. When EVERY tool call in a single generation is silent, the agent loop terminates after dispatch. Use for pure side-effect tools: `update_state`, `set_locale`, telemetry pings.
372
+
373
+ ```ts
374
+ ai.tool({
375
+ name: "update_state",
376
+ description: "Persist customer slot-fill across turns.",
377
+ mode: "silent",
378
+ input: v.object({ preferences: v.array(v.string()).optional() }),
379
+ execute: async (patch, ctx) => {
380
+ ctx.artifacts.stateUpdate = patch;
381
+ return { ok: true }; // model never sees this
382
+ },
383
+ });
384
+ ```
385
+
386
+ **All-silent rule.** The loop terminates only when EVERY tool call this trip is silent. Silent + feedback in the same generation → loop continues (the feedback tool still round-trips, the silent one piggybacks).
387
+
388
+ **Constraints for silent tools.** MUST be cheap + fast (HTTP request still open until dispatch resolves), should be idempotent (no surface to communicate failure to the model), side-effect-only.
389
+
390
+ ## Tool context — `ctx.artifacts` side-channel
391
+
392
+ `execute` accepts an optional **second argument** — a `ToolContext` with a mutable `artifacts` bag and the dispatch's `signal`. Use it to capture system-only data (renderable blocks, citations, files, telemetry, soft signals) that the LLM should NOT see.
393
+
394
+ ```ts
395
+ ai.tool({
396
+ name: "search_catalog",
397
+ input: v.object({ query: v.string() }),
398
+ execute: async (input, ctx) => {
399
+ const items = await searchItems(input.query);
400
+
401
+ // Side-channel — never reaches the LLM.
402
+ ctx.artifacts.blocks ??= [];
403
+ ctx.artifacts.blocks.push({ type: "items", itemIds: items.map(i => i.id) });
404
+
405
+ // LLM-visible — what the agent reasons over.
406
+ return { total: items.length };
407
+ },
408
+ });
409
+ ```
410
+
411
+ Under a supervisor: bag starts empty per iteration, accumulates writes from all tool calls, merges into state at iteration end (auto-spread by default; `finalizeArtifacts` for concat / dedupe). See [`@warlock.js/ai/run-supervisor/SKILL.md`](@warlock.js/ai/run-supervisor/SKILL.md).
412
+
413
+ Standalone (no supervisor): framework supplies `{ artifacts: {} }`. Mutations are harmless no-ops.
414
+
415
+ ## Type contract for artifacts
416
+
417
+ The supervisor declares an `artifactsSchema`; tools registered to it inherit typed `ctx.artifacts.*`. Standalone tools fall back to `Record<string, unknown>`.
418
+
419
+ ```ts
420
+ ai.supervisor({
421
+ artifactsSchema: v.object({
422
+ blocks: v.array(blockSchema).optional(),
423
+ citations: v.array(citationSchema).optional(),
424
+ }),
425
+ // tools see ctx.artifacts typed as { blocks?, citations? }
426
+ });
427
+ ```
428
+
429
+ ## Error categorization
430
+
431
+ `invoke()` never throws — failures surface on the returned `error` field, and the agent records them on the dispatch's `ToolCall.error`. The error class depends on what failed:
432
+
433
+ - **Input schema rejected the model's args** → `SchemaValidationError` (`code: "SCHEMA_VALIDATION_FAILED"`), `issues` preserved. NOT wrapped in `ToolExecutionError`.
434
+ - **Schema's own `validate()` threw** → `SchemaValidationError` wrapping the cause.
435
+ - **Your `execute()` threw** → `ToolExecutionError` (`code: "TOOL_EXEC_FAILED"`, category `tool`) with `toolName`, and the thrown value on `error.cause`.
436
+
437
+ `ToolExecutionError` carries `toolName` always; `tripIndex` is stamped by the agent that dispatched it. The validation failure is fed back to the model on the next trip so it can correct within the `maxTrips` loop.
438
+
439
+ See [`@warlock.js/ai/handle-ai-errors/SKILL.md`](@warlock.js/ai/handle-ai-errors/SKILL.md).
440
+
441
+ ## Inspecting tool calls
442
+
443
+ ```ts
444
+ const result = await myAgent.execute("Pick a city and tell me the weather.");
445
+
446
+ const toolCalls = result.report.children.filter((c) => c.type === "tool");
447
+
448
+ for (const call of toolCalls) {
449
+ console.log(call.tripIndex, call.name, call.input, call.output, call.duration);
450
+ }
451
+ ```
452
+
453
+ Tool dispatches are child `BaseReport` nodes on `report.children` (not a separate `report.toolCalls` field) — filter by `c.type === "tool"`. Each `ToolCall` is a `BaseReport & { type: "tool", tripIndex, input, output?, error? }`, so it carries `name` / `startedAt` / `endedAt` / `duration` from the report base.
454
+
455
+ ## Events
456
+
457
+ - `agent.tool.calling` — `{ tool, input, tripIndex }`
458
+ - `agent.tool.called` — `ToolCall & { tool }` (full record)
459
+ - `agent.tool.failed` — `{ tool, input, error, tripIndex }`
460
+
461
+ Subscribe at factory / instance / per-call.
462
+
463
+ ## Pattern — workflow as a tool
464
+
465
+ ```ts
466
+ const wrapped = myWorkflow.asTool({
467
+ description: "Run the catalog ingestion workflow",
468
+ inputSchema: v.object({ url: v.string() }),
469
+ });
470
+
471
+ const agent = ai.agent({ model, tools: [wrapped] });
472
+ ```
473
+
474
+ Workflow errors surface as `ToolExecutionError` with `cause` pointing at the original `WorkflowError` subclass.
475
+
476
+ ## See also
477
+
478
+ - [`@warlock.js/ai/run-ai-agent/SKILL.md`](@warlock.js/ai/run-ai-agent/SKILL.md) — how tools plug into the trip loop
479
+ - [`@warlock.js/ai/handle-ai-errors/SKILL.md`](@warlock.js/ai/handle-ai-errors/SKILL.md) — error hierarchy
480
+ - [`@warlock.js/ai/run-supervisor/SKILL.md`](@warlock.js/ai/run-supervisor/SKILL.md) — artifacts under a supervisor
481
+ - [`@warlock.js/ai/run-ai-workflow/SKILL.md`](@warlock.js/ai/run-ai-workflow/SKILL.md) — `workflow.asTool()` composition
482
+
483
+
484
+ ## embed-text `@warlock.js/ai/embed-text/SKILL.md`
485
+
486
+ ---
487
+ name: embed-text
488
+ description: 'Text-to-vector via sdk.embedder({...}) — embed(string) for single, embedMany(string[]) for batch. Peer primitive on the SDK adapter, not wired into agents. Compose into RAG tools, workflow run steps, or ai.middleware.semanticCache. Triggers: `sdk.embedder`, `EmbedderContract`, `embedder.embed`, `embedder.embedMany`, `EmbeddingResult`, `EmbeddingBatchResult`, `dimensions`; ''embed text'', ''build RAG tool'', ''populate vector store'', ''embedding batch''; typical import `import { OpenAISDK } from "@warlock.js/ai-openai"`. Skip: cache similarity — `@warlock.js/cache/use-cache-similarity/SKILL.md`; pgvector queries — `@warlock.js/cascade/search-by-vector/SKILL.md`; competing libs `langchain` embeddings, raw `openai.embeddings.create`.'
489
+ ---
490
+
491
+ # Embeddings — peer primitive on the SDK adapter
492
+
493
+ `EmbedderContract` is a sibling of `ModelContract` on `SDKAdapterContract`, not part of the agent loop. Text-in / vector-out. No streaming, no tools, no relationship to chat completions.
494
+
495
+ ## Contract
496
+
497
+ ```ts
498
+ interface EmbedderContract {
499
+ readonly name: string;
500
+ readonly provider: string;
501
+ readonly dimensions: number; // 0 until first call when no override given
502
+
503
+ embed(input: string): Promise<EmbeddingResult>;
504
+ embedMany(inputs: string[]): Promise<EmbeddingBatchResult>;
505
+ }
506
+ ```
507
+
508
+ Single and batch are deliberately split — different cost profiles, different per-request token caps, different failure modes.
509
+
510
+ The `embedder()` method is **optional** on `SDKAdapterContract` — not every provider supports embeddings:
511
+
512
+ ```ts
513
+ if (typeof sdk.embedder === "function") {
514
+ const embedder = sdk.embedder({ name: "text-embedding-3-small" });
515
+ }
516
+ ```
517
+
518
+ ## OpenAI adapter — first implementation
519
+
520
+ ```ts
521
+ import { OpenAISDK } from "@warlock.js/ai-openai";
522
+
523
+ const openai = new OpenAISDK({ apiKey: process.env.OPENAI_API_KEY! });
524
+ const embedder = openai.embedder({ name: "text-embedding-3-small" });
525
+
526
+ const one = await embedder.embed("Hello, world.");
527
+ // { vector: number[], dimensions: number, usage: { promptTokens, totalTokens } }
528
+
529
+ const many = await embedder.embedMany(["foo", "bar", "baz"]);
530
+ // { vectors: number[][], dimensions: number, usage: { promptTokens, totalTokens } }
531
+ ```
532
+
533
+ ## Not wired into the agent loop
534
+
535
+ Embeddings are deliberately not automatic. Consumers obtain an embedder from the adapter and call it directly. Composes into:
536
+
537
+ - **Retrieval tools** the agent can call (RAG pattern).
538
+ - **`run` steps** in a workflow (vector ingest, catalog item embedding).
539
+ - **Query vectors** for `ai.middleware.semanticCache` — see [`@warlock.js/ai/attach-ai-middleware/SKILL.md`](@warlock.js/ai/attach-ai-middleware/SKILL.md).
540
+ - **Cascade vector columns** for native pgvector search — see [`@warlock.js/cascade/search-by-vector/SKILL.md`](@warlock.js/cascade/search-by-vector/SKILL.md).
541
+ - **Cache similarity retrieval** via `cache.set({ vector })` + `cache.similar(...)` — see [`@warlock.js/cache/use-cache-similarity/SKILL.md`](@warlock.js/cache/use-cache-similarity/SKILL.md).
542
+
543
+ ## Usage example — workflow `run` step
544
+
545
+ ```ts
546
+ ai.step({
547
+ name: "embed",
548
+ run: async (ctx) => {
549
+ const text = `${ctx.steps.extract.output.name} ${ctx.steps.extract.output.description}`;
550
+ const { vector } = await embedder.embed(text);
551
+ ctx.state.embedding = vector;
552
+ },
553
+ output: { extract: (ctx) => ({ dims: (ctx.state.embedding as number[]).length }) },
554
+ });
555
+ ```
556
+
557
+ ## Pattern — RAG tool
558
+
559
+ ```ts
560
+ import { v } from "@warlock.js/seal";
561
+
562
+ const searchKb = ai.tool({
563
+ name: "searchKb",
564
+ description: "Search the knowledge base for relevant passages.",
565
+ input: v.object({ query: v.string(), k: v.number().optional() }),
566
+ execute: async ({ query, k }) => {
567
+ const { vector } = await embedder.embed(query);
568
+ const hits = await vectorStore.query(vector, { topK: k ?? 5 });
569
+ return hits.map((h) => ({ text: h.text, score: h.score, source: h.source }));
570
+ },
571
+ });
572
+
573
+ ai.agent({ model, tools: [searchKb] });
574
+ ```
575
+
576
+ ## Dimensions
577
+
578
+ `embedder.dimensions` is `0` on a fresh embedder when no override is given — populated from the first embed call's response. Pre-seed via the adapter's `dimensions` config option when you need the value before the first call (e.g. to size a vector column in a migration schema).
579
+
580
+ ## Retrieval is app-level
581
+
582
+ No built-in vector store. Bring your own (pgvector / Qdrant / Pinecone / Chroma / cache's `similar()`) and wrap it in an `ai.tool({...})`.
583
+
584
+ ## See also
585
+
586
+ - [`@warlock.js/ai/run-ai-agent/SKILL.md`](@warlock.js/ai/run-ai-agent/SKILL.md) — composing embedders into tools
587
+ - [`@warlock.js/ai/run-ai-workflow/SKILL.md`](@warlock.js/ai/run-ai-workflow/SKILL.md) — embeddings inside `run` steps
588
+ - [`@warlock.js/ai/persist-ai-data/SKILL.md`](@warlock.js/ai/persist-ai-data/SKILL.md) — performance guidance on vector storage
589
+ - [`@warlock.js/cache/use-cache-similarity/SKILL.md`](@warlock.js/cache/use-cache-similarity/SKILL.md) — cache as a vector store
590
+ - [`@warlock.js/cascade/search-by-vector/SKILL.md`](@warlock.js/cascade/search-by-vector/SKILL.md) — cascade `similarTo` query method
591
+
592
+
593
+ ## handle-ai-errors `@warlock.js/ai/handle-ai-errors/SKILL.md`
594
+
595
+ ---
596
+ 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`.'
598
+ ---
599
+
600
+ # Typed errors — `AIError` hierarchy
601
+
602
+ Every error surfaced by `@warlock.js/ai` and every adapter package is an `AIError` subclass with a stable `code`. The base extends platform `Error`; it does NOT extend `HttpError`. Plain `Error` never leaks.
603
+
604
+ ## Two invariants
605
+
606
+ 1. **`execute()` never throws.** Every `agent.execute()` / `workflow.execute()` resolves with a well-formed result. Failures funnel into `result.error`. Same for `stream.result`.
607
+ 2. **Every error is an `AIError`.** Both core and adapter packages funnel everything through `AIError` subclasses. Branch on `error.code` (stable string) or `instanceof`.
608
+
609
+ ## Dispatch pattern
610
+
611
+ ```ts
612
+ import {
613
+ AIError,
614
+ ProviderRateLimitError,
615
+ ProviderAuthError,
616
+ ContextLengthExceededError,
617
+ ContentFilterError,
618
+ SchemaValidationError,
619
+ ToolExecutionError,
620
+ WorkflowDriftError,
621
+ // ...
622
+ } from "@warlock.js/ai";
623
+
624
+ const result = await agent.execute(input);
625
+
626
+ if (!result.error) return result.data;
627
+
628
+ if (result.error instanceof ProviderRateLimitError) {
629
+ await sleep(result.error.retryAfter ?? 1000);
630
+ return retry();
631
+ }
632
+
633
+ if (result.error instanceof ContextLengthExceededError) {
634
+ return truncateAndRetry(result.error);
635
+ }
636
+
637
+ // Or branch on stable code string (good for persisted logs / metrics)
638
+ switch (result.error.code) {
639
+ case "PROVIDER_RATE_LIMIT": /* ... */ break;
640
+ case "CONTENT_FILTER": /* ... */ break;
641
+ case "WORKFLOW_DRIFT": /* ... */ break;
642
+ }
643
+ ```
644
+
645
+ Codes are the public contract — class names may evolve; codes stay.
646
+
647
+ ## Coarse dispatch via `error.category`
648
+
649
+ Too granular to dashboard on `code` — every `AIError` carries a coarser `category`:
650
+
651
+ ```ts
652
+ type ErrorCategory =
653
+ | "auth" | "rate-limit" | "timeout" | "validation" | "content-filter"
654
+ | "provider" | "tool" | "cancelled" | "max-trips" | "max-iterations"
655
+ | "max-steps" | "schema" | "drift" | "routing" | "guardrail"
656
+ | "budget" | "quota" | "context-length" | "unknown";
657
+
658
+ switch (result.error.category) {
659
+ case "rate-limit": return retryWithBackoff();
660
+ case "timeout": return retryOnce();
661
+ case "auth": return escalate(); // not retryable
662
+ case "content-filter": return policyMessage(); // not retryable
663
+ case "schema": return repair(); // use agent `repair`
664
+ }
665
+
666
+ metrics.increment("ai.error", { category: result.error.category });
667
+ ```
668
+
669
+ Each typed subclass declares its `static defaultCategory`. The 4th-arg category override exists only on the base `AIError` for direct `new AIError(...)` usage.
670
+
671
+ ## Hierarchy
672
+
673
+ ```
674
+ AIError (base — code, category, message, cause?, context?)
675
+ ├── AgentExecutionError AGENT_EXEC_FAILED
676
+ │ ├── AgentCancelledError AGENT_CANCELLED { cancelledAt?, reason? } — caller pulled the plug
677
+ │ └── AgentMaxTripsError AGENT_MAX_TRIPS { maxTrips } — runaway tool loop hit the cap
678
+ ├── SchemaValidationError SCHEMA_VALIDATION_FAILED { issues? }
679
+ ├── ToolExecutionError TOOL_EXEC_FAILED { toolName, tripIndex? }
680
+ ├── WorkflowError WORKFLOW_ERROR (base)
681
+ │ ├── StepFailedError STEP_FAILED { stepName, attempts }
682
+ │ ├── WorkflowDriftError WORKFLOW_DRIFT { savedSignature, currentSignature, runId }
683
+ │ ├── WorkflowCancelledError WORKFLOW_CANCELLED { cancelledAt, reason }
684
+ │ ├── MaxStepsExceededError WORKFLOW_MAX_STEPS { maxSteps }
685
+ │ └── RoutingError WORKFLOW_INVALID_GOTO { stepName, targetName }
686
+ ├── SupervisorFailedError SUPERVISOR_FAILED (base + authoring/runtime)
687
+ │ ├── MaxIterationsError SUPERVISOR_MAX_ITERATIONS { maxIterations }
688
+ │ ├── SupervisorRoutingError SUPERVISOR_INVALID_ROUTE
689
+ │ ├── SupervisorCancelledError SUPERVISOR_CANCELLED { cancelledAt, reason }
690
+ │ └── SupervisorDriftError SUPERVISOR_DRIFT { savedSignature, currentSignature, runId }
691
+ ├── ProviderError PROVIDER_ERROR (base + catch-all)
692
+ │ ├── ProviderRateLimitError PROVIDER_RATE_LIMIT { retryAfter? } — transient
693
+ │ ├── QuotaExceededError PROVIDER_QUOTA_EXCEEDED — NOT retryable (billing cap)
694
+ │ ├── ProviderTimeoutError PROVIDER_TIMEOUT
695
+ │ ├── ContextLengthExceededError CONTEXT_LENGTH_EXCEEDED { limit?, actual?, modelName? }
696
+ │ ├── ContentFilterError CONTENT_FILTER { reason?, categories? }
697
+ │ ├── InvalidRequestError PROVIDER_INVALID_REQUEST
698
+ │ └── ProviderAuthError PROVIDER_AUTH
699
+ ├── BudgetExceededError BUDGET_EXCEEDED { limit, actual, unit } — from ai.middleware.budget
700
+ └── GuardrailViolationError GUARDRAIL_VIOLATION { phase, reason } — from ai.middleware.guardrail
701
+ ```
702
+
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.
704
+
705
+ ## Error fields
706
+
707
+ - `code` — stable `AIErrorCode` string.
708
+ - `category` — coarse `ErrorCategory`.
709
+ - `message` — human-readable.
710
+ - `cause?` — root error (often a provider SDK error).
711
+ - `context?` — `Record<string, unknown>` for provider-raw diagnostics (`status`, `requestId`, `headers`).
712
+
713
+ Typed fields (`retryAfter`, `toolName`, `issues`, `stepName`, …) are first-class consumer surface.
714
+
715
+ ## Retry strategy
716
+
717
+ | Error family | Retryable? |
718
+ | --- | --- |
719
+ | `ProviderRateLimitError` | Yes — back off by `retryAfter` ms |
720
+ | `ProviderTimeoutError` | Yes — short delay |
721
+ | `ProviderError` (generic) | Maybe — depends on cause |
722
+ | `QuotaExceededError` | **No** — needs human intervention |
723
+ | `ProviderAuthError` | **No** — fix config / rotate key |
724
+ | `ContextLengthExceededError` | Only after truncating input |
725
+ | `ContentFilterError` | Usually **no** — the prompt itself is the issue |
726
+ | `SchemaValidationError` | Use agent `repair: { maxAttempts }` instead |
727
+ | `ToolExecutionError` | Depends on `cause` |
728
+ | `WorkflowDriftError` | **No** — manual migration or `force: true` |
729
+ | `WorkflowCancelledError` | **No** — caller-driven cancel |
730
+ | `MaxStepsExceededError` / `RoutingError` | **No** — programmer error |
731
+ | `BudgetExceededError` | **No** — raise the cap, split the workload |
732
+ | `GuardrailViolationError` (`phase: "input"`) | **No** — block / sanitize at product layer |
733
+ | `GuardrailViolationError` (`phase: "output"`) | Sometimes — re-prompt with adjusted system message |
734
+
735
+ ## Why extend `Error`, not `HttpError`
736
+
737
+ - `@warlock.js/ai` is a standalone product — used from CLIs / workers / scripts as often as HTTP handlers.
738
+ - Coupling to a web framework pulls HTTP into every consumer.
739
+ - AI errors aren't HTTP errors anyway — "rate limit" is a 429 the *upstream provider* returned, not one the server returns.
740
+
741
+ The **consumer app** layer (`src/app/ai/`) wraps framework errors with its own `AIError` subclass that extends `HttpError`. See `domains/ai/conventions/errors.md`.
742
+
743
+ ## OpenAI adapter — status + code dispatch
744
+
745
+ The OpenAI wrapper categorizes via `APIError.status + code` combined:
746
+
747
+ - `APIError.code` is semantically stable (`context_length_exceeded`, `content_filter`, `invalid_api_key`, etc.) across SDK versions; message strings are not.
748
+ - Status alone collapses three distinct failure modes into one bucket (`400` = context-length OR content-filter OR bad-model-name).
749
+ - When `code` is missing (proxied deployments), fall back to `status`.
750
+ - Name-based detection catches `APIConnectionTimeoutError` and Node-level `ETIMEDOUT` / `ECONNABORTED`.
751
+
752
+ ## Pattern — full fallback ladder
753
+
754
+ ```ts
755
+ async function runWithFallbacks(input: string) {
756
+ for (let attempt = 0; attempt < 3; attempt++) {
757
+ const { data, error } = await myAgent.execute(input);
758
+
759
+ if (!error) return data;
760
+
761
+ if (error instanceof ProviderRateLimitError) {
762
+ await sleep(error.retryAfter ?? 2000);
763
+ continue;
764
+ }
765
+
766
+ if (error instanceof ContextLengthExceededError) {
767
+ input = truncate(input, error.limit ?? 4000);
768
+ continue;
769
+ }
770
+
771
+ if (error instanceof QuotaExceededError || error instanceof ProviderAuthError) {
772
+ throw error; // not retryable
773
+ }
774
+
775
+ throw error; // unknown — give up
776
+ }
777
+
778
+ throw new Error("exhausted retries");
779
+ }
780
+ ```
781
+
782
+ ## See also
783
+
784
+ - [`@warlock.js/ai/run-ai-agent/SKILL.md`](@warlock.js/ai/run-ai-agent/SKILL.md) — `AgentResult.error`
785
+ - [`@warlock.js/ai/run-ai-workflow/SKILL.md`](@warlock.js/ai/run-ai-workflow/SKILL.md) — `WorkflowError` subclasses
786
+ - [`@warlock.js/ai/define-ai-tool/SKILL.md`](@warlock.js/ai/define-ai-tool/SKILL.md) — `ToolExecutionError` wrapping
787
+ - [`@warlock.js/ai/log-ai-calls/SKILL.md`](@warlock.js/ai/log-ai-calls/SKILL.md) — error logging
788
+ - `domains/ai/conventions/errors.md` — framework vs app error convention
789
+
790
+
791
+ ## log-ai-calls `@warlock.js/ai/log-ai-calls/SKILL.md`
792
+
793
+ ---
794
+ name: log-ai-calls
795
+ description: 'Framework logging delegated to @warlock.js/logger — every primitive emits via the log singleton, configure channels / levels / redaction once at boot. Four-arg call convention (module, action, message, context). Triggers: `log.configure`, `log.setMinLevel`, `log.setChannels`, `ConsoleLog`, `FileLog`, `LogChannel`, `redact.paths`, `ai.agent.<name>` / `ai.workflow.<name>` / `ai.supervisor.<name>` modules; ''configure ai logging'', ''mask prompts in logs'', ''silence logs in tests'', ''capture log entries''; typical import `import { log } from "@warlock.js/logger"`. Skip: error hierarchy — `@warlock.js/ai/handle-ai-errors/SKILL.md`; competing libs `pino`, `winston`, `console.log`.'
796
+ ---
797
+
798
+ # Logging — `log` from `@warlock.js/logger`
799
+
800
+ `@warlock.js/ai` does not own a logger contract. Every primitive imports the `log` singleton from [`@warlock.js/logger`](@warlock.js/logger/logger-basics/SKILL.md) directly and emits structured entries through it. Configuration — channels, levels, redaction — lives entirely on the logger.
801
+
802
+ **No `ai.config({ logger })`. No per-primitive `logger:` override.** Configure once at app boot; the framework picks it up.
803
+
804
+ ## Installation — configure at boot
805
+
806
+ ```ts
807
+ import { log, ConsoleLog, FileLog } from "@warlock.js/logger";
808
+
809
+ log.configure({
810
+ channels: [
811
+ new ConsoleLog(),
812
+ new FileLog({ chunk: "daily" }),
813
+ ],
814
+ autoFlushOn: ["SIGINT", "SIGTERM", "beforeExit"],
815
+ });
816
+
817
+ log.setMinLevel("info");
818
+ ```
819
+
820
+ That's it. Every agent / workflow / supervisor running in the process emits to the configured channels.
821
+
822
+ ## Call convention — four positional args
823
+
824
+ Every framework log call uses the 4-arg positional form:
825
+
826
+ ```ts
827
+ log.info("ai.agent", "trip.started", "agent starting trip", { tripIndex, model });
828
+ ```
829
+
830
+ - **`module`** — emitting primitive, name-suffixed (`"ai.agent.<name>"`, `"ai.workflow.<name>"`, `"ai.supervisor.<name>"`); provider adapters use `"ai.openai"` etc.
831
+ - **`action`** — mirrors event names without the primitive prefix (`"trip.started"`, `"tool.called"`).
832
+ - **`message`** — human-readable summary.
833
+ - **`context`** — structured bag of diagnostic fields.
834
+
835
+ `action` strips the prefix of the corresponding event (`agent.trip.started` → `trip.started`) so grep filters and event handlers share vocabulary.
836
+
837
+ ## Level mapping
838
+
839
+ | Level | Framework usage |
840
+ | --- | --- |
841
+ | `debug` | Internals (request/response bodies, token counts per trip) |
842
+ | `info` | Milestones (agent starting, agent completed) |
843
+ | `warn` | Retries, repair attempts, recoverable tool failures |
844
+ | `error` | Terminal failures surfaced via `result.error` |
845
+ | `success` | Tool-call success |
846
+
847
+ Streaming deltas are intentionally **not** logged at token granularity — trip boundaries carry the same information at readable volume.
848
+
849
+ ## What gets logged
850
+
851
+ ### Agent
852
+
853
+ | Action | Level | Context |
854
+ |---|---|---|
855
+ | `agent.starting` | `info` | inputLength, model, maxTrips |
856
+ | `trip.started` | `debug` | tripIndex |
857
+ | `tool.calling` | `debug` | tool name, action, tripIndex |
858
+ | `tool.called` | `success` | tool name, duration, tripIndex |
859
+ | `tool.failed` | `warn` | tool name, error code, tripIndex |
860
+ | `repair.attempting` | `warn` | tripIndex, validation issues |
861
+ | `agent.completed` | `info` | totalUsage, totalDuration, trip count |
862
+ | `agent.error` | `error` | error code, message, stack |
863
+
864
+ ### Workflow
865
+
866
+ `workflow.starting` / `step.starting` / `step.completed` / `step.failed` / `workflow.completed` / `workflow.error`. Module is `ai.workflow.<name>`.
867
+
868
+ ### Supervisor
869
+
870
+ `supervisor.starting` / `iteration.starting` / `router.deciding` / `router.decided` / `agent.starting` (per dispatched agent) / `iteration.completed` / `evaluate.verdict` / `supervisor.completed`. Module is `ai.supervisor.<name>`.
871
+
872
+ ### Provider adapter
873
+
874
+ `ai.openai` (and future adapters) emit `request` (debug) and `response` (debug) per call, plus `error` on the wrapped `AIError`.
875
+
876
+ ## Redaction
877
+
878
+ Redaction is a `@warlock.js/logger` feature — configure once on the logger, applies to every framework log automatically.
879
+
880
+ ```ts
881
+ log.configure({
882
+ redact: {
883
+ paths: [
884
+ "context.messages", // prompts
885
+ "context.input", // user input
886
+ "context.apiKey", // never log this anyway, but defense-in-depth
887
+ ],
888
+ },
889
+ });
890
+ ```
891
+
892
+ See [`@warlock.js/logger/redact-sensitive-log-fields/SKILL.md`](@warlock.js/logger/redact-sensitive-log-fields/SKILL.md) for the full redaction surface.
893
+
894
+ ## Events vs. logs — two channels, one source
895
+
896
+ - **Events** are push-model (subscribers), typed payloads, per-execution lifetime — ideal for UI streaming, SSE, metrics.
897
+ - **Logs** are pull-model (written to channels), structured-string + context, persistent — ideal for grep, post-mortem.
898
+
899
+ Both fire from the same internal emit so every event produces both.
900
+
901
+ ## Patterns
902
+
903
+ ### Silence everything in tests
904
+
905
+ ```ts
906
+ import { log } from "@warlock.js/logger";
907
+
908
+ beforeAll(() => log.setChannels([]));
909
+ ```
910
+
911
+ ### Capture all framework log entries in a test
912
+
913
+ ```ts
914
+ import { log, LogChannel } from "@warlock.js/logger";
915
+
916
+ class Capture extends LogChannel {
917
+ public name = "capture";
918
+ public entries: any[] = [];
919
+ public log(data) { this.entries.push(data); }
920
+ }
921
+
922
+ const capture = new Capture();
923
+ log.setChannels([capture]);
924
+ ```
925
+
926
+ See [`@warlock.js/logger/test-logging-code/SKILL.md`](@warlock.js/logger/test-logging-code/SKILL.md) for the test patterns.
927
+
928
+ ## See also
929
+
930
+ - [`@warlock.js/logger/logger-basics/SKILL.md`](@warlock.js/logger/logger-basics/SKILL.md) — logger foundations
931
+ - [`@warlock.js/logger/configure-logger/SKILL.md`](@warlock.js/logger/configure-logger/SKILL.md) — startup setup
932
+ - [`@warlock.js/logger/redact-sensitive-log-fields/SKILL.md`](@warlock.js/logger/redact-sensitive-log-fields/SKILL.md) — redaction
933
+ - [`@warlock.js/ai/handle-ai-errors/SKILL.md`](@warlock.js/ai/handle-ai-errors/SKILL.md) — what lands on the `error` channel
934
+
935
+
936
+ ## persist-ai-data `@warlock.js/ai/persist-ai-data/SKILL.md`
937
+
938
+ ---
939
+ 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`.'
941
+ ---
942
+
943
+ # Persistence — `@warlock.js/cache` everywhere
944
+
945
+ `@warlock.js/ai` owns no persistence primitives. Anything that needs durable state — supervisor / workflow snapshot resume, semantic cache, future memory — accepts a `CacheDriver` from `@warlock.js/cache`. The cache package ships memory / lru-memory / file / redis / pg drivers; pg adds optional `pgvector` for similarity retrieval.
946
+
947
+ ## The big picture
948
+
949
+ ```
950
+ ┌──────────────┐ ┌─────────────────────────┐
951
+ │ ai.config │ ───▶ │ @warlock.js/cache │
952
+ │ defaultStore │ │ CacheDriver │
953
+ └──────────────┘ │ (memory|redis|pg|...) │
954
+ └────────────▲────────────┘
955
+
956
+ ┌─────────────────────────────┼─────────────────────────────┐
957
+ ┌──────────────┐ ┌────────────────────┐ ┌──────────────────┐
958
+ │ supervisor │ │ workflow │ │ semanticCache │
959
+ │ snapshotStore│ │ snapshotStore │ │ store (vector) │
960
+ └──────────────┘ └────────────────────┘ └──────────────────┘
961
+ ```
962
+
963
+ ## Resolution order
964
+
965
+ ```
966
+ options.store ?? ai.config({ defaultStore }) ?? undefined
967
+ ```
968
+
969
+ When neither is set:
970
+ - **Snapshot consumers** silently skip writes and throw on `resume()`.
971
+ - **Semantic cache** throws at construction.
972
+
973
+ ## `ai.config({ defaultStore })` — set once at boot
974
+
975
+ ```ts
976
+ import { ai } from "@warlock.js/ai";
977
+ import { cache } from "@warlock.js/cache";
978
+
979
+ ai.config({
980
+ defaultStore: cache.driver("redis", { client: redisClient }),
981
+ });
982
+ ```
983
+
984
+ Every consumer that doesn't supply its own `store` / `snapshotStore` picks this up. Per-declaration overrides win.
985
+
986
+ ## Picking a driver
987
+
988
+ | Driver | KV | TTL | Tags | `similar()` | Fits |
989
+ |---|---|---|---|---|---|
990
+ | `memory` / `lru-memory` | ✅ | ✅ | ✅ | ✅ (brute force) | Dev / tests |
991
+ | `file` | ✅ | ✅ | ✅ | ❌ | Single-process persistence |
992
+ | `null` | no-op | no-op | no-op | `[]` | Test isolation |
993
+ | `redis` | ✅ | ✅ | ✅ | (RediSearch, separate phase) | Production KV + future similarity |
994
+ | `pg` | ✅ | ✅ | ✅ | ✅ (pgvector) | Production semantic cache |
995
+
996
+ Brute-force memory drivers carry an `O(N)` similarity scan — fine up to a few thousand entries.
997
+
998
+ ## Snapshot resume — workflow + supervisor
999
+
1000
+ ### Wiring
1001
+
1002
+ ```ts
1003
+ ai.config({ defaultStore: cache.driver("redis", { client }) });
1004
+
1005
+ const wf = ai.workflow({
1006
+ name: "ticket-processor",
1007
+ steps: [...],
1008
+ // snapshotStore optional — falls back to defaultStore
1009
+ });
1010
+
1011
+ const sup = ai.supervisor({
1012
+ name: "support-team",
1013
+ router: routerAgent,
1014
+ intents: { triage, billing, resolver },
1015
+ // explicit override when this primitive needs a different driver
1016
+ snapshotStore: cache.driver("pg", { client: pgPool, table: "support_runs" }),
1017
+ });
1018
+ ```
1019
+
1020
+ ### Snapshot shapes
1021
+
1022
+ ```ts
1023
+ type WorkflowSnapshot = {
1024
+ runId: string;
1025
+ workflowName: string;
1026
+ signature: string; // structural fingerprint
1027
+ version?: string;
1028
+ input: unknown;
1029
+ state: Record<string, unknown>;
1030
+ steps: Record<string, StepSnapshot>;
1031
+ next: string | null;
1032
+ status: "running" | "completed" | "failed" | "cancelled";
1033
+ startedAt: string;
1034
+ savedAt: string;
1035
+ };
1036
+
1037
+ type SupervisorSnapshot = {
1038
+ runId: string;
1039
+ supervisorName: string;
1040
+ signature: string;
1041
+ input: string | Record<string, unknown>; // SupervisorInput
1042
+ iteration: number; // last *completed* iteration; -1 before any settle
1043
+ snapshots: IterationSnapshot[];
1044
+ status: "running" | "completed" | "failed" | "cancelled";
1045
+ startedAt: string;
1046
+ savedAt: string;
1047
+ };
1048
+ ```
1049
+
1050
+ ### Checkpoint rules
1051
+
1052
+ - Workflow: snapshot after every step settles. Parallel groups checkpoint atomically.
1053
+ - Supervisor: snapshot after every iteration. Plus once on final completion / cancel / fail.
1054
+ - Mid-step / mid-iteration crash resumes from the last completed checkpoint — partial work is **not** persisted.
1055
+ - **Idempotency is the user's responsibility.** Steps and agents may re-run on resume.
1056
+
1057
+ ## Fresh run vs. resume
1058
+
1059
+ ```ts
1060
+ const result = await wf.execute({ input, runId: "ticket-123" });
1061
+ const result = await wf.resume("ticket-123");
1062
+
1063
+ await sup.execute("urgent", { runId: "support-7" });
1064
+ await sup.resume("support-7");
1065
+ ```
1066
+
1067
+ Resume reads the snapshot, rehydrates state, continues from the snapshot's `next`.
1068
+
1069
+ ## Signature drift detection
1070
+
1071
+ `signature` is a structural fingerprint computed at construction. On `resume()`, current signature is compared to the snapshot's. Mismatch throws `WorkflowDriftError` / `SupervisorDriftError` **without executing**:
1072
+
1073
+ ```ts
1074
+ {
1075
+ code: "WORKFLOW_DRIFT",
1076
+ savedSignature: "abc123…",
1077
+ currentSignature: "def456…",
1078
+ runId: "ticket-123",
1079
+ completedSteps: ["fetch", "extract"],
1080
+ pendingStep: "classify",
1081
+ }
1082
+ ```
1083
+
1084
+ ## Recovery paths
1085
+
1086
+ Three choices when drift is detected:
1087
+
1088
+ 1. **Discard** — safest when the shape genuinely changed:
1089
+
1090
+ ```ts
1091
+ await store.remove("ticket-123");
1092
+ await wf.execute({ input, runId: "ticket-123" });
1093
+ ```
1094
+
1095
+ 2. **Force resume** — escape hatch for trivial edits you know are safe:
1096
+
1097
+ ```ts
1098
+ await wf.resume("ticket-123", { force: true });
1099
+ ```
1100
+
1101
+ 3. **Manual migration** — for changes you can mechanically translate:
1102
+
1103
+ ```ts
1104
+ const snapshot = await store.get<WorkflowSnapshot>("ticket-123");
1105
+ if (snapshot) {
1106
+ snapshot.steps.newName = snapshot.steps.oldName;
1107
+ delete snapshot.steps.oldName;
1108
+ snapshot.signature = wf.signature;
1109
+ await store.set("ticket-123", snapshot);
1110
+ await wf.resume("ticket-123");
1111
+ }
1112
+ ```
1113
+
1114
+ ## Semantic cache
1115
+
1116
+ ```ts
1117
+ ai.config({
1118
+ defaultStore: cache.driver("pg", {
1119
+ client: pgPool,
1120
+ vector: { dimensions: 1536, index: "hnsw" },
1121
+ }),
1122
+ });
1123
+
1124
+ const myAgent = ai.agent({
1125
+ model,
1126
+ middleware: [
1127
+ ai.middleware.semanticCache({
1128
+ embedder: openai.embedder({ name: "text-embedding-3-small" }),
1129
+ threshold: 0.95,
1130
+ ttlMs: 60 * 60 * 1000,
1131
+ }),
1132
+ ],
1133
+ });
1134
+ ```
1135
+
1136
+ The driver must support `similar()`. Without similarity → `CacheUnsupportedError`. See [`@warlock.js/ai/attach-ai-middleware/SKILL.md`](@warlock.js/ai/attach-ai-middleware/SKILL.md).
1137
+
1138
+ ## See also
1139
+
1140
+ - [`@warlock.js/ai/run-ai-workflow/SKILL.md`](@warlock.js/ai/run-ai-workflow/SKILL.md) — `snapshotStore` + `resume()`
1141
+ - [`@warlock.js/ai/run-supervisor/SKILL.md`](@warlock.js/ai/run-supervisor/SKILL.md) — same on supervisor
1142
+ - [`@warlock.js/ai/attach-ai-middleware/SKILL.md`](@warlock.js/ai/attach-ai-middleware/SKILL.md) — `semanticCache` middleware
1143
+ - [`@warlock.js/ai/handle-ai-errors/SKILL.md`](@warlock.js/ai/handle-ai-errors/SKILL.md) — drift errors
1144
+ - [`@warlock.js/cache/cache-basics/SKILL.md`](@warlock.js/cache/cache-basics/SKILL.md) — driver catalog
1145
+
1146
+
1147
+ ## pick-ai-provider `@warlock.js/ai/pick-ai-provider/SKILL.md`
1148
+
1149
+ ---
1150
+ 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`.'
1152
+ ---
1153
+
1154
+ # Pick an AI provider adapter
1155
+
1156
+ `@warlock.js/ai` is provider-agnostic. Concrete adapters live in sibling packages and follow the same `SDKAdapterContract`. Pick by which provider(s) your app talks to and which capabilities the model needs.
1157
+
1158
+ ## Available adapters
1159
+
1160
+ | Package | Status | Notes |
1161
+ | --- | --- | --- |
1162
+ | `@warlock.js/ai-openai` | ✅ Shipped | OpenAI + any OpenAI-compatible gateway (OpenRouter, Together.ai, etc.) |
1163
+ | `@warlock.js/ai-anthropic` | ✅ Shipped | Native Claude API (Opus / Sonnet / Haiku) |
1164
+ | `@warlock.js/ai-bedrock` | ✅ Shipped | AWS Bedrock — Converse API + Titan embeddings |
1165
+ | `@warlock.js/ai-google` | ✅ Shipped | Gemini direct via `@google/genai`, native batch embeddings |
1166
+ | `@warlock.js/ai-ollama` | ✅ Shipped | Local models via the official `ollama` client |
1167
+
1168
+ All five first-party adapters share the same `SDKAdapterContract`, so switching providers is a one-line change at the model construction site. `ai-openrouter` is intentionally deferred — use `ai-openai` with a `baseURL` pointed at OpenRouter.
1169
+
1170
+ ## Decision tree
1171
+
1172
+ - **Default first choice:** `@warlock.js/ai-openai` direct to OpenAI. Best support, predictable behavior, native structured-output, native vision on `gpt-4o*`, embeddings, streaming.
1173
+ - **Need many models / cost arbitrage:** `@warlock.js/ai-openai` against OpenRouter. Same code, different `baseURL` + `provider: "openrouter"` on the SDK.
1174
+ - **Need native Claude features:** `@warlock.js/ai-anthropic` — Opus / Sonnet / Haiku via the native Messages API.
1175
+ - **Need local / self-hosted models:** `@warlock.js/ai-ollama`, or a local OpenAI-compatible gateway via `ai-openai`.
1176
+ - **Need AWS Bedrock pricing / compliance:** `@warlock.js/ai-bedrock` — Converse API + Titan embeddings.
1177
+ - **Need Gemini:** `@warlock.js/ai-google` — Gemini direct via `@google/genai`.
1178
+
1179
+ ## The adapter contract
1180
+
1181
+ ```ts
1182
+ interface SDKAdapterContract {
1183
+ model(config): ModelContract; // chat completions / tool calls / structured output
1184
+ count(text, model?): Promise<number>; // token counting
1185
+ embedder?(config): EmbedderContract; // optional — not every provider supports embeddings
1186
+ }
1187
+ ```
1188
+
1189
+ Adapters are classes — `new OpenAISDK({ apiKey })`, `new AnthropicSDK({ apiKey })`. They expose:
1190
+
1191
+ - `model({ name, ...options })` — returns a `ModelContract`. The provider label lives on the returned `ModelContract.provider` (`"openai"`, `"openrouter"`, …), not on the SDK.
1192
+ - `count(text, model?)` — provider-appropriate token count.
1193
+ - `embedder({ name })` — text-to-vector. Optional; check `typeof sdk.embedder === "function"` before calling.
1194
+
1195
+ The `ModelContract.capabilities` field declares what the model supports — two flags today, both optional (absent = treat as `false`):
1196
+
1197
+ ```ts
1198
+ type ModelCapabilities = {
1199
+ structuredOutput?: boolean; // native response_format: json_schema support?
1200
+ vision?: boolean; // can accept image attachments?
1201
+ };
1202
+ ```
1203
+
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.
1205
+
1206
+ ## OpenAI adapter — usage
1207
+
1208
+ ```ts
1209
+ import { OpenAISDK } from "@warlock.js/ai-openai";
1210
+
1211
+ // Direct OpenAI
1212
+ const openai = new OpenAISDK({
1213
+ apiKey: process.env.OPENAI_API_KEY!,
1214
+ pricing: {
1215
+ "gpt-4o-mini": { input: 0.15, output: 0.6, cachedInput: 0.075 },
1216
+ "gpt-4o": { input: 5.0, output: 15.0 },
1217
+ },
1218
+ });
1219
+
1220
+ const agent = ai.agent({ model: openai.model({ name: "gpt-4o-mini" }) });
1221
+ ```
1222
+
1223
+ ### Via OpenRouter (cost arbitrage, many providers)
1224
+
1225
+ ```ts
1226
+ const openrouter = new OpenAISDK({
1227
+ apiKey: process.env.OPENROUTER_API_KEY!,
1228
+ baseURL: "https://openrouter.ai/api/v1",
1229
+ provider: "openrouter", // labels reports correctly
1230
+ });
1231
+
1232
+ const agent = ai.agent({ model: openrouter.model({ name: "anthropic/claude-3.5-sonnet" }) });
1233
+ ```
1234
+
1235
+ Same `OpenAISDK` class, different `baseURL`. Reports label the provider via the `provider` field for downstream metrics.
1236
+
1237
+ ### Per-model overrides
1238
+
1239
+ ```ts
1240
+ const openai = new OpenAISDK({ apiKey });
1241
+
1242
+ // Override capabilities for a custom or fine-tuned model
1243
+ const customModel = openai.model({
1244
+ name: "my-org/custom-gpt-4-finetuned",
1245
+ vision: true, // override capabilities.vision
1246
+ structuredOutput: true,
1247
+ pricing: { input: 1.0, output: 3.0 }, // per-model pricing (wins over SDK registry)
1248
+ });
1249
+ ```
1250
+
1251
+ ## Pricing — per-channel cost breakdown
1252
+
1253
+ Configure `pricing` on the model (or via the SDK registry) and every report carries `Usage.cost`:
1254
+
1255
+ ```ts
1256
+ const openai = new OpenAISDK({
1257
+ apiKey,
1258
+ pricing: { "gpt-4o-mini": { input: 0.15, output: 0.6, cachedInput: 0.075 } },
1259
+ });
1260
+
1261
+ const { usage } = await ai.agent({ model: openai.model({ name: "gpt-4o-mini" }) }).execute("hi");
1262
+
1263
+ usage.cost;
1264
+ // { input: 0.0000045, output: 0.000192, cachedInput: 0.000009 } — USD breakdown
1265
+ ```
1266
+
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.
1268
+
1269
+ ## Embeddings
1270
+
1271
+ OpenAI ships the first embedder:
1272
+
1273
+ ```ts
1274
+ const embedder = openai.embedder({ name: "text-embedding-3-small" });
1275
+ const { vector } = await embedder.embed("Hello, world.");
1276
+ ```
1277
+
1278
+ See [`@warlock.js/ai/embed-text/SKILL.md`](@warlock.js/ai/embed-text/SKILL.md).
1279
+
1280
+ ## Multi-provider apps
1281
+
1282
+ Pattern: one SDK instance per provider, mix at the call site:
1283
+
1284
+ ```ts
1285
+ const openai = new OpenAISDK({ apiKey: process.env.OPENAI_API_KEY! });
1286
+ const openrouter = new OpenAISDK({
1287
+ apiKey: process.env.OPENROUTER_API_KEY!,
1288
+ baseURL: "https://openrouter.ai/api/v1",
1289
+ provider: "openrouter",
1290
+ });
1291
+
1292
+ const fastAgent = ai.agent({ model: openai.model({ name: "gpt-4o-mini" }) });
1293
+ const claudeAgent = ai.agent({ model: openrouter.model({ name: "anthropic/claude-3.5-sonnet" }) });
1294
+ ```
1295
+
1296
+ Reports label per-agent provider correctly. Pricing applies per SDK instance.
1297
+
1298
+ ## When the adapter changes
1299
+
1300
+ If you switch providers mid-project (e.g. OpenAI → Anthropic):
1301
+
1302
+ 1. The agent factory call signature stays the same — `ai.agent({ model: <newSdk>.model({...}) })`.
1303
+ 2. Capabilities matter — if the new model doesn't support `structuredOutput` natively, fall back to the soft "respond in JSON only" instruction (framework handles it).
1304
+ 3. Errors stay typed — `ProviderAuthError`, `ContextLengthExceededError`, etc. are adapter-agnostic.
1305
+ 4. Pricing matrix needs updating per the new provider's rates.
1306
+
1307
+ ## See also
1308
+
1309
+ - [`@warlock.js/ai-openai/setup-openai/SKILL.md`](@warlock.js/ai-openai/setup-openai/SKILL.md) — full OpenAI adapter docs
1310
+ - [`@warlock.js/ai/run-ai-agent/SKILL.md`](@warlock.js/ai/run-ai-agent/SKILL.md) — model passed into `ai.agent({...})`
1311
+ - [`@warlock.js/ai/embed-text/SKILL.md`](@warlock.js/ai/embed-text/SKILL.md) — embedder primitive on the SDK
1312
+ - [`@warlock.js/ai/handle-ai-errors/SKILL.md`](@warlock.js/ai/handle-ai-errors/SKILL.md) — adapter error categorization
1313
+
1314
+
1315
+ ## run-ai-agent `@warlock.js/ai/run-ai-agent/SKILL.md`
1316
+
1317
+ ---
1318
+ 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`.'
1320
+ ---
1321
+
1322
+ # `ai.agent()` — single-turn primitive
1323
+
1324
+ The lowest rung of the 4-primitive ladder. One LLM call, optional tool loop, optional structured output. Stateless across calls.
1325
+
1326
+ ## Factory shape
1327
+
1328
+ ```ts
1329
+ import { ai } from "@warlock.js/ai";
1330
+ import { OpenAISDK } from "@warlock.js/ai-openai";
1331
+
1332
+ const openai = new OpenAISDK({ apiKey: process.env.OPENAI_API_KEY! });
1333
+
1334
+ ai.agent({
1335
+ name?: string, // optional — anonymous gets a fingerprint
1336
+ model: openai.model({ name: "gpt-4o-mini" }),
1337
+ systemPrompt?: string | SystemPromptContract,
1338
+ tools?: ToolContract<any, any>[],
1339
+ placeholders?: Record<string, unknown>,
1340
+ maxTrips?: number, // default 10
1341
+ modelOptions?: ModelCallOptions,
1342
+ output?: StandardSchemaV1<T>, // default structured-output schema
1343
+ middleware?: AgentMiddleware[],
1344
+ streamingToolGuard?: StreamingToolGuardConfig, // opt-in tool-call recovery from text leaks
1345
+ on?: AgentEventHandlers,
1346
+ version?: string, // mirrored onto reports for trip archives
1347
+ });
1348
+ ```
1349
+
1350
+ The factory returns an `AgentContract<TOutput>`. Every execution spawns a fresh internal `Execution` — the factory holds no per-call state.
1351
+
1352
+ ## Anonymous agents
1353
+
1354
+ `name` is optional. Anonymous agents receive a deterministic fingerprint:
1355
+
1356
+ ```
1357
+ anon_<provider>_<model>[_<tool1>+<tool2>+...]
1358
+ ```
1359
+
1360
+ Same config across process restarts → same synthetic name. Keeps workflow signature drift detection honest when you compose anonymous agents into a workflow.
1361
+
1362
+ ## Execute surface
1363
+
1364
+ ```ts
1365
+ agent.execute(input: string, options?: AgentExecuteOptions): Promise<AgentResult<T>>;
1366
+ agent.stream(input: string, options?: AgentExecuteOptions): StreamContract<AgentResult<T>>;
1367
+ ```
1368
+
1369
+ `AgentExecuteOptions` — every field optional:
1370
+
1371
+ ```ts
1372
+ {
1373
+ history?: Message[];
1374
+ attachments?: Attachment[]; // images today; PDFs later
1375
+ placeholders?: Record<string, unknown>;
1376
+ output?: StandardSchemaV1<T>; // typed structured output → result.data
1377
+ responseSchema?: Record<string, unknown>; // hand-crafted JSON Schema escape hatch
1378
+ systemPrompt?: SystemPromptContract; // per-call override
1379
+ repair?: { maxAttempts?: number }; // opt-in re-ask on validation failure
1380
+ signal?: AbortSignal; // cancellation
1381
+ sessionId?: string; // stitch many runs into one session
1382
+ streamingToolGuard?: StreamingToolGuardConfig;
1383
+ on?: AgentEventHandlers;
1384
+ }
1385
+ ```
1386
+
1387
+ ## `streamingToolGuard` — recover tool calls leaked as text
1388
+
1389
+ Cheap and fast models occasionally emit a registered tool's structured input as **literal text in the content stream** instead of as a real `tool_call`. Without intervention, customers watch raw JSON build character-by-character.
1390
+
1391
+ ```ts
1392
+ ai.agent({
1393
+ model: someFastModel,
1394
+ tools: [suggestFollowupsTool, searchCatalogTool],
1395
+ streamingToolGuard: {}, // empty object = on with defaults
1396
+ });
1397
+ ```
1398
+
1399
+ Recovery conditions: the buffered JSON must (a) parse cleanly, (b) carry a `name` or `tool` key resolving to a registered tool, AND (c) carry an `arguments` or `input` key whose value validates against that tool's input schema. Anything else flushes back as text — the guard never invents calls.
1400
+
1401
+ **Off by default.** Set this explicitly on agents whose registered tools have been observed to leak.
1402
+
1403
+ ## `sessionId` — stitch many runs into one user session
1404
+
1405
+ ```ts
1406
+ const sessionId = "sess_user_42_2026-05-12";
1407
+ await agent.execute("what's my order?", { sessionId });
1408
+ await agent.execute("cancel it", { sessionId }); // 30 seconds later, same session
1409
+ ```
1410
+
1411
+ The framework stamps it onto every report node this run produces. Cost dashboards can group by `sessionId` without joining the report tree.
1412
+
1413
+ ## Result shape — `AgentResult<T>`
1414
+
1415
+ ```ts
1416
+ type AgentResult<T> = {
1417
+ type: "agent";
1418
+ data?: T; // structured output when `output` schema was supplied
1419
+ text?: string; // raw final LLM text
1420
+ report: AgentReport; // trips, toolCalls, status, timing
1421
+ usage: Usage; // aggregated token usage + cost breakdown
1422
+ error?: AIError;
1423
+ };
1424
+
1425
+ type AgentReport = {
1426
+ runId: string;
1427
+ rootRunId: string;
1428
+ name: string;
1429
+ status: "completed" | "failed" | "cancelled";
1430
+ startedAt: string;
1431
+ endedAt: string;
1432
+ duration: number;
1433
+ model: { name: string; provider: string };
1434
+ trips: LLMTrip[];
1435
+ children: ToolCall[]; // tool dispatches — filter by `c.type === "tool"`
1436
+ };
1437
+ ```
1438
+
1439
+ Tool calls are NOT a separate `report.toolCalls` field — every tool dispatch is a child `BaseReport` node (`type: "tool"`) on `report.children`. Filter the tree to isolate them:
1440
+
1441
+ ```ts
1442
+ const toolCalls = report.children.filter((c) => c.type === "tool");
1443
+ const nestedAgents = report.children.filter((c) => c.type === "agent");
1444
+ ```
1445
+
1446
+ Canonical destructuring:
1447
+
1448
+ ```ts
1449
+ const { data, text, report, usage, error } = await agent.execute(input);
1450
+
1451
+ if (error) {
1452
+ logger.warn(error.code, { duration: report.duration, trips: report.trips.length });
1453
+ return;
1454
+ }
1455
+ ```
1456
+
1457
+ ## Pattern — structured output
1458
+
1459
+ ```ts
1460
+ import { v, type Infer } from "@warlock.js/seal";
1461
+
1462
+ const summarySchema = v.object({
1463
+ summary: v.string(),
1464
+ keyPoints: v.array(v.string()).min(1),
1465
+ });
1466
+
1467
+ const result = await myAgent.execute(input, { output: summarySchema });
1468
+
1469
+ if (result.data) {
1470
+ // typed as Infer<typeof summarySchema>
1471
+ }
1472
+ ```
1473
+
1474
+ Adapters with `capabilities.structuredOutput: true` forward the schema natively. Adapters without it get a soft "respond in JSON only" instruction. Client-side validation always runs.
1475
+
1476
+ ## Pattern — output baked into the agent
1477
+
1478
+ ```ts
1479
+ const titleAgent = ai.agent({
1480
+ model: openai.model({ name: "gpt-4o-mini" }),
1481
+ output: titleSchema, // typed end-to-end via AgentContract<Infer<typeof titleSchema>>
1482
+ systemPrompt: "...",
1483
+ });
1484
+
1485
+ const result = await titleAgent.execute(currentMessage, { history });
1486
+ // ^? AgentResult<{ title?: string }>
1487
+ ```
1488
+
1489
+ Call-site `options.output` fully **replaces** `config.output` for that run — no merging.
1490
+
1491
+ ## Pattern — repair on validation failure
1492
+
1493
+ ```ts
1494
+ await myAgent.execute(input, {
1495
+ output: schema,
1496
+ repair: { maxAttempts: 1 }, // re-ask once on parse/validation failure
1497
+ });
1498
+ ```
1499
+
1500
+ Disabled by default. Each repair attempt counts against `maxTrips`.
1501
+
1502
+ ## Pattern — image attachments
1503
+
1504
+ ```ts
1505
+ await myAgent.execute("What's in this?", {
1506
+ attachments: ["./photo.png", "https://cdn.example.com/cat.jpg"],
1507
+ });
1508
+ ```
1509
+
1510
+ Shorthand strings infer the image kind from extension. Tagged form for explicit control:
1511
+
1512
+ ```ts
1513
+ attachments: [
1514
+ { type: "image", source: "./photo" },
1515
+ { type: "image", source: { base64: "...", mediaType: "image/png" } },
1516
+ ];
1517
+ ```
1518
+
1519
+ Model must declare `capabilities.vision`. OpenAI adapter auto-infers from name; override with `openai.model({ name, vision: true })`.
1520
+
1521
+ ## Pattern — streaming
1522
+
1523
+ ```ts
1524
+ const stream = myAgent.stream(input);
1525
+
1526
+ for await (const event of stream) {
1527
+ if (event.type === "agent.trip.streaming") {
1528
+ process.stdout.write(event.delta);
1529
+ }
1530
+ }
1531
+
1532
+ const result = await stream.result;
1533
+ ```
1534
+
1535
+ Or use `.on({ "agent.trip.streaming": ..., "agent.completed": ..., "agent.error": ... })` alongside iteration.
1536
+
1537
+ ## Pattern — cancellation
1538
+
1539
+ ```ts
1540
+ const ctrl = new AbortController();
1541
+ const resultPromise = myAgent.execute(input, { signal: ctrl.signal });
1542
+
1543
+ setTimeout(() => ctrl.abort("too slow"), 30_000);
1544
+
1545
+ const { error, report } = await resultPromise;
1546
+ if (report.status === "cancelled") {
1547
+ // error is an AgentCancelledError (code "AGENT_CANCELLED",
1548
+ // category "cancelled") carrying `cancelledAt` + `reason`
1549
+ }
1550
+ ```
1551
+
1552
+ Between-trip abort is guaranteed. Mid-trip best-effort.
1553
+
1554
+ ## Events — dot-notation + 3-tier subscription
1555
+
1556
+ - `agent.starting`, `agent.trip.started`, `agent.trip.streaming`, `agent.trip.completed`
1557
+ - `agent.tool.calling`, `agent.tool.called`, `agent.tool.failed`
1558
+ - `agent.completed`, `agent.error`
1559
+
1560
+ Three subscription tiers — fire in order **factory → instance → per-call**:
1561
+
1562
+ ```ts
1563
+ ai.agent({ model, on: { "agent.starting": () => metrics.inc("agent.runs") } });
1564
+
1565
+ const unsubscribe = myAgent.on("agent.error", ({ error }) => logger.error(error));
1566
+
1567
+ await myAgent.execute("go", {
1568
+ on: { "agent.trip.completed": ({ trip }) => console.log(trip.duration) },
1569
+ });
1570
+ ```
1571
+
1572
+ Every event payload carries `runId` and `rootRunId`. Same identity fields ride on stream events.
1573
+
1574
+ ## When NOT to use this primitive
1575
+
1576
+ - Multi-step pipeline with a fixed shape → [`@warlock.js/ai/run-ai-workflow/SKILL.md`](@warlock.js/ai/run-ai-workflow/SKILL.md)
1577
+ - Multi-agent routing with iteration → [`@warlock.js/ai/run-supervisor/SKILL.md`](@warlock.js/ai/run-supervisor/SKILL.md)
1578
+
1579
+ ## See also
1580
+
1581
+ - [`@warlock.js/ai/define-ai-tool/SKILL.md`](@warlock.js/ai/define-ai-tool/SKILL.md) — tool wiring + schema validation
1582
+ - [`@warlock.js/ai/write-system-prompt/SKILL.md`](@warlock.js/ai/write-system-prompt/SKILL.md) — persona / instruction builders
1583
+ - [`@warlock.js/ai/handle-ai-errors/SKILL.md`](@warlock.js/ai/handle-ai-errors/SKILL.md) — `AIError` hierarchy
1584
+
1585
+
1586
+ ## run-ai-workflow `@warlock.js/ai/run-ai-workflow/SKILL.md`
1587
+
1588
+ ---
1589
+ name: run-ai-workflow
1590
+ description: 'Build durable resumable pipelines with ai.workflow({...}) + ai.step({...}) — lifecycle (skip / before / run|agent|parallel / output / after / nextStep), retry, parallel groups, snapshot resume. Triggers: `ai.workflow`, `ai.step`, `wf.execute`, `wf.resume`, `WorkflowContext`, `WorkflowResult`, `StepSnapshot`, `nextStep`, `onFailure`, `WorkflowDriftError`; ''build a workflow'', ''define a step'', ''resume after crash'', ''parallel steps'', ''retry with backoff''; typical import `import { ai } from "@warlock.js/ai"`. Skip: agent — `@warlock.js/ai/run-ai-agent/SKILL.md`; supervisor — `@warlock.js/ai/run-supervisor/SKILL.md`; competing libs `temporal`, `inngest`, `bullmq`.'
1591
+ ---
1592
+
1593
+ # `ai.workflow()` — static, deterministic pipelines
1594
+
1595
+ Second rung of the 4-primitive ladder. A named, ordered set of steps with a stable signature. Each step is exactly one of: an agent call (`agent`), a `run` function, or a parallel group (`parallel`). Compose another workflow in by wrapping it with `workflow.asTool()` and calling it from a `run` step. Durable (resumable via any `CacheDriver` from `@warlock.js/cache`), observable, cancellable.
1596
+
1597
+ ## When NOT to use a workflow
1598
+
1599
+ - Unknown shape at author time → wait for `ai.planner()` (v3)
1600
+ - Quality-loop until goal met → [`@warlock.js/ai/run-supervisor/SKILL.md`](@warlock.js/ai/run-supervisor/SKILL.md)
1601
+ - Multi-turn conversation with persistent session → orchestrator (v2)
1602
+ - Iterate a runtime list of items → `ai.batch()` utility wrapping a workflow
1603
+
1604
+ ## Minimal shape
1605
+
1606
+ ```ts
1607
+ import { ai } from "@warlock.js/ai";
1608
+ import { MemoryCacheDriver } from "@warlock.js/cache";
1609
+ import { v } from "@warlock.js/seal";
1610
+
1611
+ ai.config({ defaultStore: new MemoryCacheDriver() });
1612
+
1613
+ type CatalogInput = { url: string };
1614
+ type CatalogOutput = { id: string };
1615
+ type CatalogState = { html?: string; catalogId?: string };
1616
+
1617
+ const wf = ai.workflow<CatalogInput, CatalogOutput, CatalogState>({
1618
+ name: "catalog-item",
1619
+ output: {
1620
+ extract: (ctx) => ({ id: ctx.state.catalogId ?? "" }),
1621
+ schema: v.object({ id: v.string() }),
1622
+ },
1623
+ steps: [
1624
+ ai.step<CatalogInput, CatalogState>({
1625
+ name: "fetch",
1626
+ run: async (ctx) => {
1627
+ ctx.state.html = await fetch(ctx.input.url).then(r => r.text());
1628
+ },
1629
+ }),
1630
+ ai.step<CatalogInput, CatalogState>({
1631
+ name: "extract",
1632
+ agent: extractorAgent,
1633
+ input: (ctx) => ({ prompt: `Extract from: ${ctx.state.html}` }),
1634
+ output: {
1635
+ extract: (ctx) => ctx.agentResult?.data,
1636
+ schema: itemSchema,
1637
+ },
1638
+ retry: { attempts: 3, backoff: "exponential" },
1639
+ }),
1640
+ ],
1641
+ });
1642
+ ```
1643
+
1644
+ ## Generics
1645
+
1646
+ ```ts
1647
+ ai.workflow<TInput, TOutput, TState, TContext>(...)
1648
+ ai.step<TInput, TState, TContext>(...)
1649
+ ```
1650
+
1651
+ Order: Input/Output describe the public contract, State before Context because step bodies touch state more often. Defaults (`unknown`, `Record<string, unknown>`) let partial typing work.
1652
+
1653
+ ## Execute — two interchangeable shapes
1654
+
1655
+ ```ts
1656
+ // canonical — mirrors agent.execute
1657
+ const result = await wf.execute(
1658
+ { url: "https://..." },
1659
+ { runId: "catalog-123", signal: AbortSignal.timeout(60_000) },
1660
+ );
1661
+
1662
+ // single-object — ergonomic alt
1663
+ const result = await wf.execute({
1664
+ input: { url: "https://..." },
1665
+ runId: "catalog-123",
1666
+ });
1667
+ ```
1668
+
1669
+ `WorkflowRunOptions` carries `runId`, `signal`, `on`, `context`, `sessionId`. `WorkflowDefinition.version` mirrors onto every produced report.
1670
+
1671
+ ## `execute()` never throws
1672
+
1673
+ All failures funnel into `result.error`:
1674
+
1675
+ - `StepFailedError` / `STEP_FAILED`
1676
+ - `RoutingError` / `WORKFLOW_INVALID_GOTO`
1677
+ - `WorkflowDriftError` / `WORKFLOW_DRIFT`
1678
+ - `WorkflowCancelledError` / `WORKFLOW_CANCELLED`
1679
+ - `MaxStepsExceededError` / `WORKFLOW_MAX_STEPS`
1680
+
1681
+ See [`@warlock.js/ai/handle-ai-errors/SKILL.md`](@warlock.js/ai/handle-ai-errors/SKILL.md).
1682
+
1683
+ ## Result shape
1684
+
1685
+ ```ts
1686
+ const { data, report, usage, error } = await wf.execute(input);
1687
+ ```
1688
+
1689
+ ```ts
1690
+ type WorkflowResult<TOutput> = {
1691
+ type: "workflow";
1692
+ data?: TOutput; // from workflow.output.extract
1693
+ report: WorkflowReport; // runId, signature, status, timings, per-step snapshots
1694
+ usage: Usage; // aggregated across all agent calls
1695
+ error?: AIError;
1696
+ };
1697
+ ```
1698
+
1699
+ `report.steps[name]` holds a frozen `StepSnapshot` with `output`, `status`, `attempts`, `attemptHistory`, timings, nested children for parallel groups.
1700
+
1701
+ ## Step lifecycle
1702
+
1703
+ ```
1704
+ skip? → before? → (run | agent | parallel) → output.extract (+ schema) → after? → nextStep?
1705
+ ```
1706
+
1707
+ Exactly one of `run` / `agent` / `parallel` per step (enforced at `ai.step()` author time).
1708
+
1709
+ | Phase | Purpose |
1710
+ | --- | --- |
1711
+ | `skip` | Return `true` to skip the step. Output becomes `undefined`. `nextStep` still fires. |
1712
+ | `before` | Pre-work — fetch, set state, validate. |
1713
+ | `run` | Core non-agent work. |
1714
+ | `agent` | Agent to execute. Takes `input(ctx)` as prompt builder. |
1715
+ | `input` | Required when `agent` is set. |
1716
+ | `output` | `{ extract, schema? }` — extracts the step's output. |
1717
+ | `after` | Post-work — save, notify. |
1718
+ | `nextStep` | Step-level routing on `completed` / `skipped`. |
1719
+ | `onFailure` | Step-level recovery routing after retries exhaust. |
1720
+ | `onCancel` | Cleanup if cancelled in-flight. |
1721
+
1722
+ Errors in `before`/`run`/`agent`/`after`/`output` are retryable. Errors in `nextStep` and `onFailure` terminate the workflow with `RoutingError`.
1723
+
1724
+ ## Context (`ctx`)
1725
+
1726
+ ```ts
1727
+ type WorkflowContext<TInput, TState, TContext> = {
1728
+ readonly input: TInput; // frozen — durable cause
1729
+ readonly context: TContext; // frozen — per-execution
1730
+ readonly steps: Record<string, StepSnapshot>; // frozen snapshots of COMPLETED steps
1731
+ state: TState; // mutable current shared state
1732
+ readonly agentResult?: AgentResult<unknown>; // set when current step has an agent
1733
+ readonly runId: string;
1734
+ readonly signal?: AbortSignal;
1735
+ readonly startedAt: Date;
1736
+ };
1737
+ ```
1738
+
1739
+ `input`, `context`, `steps` are deep-frozen. `state` is mutable during a step and frozen into `steps[name].state` on completion.
1740
+
1741
+ ### `input` vs `context`
1742
+
1743
+ - `input` answers *what* to process — persisted in the snapshot, replayed verbatim on `resume()`.
1744
+ - `context` answers *who's running it* — tenancy, user, locale, traceId. **Never persisted.** Callers pass fresh on every `execute()` and `resume()`.
1745
+
1746
+ **Resume rule.** No fingerprinting on context. Persistence-scoping fields (e.g. `organizationId`) MUST match across resume — silent data corruption otherwise.
1747
+
1748
+ ## State vs `steps[x].output` — performance
1749
+
1750
+ - **Small control data** (flags, counters) → `ctx.state`. Cheap.
1751
+ - **Large artifacts** (HTML blobs, embedding vectors) → producer's `output.extract`, read via `ctx.steps[prev].output`. `ctx.state` clones on every retry attempt; `ctx.steps` clones once on step commit.
1752
+
1753
+ ## Parallel children
1754
+
1755
+ ```ts
1756
+ ai.step({
1757
+ name: "generate",
1758
+ parallel: [
1759
+ ai.step({ name: "draft", agent: writerAgent, input, output }),
1760
+ ai.step({ name: "suggest-articles", agent: kbAgent, input, output }),
1761
+ ],
1762
+ });
1763
+ ```
1764
+
1765
+ - Children share `ctx.state` — last-write-wins.
1766
+ - Addressable by flat (`ctx.steps.draft`) AND nested (`ctx.steps.generate.steps.draft`) path.
1767
+ - Any child fails → all siblings still complete (atomic settlement); parent's `error` becomes the first child's error.
1768
+ - Checkpoint atomically after all children settle.
1769
+
1770
+ ## Routing — `nextStep` (success) + `onFailure` (failure)
1771
+
1772
+ ```ts
1773
+ ai.step({
1774
+ name: "qa",
1775
+ agent: qaReviewerAgent,
1776
+ input,
1777
+ output,
1778
+ nextStep: (ctx) => {
1779
+ if (!ctx.agentResult?.data.approved) {
1780
+ ctx.state.qaFeedback = ctx.agentResult?.data.feedback;
1781
+ return { goto: "draft" }; // success-path route
1782
+ }
1783
+ },
1784
+ onFailure: (ctx, error) => {
1785
+ if (error.code === "PROVIDER_RATE_LIMIT") {
1786
+ return { goto: "fallbackQa" };
1787
+ }
1788
+ // void → halt with the original StepFailedError
1789
+ },
1790
+ });
1791
+ ```
1792
+
1793
+ Returns: `{ goto: "stepName" }`, `{ end: true }`, or `void` (fall through / halt).
1794
+
1795
+ **Guards:** `maxSteps` (default 100) hard-fails with `MaxStepsExceededError`. `loopWarnAfter` (default 5) emits `workflow.loop.warning`.
1796
+
1797
+ ## Retry
1798
+
1799
+ ```ts
1800
+ retry: {
1801
+ attempts: 3, // default 1 = no retry
1802
+ backoff: "exponential", // "none" | "linear" | "exponential" | (attempt) => ms
1803
+ retryOn: (error, attempt) => true,
1804
+ onRetry: (attempt, error) => {},
1805
+ }
1806
+ ```
1807
+
1808
+ Exponential defaults: 500 ms → 1 s → 2 s → 4 s → 8 s, capped at 30 s. `AbortError` short-circuits retry.
1809
+
1810
+ ## Cancellation
1811
+
1812
+ ```ts
1813
+ const ctrl = new AbortController();
1814
+ const result = wf.execute({ input, signal: ctrl.signal });
1815
+ ctrl.abort("user cancelled");
1816
+ ```
1817
+
1818
+ Between-step cancellation is guaranteed. Mid-step is best-effort. `status: "cancelled"` on return with partial `report.steps`; checkpoint written before returning (resume works).
1819
+
1820
+ ## Persistence & resume
1821
+
1822
+ See [`@warlock.js/ai/persist-ai-data/SKILL.md`](@warlock.js/ai/persist-ai-data/SKILL.md).
1823
+
1824
+ ```ts
1825
+ await wf.execute({ input, runId: "ticket-123" }); // fresh run
1826
+ await wf.resume("ticket-123"); // after crash
1827
+ ```
1828
+
1829
+ ## Events — three-tier subscription
1830
+
1831
+ `workflow.starting`, `workflow.step.{starting|streaming|completed|skipped|retrying|failed}`, `workflow.loop.warning`, `workflow.cancelled`, `workflow.completed`, `workflow.error`.
1832
+
1833
+ Subscription order: **definition → instance → per-call** (all matching handlers fire).
1834
+
1835
+ Every payload carries `runId` and `rootRunId`.
1836
+
1837
+ ## Design reference
1838
+
1839
+ `domains/ai/design/workflow.md` — locked spec, §1–§16 covers every rule with five PoC examples.
1840
+
1841
+ ## See also
1842
+
1843
+ - [`@warlock.js/ai/run-ai-agent/SKILL.md`](@warlock.js/ai/run-ai-agent/SKILL.md) — agents inside steps
1844
+ - [`@warlock.js/ai/persist-ai-data/SKILL.md`](@warlock.js/ai/persist-ai-data/SKILL.md) — snapshot resume + drift
1845
+ - [`@warlock.js/ai/handle-ai-errors/SKILL.md`](@warlock.js/ai/handle-ai-errors/SKILL.md) — `WorkflowError` subclasses
1846
+
1847
+
1848
+ ## run-supervisor `@warlock.js/ai/run-supervisor/SKILL.md`
1849
+
1850
+ ---
1851
+ 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`.'
1853
+ ---
1854
+
1855
+ # `ai.supervisor()` — multi-intent routing
1856
+
1857
+ A supervisor takes one input, picks which intent(s) handle it, runs them, optionally evaluates the result, and either terminates or iterates. Stateless between runs unless you wire `snapshotStore` for resume.
1858
+
1859
+ ## When to reach for it
1860
+
1861
+ - **`agent`** — one model + tools, single task. Doesn't fit when the right specialist depends on the input.
1862
+ - **`workflow`** — fixed step order. Doesn't fit when routing decisions need an LLM or vary per request.
1863
+ - **`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.
1865
+
1866
+ ## Three dispatch surfaces
1867
+
1868
+ | | When it fires | Iterations |
1869
+ | --- | --- | --- |
1870
+ | `classifier` | iter-0 prelude — picks the FIRST intent | 1 |
1871
+ | `router` | iter 0+ (no classifier); iter 1+ (with classifier) | 1..maxIterations |
1872
+ | `route` | iter 0+ (no classifier); iter 1+ (with classifier) | 1..maxIterations |
1873
+
1874
+ `router` and `route` are mutually exclusive. `classifier` composes with either. Classifier alone (no router/route) → terminates after iter 0.
1875
+
1876
+ `classifier` is mutually exclusive with `initialAgent`.
1877
+
1878
+ Quick decision tree:
1879
+ - Pure classification → `classifier` alone.
1880
+ - Multi-step reasoning → `router` + `intents` with rich descriptions.
1881
+ - Deterministic routing → `route` callback.
1882
+ - Classify-then-iterate → `classifier` + `router`/`route`.
1883
+
1884
+ ## Two routing modes — `route` XOR `router`
1885
+
1886
+ ### Deterministic — `route(ctx)`
1887
+
1888
+ ```ts
1889
+ const triageBot = ai.supervisor({
1890
+ name: "triage",
1891
+ intents: { billing, shipping, returns },
1892
+ route: (ctx) => {
1893
+ const text = typeof ctx.input === "string" ? ctx.input.toLowerCase() : "";
1894
+ if (text.includes("refund")) return "billing";
1895
+ if (text.includes("ship")) return "shipping";
1896
+ return "returns";
1897
+ },
1898
+ });
1899
+ ```
1900
+
1901
+ `route` returns `string | string[] | typeof END`. Array → fan-out.
1902
+
1903
+ ### LLM-driven — `router` agent
1904
+
1905
+ ```ts
1906
+ const routerAgent = ai.agent({
1907
+ output: v.object({ next: v.string(), reasoning: v.string() }),
1908
+ // ...
1909
+ });
1910
+
1911
+ const supportBot = ai.supervisor({
1912
+ router: routerAgent,
1913
+ intents: { billing, shipping, returns, escalate },
1914
+ evaluate: (ctx) =>
1915
+ Object.values(ctx.result).some((b: any) => b.data?.resolved)
1916
+ ? { satisfied: true }
1917
+ : undefined,
1918
+ });
1919
+ ```
1920
+
1921
+ The router agent's output MUST include `next: string | string[] | typeof END`; `reasoning: string` is optional but recommended.
1922
+
1923
+ `evaluate` pairs with both `router` AND `route` — state-driven termination is useful in either dispatch mode.
1924
+
1925
+ ## The `intents` map — five accepted shapes
1926
+
1927
+ ```ts
1928
+ intents: {
1929
+ billing: billingAgent, // (a) AgentContract
1930
+ escalate: escalationWorkflow, // (b) WorkflowInstance
1931
+ refund: async (ctx) => ({ refundId: await callRefundAPI(ctx.input) }), // (c) callback
1932
+ triage: { // (d) agent entry
1933
+ agent: triageAgent,
1934
+ description: "First-pass classifier",
1935
+ placeholders: (ctx) => ({ ticket: ctx.input }),
1936
+ output: v.object({ category: v.string() }),
1937
+ },
1938
+ cancel: { // (e) callback entry
1939
+ run: async (ctx) => ({ cancelledId: await cancelOrder(ctx.input) }),
1940
+ description: "Cancel on customer request",
1941
+ output: v.object({ cancelledId: v.string() }),
1942
+ },
1943
+ }
1944
+ ```
1945
+
1946
+ Runtime detects shape in order: `function → "run" in value → "agent" in value → instanceof`. Mixed dispatch fields (`{ agent, run }` together) throw at construction.
1947
+
1948
+ **Under a router**, every intent MUST have a non-empty `description` so the LLM has signal. Bare callback shorthand has no description — upgrade to `{ run, description }` under a router.
1949
+
1950
+ ## State model
1951
+
1952
+ A supervisor builds up typed `state` across iterations. Each intent contributes a slice; final state validates against the supervisor's `output` schema.
1953
+
1954
+ ```ts
1955
+ type RefundOutput = { category: string; order?: { id: string }; reply: string };
1956
+
1957
+ const refundSupervisor = ai.supervisor<RefundOutput>({
1958
+ name: "refund-support",
1959
+ output: outputSchema,
1960
+ intents: {
1961
+ classify: { agent: classifierAgent, output: v.object({ category: v.string() }) },
1962
+ lookupOrder: {
1963
+ run: async (ctx) => ({ order: await ordersRepo.find(extractId(ctx.input)) }),
1964
+ },
1965
+ compose: { agent: replyAgent, output: v.object({ reply: v.string() }) },
1966
+ },
1967
+ router: routerAgent,
1968
+ evaluate: (ctx) => (ctx.state.reply ? { satisfied: true } : undefined),
1969
+ });
1970
+ ```
1971
+
1972
+ Each branch's output strip-merges into state per its declared `output` schema. Last-write-wins on fan-out conflict (warning logged).
1973
+
1974
+ ## Per-intent `next` — skip the router
1975
+
1976
+ ```ts
1977
+ intents: {
1978
+ classify: {
1979
+ agent: classifierAgent,
1980
+ next: (ctx) => ctx.state.category === "refund" ? "lookupOrder" : "escalate",
1981
+ },
1982
+ lookupOrder: {
1983
+ run: async (ctx) => ({ order: await ordersRepo.find(extractId(ctx.input)) }),
1984
+ next: (ctx) => ctx.state.order ? "compose" : "escalate",
1985
+ },
1986
+ compose: { agent: replyAgent, next: () => END },
1987
+ }
1988
+ ```
1989
+
1990
+ Returns: `string` (intent name), `string[]` (fan-out), `END` (terminate), `undefined` (fall back to router). Order of authority: `evaluate` → `intent.next` → `router/route`.
1991
+
1992
+ ## Stream-mode intents
1993
+
1994
+ For chat-style prose replies, opt out of structured-output coercion:
1995
+
1996
+ ```ts
1997
+ intents: {
1998
+ smalltalk: {
1999
+ agent: smalltalkAgent,
2000
+ mode: "stream",
2001
+ streamTo: "reply", // raw text → state.reply
2002
+ },
2003
+ }
2004
+ ```
2005
+
2006
+ Token deltas surface as `supervisor.agent.streaming`. `mode: "stream"` + `output` together throws — they're mutually exclusive. Stream mode is agent-only (workflows can't stream this way).
2007
+
2008
+ ## `ack` — fast preamble
2009
+
2010
+ When the router agent / first specialist takes 5+ seconds and users feel it:
2011
+
2012
+ ```ts
2013
+ ack: (ctx) => ({ ack: "Got it, one moment..." }) // bare callback
2014
+ ack: { run: (ctx) => ({ ack: pickHedge(ctx.input) }), output: v.object({ ack: v.string() }) }
2015
+ ack: { agent: tinyAckAgent, placeholders: (ctx) => ({ tier: ctx.context.customerTier as string }) }
2016
+ ```
2017
+
2018
+ Fires on iter-0 only, in parallel with the routing decision. **Same-model trap:** if ack uses the same model+provider as the router, ack often takes longer than the router. The callback forms (1+2) are right for the common case.
2019
+
2020
+ ## Classifier — `classifier`
2021
+
2022
+ Iter-0 prelude. Output locked to `{ intent, reasoning?, confidence? }`.
2023
+
2024
+ ```ts
2025
+ classifier: classifyAgent
2026
+ // or with refine:
2027
+ classifier: {
2028
+ agent: classifyAgent,
2029
+ refine: (ctx) => {
2030
+ const { confidence } = ctx.result.data;
2031
+ if ((confidence ?? 1) < 0.7) return { intent: "fallback" };
2032
+ return undefined;
2033
+ },
2034
+ }
2035
+ ```
2036
+
2037
+ `refine` shapes: `undefined` (keep), `END` (halt), `{ intent: "x", ...slice }` (override + merge), `{ ...slice }` (keep intent, merge).
2038
+
2039
+ LLM-reported `confidence` is poorly calibrated — use it as a soft signal alongside heuristics.
2040
+
2041
+ ## Tool artifacts — `ctx.artifacts`
2042
+
2043
+ Tools mutate `ctx.artifacts`; supervisor merges into `state` at iteration end.
2044
+
2045
+ ```ts
2046
+ ai.supervisor({
2047
+ artifactsSchema: v.object({ blocks: v.array(blockSchema).optional() }),
2048
+ finalizeArtifacts: (state, artifacts) => ({
2049
+ ...state,
2050
+ blocks: [...(state.blocks ?? []), ...(artifacts.blocks ?? [])],
2051
+ }),
2052
+ });
2053
+ ```
2054
+
2055
+ Default merger — auto-spread (`{...state, ...artifacts}`). `finalizeArtifacts` for concat / dedupe across iterations. Bag resets every iteration.
2056
+
2057
+ ## Callback intents — `ctx.intents.X.execute()` + `ctx.run` / `ctx.stream`
2058
+
2059
+ ```ts
2060
+ intents: {
2061
+ "special-refund": async (ctx) => {
2062
+ if ((ctx.input as { amount: number }).amount > 1_000) {
2063
+ await ctx.intents["audit-log"].execute(); // dispatch registered intent
2064
+ }
2065
+ return await callRefundAPI(ctx.input);
2066
+ },
2067
+
2068
+ // Inline (non-registered) execution
2069
+ classify: async (ctx) => {
2070
+ const { data } = await ctx.run(classifierAgent, ctx.input);
2071
+ return { category: (data as { label: string }).label };
2072
+ },
2073
+
2074
+ chatInline: async (ctx) => {
2075
+ const stream = ctx.stream(someAgent, enrich(ctx.input));
2076
+ const final = await stream.result;
2077
+ return { reply: final.text };
2078
+ },
2079
+ }
2080
+ ```
2081
+
2082
+ Cycle protection: per-branch call stack. Re-entry on same intent → `SUPERVISOR_DISPATCH_CYCLE`.
2083
+
2084
+ ## Per-call options
2085
+
2086
+ ```ts
2087
+ await supportBot.execute(message, {
2088
+ context: { userId, db, traceId }, // request-scoped bag, never persisted
2089
+ history: priorMessages, // Message[] forwarded to router + agents
2090
+ sessionId: "sess_user_42", // stamps onto every report node
2091
+ signal: AbortSignal.timeout(60_000),
2092
+ runId: "support-2026-04-26-7", // for snapshot resume
2093
+ });
2094
+ ```
2095
+
2096
+ `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
+
2098
+ ## Iteration model
2099
+
2100
+ 1. Router/route picks `next` (or `END`).
2101
+ 2. Picked intents dispatch (parallel for fan-out).
2102
+ 3. `evaluate` (if provided) inspects results.
2103
+ 4. If satisfied or `END` → terminate. Otherwise → loop.
2104
+
2105
+ Hard cap via `maxIterations` (default 10). Hitting cap surfaces `MaxIterationsError`.
2106
+
2107
+ ## Streaming
2108
+
2109
+ ```ts
2110
+ const stream = supportBot.stream(message);
2111
+
2112
+ for await (const event of stream) {
2113
+ if (event.type === "supervisor.agent.streaming") {
2114
+ process.stdout.write(event.delta);
2115
+ }
2116
+ }
2117
+
2118
+ const result = await stream.result;
2119
+ ```
2120
+
2121
+ Token-level streaming requires the dispatched agents to be streamed (supervisor calls `agent.stream()` internally). Callbacks don't stream tokens.
2122
+
2123
+ ## Snapshot resume
2124
+
2125
+ ```ts
2126
+ import { ai } from "@warlock.js/ai";
2127
+ import { cache } from "@warlock.js/cache";
2128
+
2129
+ ai.config({ defaultStore: cache.driver("redis", { client }) });
2130
+
2131
+ await supportBot.execute(message, { runId: "support-7" }); // fresh
2132
+ await supportBot.resume("support-7"); // after crash
2133
+ ```
2134
+
2135
+ Signature drift detection throws `SupervisorDriftError` on shape mismatch — `force: true` bypasses. See [`@warlock.js/ai/persist-ai-data/SKILL.md`](@warlock.js/ai/persist-ai-data/SKILL.md).
2136
+
2137
+ ## `asTool()` — supervisor as a tool
2138
+
2139
+ ```ts
2140
+ const supportTool = supportBot.asTool({
2141
+ description: "Route a customer support request to the right specialist",
2142
+ inputSchema: v.object({ message: v.string() }),
2143
+ });
2144
+
2145
+ const escalationAgent = ai.agent({ model, tools: [supportTool] });
2146
+ ```
2147
+
2148
+ ## Design reference
2149
+
2150
+ `domains/ai/design/supervisor.md` — full design rationale.
2151
+
2152
+ ## See also
2153
+
2154
+ - [`@warlock.js/ai/run-ai-agent/SKILL.md`](@warlock.js/ai/run-ai-agent/SKILL.md) — dispatchable units
2155
+ - [`@warlock.js/ai/run-ai-workflow/SKILL.md`](@warlock.js/ai/run-ai-workflow/SKILL.md) — when steps are known up front
2156
+ - [`@warlock.js/ai/persist-ai-data/SKILL.md`](@warlock.js/ai/persist-ai-data/SKILL.md) — `snapshotStore` + resume
2157
+ - [`@warlock.js/ai/attach-ai-middleware/SKILL.md`](@warlock.js/ai/attach-ai-middleware/SKILL.md) — `semanticCache` fits under each agent's middleware
2158
+ - [`@warlock.js/ai/define-ai-tool/SKILL.md`](@warlock.js/ai/define-ai-tool/SKILL.md) — tool artifacts side-channel
2159
+
2160
+
2161
+ ## write-system-prompt `@warlock.js/ai/write-system-prompt/SKILL.md`
2162
+
2163
+ ---
2164
+ 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.'
2166
+ ---
2167
+
2168
+ # System prompts — immutable builders
2169
+
2170
+ Three factories — `ai.systemPrompt()`, `ai.persona()`, `ai.instruction()` — compose into the `systemPrompt` option accepted by every agent / workflow step.
2171
+
2172
+ ## The namespace
2173
+
2174
+ ```ts
2175
+ import { ai } from "@warlock.js/ai";
2176
+
2177
+ ai.systemPrompt(); // empty — chain .persona(), .instruction() onto it
2178
+ ai.systemPrompt("literal text"); // one-shot string form
2179
+ ai.systemPrompt([block1, block2]); // array form — blocks render in declaration order
2180
+
2181
+ ai.persona(text); // PersonaContract block
2182
+ ai.instruction(text); // InstructionContract block
2183
+ ```
2184
+
2185
+ ## Two shapes, same result
2186
+
2187
+ ### String form — one-shot
2188
+
2189
+ ```ts
2190
+ ai.agent({
2191
+ model,
2192
+ systemPrompt: "You are a concise senior TypeScript engineer.",
2193
+ });
2194
+ ```
2195
+
2196
+ ### Builder form — composable
2197
+
2198
+ ```ts
2199
+ const prompt = ai.systemPrompt()
2200
+ .persona("You are Alex, a senior TypeScript engineer.")
2201
+ .instruction("Explain things assuming the reader is a Go developer.")
2202
+ .instruction("Always cite the relevant TypeScript handbook section.");
2203
+
2204
+ const myAgent = ai.agent({ model, systemPrompt: prompt });
2205
+ ```
2206
+
2207
+ ### Array form — explicit order
2208
+
2209
+ ```ts
2210
+ ai.systemPrompt([
2211
+ ai.persona("You are Alex, a TypeScript expert."),
2212
+ ai.instruction("Respond in {{language|English}}."),
2213
+ ]);
2214
+ ```
2215
+
2216
+ ## Block ordering
2217
+
2218
+ `SystemPrompt` stores `blocks: readonly SystemPromptBlockContract[]` — not separate persona + instructions fields. Rendering honors insertion order.
2219
+
2220
+ - **Chained `.persona(x)`** — replaces the existing persona in place, or prepends when none exists. Default persona-first layout.
2221
+ - **Chained `.instruction(y)`** — appends.
2222
+ - **Array form** — verbatim.
2223
+
2224
+ ## Immutability — safe forking
2225
+
2226
+ Every mutation returns a **new** `SystemPrompt`. The original is never touched:
2227
+
2228
+ ```ts
2229
+ const base = ai.systemPrompt().persona(alex).instruction(cite);
2230
+ const arabic = base.instruction("Prefer Arabic comments");
2231
+
2232
+ // base still has 2 blocks, arabic has 3. Neither affects the other.
2233
+ ```
2234
+
2235
+ `Persona` and `Instruction` follow the same rule — their `text` is `readonly`.
2236
+
2237
+ ## Mustache placeholders
2238
+
2239
+ `{{key}}` and `{{key|defaultValue}}` substitute at render time:
2240
+
2241
+ ```ts
2242
+ const prompt = ai.systemPrompt()
2243
+ .persona("You are Alex, a TypeScript expert.")
2244
+ .instruction("Respond in {{language|English}}.");
2245
+
2246
+ await myAgent.execute("Why use generics?", {
2247
+ placeholders: { language: "Arabic" },
2248
+ });
2249
+ ```
2250
+
2251
+ Or set defaults on the agent — per-call values override them:
2252
+
2253
+ ```ts
2254
+ ai.agent({ model, systemPrompt: prompt, placeholders: { language: "Arabic" } });
2255
+ ```
2256
+
2257
+ Substitution works on the **rendered** concatenation of every block, so `{{key}}` inside a persona and inside an instruction both resolve against the same placeholder bag.
2258
+
2259
+ ## Per-call overrides
2260
+
2261
+ Replace the agent's system prompt for a single run:
2262
+
2263
+ ```ts
2264
+ await myAgent.execute(input, { systemPrompt: alternativePrompt });
2265
+ ```
2266
+
2267
+ Useful for A/B testing, request-scoped personalization, or turn-by-turn prompt variation.
2268
+
2269
+ ## Tagged discriminator (not `instanceof`)
2270
+
2271
+ All blocks implement `SystemPromptBlockContract { readonly type: string; readonly text; resolve() }`. Runtime discrimination uses the string `type` tag (`"persona"`, `"instruction"`, future kinds) — **not** `instanceof`.
2272
+
2273
+ Why: `instanceof` breaks across duplicate package copies (different `node_modules` trees), realms, bundler scopes.
2274
+
2275
+ ## Pattern — forking a base prompt
2276
+
2277
+ ```ts
2278
+ const base = ai.systemPrompt()
2279
+ .persona("You are a support agent for Acme Corp.")
2280
+ .instruction("Cite policy §{{policy}} when denying a refund.");
2281
+
2282
+ const enterprise = base.instruction("Escalate immediately for Enterprise customers.");
2283
+ const trial = base.instruction("Offer a 14-day extension before closing the ticket.");
2284
+ ```
2285
+
2286
+ Three distinct prompts, one common foundation. Base is immutable — safe to share.
2287
+
2288
+ ## See also
2289
+
2290
+ - [`@warlock.js/ai/run-ai-agent/SKILL.md`](@warlock.js/ai/run-ai-agent/SKILL.md) — `systemPrompt` on factory + per-call override
2291
+ - [`@warlock.js/ai/run-ai-workflow/SKILL.md`](@warlock.js/ai/run-ai-workflow/SKILL.md) — per-step agent references inherit their own system prompt
2292
+
2293
+