{"record":{"id":"f8d64561eb69e773","repo":"JuliusBrussee/caveman","slug":"cave-cache-volatile-stable-slot","errorCode":"cave_cache_volatile_stable_slot","errorMessage":"cave_cache_volatile_stable_slot","messagePattern":"cave_cache_volatile_stable_slot","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/runtime.ts","lineNumber":1405,"sourceCode":"      provider: routedModel.provider,\n      model: routedModel.id,\n      api: routedModel.api,\n      prefixDigest,\n      buildIdentity: buildIdentity ?? null,\n      lockedPlanSHA256: buildIdentity?.planSha256 ?? null,\n      executionPlanSHA256: efficiencyPlan === undefined\n        ? null\n        : sha256(stableStringify(efficiencyPlan)),\n    }));\n    if (conversation?.fingerprint !== undefined &&\n        conversation.fingerprint !== conversationFingerprint) {\n      conversation.cachePrefixDigest = undefined;\n      conversation.providerFrozen = undefined;\n      conversation.originalFrozen = undefined;\n    }\n    if (conversation) conversation.fingerprint = conversationFingerprint;\n    if (efficiencyPlan?.segment_routes.length && volatileStablePrefix(instructions, definition.tools)) {\n      throw new Error(\"cave_cache_volatile_stable_slot\");\n    }\n    // Every x-cave-* header exists for the Caveman gateway: x-cave-api-key is\n    // an account credential, and agent/workflow/session/cache-epoch/prefix\n    // digest/context bill/build+plan digests are account-linked identifiers and\n    // internal telemetry. A request that does not go through the gateway goes to\n    // a third party, so it carries none of them.\n    const headers = gatewayActive\n      ? runtimeHeaders(\n        definition.id,\n        options.workflow ?? definition.id,\n        sessionId,\n        buildIdentity,\n        bill,\n        appliedPlan.appliedTransformIDs,\n        prefixDigest,\n        conversationFingerprint,\n        executionContext.invocationState.batch?.apiKey,\n        executionContext.invocationState.batch === undefined ? undefined : executionContext.invocationTrace,","sourceCodeStart":1387,"sourceCodeEnd":1423,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/runtime.ts#L1387-L1423","documentation":"Cache-safety guard for plans with segment routing: thrown when an efficiency plan has non-empty segment_routes but the stable prefix slot of the run — instructions and tool definitions — is volatile (volatileStablePrefix(instructions, definition.tools) is true). Prompt-cache stability depends on the leading segments being byte-stable across calls; if dynamic content (timestamps, per-request data) occupies the stable slot, cache digests and any warm-prefix accounting would be lies, so the run refuses.","triggerScenarios":"Running with a locked build / candidate plan whose segment_routes route later segments, while the agent's instructions string or a tool description embeds per-run dynamic content (Date.now(), request ids, user text) in the leading stable position.","commonSituations":"Templating a timestamp or the current user's name directly into the system prompt; putting volatile values into the first tool's description; reusing a plan locked against a different (stable) prompt shape after the prompt was made dynamic.","solutions":["Move dynamic content out of the stable prefix: put it in the run input (a later, routable segment) instead of instructions","Make tool definitions static: move per-run parameters out of tool names/descriptions and into tool call parameters","If the prompt must change per run, place the volatile part after the stable segments so segment routing can isolate it","Re-lock the build after prompt changes so plan segments match the new prompt shape"],"exampleFix":"// before\nconst agent = defineAgent({\n  instructions: `You are helper. Today is ${new Date().toISOString()}.`,\n  tools: [searchTool],\n});\nawait agent.run(userInput, { candidatePlan });\n\n// after\nconst agent = defineAgent({\n  instructions: \"You are helper.\",\n  tools: [searchTool],\n});\nawait agent.run(`Today is ${new Date().toISOString()}\\n\\n${userInput}`, {\n  candidatePlan,\n});","handlingStrategy":"validation","validationCode":"// Reject obviously volatile content in the stable prefix before running with a plan.\nconst VOLATILE = /\\$\\{?(Date\\.now|new Date|Math\\.random|Date\\.)|\\d{4}-\\d{2}-\\d{2}T/;\nfunction assertStablePrefix(instructions: string, tools: readonly ToolDefinition[]) {\n  if (VOLATILE.test(instructions) || tools.some(t => VOLATILE.test(t.description ?? \"\"))) {\n    throw new Error(\"volatile content in stable prompt prefix; move it into run input\");\n  }\n}","typeGuard":"const hasVolatilePrefix = (instructions: string, tools: readonly ToolDefinition[]): boolean =>\n  VOLATILE.test(instructions) || tools.some(t => VOLATILE.test(t.description ?? \"\"));","tryCatchPattern":null,"preventionTips":["Keep instructions and tool descriptions byte-stable across runs","Inject per-run data (time, user, request ids) via the run input, not the system prompt","Re-lock builds whenever prompt structure changes"],"tags":["cache","prompt-stability","plans"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}