87a3b163e7
First behavior-preserving slice of the Maven redesign. Establishes explicit ingress/routing boundaries and enough observability to refactor later without changing current routing, action, clarification, or execution semantics. Types introduced: - NormalizedInput (internal/router/source.go): Text + InputSource, the typed ingress boundary replacing raw string at the turn entry. - InputSource (internal/router/source.go): channel provenance enum (tap:voice, tap:text). Reuses the existing turnSource distinction. - RouteProducer (internal/router/intent.go): which cascade stage produced the decision (grammar, heads, llm, classifier). Changes: - Decision carries a Producer RouteProducer field, set at each cascade stage (grammar, heads, LLM, classifier). - turnRoute carries NormalizedInput instead of bare text string. - runTurn takes NormalizedInput instead of (text, src). - decision.Record carries InputSource and RouteProducer for observability; RoutingTrace persists route_producer (migration #27). - turnSource is now a type alias for router.InputSource. Behavior preserved: - Stage-0 grammars unchanged: same order, same matching, same confidence. - Cascade fallthrough order unchanged (grammar → heads → llm → classifier). - Clarification behavior unchanged. - Action dispatch unchanged. - No new linguistic normalization.
102 lines
4.5 KiB
Go
102 lines
4.5 KiB
Go
package router
|
|
|
|
// Source — where the answer to a query lives. It is the second half of a
|
|
// routing decision and it used to be made outside the router entirely (V-655).
|
|
//
|
|
// The cascade sorted an utterance into one of seven intents with stage 0 rules,
|
|
// the resident model and the classifier behind it, a fixture measuring it and
|
|
// the decision trace recording it. Then IntentQuery handed the turn to
|
|
// querySources in the daemon, a chain of twenty-two branches deciding by seed
|
|
// similarity in a fixed order, with none of that. So the careful sorter did the
|
|
// easy half and the sloppy one did the hard half: on 2026-08-07 weather claimed
|
|
// "что такое TCP?" and answered "для какого города?", because weather read one
|
|
// percent closer to the turn than the pile of leftover seeds did, and one
|
|
// percent was enough. Search would have answered it and search was never asked.
|
|
//
|
|
// "query" is not a destination. It is a shrug. This is the field that says
|
|
// where to look.
|
|
//
|
|
// # Why twelve and not twenty-two
|
|
//
|
|
// A destination is what a decider can plausibly name from the utterance alone,
|
|
// not one entry per source. Three of the daemon's sources are successive passes
|
|
// over his own words and a fourth reads the facts by key: which of them lands
|
|
// the hit is an ordering detail inside the chain, and no utterance says. They
|
|
// are SourceRecall together. The same goes for the metasearch, the offline
|
|
// encyclopedia and a page he named by URL, which are SourceWorld.
|
|
//
|
|
// # Empty is a real value and it is the floor
|
|
//
|
|
// SourceUnknown means nobody decided. The daemon then walks the whole chain in
|
|
// its original order, which is the behaviour that shipped before this field
|
|
// existed. So the classifier arm sets nothing and costs nothing, and a box
|
|
// whose model is down routes queries exactly as it did.
|
|
type Source string
|
|
|
|
const (
|
|
// SourceUnknown — no decider named a destination. Walk the chain.
|
|
SourceUnknown Source = ""
|
|
|
|
// His own data.
|
|
SourceRecall Source = "recall" // notes, facts and what he has said before
|
|
SourceCalendar Source = "calendar" // events, and the only date-aware destination
|
|
SourceTasks Source = "tasks" // the task list
|
|
SourceList Source = "list" // the shopping and other named lists
|
|
SourceMoney Source = "money" // the spending facts the poller writes
|
|
|
|
// The surroundings.
|
|
SourceWeather Source = "weather" // the forecast for a place
|
|
SourceHome Source = "home" // lights, devices, the house
|
|
SourceNetwork Source = "network" // the LAN and what is on it
|
|
SourceFeeds Source = "feeds" // the RSS she reads
|
|
SourceAttention Source = "attention" // what Praxis says needs looking at
|
|
|
|
// Everything else.
|
|
SourceSelf Source = "self" // a question about Maven herself
|
|
SourceWorld Source = "world" // search, the ZIMs, a page he named
|
|
)
|
|
|
|
// Sources — every destination a decider may name, in a fixed order so a prompt,
|
|
// a grammar table and a test all read the same list. SourceUnknown is not a
|
|
// member: it is the absence of a choice, not one of the choices.
|
|
var Sources = []Source{
|
|
SourceRecall, SourceCalendar, SourceTasks, SourceList, SourceMoney,
|
|
SourceWeather, SourceHome, SourceNetwork, SourceFeeds, SourceAttention,
|
|
SourceSelf, SourceWorld,
|
|
}
|
|
|
|
// ValidSource reports whether s is one a decider may name. Anything else,
|
|
// including a destination invented by a model, is dropped back to
|
|
// SourceUnknown by the caller rather than trusted.
|
|
func ValidSource(s Source) bool {
|
|
for _, known := range Sources {
|
|
if s == known {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// InputSource — which channel this utterance arrived on. The same provenance
|
|
// vocabulary facts use (internal/event). Threaded through the turn because a
|
|
// turn can write a fact, and a fact that lies about where it came from is
|
|
// worse than no fact: provenance is the first column read when asking why a
|
|
// daemon-wide setting is the way it is.
|
|
type InputSource string
|
|
|
|
const (
|
|
// InputSourceVoice — a real microphone (PushToTalk).
|
|
InputSourceVoice InputSource = "tap:voice"
|
|
// InputSourceText — mavweb /api/chat, telegram, or any text entry point.
|
|
InputSourceText InputSource = "tap:text"
|
|
)
|
|
|
|
// NormalizedInput — the typed ingress boundary for a turn. Text is the raw
|
|
// utterance after STT (voice) or as typed (text). Source identifies the
|
|
// channel. This slice performs no new linguistic normalization: text and voice
|
|
// paths continue to converge onto the same turn path as they did before.
|
|
type NormalizedInput struct {
|
|
Text string
|
|
Source InputSource
|
|
}
|