routing

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 11, 2026 License: AGPL-3.0 Imports: 37 Imported by: 0

Documentation

Overview

Package routing provides tool selection using a fast micro LLM.

Description

The routing package implements a ToolRouter that uses a dedicated micro LLM (like granite4:micro-h) to quickly classify queries and select the appropriate tool before the main reasoning LLM processes the request.

This separation of concerns allows:

  • Fast tool selection (~50-100ms) vs full LLM reasoning (~10-30s)
  • Better resource utilization (small model for routing, large for reasoning)
  • Parallel model execution on GPUs with sufficient VRAM

Thread Safety

All types in this package are designed for concurrent use.

Index

Constants

View Source
const (
	// SemanticDuplicateThreshold is the similarity threshold for blocking.
	// Calls with similarity >= this are considered semantic duplicates.
	SemanticDuplicateThreshold = 0.8

	// SemanticPenaltyThreshold is the similarity threshold for penalizing.
	// Calls with similarity >= this (but < duplicate threshold) get penalized.
	SemanticPenaltyThreshold = 0.3

	// DefaultSemanticPenaltyWeight is the default UCB1 penalty weight.
	// SemanticPenalty = similarity * weight.
	DefaultSemanticPenaltyWeight = 0.5
)
View Source
const (
	ErrCodeTimeout          = "ROUTER_TIMEOUT"
	ErrCodeLowConfidence    = "ROUTER_LOW_CONFIDENCE"
	ErrCodeParseError       = "ROUTER_PARSE_ERROR"
	ErrCodeModelUnavailable = "ROUTER_MODEL_UNAVAILABLE"
	ErrCodeNoTools          = "ROUTER_NO_TOOLS"
	ErrCodeEmptyResponse    = "ROUTER_EMPTY_RESPONSE" // GR-Phase1: Model returned empty response
)

Common router error codes.

View Source
const DefaultMaxProofNumber = 100.0

DefaultMaxProofNumber is the default maximum proof number for normalization. Proof numbers in PN-MCTS typically range from 1-100 for most tool selections.

View Source
const MaxToolCallHistorySize = 100

MaxToolCallHistorySize is the maximum number of calls to retain in history. Older calls are evicted when this limit is reached. R1.1 Fix: Prevent unbounded memory growth. Deprecated: Use SemanticConfig.MaxHistorySize instead.

Variables

This section is empty.

Functions

func BuildAssignmentFromSteps

func BuildAssignmentFromSteps(steps []crs.StepRecord) map[string]bool

BuildAssignmentFromSteps builds a variable assignment from step history.

Description:

Creates the assignment map used for clause checking. Includes:
  - prev_tool:<name> for the previous tool
  - outcome:<result> for the previous outcome
  - error:<category> for the previous error category
  - prev_prev_tool:<name> for the tool before previous

Inputs:

steps - Step history from CRS.

Outputs:

map[string]bool - The variable assignment.

Thread Safety: Safe for concurrent use.

func ExtractQueryFromMetadata

func ExtractQueryFromMetadata(metadata map[string]string) string

ExtractQueryFromMetadata extracts query from trace step metadata.

Description:

Trace steps store parameters in Metadata as string values.
This function extracts the query for semantic comparison.

Inputs:

metadata - Map of parameter name to string value.

Outputs:

string - The query string, or empty if not found.

func ExtractQueryFromParams

func ExtractQueryFromParams(params map[string]any) string

ExtractQueryFromParams extracts the query/pattern parameter from tool params.

Description:

Different tools use different parameter names for their "query" concept.
This function extracts the relevant parameter for semantic comparison.

Inputs:

params - Map of parameter name to value.

Outputs:

string - The query/pattern string, or empty if not found.

func ExtractQueryTerms

func ExtractQueryTerms(query string) map[string]bool

func JaccardSimilarity

func JaccardSimilarity(a, b map[string]bool) float64

JaccardSimilarity calculates the Jaccard similarity between two term sets.

Description:

Jaccard = |intersection| / |union|
Returns 0.0 if either set is empty, 1.0 if identical.

Inputs:

a, b - Term sets to compare. Nil maps are treated as empty.

Outputs:

float64 - Similarity score in range [0.0, 1.0].

Thread Safety: Safe for concurrent use (read-only on inputs).

func NewRouterAdapter

func NewRouterAdapter(router ToolRouter) agent.ToolRouter

NewRouterAdapter creates an adapter that implements agent.ToolRouter.

Inputs

  • router: The underlying ToolRouter (Granite4Router, EscalatingRouter, etc).

Outputs

  • agent.ToolRouter: The adapted router.

func RecordModelWarmup

func RecordModelWarmup(model string, durationSec float64, success bool)

RecordModelWarmup records model warmup metrics.

Inputs:

model - The model name.
durationSec - Warmup duration in seconds.
success - Whether warmup succeeded.

func RecordParamExtractionLatency

func RecordParamExtractionLatency(model, status string, durationSec float64)

RecordParamExtractionLatency records the latency of a parameter extraction call.

Inputs:

model - The extractor model name.
status - "success", "timeout", "error", or "parse_error".
durationSec - Duration in seconds.

func RecordParamExtractionTotal

func RecordParamExtractionTotal(model, status string)

RecordParamExtractionTotal records a parameter extraction attempt.

Inputs:

model - The extractor model name.
status - "success", "timeout", "error", "parse_error", or "fallback".

func RecordRouterInit

func RecordRouterInit(model string, success bool, reason string)

RecordRouterInit records router initialization metrics.

Description

Tracks router initialization attempts, successes, and failures. Use this to monitor router health and identify initialization issues.

Inputs

model - The router model name.
success - Whether initialization succeeded.
reason - Failure reason if !success (e.g., "model_manager_nil", "warmup_failed").
         Empty string if success=true.

Thread Safety

Safe for concurrent use.

func RecordRoutingConfidence

func RecordRoutingConfidence(model string, confidence float64)

RecordRoutingConfidence records a confidence score.

Inputs:

model - The router model name.
confidence - The confidence score (0.0-1.0).

func RecordRoutingError

func RecordRoutingError(model, errorType string)

RecordRoutingError records a routing error.

Inputs:

model - The router model name.
errorType - Error type (e.g., "timeout", "parse_error").

func RecordRoutingFallback

func RecordRoutingFallback(model, reason string)

RecordRoutingFallback records a fallback to main LLM.

Inputs:

model - The router model name (empty if router disabled).
reason - "error", "low_confidence", or "disabled".

func RecordRoutingLatency

func RecordRoutingLatency(model, status string, durationSec float64)

RecordRoutingLatency records the latency of a routing decision.

Inputs:

model - The router model name.
status - "success", "error", or "low_confidence".
durationSec - Duration in seconds.

func RecordRoutingSelection

func RecordRoutingSelection(model, tool string)

RecordRoutingSelection records a successful tool selection.

Inputs:

model - The router model name.
tool - The selected tool name.

func RecordSpeculativeExtraction

func RecordSpeculativeExtraction(outcome string, durationSec float64)

RecordSpeculativeExtraction records the outcome and duration of a speculative extraction.

Description:

CRS-14: Tracks speculative extraction outcomes to determine hit/mispredict
rates and latency impact for ROI analysis.

Inputs:

outcome - "hit", "hit_conversion_failed", "mispredict_reextracted",
          "mispredict_regex_fallback", or "error".
durationSec - Wall-clock time from speculative launch to outcome.

Thread Safety: Safe for concurrent use (Prometheus metrics are thread-safe).

func RecordUCB1AllBlocked

func RecordUCB1AllBlocked()

RecordUCB1AllBlocked records when all tools are blocked.

func RecordUCB1BlockedSelection

func RecordUCB1BlockedSelection(tool, reasonType string)

RecordUCB1BlockedSelection records a tool blocked by clause.

Inputs:

tool - The blocked tool name.
reasonType - Category of the blocking reason.

func RecordUCB1CacheHit

func RecordUCB1CacheHit()

RecordUCB1CacheHit records a cache hit.

func RecordUCB1CacheInvalidation

func RecordUCB1CacheInvalidation(reason string)

RecordUCB1CacheInvalidation records a cache invalidation.

Inputs:

reason - "ttl_expired", "generation_changed", or "evicted".

func RecordUCB1CacheMiss

func RecordUCB1CacheMiss()

RecordUCB1CacheMiss records a cache miss.

func RecordUCB1ForcedMove

func RecordUCB1ForcedMove(tool string)

RecordUCB1ForcedMove records a forced move.

Inputs:

tool - The forced tool name.

func RecordUCB1ScoringLatency

func RecordUCB1ScoringLatency(durationSec float64)

RecordUCB1ScoringLatency records UCB1 scoring duration.

Inputs:

durationSec - Duration in seconds.

func RecordUCB1Selection

func RecordUCB1Selection(tool string, score, proofPenalty, explorationBonus float64)

RecordUCB1Selection records a UCB1 tool selection.

Inputs:

tool - The selected tool name.
score - The UCB1 final score.
proofPenalty - The proof penalty applied.
explorationBonus - The exploration bonus.

Types

type AgentPreFilterResult

type AgentPreFilterResult struct {
	// NarrowedSpecs is the filtered set of tool specs for the router.
	NarrowedSpecs []agent.ToolRouterSpec

	// ForcedTool is set when the pre-filter deterministically selects a tool.
	ForcedTool string

	// ForcedReason explains why the tool was forced.
	ForcedReason string

	// Duration is how long the pre-filter took.
	Duration time.Duration

	// AppliedRules lists the rules that fired during filtering.
	AppliedRules []string

	// OriginalCount is the number of tools before filtering.
	OriginalCount int

	// NarrowedCount is the number of tools after filtering.
	NarrowedCount int
}

AgentPreFilterResult contains the pre-filter output using agent package types.

Description:

Same as PreFilterResult but uses agent.ToolRouterSpec instead of
routing.ToolSpec, for direct use in the execute phase.

type AllBlockedResult

type AllBlockedResult struct {
	// AllBlocked is true if every tool is blocked by clauses.
	AllBlocked bool `json:"all_blocked"`

	// BlockedCount is the number of blocked tools.
	BlockedCount int `json:"blocked_count"`

	// TotalTools is the total number of tools checked.
	TotalTools int `json:"total_tools"`

	// Reasons maps tool names to block reasons.
	Reasons map[string]string `json:"reasons,omitempty"`
}

AllBlockedResult contains the result when all tools are blocked.

type BM25Index

type BM25Index struct {
	// contains filtered or unexported fields
}

BM25Index is a pre-built inverted index over tool descriptions.

Description

Implements Okapi BM25 ranking over a corpus of tool "documents" where each document is the concatenation of a tool's keywords and use_when text. At query time, BM25 produces a ranked score for each tool that is proportional to how well its document matches the query terms, weighted by term rarity across all tools (IDF).

This replaces plain substring keyword counting, which has no IDF weighting and no morphological flexibility.

Thread Safety

BM25Index is immutable after construction via BuildBM25Index. Safe for concurrent use without additional synchronization.

func BuildBM25Index

func BuildBM25Index(specs []ToolSpec) *BM25Index

BuildBM25Index constructs a BM25Index from a slice of ToolSpecs.

Description

Each tool's "document" is built from its keywords and use_when text. Tokenization reuses ExtractQueryTerms from semantic.go (same package), which handles camelCase splitting and noise-word removal. IDF is computed with Lucene-style add-one smoothing to avoid zero division.

Inputs

  • specs: Tool specifications to index. Empty slice returns a valid but empty index that will produce zero scores for all queries.

Outputs

  • *BM25Index: The constructed index. Never nil.

Thread Safety

The returned index is immutable and safe for concurrent use.

func (*BM25Index) IsEmpty

func (idx *BM25Index) IsEmpty() bool

IsEmpty reports whether the index contains no tool documents.

Description

Returns true for an index built from nil or empty specs (the initial state of a lazily-initialized PreFilter). Used by scoreHybrid to trigger the one-time corpus build when allSpecs first becomes available.

Thread Safety

The BM25Index is immutable after construction. IsEmpty() is safe to call concurrently, but the caller must hold at least a read lock on the enclosing PreFilter.bm25mu when reading the *BM25Index pointer itself.

func (*BM25Index) Score

func (idx *BM25Index) Score(query string) map[string]float64

Score computes the BM25 score for each tool given a query string.

Description

Tokenizes the query, then for each tool computes:

score(tool, query) = Σ_t [ idf(t) × (tf(t,doc) × (k1+1)) / (tf(t,doc) + k1 × (1 - b + b × dl/avgdl)) ]

where t ranges over unique query terms present in the tool's document. Scores are normalized to [0.0, 1.0] by dividing by the maximum score.

Inputs

  • query: The raw query string. Empty query returns empty scores map.

Outputs

  • map[string]float64: Tool name → normalized BM25 score in [0.0, 1.0]. Tools with zero BM25 score are omitted from the result.

Thread Safety

Safe for concurrent use. Does not modify the index.

type BadgerRouterCacheStore

type BadgerRouterCacheStore struct {
	// contains filtered or unexported fields
}

BadgerRouterCacheStore implements RouterCacheStore backed by a BadgerDB instance. The DB is expected to be a service-global singleton opened at startup with its own path, separate from per-project CRS journals.

Description

Vectors are gob-encoded as map[string][]float32. Encoding is compact (~4 bytes/float32; 30 tools × 768 dims ≈ 90KB) and fast (~5µs encode/decode). The key is the corpus hash prefixed with the storage layout version string.

TTL is enforced by BadgerDB's native GC — no application-level expiry check is needed. Expired keys return ErrKeyNotFound, which this store treats as a cache miss.

Thread Safety

Safe for concurrent use. BadgerDB transactions are per-goroutine.

func NewBadgerRouterCacheStore

func NewBadgerRouterCacheStore(db *badgerstore.DB, ttl time.Duration, logger *slog.Logger) *BadgerRouterCacheStore

NewBadgerRouterCacheStore creates a BadgerRouterCacheStore backed by the given DB instance.

Description

The DB must be opened by the caller (typically in main) and must not be closed before the store is done being used. The caller is responsible for the DB lifecycle — this store does not own the DB.

Inputs

  • db: Opened BadgerDB wrapper. Must not be nil.
  • ttl: Lifetime for each cached entry. Pass 0 to use the default (7 days).
  • logger: Logger for cache hit/miss diagnostics. May be nil.

Outputs

  • *BadgerRouterCacheStore: Ready-to-use store. Never nil.

Thread Safety

The returned store is safe for concurrent use.

func (*BadgerRouterCacheStore) LoadEmbeddings

func (s *BadgerRouterCacheStore) LoadEmbeddings(ctx context.Context, corpusHash string) (map[string][]float32, error)

LoadEmbeddings retrieves cached unit-normalized tool embedding vectors.

Description

Looks up the key routing/emb/v1/{corpusHash}. Returns (nil, nil) on miss (key not found or TTL expired). Returns (nil, error) on storage or decode failure. Returns (vectors, nil) on success.

Inputs

  • ctx: Context for cancellation.
  • corpusHash: Hex SHA256 of the tool corpus + model name (from computeCorpusHash).

Outputs

  • map[string][]float32: Tool name → unit-normalized vector. Nil on miss or error.
  • error: Non-nil on storage or decode failure. Nil on miss and on success.

Thread Safety

Safe for concurrent use.

func (*BadgerRouterCacheStore) SaveEmbeddings

func (s *BadgerRouterCacheStore) SaveEmbeddings(ctx context.Context, corpusHash string, vectors map[string][]float32) error

SaveEmbeddings persists unit-normalized tool embedding vectors with a 7-day TTL.

Description

Encodes vectors as gob-encoded map[string][]float32 and writes to BadgerDB under the key routing/emb/v1/{corpusHash} with the configured TTL. After TTL expires, the key is invisible to LoadEmbeddings (returns cache miss).

Inputs

  • ctx: Context for cancellation.
  • corpusHash: Hex SHA256 of the tool corpus + model name.
  • vectors: Unit-normalized embedding vectors, keyed by tool name. Must not be empty.

Outputs

  • error: Non-nil on encode or storage failure.

Thread Safety

Safe for concurrent use.

type CacheMetrics

type CacheMetrics struct {
	// Hits is the number of cache hits.
	Hits int64 `json:"hits"`

	// Misses is the number of cache misses.
	Misses int64 `json:"misses"`

	// Invalidations is the number of invalidated entries.
	Invalidations int64 `json:"invalidations"`

	// HitRate is hits / (hits + misses).
	HitRate float64 `json:"hit_rate"`

	// Size is the current number of entries.
	Size int `json:"size"`
}

CacheMetrics contains cache performance statistics.

type ClauseChecker

type ClauseChecker interface {
	// IsBlocked checks if the assignment violates any clause.
	//
	// Inputs:
	//   - assignment: The variable assignment to check.
	//
	// Outputs:
	//   - bool: True if blocked by a clause.
	//   - string: Reason for blocking.
	IsBlocked(assignment map[string]bool) (bool, string)
}

ClauseChecker checks if an assignment violates learned clauses.

Description:

Interface for checking clause violations without tight coupling to CRS.

func NewClauseCheckerFromConstraintIndex

func NewClauseCheckerFromConstraintIndex(view crs.ConstraintIndexView) ClauseChecker

NewClauseCheckerFromConstraintIndex creates a ClauseChecker from a ConstraintIndexView.

Inputs:

view - The constraint index view.

Outputs:

ClauseChecker - The adapter.

type CodeContext

type CodeContext struct {
	// Language is the primary programming language (e.g., "go", "python").
	Language string `json:"language,omitempty"`

	// Files is the number of files currently loaded/indexed.
	Files int `json:"files,omitempty"`

	// Symbols is the number of symbols available for search.
	Symbols int `json:"symbols,omitempty"`

	// CurrentFile is the file currently being viewed/edited.
	CurrentFile string `json:"current_file,omitempty"`

	// RecentTools lists recently used tools (for context awareness).
	// DEPRECATED: Use ToolHistory instead for richer context.
	RecentTools []string `json:"recent_tools,omitempty"`

	// PreviousErrors contains tools that failed in this session.
	// The router should avoid suggesting these unless it can fix the issue.
	PreviousErrors []ToolError `json:"previous_errors,omitempty"`

	// ToolHistory contains the sequence of tools used with their results.
	// This enables history-aware routing where the router can see what
	// was already tried and what was learned from each tool.
	ToolHistory []ToolHistoryEntry `json:"tool_history,omitempty"`

	// Progress describes the current state of information gathering.
	// Example: "Found 3 entry points, read 2 files, identified main handler"
	Progress string `json:"progress,omitempty"`

	// StepNumber is the current execution step (1-indexed).
	StepNumber int `json:"step_number,omitempty"`
}

CodeContext provides optional context about the codebase.

Description

Gives the router additional context to make better decisions. For example, knowing the primary language helps select language-specific tools.

History-Aware Routing

This struct is designed to leverage Mamba2's O(n) linear complexity and 1M token context window. By including tool history with summaries, the router can make informed decisions about what information is still needed rather than suggesting the same tools repeatedly.

type EscalatingRouter

type EscalatingRouter struct {
	// contains filtered or unexported fields
}

EscalatingRouter wraps a primary ToolRouter and escalates to a larger model when the primary router's confidence is below a threshold.

Description:

Delegates to the primary router first. If the primary returns a selection
with confidence below the threshold AND an escalation router is configured,
re-routes the query through the escalation router with the full tool set
(bypassing the prefilter). If escalation fails, falls back to the primary
result (best effort).

Inputs:

primary   - The fast primary router (e.g., granite4:micro-h). Must not be nil.
escalation - The larger escalation router. Nil disables escalation.
threshold  - Minimum primary confidence to skip escalation. Default: 0.7.
allSpecs   - Full tool set for escalation (bypasses prefilter).
timeout    - Maximum time for escalation call. Default: 3s.
logger     - Logger instance. Must not be nil.

Thread Safety: Safe for concurrent use (delegates to thread-safe routers).

func NewEscalatingRouter

func NewEscalatingRouter(primary ToolRouter, escalation ToolRouter, threshold float64, allSpecs []ToolSpec, timeout time.Duration, logger *slog.Logger) *EscalatingRouter

NewEscalatingRouter creates a new EscalatingRouter.

Description:

Creates a router that delegates to primary first, then escalates to
a larger model when confidence is low. If escalation is nil, behaves
identically to the primary router (zero overhead).

Inputs:

primary    - The fast primary router. Must not be nil.
escalation - The larger escalation router. Nil disables escalation.
threshold  - Minimum primary confidence to skip escalation.
allSpecs   - Full tool set for escalation calls.
timeout    - Maximum time for escalation. Zero uses default (3s).
logger     - Logger instance. Must not be nil.

Outputs:

*EscalatingRouter - The constructed router. Never nil.

Limitations:

allSpecs must be set at construction time. If the tool set changes,
a new EscalatingRouter must be created.

Assumptions:

Both primary and escalation routers are already warmed.

func (*EscalatingRouter) Close

func (r *EscalatingRouter) Close() error

Close releases resources held by both routers.

Outputs:

error - Non-nil if either router's Close fails.

func (*EscalatingRouter) Model

func (r *EscalatingRouter) Model() string

Model returns the primary router's model name.

Outputs:

string - The model name of the primary router.

func (*EscalatingRouter) SelectTool

func (r *EscalatingRouter) SelectTool(ctx context.Context, query string, availableTools []ToolSpec, codeCtx *CodeContext) (*ToolSelection, error)

SelectTool chooses the best tool, escalating to a larger model if needed.

Description:

  1. Calls the primary router with the prefiltered candidate set.
  2. If the primary's confidence >= threshold, returns immediately.
  3. If confidence is low and escalation is configured, calls the escalation router with the FULL tool set (all 55 tools).
  4. If escalation fails or times out, returns the primary result (best effort).

Inputs:

ctx            - Context for cancellation/timeout. Must not be nil.
query          - The user's question or request.
availableTools - Prefiltered tools for the primary router.
codeCtx        - Optional code context (symbols, files loaded, etc.)

Outputs:

*ToolSelection - The selected tool and confidence.
error          - Non-nil only if the primary router fails.

Thread Safety: Safe for concurrent use.

func (*EscalatingRouter) SetAllSpecs

func (r *EscalatingRouter) SetAllSpecs(specs []ToolSpec)

SetAllSpecs sets the full tool set used for escalation calls.

Description:

This is a post-construction setter for cases where the full tool set
is not available at EscalatingRouter creation time (e.g., when tool
registration happens after router wiring). Must be called before the
first query if escalation is enabled.

Inputs:

specs - The full tool spec set. Nil disables escalation passthrough.

Thread Safety: NOT safe for concurrent use with SelectTool. Call during init only.

type ForcedMoveChecker

type ForcedMoveChecker struct {
	// contains filtered or unexported fields
}

ForcedMoveChecker detects when only one tool is viable.

Description:

Uses unit propagation logic from CDCL to detect forced moves.
A move is forced when all but one tool are blocked by learned clauses.

This is analogous to SAT unit propagation:
  - Each tool is a "variable"
  - Clauses constrain which tools can be used
  - When only one tool satisfies all clauses, it's forced

Thread Safety: Safe for concurrent use (stateless).

func NewForcedMoveChecker

func NewForcedMoveChecker() *ForcedMoveChecker

NewForcedMoveChecker creates a new forced move checker.

Outputs:

*ForcedMoveChecker - The checker instance.

func NewForcedMoveCheckerWithConfig

func NewForcedMoveCheckerWithConfig(config *ForcedMoveCheckerConfig) *ForcedMoveChecker

NewForcedMoveCheckerWithConfig creates a checker with config.

Inputs:

config - The configuration. If nil, uses defaults.

Outputs:

*ForcedMoveChecker - The checker instance.

func (*ForcedMoveChecker) CheckAllBlocked

func (c *ForcedMoveChecker) CheckAllBlocked(
	availableTools []string,
	clauseChecker ClauseChecker,
	currentAssignment map[string]bool,
) AllBlockedResult

CheckAllBlocked checks if all available tools are blocked.

Description:

When all tools are blocked, the agent must synthesize an answer from
gathered information. This detects that situation.

Inputs:

availableTools - List of tool names.
clauseChecker - The clause checker.
currentAssignment - Current variable assignment.

Outputs:

AllBlockedResult - Result indicating if all blocked.

Thread Safety: Safe for concurrent use.

func (*ForcedMoveChecker) CheckForcedMove

func (c *ForcedMoveChecker) CheckForcedMove(
	availableTools []string,
	clauseChecker ClauseChecker,
	currentAssignment map[string]bool,
) ForcedMoveResult

CheckForcedMove uses unit propagation to detect if only one tool is viable.

Description:

For each available tool, checks if selecting it would violate any
learned clause. If only one tool passes all clause checks, it's forced.

Inputs:

availableTools - List of tool names that could be selected.
clauseChecker - The clause checker for violation detection.
currentAssignment - Current variable assignment (step history context).

Outputs:

ForcedMoveResult - Result containing forced tool or viable count.

Thread Safety: Safe for concurrent use.

func (*ForcedMoveChecker) CheckForcedMoveFromSteps

func (c *ForcedMoveChecker) CheckForcedMoveFromSteps(
	availableTools []string,
	steps []crs.StepRecord,
	clauseChecker ClauseChecker,
) ForcedMoveResult

CheckForcedMoveFromSteps is a convenience method using CRS step history.

Description:

Builds the current assignment from step history and checks for forced moves.

Inputs:

availableTools - List of tool names.
steps - Step history from CRS.
clauseChecker - The clause checker.

Outputs:

ForcedMoveResult - Result containing forced tool or viable count.

Thread Safety: Safe for concurrent use.

type ForcedMoveCheckerConfig

type ForcedMoveCheckerConfig struct {
	// Logger for debug output. If nil, uses default.
	Logger *slog.Logger
}

ForcedMoveCheckerConfig configures the checker.

type ForcedMoveResult

type ForcedMoveResult struct {
	// IsForced is true if exactly one tool is viable.
	IsForced bool `json:"is_forced"`

	// ForcedTool is the tool name if IsForced is true.
	ForcedTool string `json:"forced_tool,omitempty"`

	// ViableCount is the number of viable (non-blocked) tools.
	ViableCount int `json:"viable_count"`

	// BlockedTools lists tools blocked by clauses.
	BlockedTools []string `json:"blocked_tools,omitempty"`

	// BlockReasons maps blocked tool to the reason.
	BlockReasons map[string]string `json:"block_reasons,omitempty"`
}

ForcedMoveResult contains the result of forced move detection.

Description:

When unit propagation determines that only one tool is viable (all others
are blocked by learned clauses), that tool is "forced". This allows the
agent to skip the router entirely in deterministic situations.

type Granite4Router

type Granite4Router struct {
	// contains filtered or unexported fields
}

Granite4Router implements ToolRouter using a fast model for tool routing.

Description

Uses a fast model (e.g., IBM Granite4 with hybrid Mamba-2 architecture) for ultra-fast tool routing. The ChatClient interface allows any provider (Ollama, Anthropic, OpenAI, Gemini) to be used as the routing backend.

CB-60: Refactored to use providers.ChatClient instead of direct MultiModelManager dependency, enabling any LLM provider for tool routing.

Thread Safety

Granite4Router is safe for concurrent use.

func NewGranite4Router

func NewGranite4Router(chatClient providers.ChatClient, config RouterConfig) (*Granite4Router, error)

NewGranite4Router creates a new router with the specified configuration.

Description

Creates a router that uses a fast model for tool selection. The ChatClient interface allows any provider (Ollama, Anthropic, OpenAI, Gemini) to be used.

CB-60: Accepts providers.ChatClient instead of *llm.MultiModelManager.

Inputs

  • chatClient: ChatClient for sending routing queries. Must not be nil.
  • config: Router configuration.

Outputs

  • *Granite4Router: Configured router.
  • error: Non-nil if initialization fails.

Example

factory := providers.NewProviderFactory(modelManager)
client, _ := factory.CreateChatClient(cfg)
config := routing.DefaultRouterConfig()
router, err := routing.NewGranite4Router(client, config)

func NewGranite4RouterWithDefaults

func NewGranite4RouterWithDefaults(ollamaEndpoint string) (*Granite4Router, error)

NewGranite4RouterWithDefaults creates a router with default configuration.

Description

Convenience constructor that creates a MultiModelManager wrapped in an OllamaChatAdapter and uses default configuration. For more control or non-Ollama providers, use NewGranite4Router with a custom ChatClient.

Inputs

  • ollamaEndpoint: Ollama server URL.

Outputs

  • *Granite4Router: Configured router.
  • error: Non-nil if initialization fails.

func (*Granite4Router) Close

func (r *Granite4Router) Close() error

Close releases any resources held by the router.

Description

Currently a no-op since the MultiModelManager is shared. Future implementations might unload the router model here.

func (*Granite4Router) FilterBatch

func (r *Granite4Router) FilterBatch(ctx context.Context, prompt string) (string, error)

FilterBatch evaluates a batch of tool calls and returns filter decisions.

Description

Uses the router model to decide which tool calls in a batch should be executed (KEEP) vs skipped (SKIP). This helps reduce redundant tool calls when the main LLM requests multiple similar operations.

GR-39a: Implements the BatchFilterer interface for semantic deduplication.

Inputs

  • ctx: Context for cancellation/timeout.
  • prompt: Filter prompt containing tool calls with similarity scores.

Outputs

  • string: Response with KEEP/SKIP decisions (e.g., "1:KEEP 2:SKIP 3:KEEP").
  • error: Non-nil if the filter call fails.

Thread Safety

This method is safe for concurrent use.

func (*Granite4Router) Model

func (r *Granite4Router) Model() string

Model returns the model being used for routing.

func (*Granite4Router) SelectTool

func (r *Granite4Router) SelectTool(ctx context.Context, query string, availableTools []ToolSpec, codeContext *CodeContext) (*ToolSelection, error)

SelectTool chooses the best tool for the given query.

Description

Uses the micro LLM to analyze the query and select the most appropriate tool. Returns an error if the selection fails or confidence is too low.

Inputs

  • ctx: Context for cancellation/timeout.
  • query: The user's question or request.
  • availableTools: Tools currently available.
  • codeContext: Optional context about the codebase.

Outputs

  • *ToolSelection: The selected tool with confidence.
  • error: Non-nil if routing fails.

func (*Granite4Router) WarmRouter

func (r *Granite4Router) WarmRouter(ctx context.Context, lifecycle providers.ModelLifecycleManager) error

WarmRouter pre-loads the routing model using the provided lifecycle manager.

Description

Warms up the router model so the first routing request doesn't incur model loading latency. Should be called during initialization.

CB-60: Refactored to accept a ModelLifecycleManager instead of using the internal model manager. For cloud providers, warmup is a no-op.

Inputs

  • ctx: Context for cancellation.
  • lifecycle: Lifecycle manager for model warmup. If nil, warmup is skipped.

Outputs

  • error: Non-nil if warmup fails.

type ParamExtractor

type ParamExtractor struct {
	// contains filtered or unexported fields
}

ParamExtractor uses a fast LLM to extract tool parameters from natural language queries. It corrects errors using semantic understanding of hierarchical scoping (e.g., project vs. module names).

Description

The regex-based parameter extraction in extractToolParameters() fails on queries with hierarchical scope references (e.g., "in the Flask helpers module" extracts "flask" instead of "helpers"). ParamExtractor uses a dedicated small model (ministral-3:3b by default) that runs in parallel with the tool router (granite4:micro-h) on a separate Ollama instance.

IT-08e: The extractor runs speculatively on the pre-filter's top candidate BEFORE routing completes. If the router confirms the prediction, LLM params are used; otherwise regex fallback kicks in.

CB-60: Refactored to use providers.ChatClient instead of direct MultiModelManager dependency, enabling any LLM provider.

Thread Safety

ParamExtractor is safe for concurrent use.

func NewParamExtractor

func NewParamExtractor(chatClient providers.ChatClient, config ParamExtractorConfig) (*ParamExtractor, error)

NewParamExtractor creates a new LLM-based parameter extractor.

Description

Creates a ParamExtractor that uses the specified model for semantic parameter extraction. The ChatClient interface allows any provider (Ollama, Anthropic, OpenAI, Gemini) to be used.

CB-60: Accepts providers.ChatClient instead of *llm.MultiModelManager.

Inputs

  • chatClient: ChatClient for sending extraction queries. Must not be nil.
  • config: Extractor configuration.

Outputs

  • *ParamExtractor: Configured extractor.
  • error: Non-nil if chatClient is nil.

func (*ParamExtractor) ExtractParams

func (e *ParamExtractor) ExtractParams(
	ctx context.Context,
	query string,
	toolName string,
	paramSchemas []ParamSchema,
	regexHint map[string]any,
) (map[string]any, error)

ExtractParams uses the LLM to extract or correct tool parameters.

Description

Takes the user query, tool name, parameter schema, and the regex-based extraction result. Sends these to the LLM which acts as a semantic parser to correct hierarchical scoping errors. Returns the corrected parameters or an error (caller should fall back to regex result).

Inputs

  • ctx: Context for cancellation/timeout.
  • query: The user's natural language query.
  • toolName: The name of the selected tool.
  • paramSchemas: Parameter definitions for the tool.
  • regexHint: The regex extractor's output (used as a hint).

Outputs

  • map[string]any: Corrected parameter values.
  • error: Non-nil if extraction fails (caller should use regexHint).

Thread Safety

Safe for concurrent use.

func (*ParamExtractor) IsEnabled

func (e *ParamExtractor) IsEnabled() bool

IsEnabled returns true if the extractor is enabled.

Outputs

  • bool: True if the feature flag is set.

Thread Safety

Safe for concurrent use (reads immutable config).

func (*ParamExtractor) ResolveConceptualSymbol

func (e *ParamExtractor) ResolveConceptualSymbol(
	ctx context.Context,
	query string,
	candidates []agent.SymbolCandidate,
	tier0Count, tier1Count int,
	sourceContext string,
) (string, error)

ResolveConceptualSymbol uses the LLM to pick the best symbol from candidates when the query uses conceptual descriptions instead of function names.

Description

IT-12: When regex-based extraction produces words like "assigning" that don't match any symbol in the index, this method searches the index for keywords from the query, collects candidate symbols, and asks the LLM to pick the best starting point. This gives the LLM actual codebase knowledge it otherwise lacks.

Inputs

  • ctx: Context for cancellation/timeout. Must not be nil.
  • query: The user's natural language query.
  • candidates: Symbol candidates found by keyword search of the index.
  • tier0Count: Number of tier0 (best match) candidates at the start of the list.
  • tier1Count: Number of tier1 (partial match) candidates after tier0.

Outputs

  • string: The best symbol name from the candidates.
  • error: Non-nil if resolution fails (extractor disabled, no candidates, LLM returns invalid name).

Thread Safety

Safe for concurrent use.

type ParamExtractorConfig

type ParamExtractorConfig struct {
	// Model is the Ollama model to use for parameter extraction.
	// IT-08e: Dedicated small model, separate from the router.
	// Default: "ministral-3:3b"
	Model string `json:"model"`

	// Timeout is the maximum time for a parameter extraction call.
	// IT-08e: Increased from 500ms to 2s (dedicated model, no serialization).
	// Default: 2s
	Timeout time.Duration `json:"timeout"`

	// Temperature controls randomness. Lower = more deterministic.
	// Default: 0.1
	Temperature float64 `json:"temperature"`

	// MaxTokens limits the response length.
	// Default: 512
	MaxTokens int `json:"max_tokens"`

	// NumCtx is the context window size.
	// IT-08e: Reduced from 8192 to 4096 (12x headroom for ~300 token budget).
	// Default: 4096
	NumCtx int `json:"num_ctx"`

	// KeepAlive controls how long the model stays in VRAM.
	// Default: "24h"
	KeepAlive string `json:"keep_alive"`

	// Enabled is the feature flag. When false, ExtractParams is a no-op.
	// Default: true
	Enabled bool `json:"enabled"`
}

ParamExtractorConfig configures the parameter extractor.

Description

Controls the model, timeout, and feature flag for LLM parameter extraction. IT-08e: Uses a dedicated small model (ministral-3:3b) separate from the tool router to enable parallel execution without Ollama serialization.

func DefaultParamExtractorConfig

func DefaultParamExtractorConfig() ParamExtractorConfig

DefaultParamExtractorConfig returns sensible defaults.

Outputs

  • ParamExtractorConfig: Default configuration.

type ParamSchema

type ParamSchema = agent.ParamExtractorSchema

ParamSchema describes a single parameter for the LLM prompt. This is an alias for the agent-level interface type.

type PreFilter

type PreFilter struct {
	// contains filtered or unexported fields
}

PreFilter narrows tool candidates before the LLM router classifies.

Description:

Implements a 5-phase deterministic pipeline:
1. Forced mapping check (exact phrases → deterministic tool)
2. Negation detection (negation word + keyword proximity → redirect)
3. Keyword matching (registry keyword index → scored candidates)
4. Confusion pair resolution (pattern boost for ambiguous pairs)
5. Candidate selection (top N by score, floor at min)

Inputs:

registry - The tool routing registry for keyword lookup. May be nil (passthrough).
cfg - Pre-filter configuration with rules. Must not be nil.
logger - Logger for structured output. Must not be nil.

Thread Safety: Safe for concurrent use (all state is read-only after construction).

func NewPreFilter

func NewPreFilter(registry *config.ToolRoutingRegistry, cfg *config.PreFilterConfig, logger *slog.Logger, store RouterCacheStore) *PreFilter

NewPreFilter creates a new PreFilter.

Description:

Creates a pre-filter with the given registry and configuration.
If registry is nil, keyword matching (Phase 3) falls back to legacy
BestFor substring matching.

IT-06c: BM25 and embedding components are lazily initialized on the first
Filter/FilterAgentSpecs call that provides non-empty tool specs. The embedding
warm-up runs once in a background goroutine; Phase 3 degrades gracefully to
BM25-only while warm-up is in progress.

GR-61: If store is non-nil, the embedding cache will load pre-computed
vectors from BadgerDB on warm-up (skipping Ollama) and persist newly
computed vectors for future service restarts. Pass nil for tests and for
deployments without a routing cache directory.

Inputs:

registry - Tool routing registry for keyword lookup. May be nil.
cfg      - Pre-filter configuration. Must not be nil.
logger   - Logger instance. Must not be nil.
store    - Optional BadgerDB embedding cache store. Nil disables persistence.

Outputs:

*PreFilter - The constructed pre-filter.

Thread Safety: The returned PreFilter is safe for concurrent use.

func (*PreFilter) Filter

func (pf *PreFilter) Filter(ctx context.Context, query string, allSpecs []ToolSpec, sessionCounts map[string]int) *PreFilterResult

Filter narrows the tool candidate set based on query analysis.

Description:

Runs the 5-phase pipeline to either force a tool selection or
narrow the candidate set for the LLM router. Returns all specs
unchanged (passthrough) when disabled, query is empty, or no
rules match.

IT-06c: Phase 3 now uses hybrid BM25 + embedding scoring instead of
plain keyword substring counting. sessionCounts provides per-tool
selection counts for the current session; tools already selected
receive a UCB1 exploration penalty. Pass nil to disable the penalty.

Inputs:

ctx           - Context for tracing and cancellation. Must not be nil.
query         - The user's query string.
allSpecs      - All available tool specs.
sessionCounts - Current session tool selection counts (tool → count). May be nil.

Outputs:

*PreFilterResult - The filtering result with narrowed specs or forced tool.

Thread Safety: Safe for concurrent use.

func (*PreFilter) FilterAgentSpecs

func (pf *PreFilter) FilterAgentSpecs(ctx context.Context, query string, allSpecs []agent.ToolRouterSpec, sessionCounts map[string]int) *AgentPreFilterResult

FilterAgentSpecs narrows agent.ToolRouterSpec candidates.

Description:

Converts agent types to routing types, runs the pre-filter pipeline,
and converts results back. This is the primary integration point for
the execute phase.

IT-06c: sessionCounts provides per-tool selection counts for the current
session, used by Phase 3 UCB1 exploration penalty. Pass nil to disable.

Inputs:

ctx           - Context for tracing.
query         - The user's query string.
allSpecs      - All available tool specs in agent format.
sessionCounts - Per-tool selection counts for this session. May be nil.

Outputs:

*AgentPreFilterResult - The filtering result.

Thread Safety: Safe for concurrent use.

type PreFilterResult

type PreFilterResult struct {
	// NarrowedSpecs is the filtered set of tool specs for the router.
	NarrowedSpecs []ToolSpec

	// ForcedTool is set when the pre-filter deterministically selects a tool.
	// When non-empty, the router should be skipped entirely.
	ForcedTool string

	// ForcedReason explains why the tool was forced.
	ForcedReason string

	// Scores maps tool name to its pre-filter score.
	Scores map[string]float64

	// AppliedRules lists the rules that fired during filtering.
	AppliedRules []string

	// OriginalCount is the number of tools before filtering.
	OriginalCount int

	// NarrowedCount is the number of tools after filtering.
	NarrowedCount int

	// Duration is how long the pre-filter took.
	Duration time.Duration
}

PreFilterResult contains the output of a pre-filter operation.

Description:

Holds either a forced tool selection (skip router) or a narrowed
set of candidates for the router to classify.

type PromptBuilder

type PromptBuilder struct {
	// contains filtered or unexported fields
}

PromptBuilder constructs dynamic system prompts for tool routing.

Description

Builds prompts that include available tools, their descriptions, and context about the codebase. The prompt instructs the router to output a JSON object with the selected tool and confidence score.

Thread Safety

PromptBuilder is safe for concurrent use.

func NewPromptBuilder

func NewPromptBuilder() (*PromptBuilder, error)

NewPromptBuilder creates a new PromptBuilder.

Outputs

  • *PromptBuilder: Configured builder.
  • error: Non-nil if template parsing fails.

func (*PromptBuilder) BuildSystemPrompt

func (p *PromptBuilder) BuildSystemPrompt(tools []ToolSpec, context *CodeContext) (string, error)

BuildSystemPrompt generates the system prompt for the router.

Description

Creates a system prompt that includes all available tools, their descriptions, and instructions for JSON output format.

Inputs

  • tools: Available tools for selection.
  • context: Optional codebase context.

Outputs

  • string: The rendered system prompt.
  • error: Non-nil if template rendering fails.

func (*PromptBuilder) BuildUserPrompt

func (p *PromptBuilder) BuildUserPrompt(query string) string

BuildUserPrompt generates the user message containing the query.

Inputs

  • query: The user's question/request.

Outputs

  • string: The rendered user prompt.

type PromptData

type PromptData struct {
	// Tools is the list of available tools.
	Tools []ToolSpec

	// Context contains optional codebase context.
	Context *CodeContext

	// Query is the user's question/request.
	Query string
}

PromptData contains the data for prompt template rendering.

type RouterAdapter

type RouterAdapter struct {
	// contains filtered or unexported fields
}

RouterAdapter adapts a routing.ToolRouter to the agent.ToolRouter interface.

Description

The routing package defines its own types (ToolSpec, CodeContext, ToolSelection) for internal use, but the agent package expects its own types (ToolRouterSpec, ToolRouterCodeContext, ToolRouterSelection). This adapter bridges the two.

Thread Safety

RouterAdapter is safe for concurrent use if the underlying router is.

func (*RouterAdapter) Close

func (a *RouterAdapter) Close() error

Close implements agent.ToolRouter.

func (*RouterAdapter) Model

func (a *RouterAdapter) Model() string

Model implements agent.ToolRouter.

func (*RouterAdapter) SelectTool

func (a *RouterAdapter) SelectTool(ctx context.Context, query string, availableTools []agent.ToolRouterSpec, codeContext *agent.ToolRouterCodeContext) (*agent.ToolRouterSelection, error)

SelectTool implements agent.ToolRouter.

Converts agent types to routing types, calls the underlying router, and converts the result back to agent types.

func (*RouterAdapter) WarmRouter

func (a *RouterAdapter) WarmRouter(ctx context.Context, lifecycle providers.ModelLifecycleManager) error

WarmRouter exposes the underlying router's WarmRouter method.

Description

Allows callers to warm the router model. This is not part of the agent.ToolRouter interface but is useful during initialization. If the underlying router does not support WarmRouter (e.g., EscalatingRouter), this is a no-op.

CB-60: Now accepts a ModelLifecycleManager for provider-agnostic warmup.

type RouterCacheStore

type RouterCacheStore interface {
	// LoadEmbeddings retrieves cached unit-normalized tool embedding vectors
	// for the given corpus hash.
	//
	// Returns (nil, nil) on cache miss (key absent or TTL expired).
	// Returns (nil, error) on storage failure.
	// Returns (vectors, nil) on cache hit; vectors is never empty on success.
	LoadEmbeddings(ctx context.Context, corpusHash string) (map[string][]float32, error)

	// SaveEmbeddings persists unit-normalized tool embedding vectors for the
	// given corpus hash. The store applies a 7-day TTL automatically.
	//
	// Returns non-nil error only on storage failure. The caller logs the error
	// as a warning and continues — persistence failure is non-fatal; vectors
	// will be recomputed on the next service restart.
	SaveEmbeddings(ctx context.Context, corpusHash string, vectors map[string][]float32) error
}

RouterCacheStore persists tool embedding vectors across service restarts.

Description

The store is keyed by corpus hash — a SHA256 digest of all tool names, keywords, and use_when text plus the embedding model name. Any change to the tool registry or model automatically produces a different hash, so the previous entry becomes unreachable (expires via TTL) without explicit invalidation.

Both methods are nil-safe: the PreFilter and ToolEmbeddingCache check for a nil RouterCacheStore and skip persistence, operating in in-memory-only mode. This is the correct behavior for tests and for deployments that do not configure a routing cache directory.

Thread Safety

Implementations must be safe for concurrent use.

type RouterConfig

type RouterConfig struct {
	// Model is the Ollama model to use for routing (e.g., "granite4:micro-h").
	Model string `json:"model"`

	// OllamaEndpoint is the Ollama server URL (e.g., "http://localhost:11434").
	OllamaEndpoint string `json:"ollama_endpoint"`

	// Timeout is the maximum time for a routing decision.
	// Recommended: 500ms. If exceeded, returns error for fallback.
	Timeout time.Duration `json:"timeout"`

	// Temperature controls randomness. Lower = more deterministic.
	// Recommended: 0.1 for routing (we want consistency).
	Temperature float64 `json:"temperature"`

	// ConfidenceThreshold is the minimum confidence for a selection.
	// Below this, the router returns an error for fallback to main LLM.
	ConfidenceThreshold float64 `json:"confidence_threshold"`

	// KeepAlive controls how long the router model stays in VRAM.
	// "-1" = infinite (recommended when using with main LLM).
	KeepAlive string `json:"keep_alive"`

	// MaxTokens limits the router's response length.
	// Should be small since we only need JSON output.
	// Recommended: 256.
	MaxTokens int `json:"max_tokens"`

	// NumCtx sets the context window size for the router model.
	// Router only needs to see current query, available tools, and recent history.
	// Recommended: 16384 (16K tokens) to minimize VRAM usage and allow main agent larger context.
	// Note: On systems with limited VRAM (32GB), keeping router context low allows main agent 64K+ context.
	NumCtx int `json:"num_ctx"`

	// EscalationModel is the Ollama model used for escalation when the primary
	// router's confidence is below EscalationThreshold. Empty string disables
	// escalation. Example: "granite3.3:8b".
	EscalationModel string `json:"escalation_model,omitempty"`

	// EscalationThreshold is the minimum confidence from the primary router
	// below which escalation to the larger model is triggered. Default: 0.7.
	EscalationThreshold float64 `json:"escalation_threshold,omitempty"`

	// EscalationTimeout is the maximum time for an escalation routing decision.
	// Default: 3s.
	EscalationTimeout time.Duration `json:"escalation_timeout,omitempty"`
}

RouterConfig configures the tool router behavior.

Description

Controls the router's model selection, timeout, and fallback behavior.

func DefaultRouterConfig

func DefaultRouterConfig() RouterConfig

DefaultRouterConfig returns sensible defaults for the router.

Outputs

  • RouterConfig: Default configuration.

type RouterError

type RouterError struct {
	// Code is a machine-readable error code.
	Code string `json:"code"`

	// Message is a human-readable error message.
	Message string `json:"message"`

	// Retryable indicates if the error might resolve on retry.
	Retryable bool `json:"retryable"`
}

RouterError represents an error from the tool router.

func NewRouterError

func NewRouterError(code, message string, retryable bool) *RouterError

NewRouterError creates a new RouterError.

func (*RouterError) Error

func (e *RouterError) Error() string

Error implements the error interface.

type RouterResult

type RouterResult struct {
	// Tool is the tool name.
	Tool string

	// Confidence is the router's confidence (0.0-1.0).
	Confidence float64
}

RouterResult represents a tool selection from the router.

Description:

Used as input to ScoreTools to provide the router's initial ranking.

type SelectionCounts

type SelectionCounts struct {
	// contains filtered or unexported fields
}

SelectionCounts tracks tool selection counts for UCB1 exploration term.

Description:

Maintains per-session tool selection counts for the UCB1 exploration term.
Selection counts are used to calculate the exploration bonus:
  exploration = C * sqrt(ln(total) / count)

Thread Safety: Safe for concurrent use.

func NewSelectionCounts

func NewSelectionCounts() *SelectionCounts

NewSelectionCounts creates a new selection counter.

Outputs:

*SelectionCounts - The counter instance.

func (*SelectionCounts) AsMap

func (c *SelectionCounts) AsMap() map[string]int

AsMap returns the counts as a map.

Outputs:

map[string]int - Copy of the counts map.

Thread Safety: Safe for concurrent use.

func (*SelectionCounts) Get

func (c *SelectionCounts) Get(tool string) int

Get returns the count for a tool.

Inputs:

tool - The tool name.

Outputs:

int - The selection count (0 if never selected).

Thread Safety: Safe for concurrent use.

func (*SelectionCounts) Increment

func (c *SelectionCounts) Increment(tool string)

Increment increases the count for a tool.

Inputs:

tool - The tool name.

Thread Safety: Safe for concurrent use.

func (*SelectionCounts) Reset

func (c *SelectionCounts) Reset()

Reset clears all counts.

Thread Safety: Safe for concurrent use.

func (*SelectionCounts) Total

func (c *SelectionCounts) Total() int

Total returns the total selections across all tools.

Outputs:

int - Total selection count.

Thread Safety: Safe for concurrent use.

type SemanticConfig

type SemanticConfig struct {
	// DuplicateThreshold is the similarity threshold for blocking (default 0.8).
	// Calls with similarity >= this are considered semantic duplicates.
	DuplicateThreshold float64

	// PenaltyThreshold is the similarity threshold for penalizing (default 0.3).
	// Calls with similarity >= this (but < duplicate) get UCB1 penalty.
	PenaltyThreshold float64

	// PenaltyWeight is the UCB1 penalty multiplier (default 0.5).
	// SemanticPenalty = similarity * PenaltyWeight.
	PenaltyWeight float64

	// MaxHistorySize is the maximum tool calls to retain (default 100).
	MaxHistorySize int
}

SemanticConfig holds configurable thresholds for semantic routing.

Description:

Allows customization of semantic duplicate detection behavior.
Use DefaultSemanticConfig() for production defaults.

Thread Safety: Immutable after creation. Safe to share across goroutines.

func DefaultSemanticConfig

func DefaultSemanticConfig() SemanticConfig

DefaultSemanticConfig returns production-ready default configuration.

Outputs:

SemanticConfig - Configuration with default values.

Thread Safety: Safe for concurrent use (returns value type).

func (SemanticConfig) Validate

func (c SemanticConfig) Validate() bool

Validate checks that configuration values are sensible.

Outputs:

bool - True if configuration is valid.

Thread Safety: Safe for concurrent use (read-only).

type StateKeyBuilder

type StateKeyBuilder struct{}

StateKeyBuilder builds cache keys from tool execution history.

Description:

Generates deterministic, order-preserving keys from the tool execution
sequence. Unlike transposition keys (which are order-independent),
these keys preserve the sequence because tool order matters.

Key format: "gen:<generation>|<tool1>:<outcome1>→<tool2>:<outcome2>→..."

Thread Safety: StateKeyBuilder is safe for concurrent use (stateless).

func NewStateKeyBuilder

func NewStateKeyBuilder() *StateKeyBuilder

NewStateKeyBuilder creates a new state key builder.

Outputs:

*StateKeyBuilder - The builder instance.

func (*StateKeyBuilder) BuildKey

func (b *StateKeyBuilder) BuildKey(steps []crs.StepRecord, generation int64) string

BuildKey generates a cache key from step history and CRS generation.

Description:

Creates an order-preserving key that captures the tool execution sequence.
The key includes the CRS generation for automatic invalidation.

Inputs:

steps - The step history from CRS.
generation - The current CRS generation.

Outputs:

string - The cache key.

func (*StateKeyBuilder) BuildKeyFromHistory

func (b *StateKeyBuilder) BuildKeyFromHistory(history []ToolHistoryEntry, generation int64) string

BuildKeyFromHistory generates a cache key from tool history entries.

Description:

Alternative method for building keys from ToolHistoryEntry slice.

Inputs:

history - The tool history entries.
generation - The current CRS generation.

Outputs:

string - The cache key.

type ToolCallHistory

type ToolCallHistory struct {
	// contains filtered or unexported fields
}

ToolCallHistory tracks tool calls with semantic signatures.

Description:

Maintains a history of tool calls for semantic duplicate detection.
Provides methods to check if a proposed call is similar to prior calls.
R1.1 Fix: History is limited to MaxToolCallHistorySize entries.
P1.1 Fix: Tool-indexed map for O(1) tool lookup.

Thread Safety: Safe for concurrent use. All public methods acquire locks.

func NewToolCallHistory

func NewToolCallHistory() *ToolCallHistory

NewToolCallHistory creates a new empty history with default configuration.

Outputs:

*ToolCallHistory - The history instance.

Thread Safety: The returned instance is safe for concurrent use.

func NewToolCallHistoryWithConfig

func NewToolCallHistoryWithConfig(config SemanticConfig) *ToolCallHistory

NewToolCallHistoryWithConfig creates a new empty history with full configuration.

Inputs:

config - Semantic configuration. If invalid, uses defaults.

Outputs:

*ToolCallHistory - The history instance.

Thread Safety: The returned instance is safe for concurrent use.

func NewToolCallHistoryWithSize deprecated

func NewToolCallHistoryWithSize(maxSize int) *ToolCallHistory

NewToolCallHistoryWithSize creates a new empty history with specified max size.

Inputs:

maxSize - Maximum number of entries to retain. If <= 0, uses default.

Outputs:

*ToolCallHistory - The history instance.

Thread Safety: The returned instance is safe for concurrent use.

Deprecated: Use NewToolCallHistoryWithConfig for full configuration.

func (*ToolCallHistory) Add

func (h *ToolCallHistory) Add(sig ToolCallSignature)

Add records a tool call signature in the history.

Description:

Appends the signature to history. If history exceeds maxSize,
older entries are evicted (sliding window). Updates the tool index.

Inputs:

sig - The tool call signature to record.

Thread Safety: Safe for concurrent use. Acquires write lock.

func (*ToolCallHistory) Clear

func (h *ToolCallHistory) Clear()

Clear removes all recorded calls and resets the tool index.

Thread Safety: Safe for concurrent use. Acquires write lock.

func (*ToolCallHistory) Config

func (h *ToolCallHistory) Config() SemanticConfig

Config returns the semantic configuration.

Thread Safety: Safe for concurrent use (returns copy of immutable config).

func (*ToolCallHistory) GetCallsForTool

func (h *ToolCallHistory) GetCallsForTool(tool string) []ToolCallSignature

GetCallsForTool returns all calls for a specific tool.

Description:

P1.1 Fix: Uses tool index for O(1) lookup instead of O(n) scan.

Inputs:

tool - The tool name to filter by.

Outputs:

[]ToolCallSignature - Copies of calls matching the tool name.

Thread Safety: Safe for concurrent use. Acquires read lock.

func (*ToolCallHistory) GetSimilarity

func (h *ToolCallHistory) GetSimilarity(tool string, queryTerms map[string]bool) (float64, *ToolCallSignature)

GetSimilarity finds the most similar prior call for a tool+query combination.

Description:

Compares the proposed query terms against all prior calls of the same tool.
Returns the maximum similarity found and a copy of the most similar call.
I1.1 Fix: Returns copy instead of pointer to avoid data races.
P1.1 Fix: Uses tool index to only check relevant calls.

Inputs:

tool - The tool name to check.
queryTerms - Extracted terms from the proposed call's parameters.

Outputs:

maxSimilarity - Highest Jaccard similarity found (0.0-1.0).
mostSimilar - Copy of the most similar prior call, or nil if none found.

Thread Safety: Safe for concurrent use. Acquires read lock.

func (*ToolCallHistory) IsExactDuplicate

func (h *ToolCallHistory) IsExactDuplicate(tool, rawQuery string) (bool, *ToolCallSignature)

IsExactDuplicate checks if a call is an exact duplicate of a prior call.

Description:

An exact duplicate is same tool + same raw query (case-insensitive).
P1.3 Fix: Uses EqualFold directly without redundant ToLower.
I1.1 Fix: Returns copy instead of pointer to avoid data races.
P1.1 Fix: Uses tool index to only check relevant calls.

Inputs:

tool - The tool name.
rawQuery - The raw query string.

Outputs:

bool - True if an exact duplicate exists.
*ToolCallSignature - Copy of the duplicate call, or nil if none.

Thread Safety: Safe for concurrent use. Acquires read lock.

func (*ToolCallHistory) Len

func (h *ToolCallHistory) Len() int

Len returns the number of recorded calls.

Thread Safety: Safe for concurrent use.

func (*ToolCallHistory) MaxSize

func (h *ToolCallHistory) MaxSize() int

MaxSize returns the maximum history size.

Thread Safety: Safe for concurrent use (reads immutable field).

func (*ToolCallHistory) Trim

func (h *ToolCallHistory) Trim(size int)

Trim reduces the history to the specified size, keeping the most recent entries.

Description:

R1.2 Fix: Allows external callers to trim history if needed.
Rebuilds the tool index after trimming.

Inputs:

size - Maximum number of entries to keep. If <= 0, clears all entries.

Thread Safety: Safe for concurrent use. Acquires write lock.

type ToolCallSignature

type ToolCallSignature struct {
	// Tool is the tool name (e.g., "Grep", "find_callers").
	Tool string

	// QueryTerms are extracted terms from the query parameters.
	// Used for Jaccard similarity comparison.
	QueryTerms map[string]bool

	// RawQuery is the original query string before term extraction.
	// Stored for exact duplicate detection and debugging.
	RawQuery string

	// StepNumber is when this call was made (1-indexed).
	StepNumber int

	// Success indicates whether the tool call succeeded.
	Success bool
}

ToolCallSignature represents a unique tool call with semantic signature.

Description:

Captures the tool name and extracted query terms for semantic comparison.
Two calls are considered semantically equivalent if they have the same tool
and high Jaccard similarity between their query terms.

Thread Safety: Immutable after creation.

func CheckSemanticStatus

func CheckSemanticStatus(history *ToolCallHistory, tool, rawQuery string) (status string, similarity float64, similarCall *ToolCallSignature)

CheckSemanticStatus evaluates a proposed call against history.

Description:

Returns the semantic status of a proposed tool call:
- Blocked: Similarity >= DuplicateThreshold (semantic duplicate)
- Penalized: Similarity >= PenaltyThreshold (similar but not duplicate)
- Allowed: Similarity < PenaltyThreshold (sufficiently different)
S1.3 Fix: Uses configurable thresholds from history's config.
O1.1 Fix: Records Prometheus metrics.

Inputs:

history - The call history to check against. If nil, returns "allowed".
tool - The proposed tool name. If empty, returns "allowed".
rawQuery - The raw query string.

Outputs:

status - "blocked", "penalized", or "allowed".
similarity - The maximum similarity found.
similarCall - Copy of the most similar prior call, or nil.

Thread Safety: Safe for concurrent use.

type ToolEmbeddingCache

type ToolEmbeddingCache struct {
	// contains filtered or unexported fields
}

ToolEmbeddingCache pre-computes and caches embedding vectors for every tool at service startup, then uses cosine similarity at query time to score how well a user's query matches each tool.

Description

Embedding-based scoring is semantically robust: "Where is X referenced?" and "Where is X used?" produce nearly identical query vectors, both close to the find_references tool vector — regardless of exact word form.

The cache calls Ollama's /api/embed endpoint (nomic-embed-text-v2-moe by default) in parallel during Warm(). If Ollama is unavailable, the cache degrades gracefully: Score() returns (nil, nil) and the hybrid scorer falls back to BM25-only mode.

GR-61: Vectors are persisted in BadgerDB (via RouterCacheStore) between service restarts. The corpus hash (SHA256 of tool specs + model name) serves as the cache key, providing automatic invalidation when the tool registry or model changes. If the store is nil, the cache operates in in-memory-only mode (no persistence).

Thread Safety

Safe for concurrent use after Warm() completes.

func NewToolEmbeddingCache

func NewToolEmbeddingCache(logger *slog.Logger, store RouterCacheStore) *ToolEmbeddingCache

NewToolEmbeddingCache creates an unwarmed embedding cache.

Description

Reads EMBEDDING_SERVICE_URL and EMBEDDING_MODEL from the environment. Call Warm() to pre-compute tool embeddings before the cache can score queries.

GR-61: If store is non-nil, Warm() will check the BadgerDB cache before calling Ollama and persist newly computed vectors after warm-up. If store is nil, the cache operates in in-memory-only mode — correct for tests and for deployments without a routing cache directory configured.

Inputs

  • logger: Logger for warnings and debug output. Must not be nil.
  • store: Optional BadgerDB persistence store. Nil disables persistence.

Outputs

  • *ToolEmbeddingCache: Unwarmed cache. Never nil.

Thread Safety

The returned cache is safe for concurrent use after Warm() completes.

func (*ToolEmbeddingCache) IsWarmed

func (c *ToolEmbeddingCache) IsWarmed() bool

IsWarmed reports whether the cache has been successfully warmed.

Thread Safety

Safe for concurrent use.

func (*ToolEmbeddingCache) Score

func (c *ToolEmbeddingCache) Score(ctx context.Context, query string) (map[string]float64, error)

Score embeds the query and returns cosine similarity vs each cached tool vector.

Description

Returns (nil, nil) in two cases where the caller should fall back to BM25:

  1. The cache was never warmed (Ollama was unavailable at startup).
  2. The Ollama call for the query embedding fails or times out.

Returns a non-nil map on success. Scores are in [0.0, 1.0]; tools absent from the cache have implicit score 0 and are omitted from the map.

Inputs

  • ctx: Context for cancellation. A per-call timeout is applied internally.
  • query: The user's raw query string.

Outputs

  • map[string]float64: Tool name → cosine similarity in [0.0, 1.0]. Nil signals graceful degradation — caller should use BM25 only.
  • error: Always nil. Errors are absorbed and signaled via nil map.

Thread Safety

Safe for concurrent use after Warm() completes.

func (*ToolEmbeddingCache) Warm

func (c *ToolEmbeddingCache) Warm(ctx context.Context, specs []ToolSpec) error

Warm pre-computes and caches an embedding vector for every tool spec.

Description

Builds an embedding document for each tool (name + keywords + use_when), then calls Ollama in parallel (up to toolEmbeddingWarmConcurrency concurrent requests). Vectors are stored unit-normalized for efficient cosine similarity.

If any single tool fails to embed, a warning is logged and that tool is skipped — it will receive score 0 from Score(). If all tools fail, warmed remains false and Score() degrades gracefully.

Inputs

  • ctx: Context for the warm-up HTTP calls. Cancellation aborts all pending embeds.
  • specs: Tool specifications to embed. Empty slice is a no-op.

Outputs

  • error: Non-nil if the Ollama endpoint is completely unreachable. Partial failures (individual tools) are logged as warnings, not returned as errors.

Thread Safety

Not safe to call concurrently. Call once at service startup.

type ToolError

type ToolError struct {
	// Tool is the tool name that failed.
	Tool string `json:"tool"`

	// Error is the error message from the failure.
	Error string `json:"error"`

	// Timestamp when the error occurred.
	Timestamp string `json:"timestamp,omitempty"`
}

ToolError captures a failed tool attempt for router feedback.

Description

When a tool fails (e.g., missing required parameter), the error is recorded and fed back to the router. This helps the router avoid suggesting the same tool repeatedly when it cannot be used successfully.

type ToolHistoryEntry

type ToolHistoryEntry struct {
	// Tool is the tool name that was called.
	Tool string `json:"tool"`

	// Summary is a brief description of what was found/returned.
	// Example: "Found 5 callers of parseConfig in pkg/config/"
	Summary string `json:"summary"`

	// Success indicates whether the tool call succeeded.
	Success bool `json:"success"`

	// StepNumber when this tool was called.
	StepNumber int `json:"step_number,omitempty"`
}

ToolHistoryEntry captures a tool execution with its outcome.

Description

Records what tool was called, what it found, and whether it succeeded. This gives the router context about what information is already available, enabling it to suggest the NEXT logical tool rather than repeating.

type ToolRouter

type ToolRouter interface {
	// SelectTool chooses the best tool for the given query and context.
	//
	// Inputs:
	//   - ctx: Context for cancellation/timeout. Must not be nil.
	//   - query: The user's question or request.
	//   - availableTools: Tools currently available for selection.
	//   - context: Optional code context (symbols, files loaded, etc.)
	//
	// Outputs:
	//   - *ToolSelection: The selected tool and optional parameter hints.
	//   - error: Non-nil if routing fails (caller should fall back to main LLM).
	SelectTool(ctx context.Context, query string, availableTools []ToolSpec, context *CodeContext) (*ToolSelection, error)

	// Model returns the model being used for routing.
	Model() string

	// Close releases any resources held by the router.
	Close() error
}

ToolRouter selects the appropriate tool for a user query.

Description

ToolRouter uses a fast micro LLM to classify queries into tool categories before the main reasoning LLM processes the request. This allows tool selection to complete in ~50-100ms instead of the full LLM inference time.

Thread Safety

Implementations must be safe for concurrent use.

Example

router := NewGranite4Router(ollamaEndpoint, "granite4:micro-h")
selection, err := router.SelectTool(ctx, "What functions call parseConfig?", tools, codeCtx)
if err != nil {
    // Fall back to letting main LLM choose
}
// Use selection.Tool to constrain main LLM

type ToolScore

type ToolScore struct {
	// Tool is the tool name.
	Tool string `json:"tool"`

	// RouterConfidence is the router's confidence for this tool (0.0-1.0).
	RouterConfidence float64 `json:"router_confidence"`

	// ProofPenalty is the penalty based on proof number (0.0-1.0).
	// Higher proof number = higher penalty = harder to prove = less preferred.
	ProofPenalty float64 `json:"proof_penalty"`

	// SemanticPenalty is the penalty for similar prior calls (0.0-1.0).
	// Based on Jaccard similarity with previous calls of the same tool.
	// GR-38 Issue 17: Prevents redundant tool calls with similar params.
	SemanticPenalty float64 `json:"semantic_penalty"`

	// ExplorationBonus is the UCB1 exploration term.
	// Encourages trying less-used tools.
	ExplorationBonus float64 `json:"exploration_bonus"`

	// FinalScore is the combined UCB1 score.
	// Higher score = more preferred tool.
	FinalScore float64 `json:"final_score"`

	// Blocked indicates if the tool is blocked by a learned clause.
	Blocked bool `json:"blocked"`

	// BlockReason explains why the tool is blocked.
	BlockReason string `json:"block_reason,omitempty"`

	// ProofStatus contains the proof index status if available.
	ProofStatus *crs.ProofNumber `json:"proof_status,omitempty"`

	// SimilarCall is the most similar prior call, if any.
	// GR-38 Issue 17: For debugging and tracing.
	SimilarCall *ToolCallSignature `json:"similar_call,omitempty"`
}

ToolScore represents a tool's UCB1-based selection score.

Description:

Contains the breakdown of the UCB1 scoring components for transparency
and debugging. The final score is computed as:
  score = exploitation + exploration
  exploitation = router_confidence - proof_penalty
  exploration = C * sqrt(ln(total_selections) / tool_selections)

Thread Safety: ToolScore is immutable after creation.

type ToolSelection

type ToolSelection struct {
	// Tool is the selected tool name (e.g., "find_symbol_usages").
	Tool string `json:"tool"`

	// Confidence is the router's confidence in this selection (0.0-1.0).
	// Use this to decide whether to fall back to the main LLM.
	// Recommended threshold: 0.7
	Confidence float64 `json:"confidence"`

	// ParamsHint contains optional parameter suggestions.
	// Keys are parameter names, values are suggested values.
	// The main LLM can use these as hints but isn't bound by them.
	ParamsHint map[string]string `json:"params_hint,omitempty"`

	// Reasoning is a brief explanation of why this tool was selected.
	// Used for debugging and tracing.
	Reasoning string `json:"reasoning,omitempty"`

	// Duration is how long the routing decision took.
	Duration time.Duration `json:"duration,omitempty"`

	// PrefilterMiss is true when the router selected a tool that was NOT in the
	// prefiltered candidate set. This means the prefilter incorrectly excluded
	// the correct tool. The EscalatingRouter uses this flag to trigger recovery.
	// CB-62 Rev 2.
	PrefilterMiss bool `json:"prefilter_miss,omitempty"`

	// RawModelPick is the tool name the router model originally returned,
	// before any findClosestTool correction. Empty when the model's pick
	// was in the candidate set. Used for observability and recovery.
	// CB-62 Rev 2.
	RawModelPick string `json:"raw_model_pick,omitempty"`
}

ToolSelection represents the router's decision.

Description

Contains the selected tool name, confidence score, and optional hints for parameter values. The confidence score can be used to decide whether to trust the selection or fall back to the main LLM.

func (*ToolSelection) IsConfident

func (s *ToolSelection) IsConfident(threshold float64) bool

IsConfident returns true if the selection confidence is above the threshold.

Inputs

  • threshold: Minimum confidence to consider (e.g., 0.7).

Outputs

  • bool: True if Confidence >= threshold.

type ToolSelectionCache

type ToolSelectionCache struct {
	// contains filtered or unexported fields
}

ToolSelectionCache caches tool selections for identical states.

Description:

Provides lightweight state caching for tool selection decisions.
Cache entries are invalidated when:
  - TTL expires
  - CRS generation changes (clauses were added/removed)
  - Cache is explicitly cleared

This is a simplified transposition table optimized for tool selection.
Unlike the full MCTS transposition table, this cache:
  - Uses order-preserving state keys (tool sequence matters)
  - Invalidates on CRS changes (clause learning)
  - Has shorter TTL (session-scoped)

Thread Safety: Safe for concurrent use.

func NewToolSelectionCache

func NewToolSelectionCache() *ToolSelectionCache

NewToolSelectionCache creates a new tool selection cache with default config.

Outputs:

*ToolSelectionCache - The cache instance.

func NewToolSelectionCacheWithConfig

func NewToolSelectionCacheWithConfig(config *ToolSelectionCacheConfig) *ToolSelectionCache

NewToolSelectionCacheWithConfig creates a new cache with the given config.

Inputs:

config - The configuration. If nil, uses default.

Outputs:

*ToolSelectionCache - The cache instance.

func (*ToolSelectionCache) Clear

func (c *ToolSelectionCache) Clear()

Clear removes all cache entries.

Thread Safety: Safe for concurrent use.

func (*ToolSelectionCache) Get

func (c *ToolSelectionCache) Get(key string, currentGen int64) (string, float64, bool)

Get retrieves a cached selection if valid.

Description:

Returns the cached tool selection if:
  - Key exists in cache
  - Entry hasn't expired (TTL)
  - CRS generation matches (clauses haven't changed)

Inputs:

key - The state key (from StateKeyBuilder).
currentGen - The current CRS generation.

Outputs:

string - The cached tool name.
float64 - The cached score.
bool - True if cache hit, false otherwise.

Thread Safety: Safe for concurrent use.

func (*ToolSelectionCache) InvalidateByGeneration

func (c *ToolSelectionCache) InvalidateByGeneration(oldGeneration int64) int

InvalidateByGeneration removes all entries with an older generation.

Description:

Called when CRS changes (clause added/removed) to invalidate stale entries.

Inputs:

oldGeneration - Generation to invalidate.

Outputs:

int - Number of entries invalidated.

Thread Safety: Safe for concurrent use.

func (*ToolSelectionCache) Metrics

func (c *ToolSelectionCache) Metrics() CacheMetrics

Metrics returns cache performance metrics.

Outputs:

CacheMetrics - Hit/miss statistics.

Thread Safety: Safe for concurrent use.

func (*ToolSelectionCache) Put

func (c *ToolSelectionCache) Put(key, tool string, score float64, generation int64)

Put stores a tool selection in the cache.

Description:

Stores the selection keyed by state. If the cache is full,
evicts the oldest entry.

Inputs:

key - The state key.
tool - The selected tool.
score - The selection score.
generation - The CRS generation.

Thread Safety: Safe for concurrent use.

func (*ToolSelectionCache) Size

func (c *ToolSelectionCache) Size() int

Size returns the current number of cache entries.

Outputs:

int - Number of entries.

Thread Safety: Safe for concurrent use.

type ToolSelectionCacheConfig

type ToolSelectionCacheConfig struct {
	// TTL is the time-to-live for cache entries.
	// Default: 5 minutes.
	TTL time.Duration

	// MaxLen is the maximum number of entries.
	// Default: 1000.
	MaxLen int

	// Logger for debug output. If nil, uses default logger.
	Logger *slog.Logger
}

ToolSelectionCacheConfig configures the cache.

func DefaultToolSelectionCacheConfig

func DefaultToolSelectionCacheConfig() *ToolSelectionCacheConfig

DefaultToolSelectionCacheConfig returns the default cache configuration.

Outputs:

*ToolSelectionCacheConfig - Default configuration.

type ToolSpec

type ToolSpec struct {
	// Name is the tool's unique identifier (e.g., "find_symbol_usages").
	Name string `json:"name"`

	// Description explains what the tool does.
	Description string `json:"description"`

	// BestFor lists keywords that should trigger this tool.
	// CB-31e: Now populated from WhenToUse.Keywords.
	BestFor []string `json:"best_for,omitempty"`

	// UseWhen describes when to use this tool.
	// CB-31e: New field for structured routing guidance.
	UseWhen string `json:"use_when,omitempty"`

	// AvoidWhen describes when NOT to use this tool.
	// CB-31e: New field for structured routing guidance.
	AvoidWhen string `json:"avoid_when,omitempty"`

	// InsteadOf lists tools this should replace in specific scenarios.
	// CB-31e: New field for tool substitution patterns.
	InsteadOf []ToolSubstitution `json:"instead_of,omitempty"`

	// Params lists the parameter names (without full schema).
	// Used to generate parameter hints.
	Params []string `json:"params,omitempty"`

	// Category groups similar tools (e.g., "search", "read", "analyze").
	// Helps the router with coarse-grained classification.
	Category string `json:"category,omitempty"`
}

ToolSpec describes a tool for the router's system prompt.

Description

Provides enough information about a tool for the router to make a selection decision. This is a simplified view of the full tool definition optimized for fast classification.

func DefaultToolSpecs

func DefaultToolSpecs() []ToolSpec

DefaultToolSpecs returns the standard Trace tool specifications.

Description

Returns ToolSpec entries for the core Trace tools. These can be filtered based on what's actually available in the current session.

Outputs

  • []ToolSpec: Standard tool specifications.

type ToolSubstitution

type ToolSubstitution struct {
	// Tool is the name of the tool to replace.
	Tool string `json:"tool"`

	// When describes when substitution applies.
	When string `json:"when"`
}

ToolSubstitution describes when one tool should be used instead of another. CB-31e: Captures tool substitution patterns for the router.

type UCB1Scorer

type UCB1Scorer struct {
	// contains filtered or unexported fields
}

UCB1Scorer scores tools using UCB1 formula with proof integration.

Description:

UCB1 (Upper Confidence Bound) is the right algorithm for tool selection:
  - Exploits tools with high router confidence and low proof penalty
  - Explores less-used tools to discover alternatives
  - Respects learned clauses that block certain tool sequences

UCB1 Formula:
  score = exploitation + exploration
  exploitation = router_confidence - proof_penalty
  exploration = C * sqrt(ln(total_selections) / tool_selections)

Where:
  - router_confidence: From granite4:micro-h (0.0-1.0)
  - proof_penalty: Based on proof number (higher PN = more penalty)
  - C: Exploration constant (default sqrt(2) ≈ 1.41)
  - N: Total tool selections across all tools
  - n_i: Times this specific tool was selected

Thread Safety: UCB1Scorer is safe for concurrent use.

func NewUCB1Scorer

func NewUCB1Scorer() *UCB1Scorer

NewUCB1Scorer creates a new UCB1 scorer with default configuration.

Outputs:

*UCB1Scorer - The scorer instance.

func NewUCB1ScorerWithConfig

func NewUCB1ScorerWithConfig(config *UCB1ScorerConfig) *UCB1Scorer

NewUCB1ScorerWithConfig creates a new UCB1 scorer with the given configuration.

Inputs:

config - The configuration. If nil, uses default.

Outputs:

*UCB1Scorer - The scorer instance.

func (*UCB1Scorer) ScoreTools

func (s *UCB1Scorer) ScoreTools(
	routerResults []RouterResult,
	proofIndex crs.ProofIndexView,
	selectionCounts map[string]int,
	clauseChecker ClauseChecker,
	currentAssignment map[string]bool,
) []ToolScore

ScoreTools scores all tools using UCB1 and returns sorted by score.

Description:

Applies the UCB1 formula to each tool, combining router confidence with
proof penalty and exploration bonus. Tools blocked by clauses get a
negative score and are sorted to the end.

Inputs:

routerResults - Tool selections from router with confidence.
proofIndex - Proof numbers for each tool (can be nil).
selectionCounts - How many times each tool was selected.
clauseChecker - Learned clauses for blocking (can be nil).
currentAssignment - Current variable assignment for clause checking.

Outputs:

[]ToolScore - Tools sorted by score (highest first, blocked last).

Thread Safety: Safe for concurrent use.

func (*UCB1Scorer) ScoreToolsWithSemantic

func (s *UCB1Scorer) ScoreToolsWithSemantic(
	routerResults []RouterResult,
	proofIndex crs.ProofIndexView,
	selectionCounts map[string]int,
	clauseChecker ClauseChecker,
	currentAssignment map[string]bool,
	toolHistory *ToolCallHistory,
	proposedTool string,
	proposedQuery string,
) []ToolScore

ScoreToolsWithSemantic scores tools with semantic similarity awareness.

Description:

Extends ScoreTools with semantic duplicate detection. If the proposed tool
has similar prior calls in history, it gets penalized or blocked.

GR-38 Issue 17: Semantic-aware tool routing.

Inputs:

routerResults - Tool selections from router with confidence.
proofIndex - Proof numbers for each tool (can be nil).
selectionCounts - How many times each tool was selected.
clauseChecker - Learned clauses for blocking (can be nil).
currentAssignment - Current variable assignment for clause checking.
toolHistory - History of prior tool calls with semantic signatures.
proposedTool - The tool the router is suggesting.
proposedQuery - The query/params for the proposed tool.

Outputs:

[]ToolScore - Tools sorted by score (highest first, blocked last).

Thread Safety: Safe for concurrent use.

func (*UCB1Scorer) SelectBest

func (s *UCB1Scorer) SelectBest(scores []ToolScore) (string, ToolScore)

SelectBest selects the best non-blocked tool from scored results.

Description:

Returns the highest-scoring tool that is not blocked. If all tools are
blocked, returns empty string.

Inputs:

scores - Sorted tool scores from ScoreTools.

Outputs:

string - Best tool name, or empty if none available.
ToolScore - The selected tool's score, or zero value if none.

type UCB1ScorerConfig

type UCB1ScorerConfig struct {
	// ExplorationConst is the exploration constant C.
	// Default: sqrt(2) ≈ 1.41.
	ExplorationConst float64

	// ProofWeight is the weight for proof penalty.
	// Default: 0.5.
	ProofWeight float64

	// SemanticWeight is the weight for semantic similarity penalty.
	// Default: 0.5. GR-38 Issue 17.
	SemanticWeight float64

	// SemanticThreshold is the similarity threshold for blocking.
	// Calls with similarity >= threshold are blocked.
	// Default: 0.8. GR-38 Issue 17.
	SemanticThreshold float64

	// MaxUnexploredBonus is the bonus for never-selected tools.
	// Default: 2.0 * ExplorationConst.
	MaxUnexploredBonus float64

	// MaxProofNumber is the maximum expected proof number for normalization.
	// Proof numbers are normalized to [0,1] by dividing by this value.
	// Default: 100.
	MaxProofNumber float64

	// Logger for debug output. If nil, uses default logger.
	Logger *slog.Logger
}

UCB1ScorerConfig configures the UCB1 scorer.

func DefaultUCB1ScorerConfig

func DefaultUCB1ScorerConfig() *UCB1ScorerConfig

DefaultUCB1ScorerConfig returns the default UCB1 scorer configuration.

Description:

Uses standard UCB1 exploration constant sqrt(2) and moderate proof weight.

Outputs:

*UCB1ScorerConfig - Default configuration.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL