internal/decision kept a 25-turn ring and persisted nothing, on the argument that a turn record is read minutes later or never. The owner reversed that on 06-08-2026: the routing heads cannot be fitted or calibrated without real utterances, and V-631 measured that 9 of the 31 modes have no seed example at all. docs/plans/21-persisting-the-routing-trace.md carries the reversal, and CLAUDE.md now says which of its own sentences stopped being true. cmd/mavend/routingtrace.go is a second sink beside the ring, which did not move: the ring is still what /trace reads and still what a test with no store gets. A failed insert is logged and swallowed, because a trace must never change what he hears. traceSink keeps a nil store out of the interface, since a typed nil pointer there would pass the nil check and die on the first turn. Four fields the ring never carried: which reach the turn arrived on, whether stage 0 answered before the classifier was consulted, which encoder body was live (the same EmbedderID string the vector marker uses), and what the action stage actually did. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0117tgnmbgZpHVV3XSNw8Qua
3.0 KiB
Plan: persist the routing trace
Owner's call, 06-08-2026. Vikunja #629, umbrella #628.
Verdict: the per-turn decision record is now written to the database. That reverses a written decision, which is the point of this file. It is not an incidental telemetry feature and must not be read as one.
Last verified: 06-08-2026 @ 799cf55
What the old decision said
internal/decision kept a 25-turn in-memory ring and persisted nothing. The argument was
in CLAUDE.md and it was a good one. A turn record is read minutes after the turn or
never, so a table that outlives the diagnosis buys nothing. His words did not belong in it.
Why it reversed
V-546 replaces the generative router with classification heads on e5-small. Fitting
prototypes and calibrating a distance both need real utterances. V-631 measured how few
there are. Nine of the 31 modes in internal/modes have no seed example at all, and they
are exactly the nine with no deterministic matcher. The seed corpus cannot supply them. A
seed row is a phrase someone wrote for a matcher, not a thing he said. The 202 generated
contrast pairs were tried and cost four points of fixture accuracy.
So the choice was between no routing heads and a persisted trace. The owner chose the trace.
Retention, and why it is two answers
Raw trace: 14 days. routingTraceRetention in internal/store/routingtraces.go. That
is the life of a diagnosis with room for a weekend. The bound is an age and not a row
count. The useful question is what she did this week, and a busy Tuesday must not push last
Friday out.
A correction: indefinite. The owner corrects a turn on /chat (V-630). The pair is then
promoted out of the trace into a seed-shaped row and kept, because a label is not a
transcript. What stays in routing_traces is the transcript. It expires on the same 14
days as every other row, corrected or not.
What keeps it safe
The utterance is stored in clear. A 384-dimension vector of a short sentence is substantially recoverable. Storing vectors instead would be a privacy claim we cannot support, and making it would be worse than staying silent.
- Nothing here leaves the box. The rule that the owner's notes and facts are never search input covers this table too. No query source reads it, and no upstream engine can.
- Retention is enforced on write.
WriteRoutingTraceprunes every 64th row, which is hours at human rate. - Deletion already exists.
Store.Wipedrops every table the database reports, somavend -wipe -confirm-wipecovers this one with no list to edit. - The ring did not move. It is still what
/tracereads and still what a test with no store gets. The table is a second sink beside it. A failed insert is logged and swallowed, because a trace must never change what he hears.
What is not decided
Whether some utterances must never be promoted into a durable label, no matter how badly they routed. That is a content rule and it belongs beside the personal boundary, not in the trace writer. Recorded here, left to the owner.