JuliusBrussee/caveman · error

session-value artifact has no features or actions

Error message

session-value artifact has no features or actions

What it means

The artifact must carry exactly one FeatureSpec per name in the stable metadata vocabulary returned by SessionValueFeatureNames() (21 sorted names today, from audio to user_turns) plus at least one action. A FeatureSpecs array of the wrong length (features added or removed since training) or an empty Actions list fails here, because the evaluator aligns models by position.

Source

Thrown at proxy/routing/session_value.go:192

	if !validCompactPoolHash(artifact.CandidatePoolHash) || artifact.CandidatePoolHash != candidatePoolHash {
		return errors.New("session-value artifact candidate pool mismatch")
	}
	if !validSHA256Ref(artifact.TrainingManifestHash) || strings.TrimSpace(artifact.TrainingExtractor) == "" || strings.TrimSpace(artifact.OutcomeContractVersion) == "" {
		return errors.New("session-value artifact training lineage invalid")
	}
	if artifact.ValidFrom.IsZero() || artifact.ValidUntil.IsZero() || !artifact.ValidUntil.After(artifact.ValidFrom) || now.Before(artifact.ValidFrom) || !now.Before(artifact.ValidUntil) {
		return errors.New("session-value artifact outside validity window")
	}
	if artifact.RollbackParentHash != "" && (!validSHA256Ref(artifact.RollbackParentHash) || artifact.RollbackParentHash == artifact.ArtifactHash) {
		return errors.New("session-value artifact rollback lineage invalid")
	}
	if !finite(artifact.QualityUncertaintyZ) || artifact.QualityUncertaintyZ <= 0 || artifact.QualityUncertaintyZ > 5 ||
		!finite(artifact.MaxInversePropensity) || artifact.MaxInversePropensity < 1 || artifact.MaxInversePropensity > 100 {
		return errors.New("session-value artifact confidence policy invalid")
	}
	featureNames := SessionValueFeatureNames()
	if len(artifact.FeatureSpecs) != len(featureNames) || len(artifact.Actions) == 0 {
		return errors.New("session-value artifact has no features or actions")
	}
	for i, spec := range artifact.FeatureSpecs {
		if spec.Name != featureNames[i] || (i > 0 && artifact.FeatureSpecs[i-1].Name >= spec.Name) {
			return errors.New("session-value artifact feature vocabulary or order invalid")
		}
		if spec.Name == "turn_index" && !spec.Required {
			return errors.New("session-value artifact must require turn_index")
		}
		if !finite(spec.Mean) || !finite(spec.Scale) || spec.Scale <= 0 || !finite(spec.Min) || !finite(spec.Max) || spec.Min < 0 || spec.Max < spec.Min {
			return fmt.Errorf("session-value feature %q bounds invalid", spec.Name)
		}
	}
	seenActions := map[string]struct{}{}
	artifactPool := make([]Candidate, 0, len(artifact.Actions))
	for i, action := range artifact.Actions {
		wantID := CandidateActionID(Candidate{Provider: action.Provider, Model: action.Model, Effort: action.Effort})
		if wantID == "" || action.ActionID != wantID || (i > 0 && artifact.Actions[i-1].ActionID >= action.ActionID) {
			return errors.New("session-value artifact action identity or order invalid")

View on GitHub (pinned to 766dce6b13)

Solutions

  1. Regenerate the artifact against the current router's SessionValueFeatureNames() so lengths match.
  2. Lock trainer and router to the same vocabulary version and retrain whenever the vocabulary changes.
  3. Ensure at least one candidate action (provider/model/effort) is included when the artifact is produced.

Example fix

// before
artifact.FeatureSpecs = trainOnLegacyVocabulary() // 20 specs, router expects 21
artifact.Actions = nil

// after
artifact.FeatureSpecs = specsFor(routing.SessionValueFeatureNames()) // exactly 21, current names
artifact.Actions = trainActions(currentCandidatePool())             // >= 1 action
Defensive patterns

Strategy: validation

Validate before calling

// Before validation: compare collection sizes against the live vocabulary.
wanted := routing.SessionValueFeatureNames() // authoritative length and names
if len(artifact.FeatureSpecs) != len(wanted) {
	return fmt.Errorf("artifact has %d feature specs, router vocabulary has %d; regenerate after vocabulary changes",
		len(artifact.FeatureSpecs), len(wanted))
}
if len(artifact.Actions) == 0 {
	return errors.New("artifact has no actions; not loadable")
}

Prevention

When it happens

Trigger: The router's feature vocabulary changed (feature added/removed) while the artifact was trained on the old set; an artifact with specs but zero Actions; JSON arrays emptied or truncated by generation tooling.

Common situations: Router upgrade introducing a new metadata feature; a trainer that filters out features it considers unused; an artifact template whose arrays default to empty.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@766dce6b13 (2026-08-18). Data as JSON: /api/errors/5a914f998d3278e1. Report an issue: GitHub.