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)