@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.
- package/README.md +72 -0
- package/cjs/index.cjs +8625 -0
- package/cjs/index.cjs.map +1 -0
- package/esm/agent/agent-config.type.d.mts +160 -0
- package/esm/agent/agent-config.type.d.mts.map +1 -0
- package/esm/agent/agent-input-builder.mjs +111 -0
- package/esm/agent/agent-input-builder.mjs.map +1 -0
- package/esm/agent/agent-log-event.mjs +101 -0
- package/esm/agent/agent-log-event.mjs.map +1 -0
- package/esm/agent/agent-stream.d.mts +47 -0
- package/esm/agent/agent-stream.d.mts.map +1 -0
- package/esm/agent/agent-stream.mjs +82 -0
- package/esm/agent/agent-stream.mjs.map +1 -0
- package/esm/agent/agent-to-stream-event.mjs +86 -0
- package/esm/agent/agent-to-stream-event.mjs.map +1 -0
- package/esm/agent/agent.d.mts +38 -0
- package/esm/agent/agent.d.mts.map +1 -0
- package/esm/agent/agent.mjs +974 -0
- package/esm/agent/agent.mjs.map +1 -0
- package/esm/agent/index.d.mts +3 -0
- package/esm/agent/index.mjs +4 -0
- package/esm/agent/json-stream-guard.mjs +400 -0
- package/esm/agent/json-stream-guard.mjs.map +1 -0
- package/esm/ai.d.mts +64 -0
- package/esm/ai.d.mts.map +1 -0
- package/esm/ai.mjs +65 -0
- package/esm/ai.mjs.map +1 -0
- package/esm/config.d.mts +79 -0
- package/esm/config.d.mts.map +1 -0
- package/esm/config.mjs +43 -0
- package/esm/config.mjs.map +1 -0
- package/esm/contracts/agent/agent-options.type.d.mts +132 -0
- package/esm/contracts/agent/agent-options.type.d.mts.map +1 -0
- package/esm/contracts/agent/agent.contract.d.mts +92 -0
- package/esm/contracts/agent/agent.contract.d.mts.map +1 -0
- package/esm/contracts/agent/index.d.mts +2 -0
- package/esm/contracts/attachment.type.d.mts +100 -0
- package/esm/contracts/attachment.type.d.mts.map +1 -0
- package/esm/contracts/content-part.type.d.mts +55 -0
- package/esm/contracts/content-part.type.d.mts.map +1 -0
- package/esm/contracts/conversation-message.type.d.mts +51 -0
- package/esm/contracts/conversation-message.type.d.mts.map +1 -0
- package/esm/contracts/embedder.contract.d.mts +69 -0
- package/esm/contracts/embedder.contract.d.mts.map +1 -0
- package/esm/contracts/end.type.d.mts +53 -0
- package/esm/contracts/end.type.d.mts.map +1 -0
- package/esm/contracts/end.type.mjs +47 -0
- package/esm/contracts/end.type.mjs.map +1 -0
- package/esm/contracts/events/agent-events.type.d.mts +71 -0
- package/esm/contracts/events/agent-events.type.d.mts.map +1 -0
- package/esm/contracts/events/complete-event.type.d.mts +33 -0
- package/esm/contracts/events/complete-event.type.d.mts.map +1 -0
- package/esm/contracts/events/event-identity.type.d.mts +30 -0
- package/esm/contracts/events/event-identity.type.d.mts.map +1 -0
- package/esm/contracts/events/event-map.type.d.mts +80 -0
- package/esm/contracts/events/event-map.type.d.mts.map +1 -0
- package/esm/contracts/events/index.d.mts +7 -0
- package/esm/contracts/events/supervisor-events.type.d.mts +162 -0
- package/esm/contracts/events/supervisor-events.type.d.mts.map +1 -0
- package/esm/contracts/events/usage-event.type.d.mts +39 -0
- package/esm/contracts/events/usage-event.type.d.mts.map +1 -0
- package/esm/contracts/events/workflow-events.type.d.mts +51 -0
- package/esm/contracts/events/workflow-events.type.d.mts.map +1 -0
- package/esm/contracts/executable.contract.d.mts +25 -0
- package/esm/contracts/executable.contract.d.mts.map +1 -0
- package/esm/contracts/finish-reason.type.d.mts +17 -0
- package/esm/contracts/finish-reason.type.d.mts.map +1 -0
- package/esm/contracts/index.d.mts +63 -0
- package/esm/contracts/middleware/index.d.mts +3 -0
- package/esm/contracts/middleware/middleware-context.type.d.mts +99 -0
- package/esm/contracts/middleware/middleware-context.type.d.mts.map +1 -0
- package/esm/contracts/middleware/middleware-state.type.d.mts +34 -0
- package/esm/contracts/middleware/middleware-state.type.d.mts.map +1 -0
- package/esm/contracts/middleware/middleware.contract.d.mts +140 -0
- package/esm/contracts/middleware/middleware.contract.d.mts.map +1 -0
- package/esm/contracts/model-tool-call-request.type.d.mts +58 -0
- package/esm/contracts/model-tool-call-request.type.d.mts.map +1 -0
- package/esm/contracts/model.contract.d.mts +164 -0
- package/esm/contracts/model.contract.d.mts.map +1 -0
- package/esm/contracts/placeholders.type.d.mts +23 -0
- package/esm/contracts/placeholders.type.d.mts.map +1 -0
- package/esm/contracts/result/agent-result.type.d.mts +33 -0
- package/esm/contracts/result/agent-result.type.d.mts.map +1 -0
- package/esm/contracts/result/attempt-entry.type.d.mts +40 -0
- package/esm/contracts/result/attempt-entry.type.d.mts.map +1 -0
- package/esm/contracts/result/base-report.type.d.mts +137 -0
- package/esm/contracts/result/base-report.type.d.mts.map +1 -0
- package/esm/contracts/result/base-report.type.mjs +17 -0
- package/esm/contracts/result/base-report.type.mjs.map +1 -0
- package/esm/contracts/result/base-result.type.d.mts +26 -0
- package/esm/contracts/result/base-result.type.d.mts.map +1 -0
- package/esm/contracts/result/execute-result.type.d.mts +40 -0
- package/esm/contracts/result/execute-result.type.d.mts.map +1 -0
- package/esm/contracts/result/execution-report.type.d.mts +50 -0
- package/esm/contracts/result/execution-report.type.d.mts.map +1 -0
- package/esm/contracts/result/index.d.mts +14 -0
- package/esm/contracts/result/llm-trip.type.d.mts +36 -0
- package/esm/contracts/result/llm-trip.type.d.mts.map +1 -0
- package/esm/contracts/result/model-pricing.type.d.mts +51 -0
- package/esm/contracts/result/model-pricing.type.d.mts.map +1 -0
- package/esm/contracts/result/session-send-result.type.d.mts +29 -0
- package/esm/contracts/result/session-send-result.type.d.mts.map +1 -0
- package/esm/contracts/result/step-result.type.d.mts +43 -0
- package/esm/contracts/result/step-result.type.d.mts.map +1 -0
- package/esm/contracts/result/supervisor-result.type.d.mts +86 -0
- package/esm/contracts/result/supervisor-result.type.d.mts.map +1 -0
- package/esm/contracts/result/tool-call.type.d.mts +44 -0
- package/esm/contracts/result/tool-call.type.d.mts.map +1 -0
- package/esm/contracts/result/usage.type.d.mts +57 -0
- package/esm/contracts/result/usage.type.d.mts.map +1 -0
- package/esm/contracts/result/workflow-result.type.d.mts +45 -0
- package/esm/contracts/result/workflow-result.type.d.mts.map +1 -0
- package/esm/contracts/sdk-adapter.contract.d.mts +77 -0
- package/esm/contracts/sdk-adapter.contract.d.mts.map +1 -0
- package/esm/contracts/stream/stream-event.type.d.mts +68 -0
- package/esm/contracts/stream/stream-event.type.d.mts.map +1 -0
- package/esm/contracts/stream/stream.contract.d.mts +61 -0
- package/esm/contracts/stream/stream.contract.d.mts.map +1 -0
- package/esm/contracts/streaming-tool-guard-config.type.d.mts +78 -0
- package/esm/contracts/streaming-tool-guard-config.type.d.mts.map +1 -0
- package/esm/contracts/supervisor/ack-entry.type.d.mts +97 -0
- package/esm/contracts/supervisor/ack-entry.type.d.mts.map +1 -0
- package/esm/contracts/supervisor/classifier-context.type.d.mts +189 -0
- package/esm/contracts/supervisor/classifier-context.type.d.mts.map +1 -0
- package/esm/contracts/supervisor/dispatch-context.type.d.mts +235 -0
- package/esm/contracts/supervisor/dispatch-context.type.d.mts.map +1 -0
- package/esm/contracts/supervisor/evaluate-context.type.d.mts +124 -0
- package/esm/contracts/supervisor/evaluate-context.type.d.mts.map +1 -0
- package/esm/contracts/supervisor/index.d.mts +16 -0
- package/esm/contracts/supervisor/intent-entry.type.d.mts +254 -0
- package/esm/contracts/supervisor/intent-entry.type.d.mts.map +1 -0
- package/esm/contracts/supervisor/iteration-snapshot.type.d.mts +117 -0
- package/esm/contracts/supervisor/iteration-snapshot.type.d.mts.map +1 -0
- package/esm/contracts/supervisor/next.type.d.mts +25 -0
- package/esm/contracts/supervisor/next.type.d.mts.map +1 -0
- package/esm/contracts/supervisor/route-context.type.d.mts +91 -0
- package/esm/contracts/supervisor/route-context.type.d.mts.map +1 -0
- package/esm/contracts/supervisor/router-entry.type.d.mts +67 -0
- package/esm/contracts/supervisor/router-entry.type.d.mts.map +1 -0
- package/esm/contracts/supervisor/supervisor-config.type.d.mts +356 -0
- package/esm/contracts/supervisor/supervisor-config.type.d.mts.map +1 -0
- package/esm/contracts/supervisor/supervisor-execute-options.type.d.mts +90 -0
- package/esm/contracts/supervisor/supervisor-execute-options.type.d.mts.map +1 -0
- package/esm/contracts/supervisor/supervisor-input.type.d.mts +24 -0
- package/esm/contracts/supervisor/supervisor-input.type.d.mts.map +1 -0
- package/esm/contracts/supervisor/supervisor-snapshot.type.d.mts +49 -0
- package/esm/contracts/supervisor/supervisor-snapshot.type.d.mts.map +1 -0
- package/esm/contracts/supervisor/supervisor-stream-event.type.d.mts +16 -0
- package/esm/contracts/supervisor/supervisor-stream-event.type.d.mts.map +1 -0
- package/esm/contracts/supervisor/supervisor.contract.d.mts +126 -0
- package/esm/contracts/supervisor/supervisor.contract.d.mts.map +1 -0
- package/esm/contracts/system-prompt.contract.d.mts +118 -0
- package/esm/contracts/system-prompt.contract.d.mts.map +1 -0
- package/esm/contracts/tool.contract.d.mts +161 -0
- package/esm/contracts/tool.contract.d.mts.map +1 -0
- package/esm/contracts/workflow/index.d.mts +6 -0
- package/esm/contracts/workflow/next-step-result.type.d.mts +17 -0
- package/esm/contracts/workflow/next-step-result.type.d.mts.map +1 -0
- package/esm/contracts/workflow/retry-config.type.d.mts +19 -0
- package/esm/contracts/workflow/retry-config.type.d.mts.map +1 -0
- package/esm/contracts/workflow/step.contract.d.mts +189 -0
- package/esm/contracts/workflow/step.contract.d.mts.map +1 -0
- package/esm/contracts/workflow/workflow-context.type.d.mts +45 -0
- package/esm/contracts/workflow/workflow-context.type.d.mts.map +1 -0
- package/esm/contracts/workflow/workflow-snapshot.type.d.mts +24 -0
- package/esm/contracts/workflow/workflow-snapshot.type.d.mts.map +1 -0
- package/esm/contracts/workflow/workflow.contract.d.mts +142 -0
- package/esm/contracts/workflow/workflow.contract.d.mts.map +1 -0
- package/esm/errors/agent-cancelled-error.d.mts +49 -0
- package/esm/errors/agent-cancelled-error.d.mts.map +1 -0
- package/esm/errors/agent-cancelled-error.mjs +43 -0
- package/esm/errors/agent-cancelled-error.mjs.map +1 -0
- package/esm/errors/agent-execution-error.d.mts +32 -0
- package/esm/errors/agent-execution-error.d.mts.map +1 -0
- package/esm/errors/agent-execution-error.mjs +35 -0
- package/esm/errors/agent-execution-error.mjs.map +1 -0
- package/esm/errors/agent-max-trips-error.d.mts +45 -0
- package/esm/errors/agent-max-trips-error.d.mts.map +1 -0
- package/esm/errors/agent-max-trips-error.mjs +41 -0
- package/esm/errors/agent-max-trips-error.mjs.map +1 -0
- package/esm/errors/ai-error.d.mts +94 -0
- package/esm/errors/ai-error.d.mts.map +1 -0
- package/esm/errors/ai-error.mjs +68 -0
- package/esm/errors/ai-error.mjs.map +1 -0
- package/esm/errors/budget-exceeded-error.d.mts +41 -0
- package/esm/errors/budget-exceeded-error.d.mts.map +1 -0
- package/esm/errors/budget-exceeded-error.mjs +31 -0
- package/esm/errors/budget-exceeded-error.mjs.map +1 -0
- package/esm/errors/content-filter-error.d.mts +33 -0
- package/esm/errors/content-filter-error.d.mts.map +1 -0
- package/esm/errors/content-filter-error.mjs +28 -0
- package/esm/errors/content-filter-error.mjs.map +1 -0
- package/esm/errors/context-length-exceeded-error.d.mts +38 -0
- package/esm/errors/context-length-exceeded-error.d.mts.map +1 -0
- package/esm/errors/context-length-exceeded-error.mjs +31 -0
- package/esm/errors/context-length-exceeded-error.mjs.map +1 -0
- package/esm/errors/error-category.type.d.mts +30 -0
- package/esm/errors/error-category.type.d.mts.map +1 -0
- package/esm/errors/error-code.type.d.mts +19 -0
- package/esm/errors/error-code.type.d.mts.map +1 -0
- package/esm/errors/guardrail-violation-error.d.mts +55 -0
- package/esm/errors/guardrail-violation-error.d.mts.map +1 -0
- package/esm/errors/guardrail-violation-error.mjs +41 -0
- package/esm/errors/guardrail-violation-error.mjs.map +1 -0
- package/esm/errors/index.d.mts +29 -0
- package/esm/errors/index.mjs +29 -0
- package/esm/errors/invalid-request-error.d.mts +27 -0
- package/esm/errors/invalid-request-error.d.mts.map +1 -0
- package/esm/errors/invalid-request-error.mjs +31 -0
- package/esm/errors/invalid-request-error.mjs.map +1 -0
- package/esm/errors/max-iterations-error.d.mts +31 -0
- package/esm/errors/max-iterations-error.d.mts.map +1 -0
- package/esm/errors/max-iterations-error.mjs +32 -0
- package/esm/errors/max-iterations-error.mjs.map +1 -0
- package/esm/errors/max-steps-exceeded-error.d.mts +20 -0
- package/esm/errors/max-steps-exceeded-error.d.mts.map +1 -0
- package/esm/errors/max-steps-exceeded-error.mjs +21 -0
- package/esm/errors/max-steps-exceeded-error.mjs.map +1 -0
- package/esm/errors/provider-auth-error.d.mts +22 -0
- package/esm/errors/provider-auth-error.d.mts.map +1 -0
- package/esm/errors/provider-auth-error.mjs +26 -0
- package/esm/errors/provider-auth-error.mjs.map +1 -0
- package/esm/errors/provider-error.d.mts +38 -0
- package/esm/errors/provider-error.d.mts.map +1 -0
- package/esm/errors/provider-error.mjs +42 -0
- package/esm/errors/provider-error.mjs.map +1 -0
- package/esm/errors/provider-rate-limit-error.d.mts +34 -0
- package/esm/errors/provider-rate-limit-error.d.mts.map +1 -0
- package/esm/errors/provider-rate-limit-error.mjs +31 -0
- package/esm/errors/provider-rate-limit-error.mjs.map +1 -0
- package/esm/errors/provider-timeout-error.d.mts +22 -0
- package/esm/errors/provider-timeout-error.d.mts.map +1 -0
- package/esm/errors/provider-timeout-error.mjs +26 -0
- package/esm/errors/provider-timeout-error.mjs.map +1 -0
- package/esm/errors/quota-exceeded-error.d.mts +36 -0
- package/esm/errors/quota-exceeded-error.d.mts.map +1 -0
- package/esm/errors/quota-exceeded-error.mjs +40 -0
- package/esm/errors/quota-exceeded-error.mjs.map +1 -0
- package/esm/errors/routing-error.d.mts +23 -0
- package/esm/errors/routing-error.d.mts.map +1 -0
- package/esm/errors/routing-error.mjs +23 -0
- package/esm/errors/routing-error.mjs.map +1 -0
- package/esm/errors/schema-validation-error.d.mts +40 -0
- package/esm/errors/schema-validation-error.d.mts.map +1 -0
- package/esm/errors/schema-validation-error.mjs +37 -0
- package/esm/errors/schema-validation-error.mjs.map +1 -0
- package/esm/errors/step-failed-error.d.mts +20 -0
- package/esm/errors/step-failed-error.d.mts.map +1 -0
- package/esm/errors/step-failed-error.mjs +19 -0
- package/esm/errors/step-failed-error.mjs.map +1 -0
- package/esm/errors/supervisor-cancelled-error.d.mts +29 -0
- package/esm/errors/supervisor-cancelled-error.d.mts.map +1 -0
- package/esm/errors/supervisor-cancelled-error.mjs +29 -0
- package/esm/errors/supervisor-cancelled-error.mjs.map +1 -0
- package/esm/errors/supervisor-drift-error.d.mts +31 -0
- package/esm/errors/supervisor-drift-error.d.mts.map +1 -0
- package/esm/errors/supervisor-drift-error.mjs +30 -0
- package/esm/errors/supervisor-drift-error.mjs.map +1 -0
- package/esm/errors/supervisor-failed-error.d.mts +40 -0
- package/esm/errors/supervisor-failed-error.d.mts.map +1 -0
- package/esm/errors/supervisor-failed-error.mjs +43 -0
- package/esm/errors/supervisor-failed-error.mjs.map +1 -0
- package/esm/errors/supervisor-routing-error.d.mts +44 -0
- package/esm/errors/supervisor-routing-error.d.mts.map +1 -0
- package/esm/errors/supervisor-routing-error.mjs +39 -0
- package/esm/errors/supervisor-routing-error.mjs.map +1 -0
- package/esm/errors/tool-execution-error.d.mts +35 -0
- package/esm/errors/tool-execution-error.d.mts.map +1 -0
- package/esm/errors/tool-execution-error.mjs +32 -0
- package/esm/errors/tool-execution-error.mjs.map +1 -0
- package/esm/errors/workflow-cancelled-error.d.mts +23 -0
- package/esm/errors/workflow-cancelled-error.d.mts.map +1 -0
- package/esm/errors/workflow-cancelled-error.mjs +23 -0
- package/esm/errors/workflow-cancelled-error.mjs.map +1 -0
- package/esm/errors/workflow-drift-error.d.mts +25 -0
- package/esm/errors/workflow-drift-error.d.mts.map +1 -0
- package/esm/errors/workflow-drift-error.mjs +24 -0
- package/esm/errors/workflow-drift-error.mjs.map +1 -0
- package/esm/errors/workflow-error.d.mts +15 -0
- package/esm/errors/workflow-error.d.mts.map +1 -0
- package/esm/errors/workflow-error.mjs +18 -0
- package/esm/errors/workflow-error.mjs.map +1 -0
- package/esm/index.d.mts +137 -0
- package/esm/index.mjs +81 -0
- package/esm/middleware/builtins/budget.d.mts +103 -0
- package/esm/middleware/builtins/budget.d.mts.map +1 -0
- package/esm/middleware/builtins/budget.mjs +100 -0
- package/esm/middleware/builtins/budget.mjs.map +1 -0
- package/esm/middleware/builtins/guardrail.d.mts +92 -0
- package/esm/middleware/builtins/guardrail.d.mts.map +1 -0
- package/esm/middleware/builtins/guardrail.mjs +76 -0
- package/esm/middleware/builtins/guardrail.mjs.map +1 -0
- package/esm/middleware/builtins/semantic-cache.d.mts +106 -0
- package/esm/middleware/builtins/semantic-cache.d.mts.map +1 -0
- package/esm/middleware/builtins/semantic-cache.mjs +149 -0
- package/esm/middleware/builtins/semantic-cache.mjs.map +1 -0
- package/esm/middleware/helpers/compose.d.mts +42 -0
- package/esm/middleware/helpers/compose.d.mts.map +1 -0
- package/esm/middleware/helpers/compose.mjs +52 -0
- package/esm/middleware/helpers/compose.mjs.map +1 -0
- package/esm/middleware/helpers/for-tool.d.mts +55 -0
- package/esm/middleware/helpers/for-tool.d.mts.map +1 -0
- package/esm/middleware/helpers/for-tool.mjs +80 -0
- package/esm/middleware/helpers/for-tool.mjs.map +1 -0
- package/esm/middleware/helpers/index.d.mts +2 -0
- package/esm/middleware/index.d.mts +8 -0
- package/esm/middleware/index.mjs +10 -0
- package/esm/middleware/pipeline.d.mts +70 -0
- package/esm/middleware/pipeline.d.mts.map +1 -0
- package/esm/middleware/pipeline.mjs +100 -0
- package/esm/middleware/pipeline.mjs.map +1 -0
- package/esm/middleware/utils/extract-user-text.d.mts +33 -0
- package/esm/middleware/utils/extract-user-text.d.mts.map +1 -0
- package/esm/middleware/utils/extract-user-text.mjs +40 -0
- package/esm/middleware/utils/extract-user-text.mjs.map +1 -0
- package/esm/middleware/utils/index.d.mts +2 -0
- package/esm/middleware/utils/namespaced-state.d.mts +60 -0
- package/esm/middleware/utils/namespaced-state.d.mts.map +1 -0
- package/esm/middleware/utils/namespaced-state.mjs +60 -0
- package/esm/middleware/utils/namespaced-state.mjs.map +1 -0
- package/esm/mock/index.d.mts +4 -0
- package/esm/mock/index.mjs +5 -0
- package/esm/mock/mock-agent.d.mts +41 -0
- package/esm/mock/mock-agent.d.mts.map +1 -0
- package/esm/mock/mock-agent.mjs +39 -0
- package/esm/mock/mock-agent.mjs.map +1 -0
- package/esm/mock/mock-config.type.d.mts +31 -0
- package/esm/mock/mock-config.type.d.mts.map +1 -0
- package/esm/mock/mock-model.d.mts +94 -0
- package/esm/mock/mock-model.d.mts.map +1 -0
- package/esm/mock/mock-model.mjs +150 -0
- package/esm/mock/mock-model.mjs.map +1 -0
- package/esm/mock/mock-sdk.d.mts +25 -0
- package/esm/mock/mock-sdk.d.mts.map +1 -0
- package/esm/mock/mock-sdk.mjs +37 -0
- package/esm/mock/mock-sdk.mjs.map +1 -0
- package/esm/supervisor/as-tool.d.mts +45 -0
- package/esm/supervisor/as-tool.d.mts.map +1 -0
- package/esm/supervisor/as-tool.mjs +80 -0
- package/esm/supervisor/as-tool.mjs.map +1 -0
- package/esm/supervisor/cancellation.d.mts +12 -0
- package/esm/supervisor/cancellation.d.mts.map +1 -0
- package/esm/supervisor/cancellation.mjs +22 -0
- package/esm/supervisor/cancellation.mjs.map +1 -0
- package/esm/supervisor/decide.mjs +272 -0
- package/esm/supervisor/decide.mjs.map +1 -0
- package/esm/supervisor/emitter.d.mts +27 -0
- package/esm/supervisor/emitter.d.mts.map +1 -0
- package/esm/supervisor/emitter.mjs +47 -0
- package/esm/supervisor/emitter.mjs.map +1 -0
- package/esm/supervisor/entries.d.mts +141 -0
- package/esm/supervisor/entries.d.mts.map +1 -0
- package/esm/supervisor/entries.mjs +206 -0
- package/esm/supervisor/entries.mjs.map +1 -0
- package/esm/supervisor/execution.d.mts +637 -0
- package/esm/supervisor/execution.d.mts.map +1 -0
- package/esm/supervisor/execution.mjs +1868 -0
- package/esm/supervisor/execution.mjs.map +1 -0
- package/esm/supervisor/index.d.mts +11 -0
- package/esm/supervisor/index.mjs +12 -0
- package/esm/supervisor/router-prompt.d.mts +56 -0
- package/esm/supervisor/router-prompt.d.mts.map +1 -0
- package/esm/supervisor/router-prompt.mjs +52 -0
- package/esm/supervisor/router-prompt.mjs.map +1 -0
- package/esm/supervisor/signature.d.mts +28 -0
- package/esm/supervisor/signature.d.mts.map +1 -0
- package/esm/supervisor/signature.mjs +89 -0
- package/esm/supervisor/signature.mjs.map +1 -0
- package/esm/supervisor/snapshot.d.mts +54 -0
- package/esm/supervisor/snapshot.d.mts.map +1 -0
- package/esm/supervisor/snapshot.mjs +77 -0
- package/esm/supervisor/snapshot.mjs.map +1 -0
- package/esm/supervisor/supervisor-stream.d.mts +29 -0
- package/esm/supervisor/supervisor-stream.d.mts.map +1 -0
- package/esm/supervisor/supervisor-stream.mjs +89 -0
- package/esm/supervisor/supervisor-stream.mjs.map +1 -0
- package/esm/supervisor/supervisor.d.mts +27 -0
- package/esm/supervisor/supervisor.d.mts.map +1 -0
- package/esm/supervisor/supervisor.mjs +138 -0
- package/esm/supervisor/supervisor.mjs.map +1 -0
- package/esm/system-prompt/instruction.d.mts +52 -0
- package/esm/system-prompt/instruction.d.mts.map +1 -0
- package/esm/system-prompt/instruction.mjs +57 -0
- package/esm/system-prompt/instruction.mjs.map +1 -0
- package/esm/system-prompt/persona.d.mts +52 -0
- package/esm/system-prompt/persona.d.mts.map +1 -0
- package/esm/system-prompt/persona.mjs +57 -0
- package/esm/system-prompt/persona.mjs.map +1 -0
- package/esm/system-prompt/render-placeholders.d.mts +31 -0
- package/esm/system-prompt/render-placeholders.d.mts.map +1 -0
- package/esm/system-prompt/render-placeholders.mjs +58 -0
- package/esm/system-prompt/render-placeholders.mjs.map +1 -0
- package/esm/system-prompt/system-prompt.d.mts +117 -0
- package/esm/system-prompt/system-prompt.d.mts.map +1 -0
- package/esm/system-prompt/system-prompt.mjs +137 -0
- package/esm/system-prompt/system-prompt.mjs.map +1 -0
- package/esm/tool/index.d.mts +1 -0
- package/esm/tool/index.mjs +3 -0
- package/esm/tool/tool.d.mts +64 -0
- package/esm/tool/tool.d.mts.map +1 -0
- package/esm/tool/tool.mjs +184 -0
- package/esm/tool/tool.mjs.map +1 -0
- package/esm/utils/compute-cost.d.mts +58 -0
- package/esm/utils/compute-cost.d.mts.map +1 -0
- package/esm/utils/compute-cost.mjs +87 -0
- package/esm/utils/compute-cost.mjs.map +1 -0
- package/esm/utils/extract-json-payload.d.mts +33 -0
- package/esm/utils/extract-json-payload.d.mts.map +1 -0
- package/esm/utils/extract-json-payload.mjs +39 -0
- package/esm/utils/extract-json-payload.mjs.map +1 -0
- package/esm/utils/generate-run-id.d.mts +20 -0
- package/esm/utils/generate-run-id.d.mts.map +1 -0
- package/esm/utils/generate-run-id.mjs +23 -0
- package/esm/utils/generate-run-id.mjs.map +1 -0
- package/esm/utils/index.d.mts +9 -0
- package/esm/utils/index.mjs +11 -0
- package/esm/utils/json-schema.d.mts +57 -0
- package/esm/utils/json-schema.d.mts.map +1 -0
- package/esm/utils/json-schema.mjs +89 -0
- package/esm/utils/json-schema.mjs.map +1 -0
- package/esm/utils/prepare-attachment-part.d.mts +36 -0
- package/esm/utils/prepare-attachment-part.d.mts.map +1 -0
- package/esm/utils/prepare-attachment-part.mjs +140 -0
- package/esm/utils/prepare-attachment-part.mjs.map +1 -0
- package/esm/utils/resolve-attachment.d.mts +33 -0
- package/esm/utils/resolve-attachment.d.mts.map +1 -0
- package/esm/utils/resolve-attachment.mjs +73 -0
- package/esm/utils/resolve-attachment.mjs.map +1 -0
- package/esm/utils/safe-json-parse.d.mts +14 -0
- package/esm/utils/safe-json-parse.d.mts.map +1 -0
- package/esm/utils/safe-json-parse.mjs +22 -0
- package/esm/utils/safe-json-parse.mjs.map +1 -0
- package/esm/utils/stamp-report-lineage.d.mts +61 -0
- package/esm/utils/stamp-report-lineage.d.mts.map +1 -0
- package/esm/utils/stamp-report-lineage.mjs +47 -0
- package/esm/utils/stamp-report-lineage.mjs.map +1 -0
- package/esm/utils/token-count.d.mts +15 -0
- package/esm/utils/token-count.d.mts.map +1 -0
- package/esm/utils/token-count.mjs +18 -0
- package/esm/utils/token-count.mjs.map +1 -0
- package/esm/workflow/as-tool.mjs +55 -0
- package/esm/workflow/as-tool.mjs.map +1 -0
- package/esm/workflow/cancellation.mjs +44 -0
- package/esm/workflow/cancellation.mjs.map +1 -0
- package/esm/workflow/emitter.mjs +40 -0
- package/esm/workflow/emitter.mjs.map +1 -0
- package/esm/workflow/engine.mjs +362 -0
- package/esm/workflow/engine.mjs.map +1 -0
- package/esm/workflow/index.d.mts +3 -0
- package/esm/workflow/index.mjs +5 -0
- package/esm/workflow/retry.mjs +32 -0
- package/esm/workflow/retry.mjs.map +1 -0
- package/esm/workflow/router.mjs +56 -0
- package/esm/workflow/router.mjs.map +1 -0
- package/esm/workflow/run-scoped-emitter.mjs +32 -0
- package/esm/workflow/run-scoped-emitter.mjs.map +1 -0
- package/esm/workflow/signature.d.mts +7 -0
- package/esm/workflow/signature.d.mts.map +1 -0
- package/esm/workflow/signature.mjs +34 -0
- package/esm/workflow/signature.mjs.map +1 -0
- package/esm/workflow/snapshot.mjs +71 -0
- package/esm/workflow/snapshot.mjs.map +1 -0
- package/esm/workflow/state.mjs +37 -0
- package/esm/workflow/state.mjs.map +1 -0
- package/esm/workflow/step-runner.mjs +371 -0
- package/esm/workflow/step-runner.mjs.map +1 -0
- package/esm/workflow/step.d.mts +19 -0
- package/esm/workflow/step.d.mts.map +1 -0
- package/esm/workflow/step.mjs +37 -0
- package/esm/workflow/step.mjs.map +1 -0
- package/esm/workflow/workflow.d.mts +12 -0
- package/esm/workflow/workflow.d.mts.map +1 -0
- package/esm/workflow/workflow.mjs +116 -0
- package/esm/workflow/workflow.mjs.map +1 -0
- package/llms-full.txt +2293 -0
- package/llms.txt +20 -0
- package/package.json +37 -0
- package/skills/README.md +53 -0
- package/skills/ai-basics/SKILL.md +95 -0
- package/skills/attach-ai-middleware/SKILL.md +184 -0
- package/skills/define-ai-tool/SKILL.md +186 -0
- package/skills/embed-text/SKILL.md +105 -0
- package/skills/handle-ai-errors/SKILL.md +194 -0
- package/skills/log-ai-calls/SKILL.md +141 -0
- package/skills/persist-ai-data/SKILL.md +207 -0
- package/skills/pick-ai-provider/SKILL.md +164 -0
- package/skills/run-ai-agent/SKILL.md +267 -0
- package/skills/run-ai-workflow/SKILL.md +258 -0
- package/skills/run-supervisor/SKILL.md +309 -0
- 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
|
+
|