@warlock.js/ai 4.15.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +183 -158
- package/cjs/index.cjs +637 -104
- package/cjs/index.cjs.map +1 -1
- package/esm/contracts/index.d.mts +2 -2
- package/esm/contracts/memory/index.d.mts +1 -1
- package/esm/contracts/memory/memory-config.type.d.mts +29 -3
- package/esm/contracts/memory/memory-config.type.d.mts.map +1 -1
- package/esm/contracts/memory/memory-item.type.d.mts +15 -1
- package/esm/contracts/memory/memory-item.type.d.mts.map +1 -1
- package/esm/contracts/memory/memory.contract.d.mts +15 -2
- package/esm/contracts/memory/memory.contract.d.mts.map +1 -1
- package/esm/contracts/memory/recall-options.type.d.mts +12 -0
- package/esm/contracts/memory/recall-options.type.d.mts.map +1 -1
- package/esm/contracts/orchestrator/index.d.mts +1 -1
- package/esm/contracts/orchestrator/orchestrator-config.type.d.mts +38 -1
- package/esm/contracts/orchestrator/orchestrator-config.type.d.mts.map +1 -1
- package/esm/contracts/orchestrator/orchestrator.contract.d.mts +67 -3
- package/esm/contracts/orchestrator/orchestrator.contract.d.mts.map +1 -1
- package/esm/contracts/supervisor/supervisor-config.type.d.mts +23 -0
- package/esm/contracts/supervisor/supervisor-config.type.d.mts.map +1 -1
- package/esm/index.d.mts +6 -5
- package/esm/index.mjs +3 -2
- package/esm/memory/episodic-memory.mjs +14 -6
- package/esm/memory/episodic-memory.mjs.map +1 -1
- package/esm/memory/index.d.mts +1 -1
- package/esm/memory/memory.d.mts +13 -1
- package/esm/memory/memory.d.mts.map +1 -1
- package/esm/memory/memory.mjs +41 -7
- package/esm/memory/memory.mjs.map +1 -1
- package/esm/memory/procedural-memory.mjs +20 -7
- package/esm/memory/procedural-memory.mjs.map +1 -1
- package/esm/memory/semantic-memory.mjs +27 -10
- package/esm/memory/semantic-memory.mjs.map +1 -1
- package/esm/memory/working-memory.mjs +70 -13
- package/esm/memory/working-memory.mjs.map +1 -1
- package/esm/middleware/builtins/semantic-cache.d.mts +46 -1
- package/esm/middleware/builtins/semantic-cache.d.mts.map +1 -1
- package/esm/middleware/builtins/semantic-cache.mjs +60 -15
- package/esm/middleware/builtins/semantic-cache.mjs.map +1 -1
- package/esm/middleware/index.d.mts +1 -1
- package/esm/orchestrator/as-tool.d.mts +35 -9
- package/esm/orchestrator/as-tool.d.mts.map +1 -1
- package/esm/orchestrator/as-tool.mjs +67 -19
- package/esm/orchestrator/as-tool.mjs.map +1 -1
- package/esm/orchestrator/execution.d.mts.map +1 -1
- package/esm/orchestrator/execution.mjs +2 -2
- package/esm/orchestrator/execution.mjs.map +1 -1
- package/esm/orchestrator/index.d.mts +1 -1
- package/esm/orchestrator/index.mjs +1 -1
- package/esm/orchestrator/memory.d.mts +41 -5
- package/esm/orchestrator/memory.d.mts.map +1 -1
- package/esm/orchestrator/memory.mjs +53 -5
- package/esm/orchestrator/memory.mjs.map +1 -1
- package/esm/planner/plan-schema.d.mts +3 -3
- package/esm/planner/plan-schema.d.mts.map +1 -1
- package/esm/planner/plan-schema.mjs +30 -0
- package/esm/planner/plan-schema.mjs.map +1 -1
- package/esm/security/index.mjs +1 -0
- package/esm/security/outbound-policy.d.mts +9 -0
- package/esm/security/outbound-policy.d.mts.map +1 -1
- package/esm/security/outbound-policy.mjs +79 -5
- package/esm/security/outbound-policy.mjs.map +1 -1
- package/esm/security/outbound-policy.type.d.mts +8 -0
- package/esm/security/outbound-policy.type.d.mts.map +1 -1
- package/esm/security/safe-merge.d.mts +52 -0
- package/esm/security/safe-merge.d.mts.map +1 -0
- package/esm/security/safe-merge.mjs +68 -0
- package/esm/security/safe-merge.mjs.map +1 -0
- package/esm/supervisor/decide.mjs +52 -5
- package/esm/supervisor/decide.mjs.map +1 -1
- package/esm/supervisor/execution.d.mts +22 -0
- package/esm/supervisor/execution.d.mts.map +1 -1
- package/esm/supervisor/execution.mjs +46 -9
- package/esm/supervisor/execution.mjs.map +1 -1
- package/esm/supervisor/supervisor.mjs +4 -0
- package/esm/supervisor/supervisor.mjs.map +1 -1
- package/llms-full.txt +174 -10
- package/llms.txt +4 -3
- package/package.json +4 -4
- package/skills/README.md +5 -1
- package/skills/attach-ai-middleware/SKILL.md +17 -1
- package/skills/rag-loaders-and-stores/SKILL.md +3 -0
- package/skills/run-ai-agent/SKILL.md +3 -0
- package/skills/run-orchestrator/SKILL.md +6 -1
- package/skills/run-planner/SKILL.md +7 -3
- package/skills/run-supervisor/SKILL.md +11 -1
- package/skills/secure-outbound-requests/SKILL.md +85 -0
- package/skills/use-ai-memory/SKILL.md +36 -3
- package/skills/use-runtime-skills/SKILL.md +2 -1
|
@@ -134,6 +134,10 @@ function validateFactoryConfig(config) {
|
|
|
134
134
|
authoring: true,
|
|
135
135
|
maxIterations: config.maxIterations
|
|
136
136
|
} });
|
|
137
|
+
if (config.maxFanOut !== void 0 && (!Number.isInteger(config.maxFanOut) || config.maxFanOut < 1)) throw new SupervisorFailedError(`ai.supervisor("${config.name}"): \`maxFanOut\` must be an integer >= 1`, { context: {
|
|
138
|
+
authoring: true,
|
|
139
|
+
maxFanOut: config.maxFanOut
|
|
140
|
+
} });
|
|
137
141
|
}
|
|
138
142
|
function generateRunId() {
|
|
139
143
|
return `sup_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"supervisor.mjs","names":[],"sources":["../../../../../../../ai/src/supervisor/supervisor.ts"],"sourcesContent":["import type { SupervisorEventMap } from \"../contracts/events/event-map.type\";\nimport type { ExecutionReport } from \"../contracts/result/execution-report.type\";\nimport type { SupervisorResult } from \"../contracts/result/supervisor-result.type\";\nimport type { StreamContract } from \"../contracts/stream/stream.contract\";\nimport type { SupervisorIntentValue } from \"../contracts/supervisor/intent-entry.type\";\nimport type {\n SupervisorConfig,\n SupervisorEventHandler,\n} from \"../contracts/supervisor/supervisor-config.type\";\nimport type {\n SupervisorExecuteOptions,\n SupervisorResumeOptions,\n} from \"../contracts/supervisor/supervisor-execute-options.type\";\nimport type { SupervisorInput } from \"../contracts/supervisor/supervisor-input.type\";\nimport type { SupervisorStreamEvent } from \"../contracts/supervisor/supervisor-stream-event.type\";\nimport type {\n SupervisorAsToolOptions,\n SupervisorContract,\n} from \"../contracts/supervisor/supervisor.contract\";\nimport { SupervisorFailedError } from \"../errors\";\nimport { notifyObservers } from \"../observe/resolve-observers\";\nimport type { ToolContract } from \"../tool/tool\";\nimport { asTool } from \"./as-tool\";\nimport { SupervisorEmitter } from \"./emitter\";\nimport { assertRouterDescriptions, resolveIntentEntries } from \"./entries\";\nimport { SupervisorExecution } from \"./execution\";\nimport { computeSignature } from \"./signature\";\nimport { loadSnapshotForResume } from \"./snapshot\";\nimport { createSupervisorStream } from \"./supervisor-stream\";\n\n/**\n * `ai.supervisor(config)` — construct a `SupervisorContract`. Validates\n * the config at author time (throws `SupervisorFailedError` on bad\n * shape), resolves agent entries, computes a stable structural\n * signature, wires the three-tier event emitter, and returns an\n * instance that satisfies `ExecutableContract` so it can compose into\n * tools, outer agents, and (future) orchestrators uniformly.\n *\n * @example\n * const support = ai.supervisor({\n * name: \"customer-support\",\n * router: routerAgent,\n * intents: { triage, orderLookup, billingLookup, resolver },\n * evaluate: (ctx) => ctx.result.resolver?.output ? { satisfied: true } : undefined,\n * output: z.object({ response: z.string(), refund: z.boolean() }),\n * maxIterations: 6,\n * });\n */\nexport function supervisor<\n TOutput = unknown,\n TState = TOutput,\n TIntents extends Record<string, SupervisorIntentValue> = Record<string, SupervisorIntentValue>,\n TArtifacts = Record<string, unknown>,\n>(config: SupervisorConfig<TOutput, TState, TIntents, TArtifacts>): SupervisorContract<TOutput> {\n validateFactoryConfig(config as unknown as SupervisorConfig<TOutput>);\n\n const entries = resolveIntentEntries(config.intents, config.name);\n\n assertRouterDescriptions(config as SupervisorConfig<unknown>, entries);\n\n if (config.initialAgent && !entries.has(config.initialAgent)) {\n throw new SupervisorFailedError(\n `ai.supervisor(\"${config.name}\"): \\`initialAgent\\` \"${config.initialAgent}\" is not a key in \\`intents\\``,\n { context: { authoring: true } },\n );\n }\n\n const signature = computeSignature(config as SupervisorConfig<unknown>, entries);\n const emitter = new SupervisorEmitter(config.on);\n\n async function execute(\n input: SupervisorInput,\n options?: SupervisorExecuteOptions,\n ): Promise<SupervisorResult<TOutput>> {\n const runId = options?.runId ?? generateRunId();\n\n const execution = new SupervisorExecution<TOutput>({\n config: config as unknown as SupervisorConfig<TOutput>,\n entries,\n signature,\n emitter,\n input,\n runId,\n options,\n });\n\n const result = await execution.run();\n\n // Route the finished report to any resolved observers (F1/F3).\n // Gated by `config.observe` + the global observe-all flag; observer\n // errors are swallowed inside `notifyObservers`. `ai.team(...)`\n // forwards its `observe` into this same config, so a team inherits\n // observability through here with no extra wiring. Bridge the\n // pre-existing `SupervisorReport = Omit<BaseReport, \"type\">` drift\n // (the report carries `type: \"supervisor\"` at runtime) so this call\n // site adds no new type error beyond the documented baseline.\n await notifyObservers(config.observe, result.report as unknown as ExecutionReport);\n\n return result;\n }\n\n function stream(\n input: SupervisorInput,\n options?: SupervisorExecuteOptions,\n ): StreamContract<SupervisorResult<TOutput>, SupervisorStreamEvent> {\n const runId = options?.runId ?? generateRunId();\n const { controller, stream: contract } = createSupervisorStream<SupervisorResult<TOutput>>();\n\n const execution = new SupervisorExecution<TOutput>({\n config: config as unknown as SupervisorConfig<TOutput>,\n entries,\n signature,\n emitter,\n input,\n runId,\n options,\n streamController: controller,\n });\n\n // Route the finished report to resolved observers once the streamed\n // run settles. Attached to the run promise (not awaited — `stream`\n // returns synchronously); `notifyObservers` swallows observer errors.\n void execution\n .run()\n .then((result) =>\n notifyObservers(config.observe, result.report as unknown as ExecutionReport),\n );\n\n return contract;\n }\n\n async function resume(\n runId: string,\n options?: SupervisorResumeOptions,\n ): Promise<SupervisorResult<TOutput>> {\n const snapshot = await loadSnapshotForResume({\n config: config as SupervisorConfig<unknown>,\n signature,\n runId,\n options,\n });\n\n const execution = new SupervisorExecution<TOutput>({\n config: config as unknown as SupervisorConfig<TOutput>,\n entries,\n signature,\n emitter,\n input: snapshot.input,\n runId,\n options,\n resumeFrom: snapshot,\n });\n\n const result = await execution.run();\n\n await notifyObservers(config.observe, result.report as unknown as ExecutionReport);\n\n return result;\n }\n\n const instance: SupervisorContract<TOutput> = {\n name: config.name,\n inputSchema: config.inputSchema,\n signature,\n execute,\n stream,\n resume,\n on<K extends keyof SupervisorEventMap>(\n event: K,\n handler: SupervisorEventHandler<K>,\n ): () => void {\n return emitter.on(event, handler);\n },\n off<K extends keyof SupervisorEventMap>(event: K, handler: SupervisorEventHandler<K>): void {\n emitter.off(event, handler);\n },\n asTool<TToolInput = string>(\n options: SupervisorAsToolOptions<TToolInput>,\n ): ToolContract<TToolInput, TOutput> {\n return asTool<TOutput, TToolInput>(instance, options);\n },\n };\n\n return instance;\n}\n\n/**\n * Factory-time validation. Enforces the XOR + pairing rules the design\n * locked in §2 and surfaces any violation as a typed\n * `SupervisorFailedError` tagged `authoring: true`.\n */\nfunction validateFactoryConfig<T>(config: SupervisorConfig<T>): void {\n if (!config.name || typeof config.name !== \"string\") {\n throw new SupervisorFailedError(\"ai.supervisor: `name` is required and must be a string\", {\n context: { authoring: true },\n });\n }\n\n if (!config.intents || typeof config.intents !== \"object\") {\n throw new SupervisorFailedError(`ai.supervisor(\"${config.name}\"): \\`intents\\` is required`, {\n context: { authoring: true },\n });\n }\n\n const hasRoute = typeof config.route === \"function\";\n const hasRouter = !!config.router;\n\n if (hasRouter) {\n const router = config.router as { execute?: unknown } | { agent?: { execute?: unknown } };\n const isBareAgent = typeof (router as { execute?: unknown }).execute === \"function\";\n const isEntryForm =\n !isBareAgent &&\n typeof (router as { agent?: { execute?: unknown } }).agent === \"object\" &&\n typeof (router as { agent?: { execute?: unknown } }).agent?.execute === \"function\";\n\n if (!isBareAgent && !isEntryForm) {\n throw new SupervisorFailedError(\n `ai.supervisor(\"${config.name}\"): \\`router\\` must be an agent contract or a \\`{ agent, placeholders?, input? }\\` entry`,\n { context: { authoring: true } },\n );\n }\n }\n\n if (hasRoute && hasRouter) {\n throw new SupervisorFailedError(\n `ai.supervisor(\"${config.name}\"): \\`route\\` and \\`router\\` are mutually exclusive — configure exactly one`,\n { context: { authoring: true } },\n );\n }\n\n // Phase 7 / decisions §37 — `classifier` is the iter-0 prelude;\n // satisfies the \"must have a dispatch source\" rule on its own.\n // Composes with router/route (classifier drives iter 0; router/route\n // takes iter 1+). When configured alone, supervisor terminates after\n // iter 0's branch settles.\n const hasClassifier = config.classifier !== undefined;\n\n if (!hasRoute && !hasRouter && !hasClassifier) {\n throw new SupervisorFailedError(\n `ai.supervisor(\"${config.name}\"): one of \\`route\\`, \\`router\\`, or \\`classifier\\` is required`,\n { context: { authoring: true } },\n );\n }\n\n // Phase 7 — classifier and initialAgent both decide what runs first.\n // Coexistence is meaningless; throw loudly.\n if (hasClassifier && config.initialAgent) {\n throw new SupervisorFailedError(\n `ai.supervisor(\"${config.name}\"): \\`classifier\\` and \\`initialAgent\\` are mutually exclusive — both decide which intent runs first. Pick one.`,\n { context: { authoring: true } },\n );\n }\n\n // Phase 3.4 (Q9) — evaluate now pairs with both `route` and\n // `router`. State-driven termination is useful in either dispatch\n // mode; the historical router-only restriction was incidental,\n // not principled.\n\n if (config.ack !== undefined) {\n const ack = config.ack;\n const isCallback = typeof ack === \"function\";\n const isAgentEntry =\n typeof ack === \"object\" &&\n ack !== null &&\n typeof (ack as { agent?: { execute?: unknown } }).agent?.execute === \"function\";\n const isRunEntry =\n typeof ack === \"object\" &&\n ack !== null &&\n typeof (ack as { run?: unknown }).run === \"function\";\n\n if (!isCallback && !isAgentEntry && !isRunEntry) {\n throw new SupervisorFailedError(\n `ai.supervisor(\"${config.name}\"): \\`ack\\` must be an \\`{ agent, ... }\\` entry, an \\`{ run, ... }\\` entry, or a bare callback function`,\n { context: { authoring: true } },\n );\n }\n\n if (isAgentEntry && isRunEntry) {\n throw new SupervisorFailedError(\n `ai.supervisor(\"${config.name}\"): \\`ack\\` cannot declare both \\`agent\\` and \\`run\\` — pick one`,\n { context: { authoring: true } },\n );\n }\n }\n\n if (config.maxIterations !== undefined && config.maxIterations < 1) {\n throw new SupervisorFailedError(\n `ai.supervisor(\"${config.name}\"): \\`maxIterations\\` must be >= 1`,\n { context: { authoring: true, maxIterations: config.maxIterations } },\n );\n }\n}\n\nfunction generateRunId(): string {\n return `sup_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"supervisor.mjs","names":[],"sources":["../../../../../../../ai/src/supervisor/supervisor.ts"],"sourcesContent":["import type { SupervisorEventMap } from \"../contracts/events/event-map.type\";\nimport type { ExecutionReport } from \"../contracts/result/execution-report.type\";\nimport type { SupervisorResult } from \"../contracts/result/supervisor-result.type\";\nimport type { StreamContract } from \"../contracts/stream/stream.contract\";\nimport type { SupervisorIntentValue } from \"../contracts/supervisor/intent-entry.type\";\nimport type {\n SupervisorConfig,\n SupervisorEventHandler,\n} from \"../contracts/supervisor/supervisor-config.type\";\nimport type {\n SupervisorExecuteOptions,\n SupervisorResumeOptions,\n} from \"../contracts/supervisor/supervisor-execute-options.type\";\nimport type { SupervisorInput } from \"../contracts/supervisor/supervisor-input.type\";\nimport type { SupervisorStreamEvent } from \"../contracts/supervisor/supervisor-stream-event.type\";\nimport type {\n SupervisorAsToolOptions,\n SupervisorContract,\n} from \"../contracts/supervisor/supervisor.contract\";\nimport { SupervisorFailedError } from \"../errors\";\nimport { notifyObservers } from \"../observe/resolve-observers\";\nimport type { ToolContract } from \"../tool/tool\";\nimport { asTool } from \"./as-tool\";\nimport { SupervisorEmitter } from \"./emitter\";\nimport { assertRouterDescriptions, resolveIntentEntries } from \"./entries\";\nimport { SupervisorExecution } from \"./execution\";\nimport { computeSignature } from \"./signature\";\nimport { loadSnapshotForResume } from \"./snapshot\";\nimport { createSupervisorStream } from \"./supervisor-stream\";\n\n/**\n * `ai.supervisor(config)` — construct a `SupervisorContract`. Validates\n * the config at author time (throws `SupervisorFailedError` on bad\n * shape), resolves agent entries, computes a stable structural\n * signature, wires the three-tier event emitter, and returns an\n * instance that satisfies `ExecutableContract` so it can compose into\n * tools, outer agents, and (future) orchestrators uniformly.\n *\n * @example\n * const support = ai.supervisor({\n * name: \"customer-support\",\n * router: routerAgent,\n * intents: { triage, orderLookup, billingLookup, resolver },\n * evaluate: (ctx) => ctx.result.resolver?.output ? { satisfied: true } : undefined,\n * output: z.object({ response: z.string(), refund: z.boolean() }),\n * maxIterations: 6,\n * });\n */\nexport function supervisor<\n TOutput = unknown,\n TState = TOutput,\n TIntents extends Record<string, SupervisorIntentValue> = Record<string, SupervisorIntentValue>,\n TArtifacts = Record<string, unknown>,\n>(config: SupervisorConfig<TOutput, TState, TIntents, TArtifacts>): SupervisorContract<TOutput> {\n validateFactoryConfig(config as unknown as SupervisorConfig<TOutput>);\n\n const entries = resolveIntentEntries(config.intents, config.name);\n\n assertRouterDescriptions(config as SupervisorConfig<unknown>, entries);\n\n if (config.initialAgent && !entries.has(config.initialAgent)) {\n throw new SupervisorFailedError(\n `ai.supervisor(\"${config.name}\"): \\`initialAgent\\` \"${config.initialAgent}\" is not a key in \\`intents\\``,\n { context: { authoring: true } },\n );\n }\n\n const signature = computeSignature(config as SupervisorConfig<unknown>, entries);\n const emitter = new SupervisorEmitter(config.on);\n\n async function execute(\n input: SupervisorInput,\n options?: SupervisorExecuteOptions,\n ): Promise<SupervisorResult<TOutput>> {\n const runId = options?.runId ?? generateRunId();\n\n const execution = new SupervisorExecution<TOutput>({\n config: config as unknown as SupervisorConfig<TOutput>,\n entries,\n signature,\n emitter,\n input,\n runId,\n options,\n });\n\n const result = await execution.run();\n\n // Route the finished report to any resolved observers (F1/F3).\n // Gated by `config.observe` + the global observe-all flag; observer\n // errors are swallowed inside `notifyObservers`. `ai.team(...)`\n // forwards its `observe` into this same config, so a team inherits\n // observability through here with no extra wiring. Bridge the\n // pre-existing `SupervisorReport = Omit<BaseReport, \"type\">` drift\n // (the report carries `type: \"supervisor\"` at runtime) so this call\n // site adds no new type error beyond the documented baseline.\n await notifyObservers(config.observe, result.report as unknown as ExecutionReport);\n\n return result;\n }\n\n function stream(\n input: SupervisorInput,\n options?: SupervisorExecuteOptions,\n ): StreamContract<SupervisorResult<TOutput>, SupervisorStreamEvent> {\n const runId = options?.runId ?? generateRunId();\n const { controller, stream: contract } = createSupervisorStream<SupervisorResult<TOutput>>();\n\n const execution = new SupervisorExecution<TOutput>({\n config: config as unknown as SupervisorConfig<TOutput>,\n entries,\n signature,\n emitter,\n input,\n runId,\n options,\n streamController: controller,\n });\n\n // Route the finished report to resolved observers once the streamed\n // run settles. Attached to the run promise (not awaited — `stream`\n // returns synchronously); `notifyObservers` swallows observer errors.\n void execution\n .run()\n .then((result) =>\n notifyObservers(config.observe, result.report as unknown as ExecutionReport),\n );\n\n return contract;\n }\n\n async function resume(\n runId: string,\n options?: SupervisorResumeOptions,\n ): Promise<SupervisorResult<TOutput>> {\n const snapshot = await loadSnapshotForResume({\n config: config as SupervisorConfig<unknown>,\n signature,\n runId,\n options,\n });\n\n const execution = new SupervisorExecution<TOutput>({\n config: config as unknown as SupervisorConfig<TOutput>,\n entries,\n signature,\n emitter,\n input: snapshot.input,\n runId,\n options,\n resumeFrom: snapshot,\n });\n\n const result = await execution.run();\n\n await notifyObservers(config.observe, result.report as unknown as ExecutionReport);\n\n return result;\n }\n\n const instance: SupervisorContract<TOutput> = {\n name: config.name,\n inputSchema: config.inputSchema,\n signature,\n execute,\n stream,\n resume,\n on<K extends keyof SupervisorEventMap>(\n event: K,\n handler: SupervisorEventHandler<K>,\n ): () => void {\n return emitter.on(event, handler);\n },\n off<K extends keyof SupervisorEventMap>(event: K, handler: SupervisorEventHandler<K>): void {\n emitter.off(event, handler);\n },\n asTool<TToolInput = string>(\n options: SupervisorAsToolOptions<TToolInput>,\n ): ToolContract<TToolInput, TOutput> {\n return asTool<TOutput, TToolInput>(instance, options);\n },\n };\n\n return instance;\n}\n\n/**\n * Factory-time validation. Enforces the XOR + pairing rules the design\n * locked in §2 and surfaces any violation as a typed\n * `SupervisorFailedError` tagged `authoring: true`.\n */\nfunction validateFactoryConfig<T>(config: SupervisorConfig<T>): void {\n if (!config.name || typeof config.name !== \"string\") {\n throw new SupervisorFailedError(\"ai.supervisor: `name` is required and must be a string\", {\n context: { authoring: true },\n });\n }\n\n if (!config.intents || typeof config.intents !== \"object\") {\n throw new SupervisorFailedError(`ai.supervisor(\"${config.name}\"): \\`intents\\` is required`, {\n context: { authoring: true },\n });\n }\n\n const hasRoute = typeof config.route === \"function\";\n const hasRouter = !!config.router;\n\n if (hasRouter) {\n const router = config.router as { execute?: unknown } | { agent?: { execute?: unknown } };\n const isBareAgent = typeof (router as { execute?: unknown }).execute === \"function\";\n const isEntryForm =\n !isBareAgent &&\n typeof (router as { agent?: { execute?: unknown } }).agent === \"object\" &&\n typeof (router as { agent?: { execute?: unknown } }).agent?.execute === \"function\";\n\n if (!isBareAgent && !isEntryForm) {\n throw new SupervisorFailedError(\n `ai.supervisor(\"${config.name}\"): \\`router\\` must be an agent contract or a \\`{ agent, placeholders?, input? }\\` entry`,\n { context: { authoring: true } },\n );\n }\n }\n\n if (hasRoute && hasRouter) {\n throw new SupervisorFailedError(\n `ai.supervisor(\"${config.name}\"): \\`route\\` and \\`router\\` are mutually exclusive — configure exactly one`,\n { context: { authoring: true } },\n );\n }\n\n // Phase 7 / decisions §37 — `classifier` is the iter-0 prelude;\n // satisfies the \"must have a dispatch source\" rule on its own.\n // Composes with router/route (classifier drives iter 0; router/route\n // takes iter 1+). When configured alone, supervisor terminates after\n // iter 0's branch settles.\n const hasClassifier = config.classifier !== undefined;\n\n if (!hasRoute && !hasRouter && !hasClassifier) {\n throw new SupervisorFailedError(\n `ai.supervisor(\"${config.name}\"): one of \\`route\\`, \\`router\\`, or \\`classifier\\` is required`,\n { context: { authoring: true } },\n );\n }\n\n // Phase 7 — classifier and initialAgent both decide what runs first.\n // Coexistence is meaningless; throw loudly.\n if (hasClassifier && config.initialAgent) {\n throw new SupervisorFailedError(\n `ai.supervisor(\"${config.name}\"): \\`classifier\\` and \\`initialAgent\\` are mutually exclusive — both decide which intent runs first. Pick one.`,\n { context: { authoring: true } },\n );\n }\n\n // Phase 3.4 (Q9) — evaluate now pairs with both `route` and\n // `router`. State-driven termination is useful in either dispatch\n // mode; the historical router-only restriction was incidental,\n // not principled.\n\n if (config.ack !== undefined) {\n const ack = config.ack;\n const isCallback = typeof ack === \"function\";\n const isAgentEntry =\n typeof ack === \"object\" &&\n ack !== null &&\n typeof (ack as { agent?: { execute?: unknown } }).agent?.execute === \"function\";\n const isRunEntry =\n typeof ack === \"object\" &&\n ack !== null &&\n typeof (ack as { run?: unknown }).run === \"function\";\n\n if (!isCallback && !isAgentEntry && !isRunEntry) {\n throw new SupervisorFailedError(\n `ai.supervisor(\"${config.name}\"): \\`ack\\` must be an \\`{ agent, ... }\\` entry, an \\`{ run, ... }\\` entry, or a bare callback function`,\n { context: { authoring: true } },\n );\n }\n\n if (isAgentEntry && isRunEntry) {\n throw new SupervisorFailedError(\n `ai.supervisor(\"${config.name}\"): \\`ack\\` cannot declare both \\`agent\\` and \\`run\\` — pick one`,\n { context: { authoring: true } },\n );\n }\n }\n\n if (config.maxIterations !== undefined && config.maxIterations < 1) {\n throw new SupervisorFailedError(\n `ai.supervisor(\"${config.name}\"): \\`maxIterations\\` must be >= 1`,\n { context: { authoring: true, maxIterations: config.maxIterations } },\n );\n }\n\n // Width bound on parallel dispatch (see `maxFanOut` docs). Same\n // authoring-error shape as `maxIterations`, but integer-only — a\n // fractional cap would silently reject legitimate widths.\n if (\n config.maxFanOut !== undefined &&\n (!Number.isInteger(config.maxFanOut) || config.maxFanOut < 1)\n ) {\n throw new SupervisorFailedError(\n `ai.supervisor(\"${config.name}\"): \\`maxFanOut\\` must be an integer >= 1`,\n { context: { authoring: true, maxFanOut: config.maxFanOut } },\n );\n }\n}\n\nfunction generateRunId(): string {\n return `sup_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;\n}\n"],"mappings":"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"}
|
package/llms-full.txt
CHANGED
|
@@ -440,7 +440,7 @@ Only a *handler bug* — a non-sentinel throw from your handler — propagates,
|
|
|
440
440
|
|
|
441
441
|
---
|
|
442
442
|
name: attach-ai-middleware
|
|
443
|
-
description: 'Wire agent middleware — ai.middleware.budget (token / USD caps + SLO/cost contract w/ maxLatencyMs + onViolation fallback), ai.middleware.guardrail (pre / post content checks), ai.middleware.semanticCache (exact + vector cache), supervisor-level middleware, plus authoring custom hooks (execute / trip / tool). Triggers: `ai.middleware.budget`, `ai.middleware.guardrail`, `ai.middleware.semanticCache`, `ai.middleware.compose`, `ai.middleware.forTool`, `AgentMiddleware`, `BudgetExceededError`, `GuardrailViolationError`, `BudgetContract`, `maxLatencyMs`, `onViolation`, `readBudgetFallbackSignal`, `supervisor middleware`; ''cap token cost'', ''SLO budget'', ''block pii in prompts'', ''semantic cache before LLM'', ''supervisor-level middleware'', ''write custom hook''; typical import `import { ai } from "@warlock.js/ai"`. Skip: agent lifecycle — `@warlock.js/ai/run-ai-agent/SKILL.md`; cache drivers — `@warlock.js/ai/persist-ai-data/SKILL.md`; competing libs `langchain` callbacks.'
|
|
443
|
+
description: 'Wire agent middleware — ai.middleware.budget (token / USD caps + SLO/cost contract w/ maxLatencyMs + onViolation fallback), ai.middleware.guardrail (pre / post content checks), ai.middleware.semanticCache (exact + vector cache), supervisor-level middleware, plus authoring custom hooks (execute / trip / tool). Triggers: `ai.middleware.budget`, `ai.middleware.guardrail`, `ai.middleware.semanticCache`, `ai.middleware.compose`, `ai.middleware.forTool`, `AgentMiddleware`, `BudgetExceededError`, `GuardrailViolationError`, `BudgetContract`, `maxLatencyMs`, `onViolation`, `readBudgetFallbackSignal`, `supervisor middleware`, `SemanticCacheOptions`, `SemanticCacheScope`; ''cap token cost'', ''SLO budget'', ''block pii in prompts'', ''semantic cache before LLM'', ''supervisor-level middleware'', ''write custom hook'', ''isolate semantic cache per session/tenant''; 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.'
|
|
444
444
|
---
|
|
445
445
|
|
|
446
446
|
# Middleware — agent-level pipeline
|
|
@@ -545,6 +545,7 @@ ai.middleware.semanticCache({
|
|
|
545
545
|
threshold: 0.95,
|
|
546
546
|
ttlMs: 60 * 60 * 1000,
|
|
547
547
|
namespace: "support-faq",
|
|
548
|
+
// scope: "session" (default) — see below
|
|
548
549
|
});
|
|
549
550
|
```
|
|
550
551
|
|
|
@@ -558,6 +559,21 @@ ai.middleware.semanticCache({
|
|
|
558
559
|
- **Trip-zero only** — only first-trip responses are cached. Tool-using loops never serve cached tool-call responses (would infinite-loop).
|
|
559
560
|
- **Never use memory drivers in production** — linear scan per query.
|
|
560
561
|
|
|
562
|
+
### Session-scoped by default — `scope` (4.15.0)
|
|
563
|
+
|
|
564
|
+
A `semanticCache` is normally built once at app boot and shared by every end user, and a hit is returned as the model's answer with **no LLM call in between** — so without isolation, user B's merely-*similar* prompt could be served user A's cached answer, personal context included. `SemanticCacheOptions.scope` (default `"session"`) keys every entry off the run's `AgentExecuteOptions.sessionId` (`"session:<id>"`) and re-checks it as exact equality on read — the key alone never authorizes a hit.
|
|
565
|
+
|
|
566
|
+
```ts
|
|
567
|
+
ai.middleware.semanticCache({ embedder, threshold: 0.95, scope: "shared" }); // opt back into one shared pool
|
|
568
|
+
ai.middleware.semanticCache({ embedder, threshold: 0.95, scope: (ctx) => tenantIdFrom(ctx) }); // custom boundary
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
- **`"session"`** (default) — isolated per `sessionId`; a run made *without* a `sessionId` shares one unscoped pool (unchanged behavior for those calls). Thread `sessionId` through `agent.execute()` to get the isolation — composite primitives (supervisor, orchestrator) already forward their own.
|
|
572
|
+
- **`"shared"`** — one pool for every caller, regardless of session — the pre-4.15.0 behavior. The explicit opt-in for genuinely public Q&A (docs bot, FAQ) where cross-user hit rate is the point and no response can carry a caller's private context.
|
|
573
|
+
- **`(context) => key | undefined`** — derive your own boundary, e.g. per tenant. Returning `undefined` falls back to the unscoped pool.
|
|
574
|
+
|
|
575
|
+
Entries written before the upgrade are unscoped and are only read by unscoped (or `"shared"`) runs. The vector lookup overscans before filtering (mirroring the memory tiers) so a noisy foreign scope can't occupy the top-`k` and mask a caller's own hit.
|
|
576
|
+
|
|
561
577
|
## Writing your own middleware
|
|
562
578
|
|
|
563
579
|
One object. Any subset of three hook maps.
|
|
@@ -3230,6 +3246,8 @@ await kb.index(await ai.rag.loadWeb("https://docs.example.com/guide", {
|
|
|
3230
3246
|
|
|
3231
3247
|
HTML responses run through the same tag-strip pass as `loadHtml`; non-HTML text (`text/plain`, markdown) is used verbatim. `metadata.source` is the resolved URL, `metadata.contentType` the server-reported type. A non-OK response, a policy block, a timeout, or an over-cap body throws `OutboundPolicyError`.
|
|
3232
3248
|
|
|
3249
|
+
**Redirects are re-validated per hop, not delegated to the platform (4.15.0).** A page a crawl reaches can `3xx` — `guardedFetch` re-runs each `Location` through the same scheme/host/private-IP checks before following it, capped at `policy.maxRedirects` (default `5`), and strips `authorization`/`cookie`/`proxy-authorization` on a cross-origin hop. So a redirect can never smuggle `loadWeb` into a private/metadata address the original URL couldn't have reached. Full guard detail (including `assertUrlAllowed`, `fetchTextWithPolicy`, and the other call sites sharing it): [`@warlock.js/ai/secure-outbound-requests/SKILL.md`](@warlock.js/ai/secure-outbound-requests/SKILL.md).
|
|
3250
|
+
|
|
3233
3251
|
### `loadPdf` — lazy optional peer, page-precise citations
|
|
3234
3252
|
|
|
3235
3253
|
`pdf-parse` is an **optional** peer, dynamic-imported on the FIRST `loadPdf` call — importing `@warlock.js/ai` never forces it. When it is absent, the curated `PDF_PARSE_INSTALL_INSTRUCTIONS` string is thrown as a plain `Error` (a missing infra peer, not a content problem), never a raw module-resolution stack trace.
|
|
@@ -3328,6 +3346,7 @@ The `embedder`'s `dimensions` MUST equal the store's `dimensions` — a mismatch
|
|
|
3328
3346
|
|
|
3329
3347
|
- [[run-ai-rag]] — the chunk → embed → retrieve → rerank → cite pipeline that **consumes** these loaders and stores (`ai.rag({ embedder, store })`, `index()` / `retrieve()`).
|
|
3330
3348
|
- [[embed-text]] — the `sdk.embedder` primitive whose `dimensions` must match the store's `vector(N)` width.
|
|
3349
|
+
- [[secure-outbound-requests]] — the full `guardedFetch` / `OutboundPolicy` guard `loadWeb` delegates to, including per-hop redirect revalidation and the other consumers sharing it.
|
|
3331
3350
|
- [`@warlock.js/cache/use-cache-similarity/SKILL.md`](@warlock.js/cache/use-cache-similarity/SKILL.md) — the cache driver `cacheVectorStore` adapts.
|
|
3332
3351
|
|
|
3333
3352
|
|
|
@@ -3753,6 +3772,8 @@ attachments: [
|
|
|
3753
3772
|
|
|
3754
3773
|
Model must declare `capabilities.vision`. OpenAI adapter auto-infers from name; override with `openai.model({ name, vision: true })`.
|
|
3755
3774
|
|
|
3775
|
+
A URL *image* attachment is passed to the provider as a URL — the provider fetches it, not the framework, so there's no server-side SSRF surface. A **remote `{ type: "text", source: <url> }` attachment IS fetched server-side** (the adapter needs the raw text inline) and is default-DENY: it throws unless `attachmentPolicy.allowRemoteFetch: true`, and when enabled runs through the shared `guardedFetch` / `OutboundPolicy` guard — see [`@warlock.js/ai/secure-outbound-requests/SKILL.md`](@warlock.js/ai/secure-outbound-requests/SKILL.md).
|
|
3776
|
+
|
|
3756
3777
|
## Pattern — streaming
|
|
3757
3778
|
|
|
3758
3779
|
```ts
|
|
@@ -3865,6 +3886,7 @@ The one field a bare agent config doesn't surface ergonomically is `budget` (`Bu
|
|
|
3865
3886
|
- [`@warlock.js/ai/define-ai-tool/SKILL.md`](@warlock.js/ai/define-ai-tool/SKILL.md) — tool wiring + schema validation
|
|
3866
3887
|
- [`@warlock.js/ai/write-system-prompt/SKILL.md`](@warlock.js/ai/write-system-prompt/SKILL.md) — persona / instruction builders
|
|
3867
3888
|
- [`@warlock.js/ai/handle-ai-errors/SKILL.md`](@warlock.js/ai/handle-ai-errors/SKILL.md) — `AIError` hierarchy
|
|
3889
|
+
- [`@warlock.js/ai/secure-outbound-requests/SKILL.md`](@warlock.js/ai/secure-outbound-requests/SKILL.md) — the `guardedFetch` / `OutboundPolicy` guard behind a remote text attachment fetch
|
|
3868
3890
|
|
|
3869
3891
|
|
|
3870
3892
|
## run-ai-rag `@warlock.js/ai/run-ai-rag/SKILL.md`
|
|
@@ -4556,11 +4578,14 @@ ai.orchestrator({
|
|
|
4556
4578
|
recall: { k: 5, threshold: 0.7, tier: "semantic" }, // k: 0 = write-only memory
|
|
4557
4579
|
remember: true, // false = read-only (recall, never write)
|
|
4558
4580
|
rememberTier: "semantic",
|
|
4581
|
+
scope: "session", // DEFAULT — isolate memories per sessionId
|
|
4559
4582
|
injectKey: "memories", // ctx.context[injectKey] holds RecalledMemory[]
|
|
4560
4583
|
},
|
|
4561
4584
|
});
|
|
4562
4585
|
```
|
|
4563
4586
|
|
|
4587
|
+
**Memory is session-scoped by default (4.15.0).** One store instance backs every session of the orchestrator, so `scope` decides what a turn may read: `"session"` (default) keys recall + write-back to the executing `sessionId`, so one user can never recall another's remembered turns. `"shared"` pools every session into one namespace — the pre-4.15.0 behavior, safe only when every session is trusted to see every other's memories. `(sessionId) => key` derives your own boundary (e.g. a tenant id). Memories written before 4.15.0 are unscoped and are only visible under `scope: "shared"`.
|
|
4588
|
+
|
|
4564
4589
|
Recalled memories land in the per-turn `context` bag under `injectKey` (default `"memories"`) — every route / router / evaluate / dispatch callback reads them at `ctx.context.memories`. Memory never mutates the prompt itself; surfacing it stays explicit. Cancelled / failed turns never remember (they revert), regardless of `remember`. See [`@warlock.js/ai/use-ai-memory/SKILL.md`](@warlock.js/ai/use-ai-memory/SKILL.md).
|
|
4565
4590
|
|
|
4566
4591
|
## `asTool()` — orchestrator as a tool
|
|
@@ -4578,7 +4603,9 @@ const concierge = ai.agent({ model, tools: [supportTool] });
|
|
|
4578
4603
|
|
|
4579
4604
|
The tool boundary is **opaque**: the parent's `signal` / `context` / events do NOT auto-forward — anything the wrapped orchestrator needs must ride on the `inputSchema` payload. `sessionScope`:
|
|
4580
4605
|
- **`"fresh"`** (default) — each invocation gets a generated `sessionId` and empty history; no continuity across calls.
|
|
4581
|
-
- **`"shared"`** — the
|
|
4606
|
+
- **`"shared"`** — the orchestrator joins an existing session named by the DEVELOPER through `session`, never by the model: either a literal id fixed at construction (`session: "sess_42"`) or a resolver reading the out-of-band tool context (`session: (ctx) => String(ctx?.artifacts?.supportSessionId)`). Building a `"shared"` tool without `session` throws at construction, and `sessionId` / `history` in the payload are stripped, not honored.
|
|
4607
|
+
|
|
4608
|
+
A `sessionId` is bearer-equivalent to read/write on that session, so it must not be a model-visible `inputSchema` field: before 4.15.0 it was, and a prompt injection reaching the outer agent could make the nested orchestrator resume, mutate, and echo back a *victim's* conversation. `unsafeAllowModelSessionId: true` restores the old payload path — only for a fully trusted outer context where you verify session ownership yourself.
|
|
4582
4609
|
|
|
4583
4610
|
## Drift detection
|
|
4584
4611
|
|
|
@@ -4610,7 +4637,7 @@ await orch.execute(input, { sessionId, history, on: { "orchestrator.drift.checke
|
|
|
4610
4637
|
|
|
4611
4638
|
---
|
|
4612
4639
|
name: run-planner
|
|
4613
|
-
description: 'Goal-driven planning with ai.planner({...}) — an LLM GENERATES an ordered execution plan over your registered capabilities (agents / workflows / supervisors / tools), then the planner EXECUTES it, threading each step output into the next, and returns the unified {data, report, usage, error} envelope with report.type "planner". Supports DAG scheduling (dag:true + maxConcurrency off dependsOn), adaptive re-planning (replan:{maxReplans} + the onStep continue/abort/replan directive), and plan-only / approval (mode:"plan-only" → status "awaiting-approval" → approvedPlan). A plan step may delegate via ai.spawnSubAgent({...}) — a GENERAL one-shot-agent helper covered in `@warlock.js/ai/run-ai-agent/SKILL.md`; it is not planner-specific. Triggers: `ai.planner`, `planner.execute`, `spawnSubAgent`, `PlannerConfig`, `PlannerCapability`, `PlannerResult`, `PlannerReport`, `PlannerPlan`, `PlannerStep`, `PlannerStepDirective`, `PlannerPlanInvalidError`, `maxSteps`, `dag`, `maxConcurrency`, `dependsOn`, `replan`, `onStep`, `mode`, `approvedPlan`, `awaiting-approval`, `report.plan`, `report.executedSteps`; ''let the model plan the steps'', ''dynamic plan from a goal'', ''run independent steps in parallel'', ''re-plan when a step fails'', ''generate a plan for approval before running it''; typical import `import { ai } from "@warlock.js/ai"`. Skip: a FIXED known pipeline — `@warlock.js/ai/run-ai-workflow/SKILL.md`; routing one input to a specialist each turn — `@warlock.js/ai/run-supervisor/SKILL.md`; a single model + tools call — `@warlock.js/ai/run-ai-agent/SKILL.md`; competing libs `langgraph`, `crewai`.'
|
|
4640
|
+
description: 'Goal-driven planning with ai.planner({...}) — an LLM GENERATES an ordered execution plan over your registered capabilities (agents / workflows / supervisors / tools), then the planner EXECUTES it, threading each step output into the next, and returns the unified {data, report, usage, error} envelope with report.type "planner". Supports DAG scheduling (dag:true + maxConcurrency off dependsOn), adaptive re-planning (replan:{maxReplans} + the onStep continue/abort/replan directive), and plan-only / approval (mode:"plan-only" → status "awaiting-approval" → approvedPlan). A plan step may delegate via ai.spawnSubAgent({...}) — a GENERAL one-shot-agent helper covered in `@warlock.js/ai/run-ai-agent/SKILL.md`; it is not planner-specific. Triggers: `ai.planner`, `planner.execute`, `spawnSubAgent`, `PlannerConfig`, `PlannerCapability`, `PlannerResult`, `PlannerReport`, `PlannerPlan`, `PlannerStep`, `PlannerStepDirective`, `PlannerPlanInvalidError`, `maxSteps`, `dag`, `maxConcurrency`, `dependsOn`, `replan`, `onStep`, `mode`, `approvedPlan`, `awaiting-approval`, `report.plan`, `report.executedSteps`, `parsedStepCeiling`; ''let the model plan the steps'', ''dynamic plan from a goal'', ''run independent steps in parallel'', ''re-plan when a step fails'', ''generate a plan for approval before running it''; typical import `import { ai } from "@warlock.js/ai"`. Skip: a FIXED known pipeline — `@warlock.js/ai/run-ai-workflow/SKILL.md`; routing one input to a specialist each turn — `@warlock.js/ai/run-supervisor/SKILL.md`; a single model + tools call — `@warlock.js/ai/run-ai-agent/SKILL.md`; competing libs `langgraph`, `crewai`.'
|
|
4614
4641
|
---
|
|
4615
4642
|
|
|
4616
4643
|
# `ai.planner()` — LLM-generated, then executed, plans
|
|
@@ -4640,7 +4667,7 @@ const research = ai.planner({
|
|
|
4640
4667
|
{ name: "summarize", description: "Summarize text into bullet points", executable: summarizer },
|
|
4641
4668
|
{ name: "write", description: "Draft a final report", executable: writerAgent },
|
|
4642
4669
|
],
|
|
4643
|
-
maxSteps: 6, //
|
|
4670
|
+
maxSteps: 6, // soft cap; steps beyond it are recorded as "skipped" — see the parse-time ceiling below
|
|
4644
4671
|
});
|
|
4645
4672
|
|
|
4646
4673
|
const { data, report, usage, error } = await research.execute("Compare React vs Vue in 2026");
|
|
@@ -4663,6 +4690,10 @@ for (const step of report.executedSteps) { // forensic, in execution order
|
|
|
4663
4690
|
|
|
4664
4691
|
`report.type === "planner"`; `report.children[]` carries every dispatched capability report (plus the planning trip), with usage rolled up. `report.executedSteps` is the authoritative per-step record (`PlannerStepSnapshot[]`). Lazy capability loading is **deferred** — every capability is fully constructed up front.
|
|
4665
4692
|
|
|
4693
|
+
### Parse-time step ceiling (4.15.0)
|
|
4694
|
+
|
|
4695
|
+
`maxSteps` can't be expressed in the strict-mode JSON Schema the planning model is given (no `maxItems`), so a provider/proxy that ignores the prompt's step budget could make the planner deserialize an arbitrarily long `steps[]` array before `PlannerRun`'s tail-truncation logic ever ran — `maxSteps` only trimmed *after* the whole array was already parsed and normalized. Plan validation now enforces a hard **parse-time** ceiling of `maxSteps * 4` (or `100` when the schema is built without a `maxSteps`) and **rejects** — rather than truncates — a plan that exceeds it, surfacing `PlannerPlanInvalidError`. The 4× slack keeps the normal case (a model overshooting "at most N steps" slightly) working exactly as before — that overshoot is still truncated to `skipped` steps at execution time, not rejected at parse time. A plan several times its budget is treated as a malfunction worth surfacing, not a prefix worth silently executing.
|
|
4696
|
+
|
|
4666
4697
|
## DAG scheduling — `dag: true` + `maxConcurrency`
|
|
4667
4698
|
|
|
4668
4699
|
Run independent steps in parallel instead of array-order:
|
|
@@ -4729,7 +4760,7 @@ const final = await planner.execute(goal, { approvedPlan: draft.plan! });
|
|
|
4729
4760
|
|
|
4730
4761
|
`execute()` never throws — failures surface on `result.error`:
|
|
4731
4762
|
|
|
4732
|
-
- **`PlannerPlanInvalidError`** (`PLANNER_PLAN_INVALID`, category `schema`) — empty plan, a step naming an unknown capability, a DAG cycle, a `dependsOn` naming an unknown step, a stale `approvedPlan`,
|
|
4763
|
+
- **`PlannerPlanInvalidError`** (`PLANNER_PLAN_INVALID`, category `schema`) — empty plan, a step naming an unknown capability, a DAG cycle, a `dependsOn` naming an unknown step, a stale `approvedPlan`, a final-output validation failure, or (4.15.0) a plan exceeding the parse-time step ceiling (`maxSteps * 4`, default `100`).
|
|
4733
4764
|
- **`PlannerCancelledError`** (`PLANNER_CANCELLED`, category `cancelled`) — the `AbortSignal` fired. `report.status === "cancelled"`, `report.cancelledAt` set; remaining steps are `skipped`.
|
|
4734
4765
|
- A child capability's own error (agent / tool / provider) flows through unchanged on the failing step's snapshot and as `result.error`. The planner stops at the first failed step and marks the rest `skipped`.
|
|
4735
4766
|
- **`PlannerFailedError`** is the base for the `PLANNER_*` family.
|
|
@@ -4855,6 +4886,16 @@ ai.supervisor({
|
|
|
4855
4886
|
|
|
4856
4887
|
Each key references the same underlying unit; the description defaults to the unit's. Override the key base with `{ keyPrefix }` and the per-entry text with `{ description }`.
|
|
4857
4888
|
|
|
4889
|
+
#### `maxFanOut` — width cap (default `10`)
|
|
4890
|
+
|
|
4891
|
+
`maxIterations` bounds how DEEP a run goes; `maxFanOut` bounds how WIDE one decision goes. Duplicate intent names in a fan-out array are collapsed silently (branch results are indexed by intent — duplicates only burn tokens); if the DEDUPED list is still longer than the cap, the decision is rejected with `SupervisorRoutingError` (`SUPERVISOR_INVALID_ROUTE`), same as an unknown intent name. Applies to every dispatch source: `router`, `route`, `evaluate.reassignTo`, `intent.next`.
|
|
4892
|
+
|
|
4893
|
+
```ts
|
|
4894
|
+
ai.supervisor({ intents: { ...ai.fanOut(writer, 20), vote }, maxFanOut: 20, route });
|
|
4895
|
+
```
|
|
4896
|
+
|
|
4897
|
+
Raise it deliberately when you fan out wider than 10. Why it exists: the router's per-turn prompt embeds supervisor `state` and prior branch outputs, so text injected into a tool result can push an LLM router to emit a very wide `next` array — every element a real agent/workflow run, all inside the allowlist.
|
|
4898
|
+
|
|
4858
4899
|
## The `intents` map — five accepted shapes
|
|
4859
4900
|
|
|
4860
4901
|
```ts
|
|
@@ -4902,7 +4943,7 @@ const refundSupervisor = ai.supervisor<RefundOutput>({
|
|
|
4902
4943
|
});
|
|
4903
4944
|
```
|
|
4904
4945
|
|
|
4905
|
-
Each branch's output strip-merges into state per its declared `output` schema. Last-write-wins on fan-out conflict (warning logged).
|
|
4946
|
+
Each branch's output strip-merges into state per its declared `output` schema. Last-write-wins on fan-out conflict (warning logged). Keys named `__proto__` / `constructor` / `prototype` are dropped from every merged slice (branch output, `ack`, classifier, `refine`, artifacts) and logged as `state.merge.unsafe-key` — a permissive `output` schema would otherwise let a model-supplied key repoint the run state's prototype.
|
|
4906
4947
|
|
|
4907
4948
|
## Per-intent `next` — skip the router
|
|
4908
4949
|
|
|
@@ -5122,6 +5163,95 @@ const escalationAgent = ai.agent({ model, tools: [supportTool] });
|
|
|
5122
5163
|
- [`@warlock.js/ai/define-ai-tool/SKILL.md`](@warlock.js/ai/define-ai-tool/SKILL.md) — tool artifacts side-channel
|
|
5123
5164
|
|
|
5124
5165
|
|
|
5166
|
+
## secure-outbound-requests `@warlock.js/ai/secure-outbound-requests/SKILL.md`
|
|
5167
|
+
|
|
5168
|
+
---
|
|
5169
|
+
name: secure-outbound-requests
|
|
5170
|
+
description: 'The shared SSRF / resource-exhaustion guard every server-side outbound HTTP request in the framework goes through — `guardedFetch(url, policy, init?)`, `OutboundPolicy`, `assertUrlAllowed`, `fetchTextWithPolicy`, `readTextCapped`. Scheme allowlist (https-only default), host allowlist, post-DNS private/loopback/link-local/metadata-address deny, byte cap, timeout, and (4.15.0) per-hop redirect revalidation with a `maxRedirects` cap and cross-origin credential stripping. Consumed by `ai.rag.loadWeb`, remote text attachments (`prepareAttachmentPart`), and the skills `urlSource` manifest fetch — never a raw `fetch()` on a caller-influenced URL. Triggers: `guardedFetch`, `OutboundPolicy`, `ResolvedOutboundPolicy`, `assertUrlAllowed`, `fetchTextWithPolicy`, `readTextCapped`, `resolveOutboundPolicy`, `OutboundPolicyError`, `maxRedirects`, `denyPrivateIPsAfterDNS`, `hostAllowlist`, `allowedSchemes`, `maxBytes`, `SSRF`, `redirect: "manual"`, `redirect: "error"`; ''SSRF-safe fetch'', ''block a redirect into a private IP'', ''fetch a URL an agent gave me'', ''cap outbound response size'', ''allowlist hosts for outbound requests'', ''strip auth headers on a cross-origin redirect''; typical import `import { guardedFetch, assertUrlAllowed } from "@warlock.js/ai"` (also re-exported per call site). Skip: the RAG loader that wraps this for `loadWeb` — `@warlock.js/ai/rag-loaders-and-stores/SKILL.md`; the skills manifest source that wraps this for `urlSource` — `@warlock.js/ai/use-runtime-skills/SKILL.md`; prompt-injection / content guardrails (a different trust boundary) — `@warlock.js/ai/guard-input-output/SKILL.md` (ai-guard package).'
|
|
5171
|
+
---
|
|
5172
|
+
|
|
5173
|
+
# Outbound request policy — the SSRF guard
|
|
5174
|
+
|
|
5175
|
+
One `OutboundPolicy` + `guardedFetch` backs **every** server-side HTTP request the framework makes on behalf of user/model-controlled input: `ai.rag.loadWeb`, the remote-text branch of `prepareAttachmentPart` (agent `attachments`), and the skills catalog `urlSource` manifest fetch. A single audited guard instead of N ad-hoc `fetch()` call sites.
|
|
5176
|
+
|
|
5177
|
+
```ts
|
|
5178
|
+
import { guardedFetch, fetchTextWithPolicy, assertUrlAllowed, OutboundPolicyError } from "@warlock.js/ai";
|
|
5179
|
+
|
|
5180
|
+
const response = await guardedFetch("https://docs.example.com/page", {
|
|
5181
|
+
hostAllowlist: ["docs.example.com"],
|
|
5182
|
+
maxBytes: 2_000_000,
|
|
5183
|
+
timeoutMs: 5_000,
|
|
5184
|
+
});
|
|
5185
|
+
```
|
|
5186
|
+
|
|
5187
|
+
## Strict-by-default policy
|
|
5188
|
+
|
|
5189
|
+
Every field is optional; `resolveOutboundPolicy` fills safe defaults, so an untuned call is already hardened:
|
|
5190
|
+
|
|
5191
|
+
| Field | Default | Guards against |
|
|
5192
|
+
| --- | --- | --- |
|
|
5193
|
+
| `allowedSchemes` | `["https"]` | plaintext / `file:` / `data:` exfil — `http` must be opted in |
|
|
5194
|
+
| `hostAllowlist` | unset (any host) | pinning outbound targets to known hosts, e.g. `docs.example.com` allows `a.docs.example.com` |
|
|
5195
|
+
| `denyPrivateIPsAfterDNS` | `true` | **the SSRF guard itself** — resolves the host through DNS and rejects loopback / private / link-local / unique-local / cloud-metadata (`169.254.169.254`) addresses; a public hostname that resolves inward is caught |
|
|
5196
|
+
| `maxRedirects` | `5` | a redirect chain used to bypass the checks above (4.15.0 — see below) |
|
|
5197
|
+
| `maxBytes` | `5_242_880` (5 MiB) | unbounded response bodies |
|
|
5198
|
+
| `timeoutMs` | `10_000` | a hung/slow endpoint tying up the request |
|
|
5199
|
+
| `signal` | unset | caller-supplied `AbortSignal`, merged with the internal timeout |
|
|
5200
|
+
| `fetch` | global `fetch` | inject a stub for tests, or a wrapper enforcing your own app-level rules |
|
|
5201
|
+
|
|
5202
|
+
Every violation throws `OutboundPolicyError` with `context` carrying the offending URL/host/address — never a silent fallback.
|
|
5203
|
+
|
|
5204
|
+
## Redirects are never delegated to the platform (4.15.0)
|
|
5205
|
+
|
|
5206
|
+
Before 4.15.0, `assertUrlAllowed` validated only the *initial* URL, then handed the request to `fetch` with automatic redirect following — so a URL that passed validation could `3xx` into a private/metadata address or an off-allowlist host with no re-check.
|
|
5207
|
+
|
|
5208
|
+
`guardedFetch` now issues **every hop** with `redirect: "manual"` and re-runs the `Location` header through the exact same `assertUrlAllowed` (scheme, host allowlist, post-DNS private-IP deny) before following it:
|
|
5209
|
+
|
|
5210
|
+
- Capped at `policy.maxRedirects` (default `5`) — the `(maxRedirects + 1)`th hop throws `OutboundPolicyError`.
|
|
5211
|
+
- **Credential headers stripped cross-origin.** `authorization`, `cookie`, `proxy-authorization` are dropped the moment a hop's target origin differs from the current one — a redirect can't exfiltrate credentials meant for the original host.
|
|
5212
|
+
- **Method/body semantics match platform behavior.** `303` — and the legacy convention of `301`/`302` on a non-`GET`/`HEAD` method — re-issue the next hop as a bodyless `GET`.
|
|
5213
|
+
- Pass `init.redirect: "manual"` to get the raw 3xx response back (no following, no throw); `init.redirect: "error"` rejects on any redirect.
|
|
5214
|
+
- The net effect: a redirect can never reach a URL the original request could not have reached directly.
|
|
5215
|
+
|
|
5216
|
+
```ts
|
|
5217
|
+
// A caller that wants to inspect redirects itself, unfollowed:
|
|
5218
|
+
const res = await guardedFetch(url, policy, { redirect: "manual" });
|
|
5219
|
+
if (res.status >= 300 && res.status < 400) {
|
|
5220
|
+
console.log(res.headers.get("location"));
|
|
5221
|
+
}
|
|
5222
|
+
```
|
|
5223
|
+
|
|
5224
|
+
## Reading the body — `readTextCapped` / `fetchTextWithPolicy`
|
|
5225
|
+
|
|
5226
|
+
`guardedFetch` returns the raw `Response`; read its body through `readTextCapped(response, maxBytes)` to enforce the cap (a declared `content-length` over the cap fails fast, otherwise the stream is read chunk-by-chunk and aborted the moment the running total exceeds it). `fetchTextWithPolicy(url, policy, init?)` is the one-call convenience — `guardedFetch` + `readTextCapped`, returning `{ ok, status, statusText, text }` (body only read when `ok`).
|
|
5227
|
+
|
|
5228
|
+
```ts
|
|
5229
|
+
const { ok, status, text } = await fetchTextWithPolicy(url, { hostAllowlist: ["api.example.com"] });
|
|
5230
|
+
if (!ok) throw new Error(`fetch failed: ${status}`);
|
|
5231
|
+
```
|
|
5232
|
+
|
|
5233
|
+
## Who consumes this
|
|
5234
|
+
|
|
5235
|
+
| Call site | Entry point | Notes |
|
|
5236
|
+
| --- | --- | --- |
|
|
5237
|
+
| RAG web loader | `ai.rag.loadWeb(url, { policy })` | [`@warlock.js/ai/rag-loaders-and-stores/SKILL.md`](@warlock.js/ai/rag-loaders-and-stores/SKILL.md) |
|
|
5238
|
+
| Remote text attachment | `prepareAttachmentPart` via `agent.execute({ attachments })` | default-DENY — requires `attachmentPolicy.allowRemoteFetch: true`; policy travels as `attachmentPolicy.outbound`. URL *image* attachments are handed to the provider as a URL and never fetched server-side, so they carry no SSRF surface here |
|
|
5239
|
+
| Skills catalog manifest | `ai.skills({ sources: [urlSource(url, { policy })] })` | [`@warlock.js/ai/use-runtime-skills/SKILL.md`](@warlock.js/ai/use-runtime-skills/SKILL.md) — the fetched manifest is also runtime-validated record-by-record before being trusted |
|
|
5240
|
+
|
|
5241
|
+
Each call site passes its own `policy` (or `{}` for the strict defaults) — there is no global policy singleton, so tune per source (e.g. `hostAllowlist` for a known-good docs domain vs. an open web crawl).
|
|
5242
|
+
|
|
5243
|
+
## Testing
|
|
5244
|
+
|
|
5245
|
+
Inject a stubbed `policy.fetch` (`(url, init) => Response`) instead of hitting the network — every consumer above accepts `policy.fetch` all the way through. Regression coverage lives in `src/security/outbound-policy.spec.ts` (redirect-to-metadata/loopback/private block, off-allowlist redirect block, hop cap, credential stripping, clean-redirect follow).
|
|
5246
|
+
|
|
5247
|
+
## See also
|
|
5248
|
+
|
|
5249
|
+
- [`@warlock.js/ai/rag-loaders-and-stores/SKILL.md`](@warlock.js/ai/rag-loaders-and-stores/SKILL.md) — `loadWeb`, the primary consumer
|
|
5250
|
+
- [`@warlock.js/ai/use-runtime-skills/SKILL.md`](@warlock.js/ai/use-runtime-skills/SKILL.md) — `urlSource`'s manifest fetch
|
|
5251
|
+
- [`@warlock.js/ai/run-ai-agent/SKILL.md`](@warlock.js/ai/run-ai-agent/SKILL.md) — `attachments`, including the remote-text fetch path
|
|
5252
|
+
- [`@warlock.js/ai/handle-ai-errors/SKILL.md`](@warlock.js/ai/handle-ai-errors/SKILL.md) — `OutboundPolicyError`
|
|
5253
|
+
|
|
5254
|
+
|
|
5125
5255
|
## transcribe-audio `@warlock.js/ai/transcribe-audio/SKILL.md`
|
|
5126
5256
|
|
|
5127
5257
|
---
|
|
@@ -5287,14 +5417,14 @@ Scripting `[{ error: new ProviderRateLimitError("slow down") }]` drives the neve
|
|
|
5287
5417
|
|
|
5288
5418
|
---
|
|
5289
5419
|
name: use-ai-memory
|
|
5290
|
-
description: 'Agent memory with ai.memory({...}) — a provider-neutral store with FOUR tiers: WORKING (in-run scratch, recalled by recency), SEMANTIC (durable facts by cosine similarity over a @warlock.js/cache vector driver via .similar()), EPISODIC (durable events, similarity blended with recency), and PROCEDURAL (durable how-tos, similarity blended with reinforcement). remember() / recall() / clear(); wire it into ai.orchestrator({ memory }). Triggers: `ai.memory`, `memory.remember`, `memory.recall`, `memory.clear`, `MemoryContract`, `MemoryConfig`, `MemoryItem`, `RecalledMemory`, `MemoryTier`, `SemanticMemoryConfig`, `EpisodicMemoryConfig`, `ProceduralMemoryConfig`, `working`, `semantic`, `episodic`, `procedural`, `defaultTier`, `threshold`, `recencyWeight`, `halfLifeMs`, `reinforcementWeight`, `injectKey`; ''give the agent memory'', ''remember user preferences'', ''semantic recall'', ''per-session working memory'', ''episodic / event memory'', ''procedural / how-to memory'', ''recency-weighted recall'', ''reinforce a procedure''; typical import `import { ai } from "@warlock.js/ai"`. Skip: orchestrator wiring of the memory — `@warlock.js/ai/run-orchestrator/SKILL.md`; the vector cache driver itself — `@warlock.js/cache/cache-basics/SKILL.md`; embeddings primitive — `@warlock.js/ai/embed-text/SKILL.md`; competing libs `mem0`, `langchain` memory.'
|
|
5420
|
+
description: 'Agent memory with ai.memory({...}) — a provider-neutral store with FOUR tiers: WORKING (in-run scratch, recalled by recency), SEMANTIC (durable facts by cosine similarity over a @warlock.js/cache vector driver via .similar()), EPISODIC (durable events, similarity blended with recency), and PROCEDURAL (durable how-tos, similarity blended with reinforcement). remember() / recall() / clear(); wire it into ai.orchestrator({ memory }). Triggers: `ai.memory`, `memory.remember`, `memory.recall`, `memory.clear`, `MemoryContract`, `MemoryConfig`, `MemoryItem`, `RecalledMemory`, `MemoryTier`, `SemanticMemoryConfig`, `EpisodicMemoryConfig`, `ProceduralMemoryConfig`, `working`, `semantic`, `episodic`, `procedural`, `defaultTier`, `threshold`, `recencyWeight`, `halfLifeMs`, `reinforcementWeight`, `injectKey`, `maxItems`, `scope`, `RecallOptions.scope`; ''give the agent memory'', ''remember user preferences'', ''semantic recall'', ''per-session working memory'', ''episodic / event memory'', ''procedural / how-to memory'', ''recency-weighted recall'', ''reinforce a procedure'', ''cap working memory size'', ''isolate memory per session/tenant''; typical import `import { ai } from "@warlock.js/ai"`. Skip: orchestrator wiring of the memory — `@warlock.js/ai/run-orchestrator/SKILL.md`; the vector cache driver itself — `@warlock.js/cache/cache-basics/SKILL.md`; embeddings primitive — `@warlock.js/ai/embed-text/SKILL.md`; competing libs `mem0`, `langchain` memory.'
|
|
5291
5421
|
---
|
|
5292
5422
|
|
|
5293
5423
|
# `ai.memory()` — agent memory store
|
|
5294
5424
|
|
|
5295
5425
|
A single provider-neutral store that holds and retrieves what an agent / orchestrator should remember across turns. Four tiers ship in 4.3.0:
|
|
5296
5426
|
|
|
5297
|
-
- **working** — in-run scratch threaded across turns of one session. Volatile, unscored, recalled in insertion order (recency). On by default.
|
|
5427
|
+
- **working** — in-run scratch threaded across turns of one session. Volatile, unscored, recalled in insertion order (recency). On by default, size-bounded (`working: { maxItems }`, default `1000` — see below).
|
|
5298
5428
|
- **semantic** — durable *facts* stored as embeddings in a `@warlock.js/cache` driver, retrieved by cosine similarity via the driver's native `.similar()` — the same delegation the `semanticCache` middleware uses. Activates only when you pass `semantic` config.
|
|
5299
5429
|
- **episodic** — durable *events*: a timestamped log retrieved by similarity **blended with recency** (recent episodes rank higher). Embedder-backed like semantic; tune with `recencyWeight` + `halfLifeMs`.
|
|
5300
5430
|
- **procedural** — durable *how-tos*: learned procedures retrieved by similarity **blended with reinforcement** — re-remembering a procedure increments its use count so well-worn procedures rank higher. Tune with `reinforcementWeight`.
|
|
@@ -5364,7 +5494,7 @@ await mem.remember({ text: "User is on the Enterprise plan.", tier: "semantic",
|
|
|
5364
5494
|
await mem.remember([{ text: "a" }, { text: "b", tier: "working" }]); // batch
|
|
5365
5495
|
```
|
|
5366
5496
|
|
|
5367
|
-
A `MemoryItem` is `{ text, tier?, id?, metadata? }`. `text` is the only required field — it's what gets embedded (semantic) and surfaced back on recall. `tier` defaults to the factory `defaultTier`. Semantic items are embedded + indexed; working items append to the in-run buffer. **Re-remembering an item whose id (explicit or text-derived) already exists overwrites in place rather than duplicating.** `metadata` is an opaque bag round-tripped verbatim onto the recalled memory.
|
|
5497
|
+
A `MemoryItem` is `{ text, tier?, id?, scope?, metadata? }`. `text` is the only required field — it's what gets embedded (semantic) and surfaced back on recall. `tier` defaults to the factory `defaultTier`. Semantic items are embedded + indexed; working items append to the in-run buffer. **Re-remembering an item whose id (explicit or text-derived) already exists overwrites in place rather than duplicating.** `metadata` is an opaque bag round-tripped verbatim onto the recalled memory. `scope` is the ISOLATION key — see below.
|
|
5368
5498
|
|
|
5369
5499
|
### `recall(query, options?)`
|
|
5370
5500
|
|
|
@@ -5373,6 +5503,7 @@ const hits = await mem.recall("which plan is the user on?", {
|
|
|
5373
5503
|
k: 5, // cap result count (defaults to factory k)
|
|
5374
5504
|
threshold: 0.75, // raise the semantic floor for this call
|
|
5375
5505
|
tier: "semantic", // restrict to one tier; omit to query every enabled tier
|
|
5506
|
+
scope: "tenant-42", // isolation key — only memories remembered under this exact scope
|
|
5376
5507
|
});
|
|
5377
5508
|
|
|
5378
5509
|
for (const hit of hits) {
|
|
@@ -5384,6 +5515,38 @@ Returns `RecalledMemory[]` scored and ordered by descending relevance. By defaul
|
|
|
5384
5515
|
|
|
5385
5516
|
**Memory never mutates the prompt.** `recall()` hands you scored entries; surfacing the recalled text (system prefix, a synthesized "what you remember" block, …) is YOUR call so the injection point stays explicit.
|
|
5386
5517
|
|
|
5518
|
+
### Isolation — `scope` (4.15.0)
|
|
5519
|
+
|
|
5520
|
+
One store instance is normally shared by many callers (built once at boot, passed into `ai.orchestrator({ memory })`), so `scope` is what keeps one caller's memories out of another's recall:
|
|
5521
|
+
|
|
5522
|
+
```ts
|
|
5523
|
+
await mem.remember({ text: "User A's account email is a@example.com", scope: "user-a" });
|
|
5524
|
+
|
|
5525
|
+
await mem.recall("what is my email?", { scope: "user-b" }); // [] — never sees user A
|
|
5526
|
+
await mem.recall("what is my email?", { scope: "user-a" }); // user A's own memories
|
|
5527
|
+
await mem.recall("what is my email?"); // only the UNSCOPED pool
|
|
5528
|
+
```
|
|
5529
|
+
|
|
5530
|
+
- The match is **exact equality**, enforced inside every tier (`working` / `semantic` / `episodic` / `procedural`) before hits are scored, merged, or sliced — not something the caller filters afterward.
|
|
5531
|
+
- Omitting `scope` is **not** a wildcard: an unscoped recall reads only unscoped entries. There is no "all scopes" query.
|
|
5532
|
+
- Identical text under two scopes stays two independent entries (including the procedural tier's reinforcement counter).
|
|
5533
|
+
- `ai.orchestrator({ memory })` sets this automatically from the turn's `sessionId` — see [`@warlock.js/ai/run-orchestrator/SKILL.md`](@warlock.js/ai/run-orchestrator/SKILL.md).
|
|
5534
|
+
- `clear(tier?)` is scope-agnostic: it drops the tier for every scope.
|
|
5535
|
+
|
|
5536
|
+
### Working-memory cap — `working: { maxItems }` (4.15.0)
|
|
5537
|
+
|
|
5538
|
+
```ts
|
|
5539
|
+
const mem = ai.memory({
|
|
5540
|
+
working: { maxItems: 2_000 }, // default 1000; bare `working: true` also works
|
|
5541
|
+
});
|
|
5542
|
+
```
|
|
5543
|
+
|
|
5544
|
+
The working tier holds everything it's told in **process** memory for the lifetime of the `memory()` instance — which `ai.orchestrator({ memory })` resolves once and reuses for every session. Before 4.15.0 it had no cap, so a memory-backed orchestrator on the open internet was a cheap memory-exhaustion path: one permanent entry per request, forever.
|
|
5545
|
+
|
|
5546
|
+
The buffer now evicts on overflow, **FIFO over insertion order, not LRU** — recall on this tier is a pure recency proxy (newest `k`, never reordered), so the oldest entries are exactly the ones a bounded recall would never have returned anyway. `maxItems` is validated as an integer `>= 1` at construction; there is no unbounded setting — "no cap" was the vulnerability, not a configuration choice. Raise it deliberately for a long-lived single-tenant process, and put durable recall in the semantic / episodic tiers (which delegate retention to a `CacheDriver`, not process memory).
|
|
5547
|
+
|
|
5548
|
+
The bound is **global**, not per-scope — a busy session can push another session's older entries out. That's a recall-quality degradation on a volatile scratch tier, never a disclosure (the `scope` isolation filter above still applies).
|
|
5549
|
+
|
|
5387
5550
|
### `clear(tier?)`
|
|
5388
5551
|
|
|
5389
5552
|
```ts
|
|
@@ -5456,7 +5619,7 @@ const lib = ai.skills({
|
|
|
5456
5619
|
### Sources — `SkillSource` (discriminated by `type`, never `kind`)
|
|
5457
5620
|
|
|
5458
5621
|
- `{ type: "directory", path }` — reads `path/<folder>/SKILL.md` off disk (lazy `node:fs/promises`).
|
|
5459
|
-
- `{ type: "url", url, headers? }` — `
|
|
5622
|
+
- `{ type: "url", url, headers?, policy?, cacheTtlMs? }` — `urlSource(url, options)` fetches a JSON manifest of skills through the shared `guardedFetch` / `OutboundPolicy` guard (scheme/host allowlist, post-DNS private-IP deny, byte cap, timeout, per-hop redirect revalidation) — never a raw `fetch()`. A remote skill source is a prompt supply chain (bodies flow straight into model context), so every fetched record is also runtime-validated before it can be served. `policy` tunes the guard (e.g. `hostAllowlist`); see [`@warlock.js/ai/secure-outbound-requests/SKILL.md`](@warlock.js/ai/secure-outbound-requests/SKILL.md). The result is cached for the source's lifetime, or `cacheTtlMs` when set.
|
|
5460
5623
|
- `{ type: "store", store }` — any `SkillsStoreContract`, e.g. `MockSkillsStore`.
|
|
5461
5624
|
|
|
5462
5625
|
Sources merge in order; a later source wins on a name collision.
|
|
@@ -5519,6 +5682,7 @@ The optional `analytics` sink fires `catalogued` / `loaded` / `used` / `saved` /
|
|
|
5519
5682
|
- [`@warlock.js/ai/write-system-prompt/SKILL.md`](@warlock.js/ai/write-system-prompt/SKILL.md) — static persona / instruction blocks (vs. dynamic loaded skills)
|
|
5520
5683
|
- [`@warlock.js/ai/use-ai-memory/SKILL.md`](@warlock.js/ai/use-ai-memory/SKILL.md) — the procedural memory tier `proceduralSkillStore` unifies with
|
|
5521
5684
|
- [`@warlock.js/ai/run-ai-agent/SKILL.md`](@warlock.js/ai/run-ai-agent/SKILL.md) — the agent the `skills` option attaches to
|
|
5685
|
+
- [`@warlock.js/ai/secure-outbound-requests/SKILL.md`](@warlock.js/ai/secure-outbound-requests/SKILL.md) — the `guardedFetch` / `OutboundPolicy` guard the `url` source's manifest fetch runs through
|
|
5522
5686
|
|
|
5523
5687
|
|
|
5524
5688
|
## write-system-prompt `@warlock.js/ai/write-system-prompt/SKILL.md`
|
package/llms.txt
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
- [ai-basics](@warlock.js/ai/ai-basics/SKILL.md): Start with @warlock.js/ai — provider-agnostic core for agents / tools / workflows / supervisors / orchestrators. 4-primitive ladder (agent → workflow → supervisor → orchestrator, all shipped) plus planner, memory, stores, DX helpers, and the optional @warlock.js/ai-panoptic observability sidecar. Every primitive returns {data, error, usage, report}. Triggers: `ai.agent`, `ai.tool`, `ai.workflow`, `ai.supervisor`, `ai.orchestrator`, `ai.planner`, `ai.memory`, `ai.systemPrompt`, `ExecuteResult`, `BaseReport`, `AIError`, `panoptic`; 'which AI primitive do I use', 'what is warlock ai', 'pick an AI skill', 'how do I observe / trace AI runs'; typical import `import { ai } from "@warlock.js/ai"`. Skip: agent details — `@warlock.js/ai/run-ai-agent/SKILL.md`; competing libs `langchain`, `llamaindex`, `ai` (Vercel SDK); raw `openai` / `@anthropic-ai/sdk`.
|
|
10
10
|
- [ai-dx-helpers](@warlock.js/ai/ai-dx-helpers/SKILL.md): Developer-experience helpers across @warlock.js/ai — ai.batch (fan-out an executable over a dataset w/ concurrency + per-item retry), ai.fallbackModel (ordered model failover), agent.eval + ai.eval scorers + Vitest matchers (registerAiMatchers / toRouteTo / toConverge / toPassStep / toOutputShape) + ai.mockRouter, SLO/cost budget contracts (ai.middleware.budget({contract}) + readBudgetFallbackSignal), supervisor-level middleware, ai.systemPrompt.fromFile, and auto-adapt executables in tools:[]. Triggers: `ai.batch`, `BatchResult`, `ai.fallbackModel`, `FallbackModelContract`, `agent.eval`, `ai.eval`, `EvalReport`, `EvalScorer`, `ai.eval.judge`, `registerAiMatchers`, `toRouteTo`, `toConverge`, `toPassStep`, `toOutputShape`, `ai.mockRouter`, `MockSDK`, `mockAgent`, `budget({contract})`, `BudgetContract`, `maxLatencyMs`, `onViolation`, `readBudgetFallbackSignal`, `supervisor middleware`, `systemPrompt.fromFile`; 'run an agent over a list', 'fail over to a backup model', 'evaluate / score an agent', 'SLO budget', 'test a supervisor without an LLM', 'prompt from a file'; typical import `import { ai } from "@warlock.js/ai"`. Skip: core agent lifecycle — `@warlock.js/ai/run-ai-agent/SKILL.md`; the budget/guardrail/semanticCache basics — `@warlock.js/ai/attach-ai-middleware/SKILL.md`; competing libs `promptfoo`, `langsmith`.
|
|
11
11
|
- [approve-tool-calls](@warlock.js/ai/approve-tool-calls/SKILL.md): Gate an agent's tool calls behind a human with `ai.human.approval(options)` (the `tool.before` approval-gate middleware) — ships in @warlock.js/ai core. Triggers: `ai.human.approval`, `humanApproval`, `HumanApprovalOptions`, `ApprovalRequest`, `ApprovalDecision`, `ApprovalHandler`, `InterruptPolicy`, `evaluatePolicy`, `ApprovalRejectedError`, `policy: { type: "allowlist" | "denylist" | "predicate" }`, decision `{ type: "approve" | "reject" | "edit" }`; 'human in the loop', 'approve a tool call before it runs', 'ask a human before the agent sends/charges/deletes', 'pause before a dangerous tool', 'let an operator edit the tool args', 'reject a tool call with a reason the model can self-correct from'. Typical import `import { ai } from "@warlock.js/ai"`. Skip: persisting the request and resuming hours later out-of-process — `@warlock.js/ai/durable-resume/SKILL.md`; the agent/middleware/tool primitives themselves — `@warlock.js/ai`.
|
|
12
|
-
- [attach-ai-middleware](@warlock.js/ai/attach-ai-middleware/SKILL.md): Wire agent middleware — ai.middleware.budget (token / USD caps + SLO/cost contract w/ maxLatencyMs + onViolation fallback), ai.middleware.guardrail (pre / post content checks), ai.middleware.semanticCache (exact + vector cache), supervisor-level middleware, plus authoring custom hooks (execute / trip / tool). Triggers: `ai.middleware.budget`, `ai.middleware.guardrail`, `ai.middleware.semanticCache`, `ai.middleware.compose`, `ai.middleware.forTool`, `AgentMiddleware`, `BudgetExceededError`, `GuardrailViolationError`, `BudgetContract`, `maxLatencyMs`, `onViolation`, `readBudgetFallbackSignal`, `supervisor middleware`; 'cap token cost', 'SLO budget', 'block pii in prompts', 'semantic cache before LLM', 'supervisor-level middleware', 'write custom hook'; typical import `import { ai } from "@warlock.js/ai"`. Skip: agent lifecycle — `@warlock.js/ai/run-ai-agent/SKILL.md`; cache drivers — `@warlock.js/ai/persist-ai-data/SKILL.md`; competing libs `langchain` callbacks.
|
|
12
|
+
- [attach-ai-middleware](@warlock.js/ai/attach-ai-middleware/SKILL.md): Wire agent middleware — ai.middleware.budget (token / USD caps + SLO/cost contract w/ maxLatencyMs + onViolation fallback), ai.middleware.guardrail (pre / post content checks), ai.middleware.semanticCache (exact + vector cache), supervisor-level middleware, plus authoring custom hooks (execute / trip / tool). Triggers: `ai.middleware.budget`, `ai.middleware.guardrail`, `ai.middleware.semanticCache`, `ai.middleware.compose`, `ai.middleware.forTool`, `AgentMiddleware`, `BudgetExceededError`, `GuardrailViolationError`, `BudgetContract`, `maxLatencyMs`, `onViolation`, `readBudgetFallbackSignal`, `supervisor middleware`, `SemanticCacheOptions`, `SemanticCacheScope`; 'cap token cost', 'SLO budget', 'block pii in prompts', 'semantic cache before LLM', 'supervisor-level middleware', 'write custom hook', 'isolate semantic cache per session/tenant'; 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.
|
|
13
13
|
- [define-ai-tool](@warlock.js/ai/define-ai-tool/SKILL.md): 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.
|
|
14
14
|
- [detect-and-redact-pii](@warlock.js/ai/detect-and-redact-pii/SKILL.md): Detect and redact PII (and run model-graded moderation) with @warlock.js/ai-guard detectors — `ai.guardrail.pii(...)` and the optional `ai.guardrail.moderation(...)` peer. Triggers: `ai.guardrail.pii`, `piiDetector`, `PiiDetectorOptions`, `PiiCategory`, `mask`, `{label}`, `dictionary`, `onMatch`, `ai.guardrail.moderation`, `openAiModeration`, `OpenAiModerationOptions`, `blockOn`, `omni-moderation-latest`; 'redact PII from model output', 'mask SSN / credit card / email / phone / IP', 'stop PII leaking into a tool call', 'scrub sensitive data', 'add OpenAI moderation', 'block violent / self-harm content'; typical import `import "@warlock.js/ai-guard"` (registers `ai.guardrail.pii` / `.moderation`) or `import { pii, moderation } from "@warlock.js/ai-guard"`. Skip: composing the guard / wiring it into an agent — `@warlock.js/ai-guard/guard-input-output/SKILL.md`; routing a block to a human — `@warlock.js/ai-guard/escalate-block-to-human/SKILL.md`.
|
|
15
15
|
- [durable-agent-runs](@warlock.js/ai/durable-agent-runs/SKILL.md): Mid-run crash-resume for agents AND planners — opt in with durable: { store, deleteOnComplete? } on the config, pass a stable runId to execute(), and call agent.resume(runId) / planner.resume(runId) after a crash to continue from the last settled trip / plan node. Reuses the ai.snapshot.{memory,pg,redis} stores; checkpoints per-trip (agent) / per-node (planner); completed trips + nodes never re-run their tools and usage is never double-counted; a drifted definition throws AgentDriftError / PlannerDriftError (bypass with { force: true }). Triggers: `durable`, `agent.resume`, `planner.resume`, `resume(runId)`, `runId`, `AgentSnapshot`, `PlannerSnapshot`, `AgentSnapshotStatus`, `PlannerSnapshotStatus`, `AgentDriftError`, `PlannerDriftError`, `computeAgentSignature`, `agent.signature`, `deleteOnComplete`, `defaultSnapshotStore`, `ai.snapshot.pg`, `ai.snapshot.memory`, `SnapshotStore`, `force: true`; 'resume an agent after a crash', 'durable agent run', 'continue a planner from where it crashed', 'checkpoint agent state', 'idempotent tool re-run on resume', 'signature drift on resume'; typical import `import { ai } from "@warlock.js/ai"`. Skip: durable human-in-the-loop approval resume (ai.human.resume of a PendingInterrupt) — `@warlock.js/ai/durable-resume/SKILL.md`; supervisor/workflow iterate-mid-turn snapshot resume + the store contracts themselves — `@warlock.js/ai/manage-ai-stores/SKILL.md`; competing libs `temporal`, `inngest`, `restate`.
|
|
@@ -35,9 +35,10 @@
|
|
|
35
35
|
- [run-ai-team](@warlock.js/ai/run-ai-team/SKILL.md): Manager-led multi-agent teams with ai.team({...}) — transparent sugar over ai.supervisor that maps a manager → route/router, members → intents, and a gate → evaluate, returning a REAL SupervisorContract (no new loop, no new contract). Covers the built-in gate strings "quality" (review-then-fix) and "verify" (test-then-fix), a custom gate function, role mapping (roles / gateKey), and the verbatim supervisor pass-throughs (goal / output / state / maxIterations / snapshotStore / on / observe). Triggers: `ai.team`, `TeamConfig`, `TeamGate`, `TeamGateFn`, `TeamMemberValue`, `manager`, `members`, `gate`, `roles`, `gateKey`, `buildQualityGate`, `buildVerifyGate`, `SupervisorContract`, `ReportType`; 'build a team of agents', 'manager that delegates to members', 'review then fix loop', 'test then fix loop', 'quality gate for a multi-agent run', 'report type team'; typical import `import { ai } from "@warlock.js/ai"`. Skip: routing one input to a fixed roster directly — `@warlock.js/ai/run-supervisor/SKILL.md` (team is sugar over it); durable cross-turn sessions — `@warlock.js/ai/run-orchestrator/SKILL.md`; LLM-generated plans — `@warlock.js/ai/run-planner/SKILL.md`; competing libs `crewai`, `autogen`.
|
|
36
36
|
- [run-ai-workflow](@warlock.js/ai/run-ai-workflow/SKILL.md): 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`.
|
|
37
37
|
- [run-orchestrator](@warlock.js/ai/run-orchestrator/SKILL.md): Durable stateful sessions with ai.orchestrator({...}) — the capstone of the 4-primitive ladder. Wraps a supervisor with cross-turn session state (checkpointStore), per-turn windowing, drift detection, post-turn compaction, mid-turn resume (iterate: true + snapshotStore), per-turn memory, typed commands, asTool, and a 3-tier event model. Triggers: `ai.orchestrator`, `orchestrator.execute`, `orchestrator.resume`, `orchestrator.command`, `orchestrator.stream`, `OrchestratorConfig`, `OrchestratorResult`, `OrchestratorReport`, `OrchestratorContract`, `CheckpointStore`, `OrchestratorDriftError`, `sessionId`, `iterate`, `historyWindow`, `summarize`, `keepSnapshots`, `awaiting-input`, `turns[]`, `TurnSnapshot`, `CompactionResult`, `initialAgent`, `checkpointStore`; 'multi-turn conversation that persists', 'durable session across calls', 'resume an interrupted turn', 'compact session history', 'per-session memory'; typical import `import { ai } from "@warlock.js/ai"`. Skip: a single routing turn with no session — `@warlock.js/ai/run-supervisor/SKILL.md`; a fixed pipeline — `@warlock.js/ai/run-ai-workflow/SKILL.md`; the store factories themselves — `@warlock.js/ai/manage-ai-stores/SKILL.md`; competing libs `langgraph`, `crewai`.
|
|
38
|
-
- [run-planner](@warlock.js/ai/run-planner/SKILL.md): Goal-driven planning with ai.planner({...}) — an LLM GENERATES an ordered execution plan over your registered capabilities (agents / workflows / supervisors / tools), then the planner EXECUTES it, threading each step output into the next, and returns the unified {data, report, usage, error} envelope with report.type "planner". Supports DAG scheduling (dag:true + maxConcurrency off dependsOn), adaptive re-planning (replan:{maxReplans} + the onStep continue/abort/replan directive), and plan-only / approval (mode:"plan-only" → status "awaiting-approval" → approvedPlan). A plan step may delegate via ai.spawnSubAgent({...}) — a GENERAL one-shot-agent helper covered in `@warlock.js/ai/run-ai-agent/SKILL.md`; it is not planner-specific. Triggers: `ai.planner`, `planner.execute`, `spawnSubAgent`, `PlannerConfig`, `PlannerCapability`, `PlannerResult`, `PlannerReport`, `PlannerPlan`, `PlannerStep`, `PlannerStepDirective`, `PlannerPlanInvalidError`, `maxSteps`, `dag`, `maxConcurrency`, `dependsOn`, `replan`, `onStep`, `mode`, `approvedPlan`, `awaiting-approval`, `report.plan`, `report.executedSteps`; 'let the model plan the steps', 'dynamic plan from a goal', 'run independent steps in parallel', 're-plan when a step fails', 'generate a plan for approval before running it'; typical import `import { ai } from "@warlock.js/ai"`. Skip: a FIXED known pipeline — `@warlock.js/ai/run-ai-workflow/SKILL.md`; routing one input to a specialist each turn — `@warlock.js/ai/run-supervisor/SKILL.md`; a single model + tools call — `@warlock.js/ai/run-ai-agent/SKILL.md`; competing libs `langgraph`, `crewai`.
|
|
38
|
+
- [run-planner](@warlock.js/ai/run-planner/SKILL.md): Goal-driven planning with ai.planner({...}) — an LLM GENERATES an ordered execution plan over your registered capabilities (agents / workflows / supervisors / tools), then the planner EXECUTES it, threading each step output into the next, and returns the unified {data, report, usage, error} envelope with report.type "planner". Supports DAG scheduling (dag:true + maxConcurrency off dependsOn), adaptive re-planning (replan:{maxReplans} + the onStep continue/abort/replan directive), and plan-only / approval (mode:"plan-only" → status "awaiting-approval" → approvedPlan). A plan step may delegate via ai.spawnSubAgent({...}) — a GENERAL one-shot-agent helper covered in `@warlock.js/ai/run-ai-agent/SKILL.md`; it is not planner-specific. Triggers: `ai.planner`, `planner.execute`, `spawnSubAgent`, `PlannerConfig`, `PlannerCapability`, `PlannerResult`, `PlannerReport`, `PlannerPlan`, `PlannerStep`, `PlannerStepDirective`, `PlannerPlanInvalidError`, `maxSteps`, `dag`, `maxConcurrency`, `dependsOn`, `replan`, `onStep`, `mode`, `approvedPlan`, `awaiting-approval`, `report.plan`, `report.executedSteps`, `parsedStepCeiling`; 'let the model plan the steps', 'dynamic plan from a goal', 'run independent steps in parallel', 're-plan when a step fails', 'generate a plan for approval before running it'; typical import `import { ai } from "@warlock.js/ai"`. Skip: a FIXED known pipeline — `@warlock.js/ai/run-ai-workflow/SKILL.md`; routing one input to a specialist each turn — `@warlock.js/ai/run-supervisor/SKILL.md`; a single model + tools call — `@warlock.js/ai/run-ai-agent/SKILL.md`; competing libs `langgraph`, `crewai`.
|
|
39
39
|
- [run-supervisor](@warlock.js/ai/run-supervisor/SKILL.md): Multi-intent routing with ai.supervisor({...}) — classifier (iter-0 dispatch), router agent OR route callback, intents as agents / workflows / callbacks, fan-out, evaluate quality loop, ack receptionist, supervisor-level middleware. A callback that calls agent.execute() directly auto-nests agent → tool under the callback span (ambient RunFrame) with usage / cost rolled up — same for team members and orchestrator turns. Triggers: `ai.supervisor`, `ai.router`, `ai.fanOut`, `supervisor.execute`, `supervisor.resume`, `intents`, `router`, `route`, `classifier`, `evaluate`, `ack`, `artifactsSchema`, `middleware`, `END`, `ctx.intents.X.execute`, `ctx.run`, `RunFrame`, `callback span`, `children`, `parentRunId`, `rootRunId`, `trace nesting`, `sub-agent`; 'route one input across specialists', 'multi-intent dispatch', 'fan-out then evaluate', 'classifier then router', 'supervisor middleware', 'self-consistency / voting', 'why is my callback agent not nested / cost is $0', 'nest a sub-agent under a callback'; typical import `import { ai } from "@warlock.js/ai"`. Skip: durable multi-turn sessions — `@warlock.js/ai/run-orchestrator/SKILL.md`; fixed pipelines — `@warlock.js/ai/run-ai-workflow/SKILL.md`; single agent — `@warlock.js/ai/run-ai-agent/SKILL.md`; competing libs `langgraph`, `crewai`.
|
|
40
|
+
- [secure-outbound-requests](@warlock.js/ai/secure-outbound-requests/SKILL.md): The shared SSRF / resource-exhaustion guard every server-side outbound HTTP request in the framework goes through — `guardedFetch(url, policy, init?)`, `OutboundPolicy`, `assertUrlAllowed`, `fetchTextWithPolicy`, `readTextCapped`. Scheme allowlist (https-only default), host allowlist, post-DNS private/loopback/link-local/metadata-address deny, byte cap, timeout, and (4.15.0) per-hop redirect revalidation with a `maxRedirects` cap and cross-origin credential stripping. Consumed by `ai.rag.loadWeb`, remote text attachments (`prepareAttachmentPart`), and the skills `urlSource` manifest fetch — never a raw `fetch()` on a caller-influenced URL. Triggers: `guardedFetch`, `OutboundPolicy`, `ResolvedOutboundPolicy`, `assertUrlAllowed`, `fetchTextWithPolicy`, `readTextCapped`, `resolveOutboundPolicy`, `OutboundPolicyError`, `maxRedirects`, `denyPrivateIPsAfterDNS`, `hostAllowlist`, `allowedSchemes`, `maxBytes`, `SSRF`, `redirect: "manual"`, `redirect: "error"`; 'SSRF-safe fetch', 'block a redirect into a private IP', 'fetch a URL an agent gave me', 'cap outbound response size', 'allowlist hosts for outbound requests', 'strip auth headers on a cross-origin redirect'; typical import `import { guardedFetch, assertUrlAllowed } from "@warlock.js/ai"` (also re-exported per call site). Skip: the RAG loader that wraps this for `loadWeb` — `@warlock.js/ai/rag-loaders-and-stores/SKILL.md`; the skills manifest source that wraps this for `urlSource` — `@warlock.js/ai/use-runtime-skills/SKILL.md`; prompt-injection / content guardrails (a different trust boundary) — `@warlock.js/ai/guard-input-output/SKILL.md` (ai-guard package).
|
|
40
41
|
- [transcribe-audio](@warlock.js/ai/transcribe-audio/SKILL.md): Speech-to-text via ai.transcribe({ model: sdk.transcribe({ name }), audio }) — the audio-INPUT verb (Theme I), returning the uniform never-throws { data, error, usage, report } envelope with cost-truth + panoptic observation. Feed it an AudioInput = { base64; mediaType; filename? } — build one with ai.audioFromFile(path) (reads disk, infers media type incl. WhatsApp .ogg/.opus) or ai.audioFromBuffer(bytes, mediaType). Models: OpenAI whisper-1 (verbose_json, per-minute, segments + durationSeconds) or gpt-4o-transcribe (json, per-token). Triggers: `ai.transcribe`, `ai.audioFromFile`, `ai.audioFromBuffer`, `sdk.transcribe`, `openai.transcribe`, `TranscriptionModelContract`, `AudioInput`, `TranscriptionSegment`, `MockTranscriptionModel`; 'speech to text', 'transcribe audio', 'voice note to text', 'WhatsApp voice message', 'whisper', 'gpt-4o-transcribe', 'subtitle segments', 'audio input'; typical import `import { ai } from "@warlock.js/ai"` + `import { OpenAISDK } from "@warlock.js/ai-openai"`. Skip: text-to-speech / synthesizing a voice — [[generate-speech]]; competing libs raw `openai.audio.transcriptions.create`, `whisper.cpp`.
|
|
41
|
-
- [use-ai-memory](@warlock.js/ai/use-ai-memory/SKILL.md): Agent memory with ai.memory({...}) — a provider-neutral store with FOUR tiers: WORKING (in-run scratch, recalled by recency), SEMANTIC (durable facts by cosine similarity over a @warlock.js/cache vector driver via .similar()), EPISODIC (durable events, similarity blended with recency), and PROCEDURAL (durable how-tos, similarity blended with reinforcement). remember() / recall() / clear(); wire it into ai.orchestrator({ memory }). Triggers: `ai.memory`, `memory.remember`, `memory.recall`, `memory.clear`, `MemoryContract`, `MemoryConfig`, `MemoryItem`, `RecalledMemory`, `MemoryTier`, `SemanticMemoryConfig`, `EpisodicMemoryConfig`, `ProceduralMemoryConfig`, `working`, `semantic`, `episodic`, `procedural`, `defaultTier`, `threshold`, `recencyWeight`, `halfLifeMs`, `reinforcementWeight`, `injectKey`; 'give the agent memory', 'remember user preferences', 'semantic recall', 'per-session working memory', 'episodic / event memory', 'procedural / how-to memory', 'recency-weighted recall', 'reinforce a procedure'; typical import `import { ai } from "@warlock.js/ai"`. Skip: orchestrator wiring of the memory — `@warlock.js/ai/run-orchestrator/SKILL.md`; the vector cache driver itself — `@warlock.js/cache/cache-basics/SKILL.md`; embeddings primitive — `@warlock.js/ai/embed-text/SKILL.md`; competing libs `mem0`, `langchain` memory.
|
|
42
|
+
- [use-ai-memory](@warlock.js/ai/use-ai-memory/SKILL.md): Agent memory with ai.memory({...}) — a provider-neutral store with FOUR tiers: WORKING (in-run scratch, recalled by recency), SEMANTIC (durable facts by cosine similarity over a @warlock.js/cache vector driver via .similar()), EPISODIC (durable events, similarity blended with recency), and PROCEDURAL (durable how-tos, similarity blended with reinforcement). remember() / recall() / clear(); wire it into ai.orchestrator({ memory }). Triggers: `ai.memory`, `memory.remember`, `memory.recall`, `memory.clear`, `MemoryContract`, `MemoryConfig`, `MemoryItem`, `RecalledMemory`, `MemoryTier`, `SemanticMemoryConfig`, `EpisodicMemoryConfig`, `ProceduralMemoryConfig`, `working`, `semantic`, `episodic`, `procedural`, `defaultTier`, `threshold`, `recencyWeight`, `halfLifeMs`, `reinforcementWeight`, `injectKey`, `maxItems`, `scope`, `RecallOptions.scope`; 'give the agent memory', 'remember user preferences', 'semantic recall', 'per-session working memory', 'episodic / event memory', 'procedural / how-to memory', 'recency-weighted recall', 'reinforce a procedure', 'cap working memory size', 'isolate memory per session/tenant'; typical import `import { ai } from "@warlock.js/ai"`. Skip: orchestrator wiring of the memory — `@warlock.js/ai/run-orchestrator/SKILL.md`; the vector cache driver itself — `@warlock.js/cache/cache-basics/SKILL.md`; embeddings primitive — `@warlock.js/ai/embed-text/SKILL.md`; competing libs `mem0`, `langchain` memory.
|
|
42
43
|
- [use-runtime-skills](@warlock.js/ai/use-runtime-skills/SKILL.md): Progressive-disclosure agent skills with ai.skills({...}) and the first-class `skills` option on ai.agent — an always-injected cheap metadata catalog plus an on-demand loadSkill tool, backed by directory / url / store sources. Covers inject ("all" | {select:"semantic",topK,embedder}), maxLoadsPerRun, scope tags, the MockSkillsStore, semantic preload, and the inert-by-default Phase-2 self-authoring (saveSkill + default-DENY review gate → promote). Triggers: `ai.skills`, `SkillsConfig`, `SkillsContract`, `SkillSource`, `SkillInjectMode`, `SkillRecord`, `SkillCatalogEntry`, `loadSkill`, `loadSkillTool`, `saveSkill`, `saveSkillTool`, `SkillReviewGate`, `runReviewGate`, `MockSkillsStore`, `proceduralSkillStore`, `maxLoadsPerRun`, `inject`, `scope`, `review`, the agent `skills:` option; 'give an agent loadable skills', 'progressive disclosure of instructions', 'catalog of skills the model pulls on demand', 'semantic preload of skill bodies', 'let an agent author and review a skill'; typical import `import { ai } from "@warlock.js/ai"`. Skip: composing static system prompts — `@warlock.js/ai/write-system-prompt/SKILL.md`; durable agent memory tiers — `@warlock.js/ai/use-ai-memory/SKILL.md`; defining callable tools — `@warlock.js/ai/define-ai-tool/SKILL.md`.
|
|
43
44
|
- [write-system-prompt](@warlock.js/ai/write-system-prompt/SKILL.md): Compose system prompts via ai.systemPrompt() / ai.persona() / ai.instruction() — immutable builders with {{placeholder}} substitution, plus ai.systemPrompt.fromFile(path) to seed from a file read once at construction. Carry identity with .meta({ name, version, description, required }) (a name auto-registers in ai.prompts) and compose with merge(...blocks) / merge(contract) / merge(name, { fromVersion }) (provenance in meta.composedFrom). Triggers: `ai.systemPrompt`, `ai.systemPrompt.fromFile`, `ai.persona`, `ai.instruction`, `SystemPromptBlockContract`, `SystemPromptContract`, `SystemPromptMeta`, `SystemPromptMergeOptions`, `PersonaContract`, `InstructionContract`, `meta`, `merge`, `composedFrom`, `fromVersion`, `placeholders`, `{{placeholder|default}}`, `InvalidRequestError`; 'write a system prompt', 'compose persona + instructions', 'prompt from a file', 'name and version a prompt', 'merge prompts together', 'per-call prompt override', 'mustache placeholder'; typical import `import { ai } from "@warlock.js/ai"`. Skip: the named/versioned prompt registry (register / resolve / tag / diff / export / validate) — `@warlock.js/ai/manage-prompts/SKILL.md`; agent factory wiring — `@warlock.js/ai/run-ai-agent/SKILL.md`; competing libs `langchain` `PromptTemplate`, raw f-strings.
|
package/package.json
CHANGED
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
"@standard-schema/spec": "^1.0.0"
|
|
16
16
|
},
|
|
17
17
|
"peerDependencies": {
|
|
18
|
-
"@warlock.js/ai-openai": "
|
|
19
|
-
"@warlock.js/cache": "
|
|
20
|
-
"@warlock.js/logger": "
|
|
18
|
+
"@warlock.js/ai-openai": "5.0.0",
|
|
19
|
+
"@warlock.js/cache": "5.0.0",
|
|
20
|
+
"@warlock.js/logger": "5.0.0",
|
|
21
21
|
"langfuse": "*",
|
|
22
22
|
"openai": "*",
|
|
23
23
|
"pdf-parse": "*",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"optional": true
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
|
-
"version": "
|
|
47
|
+
"version": "5.0.0",
|
|
48
48
|
"main": "./cjs/index.cjs",
|
|
49
49
|
"module": "./esm/index.mjs",
|
|
50
50
|
"types": "./esm/index.d.mts",
|
package/skills/README.md
CHANGED
|
@@ -102,7 +102,11 @@ Goal-driven planning with ai.planner({...}) — an LLM GENERATES an ordered plan
|
|
|
102
102
|
|
|
103
103
|
### [`run-supervisor/`](./run-supervisor/SKILL.md)
|
|
104
104
|
|
|
105
|
-
Multi-intent routing with ai.supervisor({...}) — classifier (iter-0 dispatch), router agent OR route callback (iter 1+), intents as agents / workflows / callbacks, fan-out, evaluate quality loop, ack receptionist, ctx.intents.X.execute composition, and sub-agent trace nesting (a callback that calls agent.execute() directly auto-nests agent → tool under the callback span with cost rolled up — same for team members + orchestrator turns). Load when routing one user input across a fixed roster of specialists, or when a callback sub-agent shows as a lone $0 span instead of nesting.
|
|
105
|
+
Multi-intent routing with ai.supervisor({...}) — classifier (iter-0 dispatch), router agent OR route callback (iter 1+), intents as agents / workflows / callbacks, fan-out (+ the maxFanOut width cap), evaluate quality loop, ack receptionist, ctx.intents.X.execute composition, and sub-agent trace nesting (a callback that calls agent.execute() directly auto-nests agent → tool under the callback span with cost rolled up — same for team members + orchestrator turns). Load when routing one user input across a fixed roster of specialists, bounding how wide a router can fan out, or when a callback sub-agent shows as a lone $0 span instead of nesting.
|
|
106
|
+
|
|
107
|
+
### [`secure-outbound-requests/`](./secure-outbound-requests/SKILL.md)
|
|
108
|
+
|
|
109
|
+
The shared SSRF / resource-exhaustion guard — guardedFetch(url, policy, init?) + OutboundPolicy: scheme/host allowlist, post-DNS private/loopback/link-local/metadata-address deny, byte cap, timeout, and per-hop redirect revalidation (maxRedirects, default 5) with cross-origin credential stripping. Consumed by ai.rag.loadWeb, remote text attachments, and the skills urlSource manifest fetch. Load when fetching a URL an agent or user supplied, allowlisting outbound hosts, or investigating an OutboundPolicyError.
|
|
106
110
|
|
|
107
111
|
### [`use-ai-memory/`](./use-ai-memory/SKILL.md)
|
|
108
112
|
|