JuliusBrussee/caveman · error
maxLoadedTools must be at least the alwaysLoad tool count
Error message
maxLoadedTools must be at least the alwaysLoad tool count
What it means
When the catalog contains `alwaysLoad` tools, `maxLoadedTools` must be large enough to hold all of them; otherwise the invariant that always-load tools are always resident cannot be honored. The library refuses the configuration rather than silently dropping always-load tools.
Solutions
- Raise maxLoadedTools to at least the alwaysLoad tool count
- Remove alwaysLoad flags from tools that can be lazily loaded
- Filter the catalog to fewer alwaysLoad tools before passing it
Example fix
// before
cave.tools({ catalog, maxLoadedTools: 3 }); // catalog has 5 alwaysLoad tools
// after
const alwaysCount = catalog.filter(t => t.alwaysLoad).length;
cave.tools({ catalog, maxLoadedTools: Math.max(3, alwaysCount) }); Defensive patterns
Strategy: validation
Validate before calling
const alwaysCount = catalog.filter(t => t.alwaysLoad).length;
if (cfg.maxLoadedTools !== undefined && cfg.maxLoadedTools < alwaysCount) {
cfg.maxLoadedTools = alwaysCount;
} Type guard
const canFitAlwaysLoad = (max: number, catalog: {alwaysLoad?: boolean}[]): boolean =>
catalog.filter(t => t.alwaysLoad).length <= max; Try / catch
try {
return cave.tools({ catalog, maxLoadedTools });
} catch (e) {
if (e.message.includes("at least the alwaysLoad tool count")) {
return cave.tools({ catalog, maxLoadedTools: catalog.filter(t => t.alwaysLoad).length });
}
throw e;
} Prevention
- Compute maxLoadedTools from the alwaysLoad count dynamically
- Re-check the budget whenever adding alwaysLoad tools to the catalog
- Write a unit test asserting config validity for each catalog
When it happens
Trigger: Calling `tools()` where `catalog.filter(t => t.alwaysLoad).length > maxLoadedTools`, e.g. 5 alwaysLoad tools with maxLoadedTools: 3 (and strategy is not "all").
Common situations: Growing the set of alwaysLoad tools after setting a tight budget; lowering maxLoadedTools for cost control without re-counting alwaysLoad entries.
Understand the failure class
Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.
Related errors
- maxLoadedTools must be a positive integer
- cave_mastra_max_steps_invalid
- caveman agent: context id is required
- caveman agent: memory recallBudget must be a non-negative…
- invalid_deadline
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/9e33008b943e79e9.
Report an issue: GitHub.
Appendix: source
Thrown at packages/sdk/typescript/src/index.ts:486
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 === undefined
? initialToolCount
: Math.min(initialToolCount, config.maxLoadedTools - always.length);
initial = always.concat(lazy.slice(0, lazyLimit));
}
const cave = this;
return {
strategy,
initial,
/**
* Search the full catalog via the gateway's /sdk/v1/tool-search endpoint.
* Returns a ToolSearchResult with the reduced tool list and token counts.
*
* @param query The user's current intent / task description.
* @param options.maxTools Optional cap on returned tools.View on GitHub (pinned to 3ee70a1026)