# Plan: persist the routing trace

**Owner's call, 06-08-2026. Vikunja #629, umbrella #628.**

**Verdict: the per-turn decision record now persists.** That reverses a written decision,
which is the point of this file. It is not an incidental telemetry
feature. Do not read it 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.** `store.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 and again at start.** `WriteRoutingTrace` prunes every
  64th row, which is hours at human rate. `pruneTracesOnStart` covers the case write alone
  cannot. A box that goes quiet keeps every row until the next sixty-fourth turn. Without
  the start-time prune, the bound would hold only for a box in daily use.
- **Deletion already exists.** `Store.Wipe` drops every table the database reports, so
  `mavend -wipe -confirm-wipe` covers this one with no list to edit.
- **The ring did not move.** It is still what `/trace` reads 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.
