JuliusBrussee/caveman · error
session-value artifact feature vocabulary or order invalid
Error message
session-value artifact feature vocabulary or order invalid
What it means
FeatureSpecs must match SessionValueFeatureNames() name-for-name and index-for-index, in strictly ascending sorted order. The evaluator aligns model coefficients by position, so any renamed feature, missing entry, duplicate, or unsorted array breaks interpretation and is rejected.
Source
Thrown at proxy/routing/session_value.go:196
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")
}
if _, duplicate := seenActions[action.ActionID]; duplicate {
return errors.New("session-value artifact duplicate action")
}View on GitHub (pinned to 766dce6b13)
Solutions
- Sort FeatureSpecs by name with plain lexicographic ordering (matching sort.Strings) before sealing the artifact.
- Regenerate the artifact whenever the vocabulary changes; never hand-edit the array order.
- Assert spec.Name == SessionValueFeatureNames()[i] for every index in trainer tests.
Example fix
// before
specs := specsFromMap(featureMap) // Go map iteration: random order
// after
names := routing.SessionValueFeatureNames()
sort.Slice(specs, func(i, j int) bool { return specs[i].Name < specs[j].Name })
for i := range specs {
if specs[i].Name != names[i] {
return fmt.Errorf("feature %q not in vocabulary position %d", specs[i].Name, i)
}
} Defensive patterns
Strategy: type-guard
Type guard
// featureSpecsAligned reports whether specs exactly match the router's sorted vocabulary.
func featureSpecsAligned(specs []routing.SessionValueFeatureSpec) bool {
wanted := routing.SessionValueFeatureNames()
if len(specs) != len(wanted) {
return false
}
for i, spec := range specs {
if spec.Name != wanted[i] {
return false
}
if i > 0 && specs[i-1].Name >= spec.Name { // strictly ascending
return false
}
}
return true
} Prevention
- Always build FeatureSpecs by iterating SessionValueFeatureNames() in order — never from map iteration.
- Sort specs with plain byte-wise string comparison; avoid locale-aware collation in generation scripts.
- Assert alignment in trainer tests so vocabulary drift fails CI before shipping.
When it happens
Trigger: Trainer emitting features in training/discovery order instead of sorted order; adding or renaming a feature on one side (trainer or router) only; duplicate names appearing after merging two spec lists; locale-aware sorting in a generation script producing a different order than Go's sort.Strings.
Common situations: Vocabulary drift between trainer and router versions; hand-reordered JSON arrays; spec lists built from map iteration (random order in Go) without sorting.
Related errors
- session-value artifact has no features or actions
- session-value artifact must require turn_index
- session-value artifact action identity or order invalid
- session-value artifact schema mismatch
- session-value artifact tenant scope mismatch
AI-assisted analysis of JuliusBrussee/caveman@766dce6b13 (2026-08-18).
Data as JSON: /api/errors/69d2f605f35f20c1.
Report an issue: GitHub.