From 4a17821b06e0be2ff07162d3ef945468de3c8086 Mon Sep 17 00:00:00 2001 From: claude Date: Sun, 6 Sep 2026 21:02:13 +0400 Subject: [PATCH] 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. --- internal/router/actioncandidate.go | 34 ++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/internal/router/actioncandidate.go b/internal/router/actioncandidate.go index 04c6b17..6fac6a7 100644 --- a/internal/router/actioncandidate.go +++ b/internal/router/actioncandidate.go @@ -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,