mavend: group the recall fields into one wiring struct (V-433)

The review comment asked for basic DI. The answer is the idiom voice.go
already had for capabilities — a cohesive *Wiring struct — applied to a
group that is not a capability toggle, plus the decision written down so
it is a rule and not a habit.

recallWiring holds the embedder, the vector store, the personal boundary
and the two numbers that gate an answer. They sat in three places on
reactiveHandler, with the gate numbers a hundred lines from the store
they gate. Its zero value means no recall, so it is a value, not a
pointer like the optional-capability groups.

dataStore stays out of it. patterns.go, ecosystem_acts.go and confirm.go
use it, so it is not part of this cluster.

docs/handler-wiring.md records the choice, rejects a container or a
wire-style generator outright, defers narrow per-handler interfaces to
the package split that would justify them, and states the constraint the
task named: a wiring change does not ride a feature PR.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-04 05:57:24 +04:00
parent 246db4e609
commit ed491e23fc
15 changed files with 173 additions and 84 deletions
+35 -1
View File
@@ -1,6 +1,9 @@
package main
import "github.com/kami/maven/internal/memory"
import (
"github.com/kami/maven/internal/memory"
"github.com/kami/maven/internal/router"
)
// bestRecall is the read side of the long-term memory store: the top hit when
// it clears the confidence gate. The index holds BOTH notes and facts, and
@@ -23,3 +26,34 @@ func bestRecall(results []memory.Result, minScore, minMargin float64) (memory.Re
}
return results[0], true
}
// recallWiring — the recall subsystem's dependencies, held as one group on
// reactiveHandler (Vikunja #433). It is the worked example for the wiring
// decision in docs/handler-wiring.md: cohesive groups of fields, not thirty
// loose ones, so a handler names what it needs and the package can be split
// later without exporting the whole struct.
//
// The zero value is usable and means "no recall": no embedder, no vector
// store, and a gate that is never consulted because nothing is ever searched.
type recallWiring struct {
// embedder — reused for note write/query (same model as the classifier).
embedder router.Embedder
// memStore — the vector index over notes and facts.
memStore memory.Store
// boundary — the embedded seed sets behind the personal boundary
// (personalboundary.go). Zero value is usable and loads on first query;
// with no embedder it never loads and the boundary uses personalMarkers.
boundary personalBoundary
// minScore — the note-recall confidence gate. Top cosine below this ⇒
// "I don't know" instead of a guess. Tuned for the ONNX embedder; a knob,
// not load-bearing math (same posture as the presence thresholds). Set by
// wireVoice from VoiceConfig; default 0.55.
minScore float64
// minMargin — the second half of that gate: how far the top hit must beat
// the runner-up. 0 ⇒ margin off.
minMargin float64
}