Compare commits

...

3 Commits

Author SHA1 Message Date
claude b8250a8711 Merge the store and recalleval sweep (#227)
lookupLiveTaskByNorm wrote its live-status predicate as a SQL literal beside
the named liveTaskStatuses constants ListTasks already binds for the same
predicate. bestRecall carried two stacked doc comments, both opening the same
way, from an edit that appended rather than replaced. The daemon top-k was a
bare 3 with no tie to memoryRecallWidth, which holds the same value.

Nothing that decides what recalleval measures was touched.

(V-581)
2026-08-06 02:10:50 +04:00
claude 02ce730cb2 recalleval: fold a doubled doc comment, name the top-k literal (V-581)
bestRecall carried two stacked doc comments (both starting "bestRecall
mirrors...") from a prior edit that appended rather than replaced;
folded into one. Also named the literal 3 passed to Search as
daemonTopK, mirroring memoryRecallWidth in actions_query.go, so the
Recall3 doc and the call site cannot drift from each other again.
2026-08-06 02:10:22 +04:00
claude aaf1f0236b store/tasks: dedupe live-status literal against the named constants (V-581)
lookupLiveTaskByNorm hardcoded 'candidate','open' in SQL, drifting from
liveTaskStatuses which ListTasks already uses for the same query. Bind
the constants instead so there is one place that names the live set.
2026-08-06 02:10:22 +04:00
2 changed files with 17 additions and 12 deletions
+16 -11
View File
@@ -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 ""
+1 -1
View File
@@ -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