fix(planning): consult the plan model only when no deterministic strategy applies

Propose built every strategy to record the alternatives, and the model
strategy's build is a live call to the plan role, so planning a direct
action spent a model call on an alternative that could never be selected
(B2, docs/planning.md). The model is now recorded as "not consulted" once
a deterministic draft is chosen, and the approver's reason no longer lists
it among the alternatives considered. A test counts the calls both ways.
This commit is contained in:
Andy Dixon
2026-10-05 19:35:20 +01:00
parent b6538600d1
commit 5cf3813a2e
3 changed files with 42 additions and 1 deletions
+1 -1
View File
@@ -372,7 +372,7 @@ func (e *Executor) explain(p Plan, it volition.Intention, st Step, c capabilitie
} }
var alts []string var alts []string
for _, a := range p.Alternatives { for _, a := range p.Alternatives {
if !a.Selected && a.Why != "already failed" { if !a.Selected && a.Why != "already failed" && !strings.HasPrefix(a.Why, "not consulted") {
alts = append(alts, a.Strategy) alts = append(alts, a.Strategy)
} }
} }
+34
View File
@@ -510,6 +510,40 @@ func TestInvalidModelPlanNeverRuns(t *testing.T) {
} }
} }
// A model is consulted only when no deterministic strategy applies (B2):
// planning a direct capability action must not spend a live model call on
// an alternative that cannot be selected.
func TestModelIsNotConsultedWhenADeterministicStrategyApplies(t *testing.T) {
f := newFixture(t)
calls := 0
f.exec.Planner.Model = func(context.Context, ids.ID, string, []capabilities.Capability) (string, ids.ID, error) {
calls++
return `{"steps":[]}`, ids.Nil, nil
}
it, _ := f.vol.CreateIntention(f.ctx, f.vol.Direct(f.es, "test"), volition.Intention{MindID: f.mind.ID, Description: "check the status page", Kind: volition.IntentAct,
Proposal: &volition.Proposal{Kind: "capability", Capability: "service.status", Params: map[string]any{"service": "atlas-worker"}}})
p, err := f.exec.Start(f.ctx, f.exec.rec(), it)
if err != nil {
t.Fatal(err)
}
if calls != 0 {
t.Fatalf("the model was called %d time(s) although the direct strategy applied", calls)
}
recorded := false
for _, a := range p.Alternatives {
recorded = recorded || (a.Strategy == "model" && !a.Selected && strings.HasPrefix(a.Why, "not consulted"))
}
if p.Strategy != "direct" || !recorded {
t.Fatalf("expected the direct strategy with the model recorded as not consulted: %+v", p.Alternatives)
}
// With no deterministic strategy the model is consulted.
pursue, _ := f.vol.CreateIntention(f.ctx, f.vol.Direct(f.es, "test"), volition.Intention{MindID: f.mind.ID, Description: "make the dashboard faster", Kind: volition.IntentPursue})
f.exec.Start(f.ctx, f.exec.rec(), pursue) //nolint:errcheck
if calls != 1 {
t.Fatalf("the model should be consulted once when nothing else applies, got %d", calls)
}
}
// Regression: an executor holding a stale "pending" copy of a step must not // Regression: an executor holding a stale "pending" copy of a step must not
// move it back to "ready" after another executor has claimed it, or the // move it back to "ready" after another executor has claimed it, or the
// step can be claimed and run a second time. // step can be claimed and run a second time.
+7
View File
@@ -78,6 +78,13 @@ func (p *Planner) Propose(ctx context.Context, it volition.Intention, previous [
alts = append(alts, Alternative{Strategy: s.name, Description: "tried in an earlier revision", Why: "already failed"}) alts = append(alts, Alternative{Strategy: s.name, Description: "tried in an earlier revision", Why: "already failed"})
continue continue
} }
if s.name == "model" && chosen != nil {
// A model is consulted only when no deterministic strategy
// applies; building it here would spend a live call on an
// alternative that cannot be selected.
alts = append(alts, Alternative{Strategy: s.name, Description: "model decomposition", ExpectedConfidence: s.confidence, Why: "not consulted: a deterministic strategy applies"})
continue
}
d, err := s.build() d, err := s.build()
if err != nil { if err != nil {
alts = append(alts, Alternative{Strategy: s.name, Description: err.Error(), Why: "not buildable"}) alts = append(alts, Alternative{Strategy: s.name, Description: err.Error(), Why: "not buildable"})