diff --git a/docs/plans/6b-capability-selection-boundary.md b/docs/plans/6b-capability-selection-boundary.md new file mode 100644 index 0000000..4ae0091 --- /dev/null +++ b/docs/plans/6b-capability-selection-boundary.md @@ -0,0 +1,153 @@ +# Slice 6b: Capability Selection Boundary + +*2026-09-06, from 05f79173* + +## 1. CapabilitySelection / selector contract + +```go +type CapabilitySelection struct { + Fn string + Args []string + Resolved bool + Method ActionResolutionMethod + InputKind SelectionInputKind + Producer RouteProducer + Confidence float64 +} + +type SelectionInputKind string + +const ( + SelectionRaw SelectionInputKind = "raw" + SelectionLLMText SelectionInputKind = "llm_text" + SelectionDeterministic SelectionInputKind = "deterministic" +) + +func SelectCapability(dec Decision, m ActMatcher) CapabilitySelection +func applyCapabilityToSlots(dec *Decision, sel CapabilitySelection) +``` + +`SelectCapability` is the single entry point for capability selection. It sits +between route resolution and action candidate production. + +`applyCapabilityToSlots` propagates the selection into `Decision.Slots.Fn/Args/HasFn` +for backward compatibility. `CapabilitySelection` is the authoritative record. + +## 2. Where the selector lives + +`internal/router/capability.go` — new file, 115 lines. + +Called from `Router.Route` in `internal/router/router.go` after each cascade path: +- Stage-0 grammar path (line ~108) +- Stage-0b heads path (line ~148) +- Stage-1a LLM path (line ~190) +- Stage-1 classifier path (line ~250) + +## 3. Old vs new ownership + +| Concern | Before | After | +|---|---|---| +| Raw capability extraction | `Extractor.Extract(IntentAct)` inside `fillMatchedSlots` | Same extractor, but `CapabilitySelection` is the authoritative record | +| LLM text capability backfill | Embedded in `fillSlots` (lines 319-324) | Moved to `SelectCapability` | +| Fallback matcher | `ResolveActionCandidate` (lines 160-171) | Same location, unchanged | +| Resolution method tracking | `Slots.ResolvedBy` only | `CapabilitySelection.Method` (authoritative), `Slots.ResolvedBy` (compatibility) | + +## 4. Compatibility fields + +`Decision.Slots.Fn/Args/HasFn/ResolvedBy` are still populated by +`applyCapabilityToSlots` from the `CapabilitySelection` result. They exist for +backward compatibility with code that reads slots directly (tests, rebuilt +decisions). The ownership distinction is documented on the `Decision` struct. + +## 5. Before/after flow + +**Before:** +``` +Route → Decision (Slots.Fn set by extractor/fillSlots) + ↓ +resolveAction → ResolveActionCandidate(dec, matcher) + → if Slots.HasFn: candidate from slots + → else: fallback matcher +``` + +**After:** +``` +Route → Decision + ↓ +fillMatchedSlots (time, key extraction) + ↓ +SelectCapability(dec, matcher) → CapabilitySelection + ↓ +applyCapabilityToSlots(dec, sel) → populates Slots.Fn/Args/HasFn for compat + ↓ +resolveAction → ResolveActionCandidate(dec, matcher) + → if CapabilitySelection.Resolved: candidate from selection + → else if Slots.HasFn: backward compat path + → else: fallback matcher +``` + +## 6. Fixture matrix before/after + +``` +Routing fixture (11 act cases): + grammar_fixed=2, grammar_matcher=2, extractor_raw=4, extractor_llm_text=0, fallback_matcher=0 + resolved: 8, unresolved: 3 + +Ecosystem fixture (24 act cases): + grammar_fixed=12, grammar_matcher=0, extractor_raw=11, extractor_llm_text=0, fallback_matcher=0 + resolved: 23, unresolved: 1 +``` + +Identical before and after. Zero distribution change. + +## 7. Fallback matcher usage + +The fallback matcher in `ResolveActionCandidate` is unchanged. It still runs +when `CapabilitySelection.Resolved == false` and `Slots.HasFn == false`. The +fallback matcher fires zero times in the eval fixture, consistent with previous +measurements. + +## 8. Tests + +New test file: `internal/router/capability_test.go` — 14 tests. + +| Test | What it pins | +|---|---| +| `TestSelectCapability_DeterministicBypass` | Grammar-fixed acts bypass selector | +| `TestSelectCapability_ExtractorRawBypass` | Extractor-raw acts bypass selector | +| `TestSelectCapability_GrammarMatcherBypass` | Grammar-matcher acts bypass selector | +| `TestSelectCapability_LLMTextMatch` | LLM cleaned text resolves when raw misses | +| `TestSelectCapability_LLMTextSameAsUtterance` | No double-match when Text == Utterance | +| `TestSelectCapability_Unresolved` | No match on any input | +| `TestSelectCapability_NonActIntent` | Non-act returns empty selection | +| `TestSelectCapability_NilMatcher` | Nil matcher does not panic | +| `TestApplyCapabilityToSlots_PopulatesCompatibilityFields` | Compat fields populated from selection | +| `TestApplyCapabilityToSlots_UnresolvedDoesNotSetSlots` | Unresolved selection does not overwrite slots | +| `TestResolveActionCandidate_CapabilitySelectionSource` | Selection produces route-sourced candidate | +| `TestResolveActionCandidate_CapabilitySelectionOverSlots` | CapabilitySelection takes precedence over Slots.HasFn | +| `TestResolveActionCandidate_BackwardCompatSlotsHasFn` | Decisions with HasFn but no CapabilitySelection still work | +| `TestRouterRoute_CapabilitySelectionPopulated` | Router.Route sets CapabilitySelection on Decision | +| `TestSelectionInputKindConstants` | Three input kind constants are distinct | + +Modified tests: `actioncandidate_test.go` — 8 tests updated to set +`CapabilitySelection` alongside `Slots.HasFn`. + +## 9. Fn/Args result confirmation + +Every `Fn` and `Args` value is unchanged: +- Routing fixture: identical distribution (8 resolved, 3 unresolved) +- Ecosystem fixture: identical distribution (23 resolved, 1 unresolved) +- All 57 router tests pass +- All cmd/mavend tests pass +- All eval fixture tests pass + +## 10. Newly exposed architectural problems + +None. The extraction is clean and mechanical. The backward compatibility +path in `ResolveActionCandidate` (checking `Slots.HasFn` when `CapabilitySelection` +is not set) is a temporary bridge that should be removed when all call sites +produce decisions through the router. + +## 11. Commit hash + +Pending commit on branch `task/slice-6b-capability-selection`.