JuliusBrussee/caveman · error
cave_cache_volatile_stable_slot
cave_cache_volatile_stable_slot
Error message
cave_cache_volatile_stable_slot
What it means
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.
Source
Thrown at packages/agent/src/runtime.ts:1405
provider: routedModel.provider,
model: routedModel.id,
api: routedModel.api,
prefixDigest,
buildIdentity: buildIdentity ?? null,
lockedPlanSHA256: buildIdentity?.planSha256 ?? null,
executionPlanSHA256: efficiencyPlan === undefined
? null
: sha256(stableStringify(efficiencyPlan)),
}));
if (conversation?.fingerprint !== undefined &&
conversation.fingerprint !== conversationFingerprint) {
conversation.cachePrefixDigest = undefined;
conversation.providerFrozen = undefined;
conversation.originalFrozen = undefined;
}
if (conversation) conversation.fingerprint = conversationFingerprint;
if (efficiencyPlan?.segment_routes.length && volatileStablePrefix(instructions, definition.tools)) {
throw new Error("cave_cache_volatile_stable_slot");
}
// Every x-cave-* header exists for the Caveman gateway: x-cave-api-key is
// an account credential, and agent/workflow/session/cache-epoch/prefix
// digest/context bill/build+plan digests are account-linked identifiers and
// internal telemetry. A request that does not go through the gateway goes to
// a third party, so it carries none of them.
const headers = gatewayActive
? runtimeHeaders(
definition.id,
options.workflow ?? definition.id,
sessionId,
buildIdentity,
bill,
appliedPlan.appliedTransformIDs,
prefixDigest,
conversationFingerprint,
executionContext.invocationState.batch?.apiKey,
executionContext.invocationState.batch === undefined ? undefined : executionContext.invocationTrace,View on GitHub (pinned to 27d5a3981a)
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
Example fix
// before
const agent = defineAgent({
instructions: `You are helper. Today is ${new Date().toISOString()}.`,
tools: [searchTool],
});
await agent.run(userInput, { candidatePlan });
// after
const agent = defineAgent({
instructions: "You are helper.",
tools: [searchTool],
});
await agent.run(`Today is ${new Date().toISOString()}\n\n${userInput}`, {
candidatePlan,
}); Defensive patterns
Strategy: validation
Validate before calling
// Reject obviously volatile content in the stable prefix before running with a plan.
const VOLATILE = /\$\{?(Date\.now|new Date|Math\.random|Date\.)|\d{4}-\d{2}-\d{2}T/;
function assertStablePrefix(instructions: string, tools: readonly ToolDefinition[]) {
if (VOLATILE.test(instructions) || tools.some(t => VOLATILE.test(t.description ?? ""))) {
throw new Error("volatile content in stable prompt prefix; move it into run input");
}
} Type guard
const hasVolatilePrefix = (instructions: string, tools: readonly ToolDefinition[]): boolean => VOLATILE.test(instructions) || tools.some(t => VOLATILE.test(t.description ?? ""));
Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/f8d64561eb69e773.
Report an issue: GitHub.