router: introduce typed ingress boundary and route producer observability
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.
This commit is contained in:
@@ -68,6 +68,16 @@ type Record struct {
|
||||
Winner string `json:"winner"`
|
||||
Claims []Claim `json:"claims"`
|
||||
|
||||
// InputSource — which channel the utterance arrived on (tap:voice or
|
||||
// tap:text). Carried for observability so a trace can distinguish a voice
|
||||
// turn from a text turn without re-deriving it from surrounding claims.
|
||||
InputSource string `json:"input_source,omitempty"`
|
||||
|
||||
// RouteProducer — which cascade stage produced the routing decision.
|
||||
// Carried for observability so a trace names the winning component directly
|
||||
// rather than requiring a scan of the claims list.
|
||||
RouteProducer string `json:"route_producer,omitempty"`
|
||||
|
||||
mu sync.Mutex
|
||||
rosters []roster
|
||||
}
|
||||
@@ -152,9 +162,10 @@ func (r *Record) Finish(now time.Time) *Record {
|
||||
type recorderKey struct{}
|
||||
|
||||
// With returns a context carrying a fresh record, and the record to read after
|
||||
// the turn has answered.
|
||||
func With(ctx context.Context, utterance string) (context.Context, *Record) {
|
||||
rec := &Record{Utterance: utterance}
|
||||
// the turn has answered. inputSource identifies the channel the utterance
|
||||
// arrived on; pass empty if unknown.
|
||||
func With(ctx context.Context, utterance string, inputSource string) (context.Context, *Record) {
|
||||
rec := &Record{Utterance: utterance, InputSource: inputSource}
|
||||
return context.WithValue(ctx, recorderKey{}, rec), rec
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user