{"record":{"id":"9e33008b943e79e9","repo":"JuliusBrussee/caveman","slug":"maxloadedtools-must-be-at-least-the-alwaysload-too","errorCode":null,"errorMessage":"maxLoadedTools must be at least the alwaysLoad tool count","messagePattern":"maxLoadedTools must be at least the alwaysLoad tool count","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/sdk/typescript/src/index.ts","lineNumber":486,"sourceCode":"  tools(config: { catalog: CaveTool[]; strategy?: \"all\" | \"deferred\"; initialToolCount?: number; maxLoadedTools?: number }) {\n    const strategy = config.strategy ?? \"all\";\n    if (strategy !== \"all\" && strategy !== \"deferred\") {\n      throw new Error(\"strategy must be all or deferred\");\n    }\n    const initialToolCount = config.initialToolCount ?? 8;\n    if (!Number.isSafeInteger(initialToolCount) || initialToolCount < 0) {\n      throw new Error(\"initialToolCount must be a non-negative integer\");\n    }\n    if (config.maxLoadedTools !== undefined && (!Number.isSafeInteger(config.maxLoadedTools) || config.maxLoadedTools < 1)) {\n      throw new Error(\"maxLoadedTools must be a positive integer\");\n    }\n    let initial: CaveTool[];\n    if (strategy === \"all\") {\n      initial = config.catalog.slice();\n    } else {\n      const always = config.catalog.filter((tool) => tool.alwaysLoad);\n      if (config.maxLoadedTools !== undefined && always.length > config.maxLoadedTools) {\n        throw new Error(\"maxLoadedTools must be at least the alwaysLoad tool count\");\n      }\n      const lazy = config.catalog.filter((tool) => !tool.alwaysLoad);\n      const lazyLimit = config.maxLoadedTools === undefined\n        ? initialToolCount\n        : Math.min(initialToolCount, config.maxLoadedTools - always.length);\n      initial = always.concat(lazy.slice(0, lazyLimit));\n    }\n    const cave = this;\n\n    return {\n      strategy,\n      initial,\n      /**\n       * Search the full catalog via the gateway's /sdk/v1/tool-search endpoint.\n       * Returns a ToolSearchResult with the reduced tool list and token counts.\n       *\n       * @param query   The user's current intent / task description.\n       * @param options.maxTools Optional cap on returned tools.","sourceCodeStart":468,"sourceCodeEnd":504,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/sdk/typescript/src/index.ts#L468-L504","documentation":"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.","triggerScenarios":"Calling `tools()` where `catalog.filter(t => t.alwaysLoad).length > maxLoadedTools`, e.g. 5 alwaysLoad tools with maxLoadedTools: 3 (and strategy is not \"all\").","commonSituations":"Growing the set of alwaysLoad tools after setting a tight budget; lowering maxLoadedTools for cost control without re-counting alwaysLoad entries.","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"],"exampleFix":"// before\ncave.tools({ catalog, maxLoadedTools: 3 }); // catalog has 5 alwaysLoad tools\n// after\nconst alwaysCount = catalog.filter(t => t.alwaysLoad).length;\ncave.tools({ catalog, maxLoadedTools: Math.max(3, alwaysCount) });","handlingStrategy":"validation","validationCode":"const alwaysCount = catalog.filter(t => t.alwaysLoad).length;\nif (cfg.maxLoadedTools !== undefined && cfg.maxLoadedTools < alwaysCount) {\n  cfg.maxLoadedTools = alwaysCount;\n}","typeGuard":"const canFitAlwaysLoad = (max: number, catalog: {alwaysLoad?: boolean}[]): boolean =>\n  catalog.filter(t => t.alwaysLoad).length <= max;","tryCatchPattern":"try {\n  return cave.tools({ catalog, maxLoadedTools });\n} catch (e) {\n  if (e.message.includes(\"at least the alwaysLoad tool count\")) {\n    return cave.tools({ catalog, maxLoadedTools: catalog.filter(t => t.alwaysLoad).length });\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["typescript","configuration","tool-loading","validation"],"backgroundTag":"conflicting-config-options","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}