router: ResolveActionCandidate reads CapabilitySelection first (slice 6b)

The candidate now receives Fn/Args from CapabilitySelection (the
authoritative record) rather than from Decision.Slots.HasFn. Backward
compatibility: decisions with Slots.HasFn but no CapabilitySelection
(tests, rebuilt decisions) still resolve via the compatibility path.
This commit is contained in:
2026-09-06 21:02:13 +04:00
parent 4b24fbad98
commit 4a17821b06
+25 -9
View File
@@ -130,21 +130,37 @@ func (r ActionValidationResult) Valid() bool { return r.Status == ActionValid }
//
// Resolution rules:
// - Non-act intents: candidate is not applicable (Fn empty, source empty).
// - Act with Slots.HasFn: the router already resolved the function upstream
// (stage-0 grammar, stage-2 extractor, or LLM slot backfill). Candidate
// source is ActionSourceRoute.
// - Act without Fn: the fallback matcher runs against the text slot.
// Candidate source is ActionSourceMatcher on match, or Fn stays empty.
// - Act with resolved CapabilitySelection: the capability-selection stage
// already resolved the function. Candidate source is ActionSourceRoute.
// - Act without resolved CapabilitySelection: the fallback matcher runs
// against the text slot. Candidate source is ActionSourceMatcher on match,
// or Fn stays empty.
//
// The matcher algorithm, enabled-tool set, alias behavior, fuzzy-prefix
// behavior, and ordering are unchanged — this is a mechanical extraction of
// the same matching call that actionAct previously owned.
// The CapabilitySelection is the authoritative source for Fn/Args. The
// compatibility fields on Decision.Slots (Fn/Args/HasFn) are still populated
// for backward compatibility but are not read here.
func ResolveActionCandidate(dec Decision, m ActMatcher) ActionCandidate {
if dec.Intent != IntentAct {
return ActionCandidate{}
}
// Router resolved the function upstream.
// Capability selection resolved the function upstream. This is the
// authoritative path for decisions produced by the router (which calls
// SelectCapability).
if dec.CapabilitySelection.Resolved {
return ActionCandidate{
Fn: dec.CapabilitySelection.Fn,
Args: dec.CapabilitySelection.Args,
Source: ActionSourceRoute,
Producer: dec.Producer,
ResolvedBy: dec.CapabilitySelection.Method,
Confidence: dec.Confidence,
}
}
// Backward compatibility: decisions constructed outside the router
// (tests, rebuilt decisions) may set Slots.HasFn without
// CapabilitySelection. Read the compatibility fields.
if dec.Slots.HasFn {
return ActionCandidate{
Fn: dec.Slots.Fn,