From edcc6580b6fa748a145c28c4fa20617d79844a0b Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sun, 4 Oct 2026 19:46:55 +0100 Subject: [PATCH] fix(volition): rest an action on the belief about that action An intention's supporting beliefs came from a text search on the target, and ties on rank and confidence had no order, so a verification miss could revise an unrelated belief about the same service. Beliefs that mention the proposed action now come first, and belief search breaks ties by id. The incident scenario asserts the first support is the belief about restarting, and waits for the blocked intention and the procedural memory instead of reading them once. --- internal/beliefs/store.go | 2 +- internal/cognition/volition.go | 8 ++++++++ tests/agency/incident_test.go | 24 +++++++++++++++--------- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/internal/beliefs/store.go b/internal/beliefs/store.go index 983f2db..952ae94 100644 --- a/internal/beliefs/store.go +++ b/internal/beliefs/store.go @@ -872,7 +872,7 @@ func (s *Store) Search(ctx context.Context, mind ids.ID, text string, limit int) } q := strings.Join(toks, " | ") rows, err := s.db.Query(ctx, `SELECT `+cols+` FROM beliefs b WHERE b.mind_id=$1 AND b.status NOT IN ('superseded') AND b.search @@ to_tsquery('english', $2) - ORDER BY ts_rank_cd(b.search, to_tsquery('english', $2)) DESC, b.confidence DESC LIMIT $3`, mind, q, limit) + ORDER BY ts_rank_cd(b.search, to_tsquery('english', $2)) DESC, b.confidence DESC, b.id LIMIT $3`, mind, q, limit) if err != nil { return nil, err } diff --git a/internal/cognition/volition.go b/internal/cognition/volition.go index e04bf0a..bfed809 100644 --- a/internal/cognition/volition.go +++ b/internal/cognition/volition.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "sort" "strings" "time" @@ -495,6 +496,13 @@ func supportingBeliefs(ctx context.Context, s *brain.State, w volition.Want) []i if err != nil { return nil } + // A belief about the proposed action itself ("restarting atlas-worker + // clears the queue") is what the action rests on, so it comes first; the + // first supporting belief is the one a verification outcome revises. + action := strings.ToLower(w.Proposal.Capability[strings.LastIndex(w.Proposal.Capability, ".")+1:]) + sort.SliceStable(bs, func(i, j int) bool { + return action != "" && strings.Contains(strings.ToLower(bs[i].Proposition), action) && !strings.Contains(strings.ToLower(bs[j].Proposition), action) + }) var out []ids.ID for _, b := range bs { if len(out) == 3 { diff --git a/tests/agency/incident_test.go b/tests/agency/incident_test.go index 23d26a8..5ad56a3 100644 --- a/tests/agency/incident_test.go +++ b/tests/agency/incident_test.go @@ -72,7 +72,8 @@ func TestIncidentScenario(t *testing.T) { var req string waitFor(t, h, m, 30*time.Second, "a capability request for logs", func() bool { rs, _ := h.Deps.Capabilities.CapabilityRequests(ctx, m.ID, "pending", 5) - if len(rs) == 1 { + // The request commits before the executor blocks the intention. + if cur, _ := h.Deps.Volition.GetIntention(ctx, nil, m.ID, investigate.ID); len(rs) == 1 && cur.Status == volition.IntentionBlocked { req = rs[0].ID.String() if rs[0].Capability != "logs.query" || rs[0].Risk == "" || rs[0].Fallback == "" || rs[0].MaxOperations == nil { t.Fatalf("request not explained: %+v", rs[0]) @@ -139,6 +140,9 @@ func TestIncidentScenario(t *testing.T) { if host.count() != 0 || restart.Kind != volition.IntentRequestApproval || len(restart.BeliefIDs) == 0 { t.Fatalf("restart before approval, wrong kind or no supporting belief: restarts=%d %+v", host.count(), restart) } + if restart.BeliefIDs[0] != belief.ID { + t.Fatalf("the belief about restarting should be the first support, got %v (want %s)", restart.BeliefIDs, belief.ID) + } // 21–25: approved; restarted with a typed capability; health is verified, not assumed; it fails. post("/api/v1/approvals/"+approval+"/approve", map[string]any{"note": "go"}) @@ -182,14 +186,16 @@ func TestIncidentScenario(t *testing.T) { } return false }) - mems, _ := h.Deps.Memory.List(ctx, m.ID, memory.Filter{Kinds: []concepts.MemoryKind{concepts.Procedural}, Limit: 10}) - learnt := false - for _, mm := range mems { - learnt = learnt || strings.Contains(mm.Content, "confirmed by observation") - } - if !learnt { - t.Fatalf("what worked was not recorded as a procedure: %+v", mems) - } + // The plan completes, and records what worked, after the prediction resolves. + waitFor(t, h, m, 30*time.Second, "what worked recorded as a procedure", func() bool { + mems, _ := h.Deps.Memory.List(ctx, m.ID, memory.Filter{Kinds: []concepts.MemoryKind{concepts.Procedural}, Limit: 10}) + for _, mm := range mems { + if strings.Contains(mm.Content, "confirmed by observation") { + return true + } + } + return false + }) // 31: "Why did you do that?" is answered from the recorded chain. execs, _ := h.Deps.Capabilities.Executions(ctx, m.ID, 50)