JuliusBrussee/caveman · error
strategy must be all or deferred
Error message
strategy must be all or deferred
What it means
The tools() descriptor validates the optional strategy option, which must be exactly "all" (load all tools up front) or "deferred" (lazy tool loading). Any other string — including older strategy names from the 1.0 API — is rejected. The strategy controls the deferred-loading behavior introduced in the 2.0 breaking change, so unknown values cannot be safely interpreted.
Solutions
- Pass strategy: "all" or strategy: "deferred" exactly, or omit it to default to "all".
- Type the value as `"all" | "deferred"` in your config so invalid values fail at compile time.
- If migrating from 1.0, review the 2.0 changelog for renamed options and update call sites.
Example fix
// before
cave.tools({ catalog, strategy: "lazy" });
// after
cave.tools({ catalog, strategy: "deferred" }); Defensive patterns
Strategy: validation
Validate before calling
const isToolStrategy = (v: unknown): v is "all" | "deferred" => v === "all" || v === "deferred";
if (config.strategy !== undefined && !isToolStrategy(config.strategy)) throw new Error(`strategy must be 'all' or 'deferred', got ${JSON.stringify(config.strategy)}`); Type guard
const isToolStrategy = (v: unknown): v is "all" | "deferred" => typeof v === "string" && (v === "all" || v === "deferred");
Try / catch
try {
const desc = cave.tools({ catalog, strategy });
} catch (e) {
if (e instanceof Error && e.message.includes("strategy must be all or deferred")) {
const desc = cave.tools({ catalog }); // default strategy "all"
} else throw e;
} Prevention
- Use only "all" or "deferred"; omit the option for the default.
- Type strategy as the union `"all" | "deferred"`, never plain string.
- When migrating from the 1.0 API, re-check option names and allowed values against 2.0 docs.
When it happens
Trigger: cave.tools({ catalog, strategy: "lazy" }) or strategy: "none" or a strategy value read from config/env as free text; upgrading from 1.0 and passing a strategy value that no longer exists.
Common situations: Typos like "deffered"; using pre-2.0 strategy names after the breaking change (the doc note says search() became async in the same release); strategies chosen conditionally with string building instead of the union type.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- bedrock endpoint must be runtime or mantle
- caveman agent: unknown tool result policy
- initialToolCount must be a non-negative integer
- apiKey, baseURL, and agent are required
- ASGI context resolver or request bounds are invalid
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/1686b2cdcde9f73a.
Report an issue: GitHub.
Appendix: source
Thrown at packages/sdk/typescript/src/index.ts:471
*/
exporter(config: { serviceName?: string } = {}): OTelExporter {
return new OTelExporter(this, config.serviceName ?? this.options.agent);
}
/**
* Build a tool-catalog handle with server-side search via /sdk/v1/tool-search.
*
* strategy "all" → initial = whole catalog; search() still calls the server.
* strategy "deferred" → initial = alwaysLoad tools (subset sent on first turn);
* search() calls the server to load relevant tools on demand.
*
* Breaking change from 1.0: search() is now async and returns ToolSearchResult
* (previously sync, returning CaveTool[]). Callers must await search().
*/
tools(config: { catalog: CaveTool[]; strategy?: "all" | "deferred"; initialToolCount?: number; maxLoadedTools?: number }) {
const strategy = config.strategy ?? "all";
if (strategy !== "all" && strategy !== "deferred") {
throw new Error("strategy must be all or deferred");
}
const initialToolCount = config.initialToolCount ?? 8;
if (!Number.isSafeInteger(initialToolCount) || initialToolCount < 0) {
throw new Error("initialToolCount must be a non-negative integer");
}
if (config.maxLoadedTools !== undefined && (!Number.isSafeInteger(config.maxLoadedTools) || config.maxLoadedTools < 1)) {
throw new Error("maxLoadedTools must be a positive integer");
}
let initial: CaveTool[];
if (strategy === "all") {
initial = config.catalog.slice();
} else {
const always = config.catalog.filter((tool) => tool.alwaysLoad);
if (config.maxLoadedTools !== undefined && always.length > config.maxLoadedTools) {
throw new Error("maxLoadedTools must be at least the alwaysLoad tool count");
}
const lazy = config.catalog.filter((tool) => !tool.alwaysLoad);
const lazyLimit = config.maxLoadedTools === undefinedView on GitHub (pinned to 3ee70a1026)