JuliusBrussee/caveman · warning

cacheengine: volatile content cannot start stable epoch

Error message

cacheengine: volatile content cannot start stable epoch

What it means

Returned by (*Engine).StartEpoch when cacheguard.DetectVolatile reports volatile content within the stable prefix bytes. Even though a non-empty stable prefix was found, its bytes contain patterns the cache guard classifies as volatile (timestamps, random IDs, and similar ever-changing content), and anchoring a cache epoch on such content would poison cache hit rates.

Source

Thrown at cacheengine/engine.go:242

func (e *Engine) StartEpoch(request PlanRequest) (Plan, error) {
	if e == nil || e.guard == nil {
		return Plan{}, errors.New("cacheengine: nil engine")
	}
	if e.configErr != nil {
		return Plan{}, e.configErr
	}
	if err := validatePlanRequest(request); err != nil {
		return Plan{}, err
	}
	prefix, stableSegments, err := stablePrefix(request.Segments, e.maxStablePrefixBytes)
	if err != nil {
		return Plan{}, err
	}
	if len(stableSegments) == 0 {
		return Plan{}, errors.New("cacheengine: no stable prefix")
	}
	if len(cacheguard.DetectVolatile(prefix)) > 0 {
		return Plan{}, errors.New("cacheengine: volatile content cannot start stable epoch")
	}
	profile := normalizedProfile(request.Profile)
	economicsBasis := "modeled_input_rate_units"
	var warnings []string
	if !profile.EconomicsKnown {
		economicsBasis = "unavailable"
		warnings = []string{"cache_economics_unavailable"}
	}
	result, err := e.guard.StartNewEpoch(epochKey(request.Scope, request.Epoch, profile.ID), prefix)
	if err != nil {
		return Plan{}, err
	}
	return Plan{
		Decision:       DecisionNewEpoch,
		Reason:         string(cacheguard.DecisionNewEpoch),
		ProfileID:      profile.ID,
		Mode:           profile.Mode,
		Attribution:    profile.Attribution,

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Move volatile content (dates, IDs, nonces) out of the leading stable prefix into later unstable segments
  2. Fix segment metadata: content with detectable volatility must not be flagged stable
  3. Hoist truly static instructions (persona, tool docs) to the very start of the prompt and keep them free of interpolated values
  4. Re-run and inspect which offsets DetectVolatile flags to locate the offending bytes

Example fix

// before
segments := []cacheengine.Segment{
    {Text: "Assistant v2. Context date: " + time.Now().Format(time.RFC3339), Stable: true},
}

// after
segments := []cacheengine.Segment{
    {Text: "Assistant v2.", Stable: true},
    {Text: "Context date: " + time.Now().Format(time.RFC3339), Stable: false},
}
Defensive patterns

Strategy: validation

Validate before calling

prefix := stablePrefixBytes(req.Segments)
if volatiles := cacheguard.DetectVolatile(prefix); len(volatiles) > 0 {
	return fmt.Errorf("stable prefix contains volatile content at %v; restructure prompt", volatiles)
}

Type guard

func prefixIsStable(prefix []byte) bool {
	return len(prefix) > 0 && len(cacheguard.DetectVolatile(prefix)) == 0
}

Try / catch

if _, err := eng.StartEpoch(req); err != nil {
	if err.Error() == "cacheengine: volatile content cannot start stable epoch" {
		return Plan{}, fmt.Errorf("rebuild prompt: move timestamps/IDs below the stable prefix")
	}
	return Plan{}, err
}

Prevention

When it happens

Trigger: A stable-flagged segment whose text embeds a timestamp, request ID, nonce, or other detectable volatile token at the epoch start. Content copied from logs or generated with Date.now()-style interpolation and marked Stable: true.

Common situations: System prompts templated with the current date; stable segments that include per-session IDs; refactors that moved dynamic fields above the static prefix; LLM scaffolds that interpolate 'today' into instructions.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/7fe33858429013e2. Report an issue: GitHub.