diff --git a/internal/memory/recalleval/recalleval.go b/internal/memory/recalleval/recalleval.go index e1be017..9c70013 100644 --- a/internal/memory/recalleval/recalleval.go +++ b/internal/memory/recalleval/recalleval.go @@ -38,6 +38,11 @@ var fixtureJSON []byte // other version rather than misreading a fixture and reporting a number. const SchemaVersion = 1 +// daemonTopK — how many candidates the read path asks for, mirroring +// memoryRecallWidth in cmd/mavend/actions_query.go. Kept in step so recall@3 +// measures the same top-3 the daemon actually reads, not an arbitrary width. +const daemonTopK = 3 + // StoredNote — one thing the user said once, as it lands in the semantic store. // Kind is "note" or "fact"; both share the vector index (see // cmd/mavend/recall.go), so a fact can legitimately win a recall. @@ -267,8 +272,9 @@ type TagStat struct{ Passed, Total int } // Recall1 — fraction of answerable cases whose wanted note ranked first. func (r Report) Recall1() float64 { return ratio(r.Rank1, r.Answerable) } -// Recall3 — same, in the top three. The daemon asks for 3 (voice.go), so this -// is the ceiling a better gate or a reranker could reach. +// Recall3 — same, in the top three. The daemon asks for daemonTopK candidates +// (mirroring memoryRecallWidth in voice.go's caller), so this is the ceiling a +// better gate or a reranker could reach. func (r Report) Recall3() float64 { return ratio(r.Rank3, r.Answerable) } // Answered — fraction of answerable cases the daemon would actually answer @@ -399,7 +405,7 @@ func scoreCase(ctx context.Context, emb router.Embedder, newStore NewStore, minS o.Reasons = []string{fmt.Sprintf("embed query: %v", err)} return o, nil } - hits, err := st.Search(ctx, qvec, 3) + hits, err := st.Search(ctx, qvec, daemonTopK) o.Latency = time.Since(start) if err != nil { o.Err = err @@ -455,14 +461,13 @@ func rankNote(inTop3 bool) string { } // bestRecall mirrors cmd/mavend/recall.go — the gate the daemon actually -// applies to a memory hit. Duplicated rather than imported because package main -// is not importable; recalleval_test.go asserts the two agree in behaviour. -// The daemon returns the whole hit (a note and a fact are said differently); -// the harness only scores what came back, so it keeps returning the text. -// bestRecall mirrors the daemon's gate in cmd/mavend/recall.go, including the -// topic veto added for #470: a score that clears the gate still has to be -// about what he asked. Keep the two in step — a fixture that measures a -// weaker gate than the daemon runs flatters it. +// applies to a memory hit, including the topic veto added for #470: a score +// that clears the gate still has to be about what he asked. Duplicated rather +// than imported because package main is not importable; recalleval_test.go +// asserts the two agree in behaviour, so a weaker gate here than the daemon +// runs cannot flatter the score. The daemon returns the whole hit (a note and +// a fact are said differently); the harness only scores what came back, so it +// keeps returning the text. func bestRecall(query string, results []memory.Result, minScore, minMargin float64) string { if !memory.Confident(results, minScore, minMargin) { return "" diff --git a/internal/store/tasks.go b/internal/store/tasks.go index 8e37258..57c5b51 100644 --- a/internal/store/tasks.go +++ b/internal/store/tasks.go @@ -246,7 +246,7 @@ func (s *Store) lookupTaskByExternalID(ctx context.Context, ext string) (Task, e // lookupLiveTaskByNorm finds the outstanding task with this normalised text. func (s *Store) lookupLiveTaskByNorm(ctx context.Context, norm string) (Task, error) { row := s.db.QueryRowContext(ctx, taskSelect+` - WHERE norm = ? AND status IN ('candidate','open')`, norm) + WHERE norm = ? AND status IN (?,?)`, norm, liveTaskStatuses[0], liveTaskStatuses[1]) t, err := scanTask(row) if errors.Is(err, sql.ErrNoRows) { return Task{}, ErrTaskNotFound