router

package
v0.6.2 Latest Latest
Warning

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

Go to latest
Published: Oct 13, 2026 License: Apache-2.0 Imports: 32 Imported by: 0

Documentation

Overview

Package router implements the model-router sidecar logic. It exposes an OpenAI-compatible HTTP API, authenticates requests via Kubernetes JWT TokenReview, routes to the best available LLM provider, checkpoints conversation state, and dispatches tool calls to the appropriate executor backend.

Index

Constants

This section is empty.

Variables

View Source
var ErrUnauthenticated = errors.New("unauthenticated")

ErrUnauthenticated is returned when a request carries an invalid or missing token.

Functions

func ExtractBearerToken

func ExtractBearerToken(r *http.Request) string

ExtractBearerToken returns the token from an "Authorization: Bearer <token>" header.

func RunHTTPInput

func RunHTTPInput(ctx context.Context, cfg HTTPInputConfig, store state.Store) error

RunHTTPInput implements http input mode: waits for the agent HTTP server to become available, POSTs the run input, reads the response, and saves it to the state store for the controller to pick up.

If the HTTP call fails (e.g. timeout because the agent's handler is hanging), the error message is saved as the run's output so the user can see what went wrong in the UI, rather than the run silently timing out.

Types

type AuthHeaderFile

type AuthHeaderFile struct {
	// HeaderName is the HTTP header name (e.g., "Authorization").
	HeaderName string `json:"headerName"`
	// FilePath is the absolute path to the mounted secret file.
	FilePath string `json:"filePath"`
	// Prefix is prepended to the file contents (e.g., "Bearer " for bearer tokens).
	Prefix string `json:"prefix,omitempty"`
}

AuthHeaderFile maps an HTTP header to a file containing its secret value.

type Authenticator

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

Authenticator validates Kubernetes ServiceAccount JWT tokens via the TokenReview API. The model-router sidecar uses this to authenticate every incoming request.

Token flow:

  1. Agent pod has a projected SA token at /var/run/secrets/agentorca/token
  2. Agent framework sends it as "Authorization: Bearer <token>" (via OPENAI_API_KEY env var)
  3. Authenticator calls k8s TokenReview API to validate the token
  4. Validates the SA name matches the expected Agent SA

func NewAuthenticator

func NewAuthenticator(kubeAPIURL, namespace, saName string) *Authenticator

NewAuthenticator creates an Authenticator configured for the given run. saName is "agentorca-agent-<agent-name>" in the run's namespace.

func (*Authenticator) Authenticate

func (a *Authenticator) Authenticate(ctx context.Context, bearerToken string) error

Authenticate validates the bearer token and returns an error if invalid. Results are cached to avoid a Kubernetes API round-trip on every LLM call.

type ChatCompletionRequest

type ChatCompletionRequest struct {
	Model         string           `json:"model"`
	Messages      []Message        `json:"messages"`
	Tools         []map[string]any `json:"tools,omitempty"`
	Stream        bool             `json:"stream,omitempty"`
	StreamOptions *StreamOptions   `json:"stream_options,omitempty"`
}

ChatCompletionRequest mirrors the OpenAI chat completion request body.

type ChatCompletionResponse

type ChatCompletionResponse struct {
	ID      string     `json:"id"`
	Choices []Choice   `json:"choices"`
	Usage   TokenUsage `json:"usage"`
}

ChatCompletionResponse mirrors the OpenAI chat completion response body.

type Choice

type Choice struct {
	Message      Message `json:"message"`
	FinishReason string  `json:"finish_reason"`
}

Choice represents a single completion choice.

type Config

type Config struct {
	// RunName is the AgentRun name — used as checkpoint key prefix and for TokenReview validation.
	RunName string
	// RunNamespace is the namespace of the AgentRun.
	RunNamespace string
	// AgentSAName is the expected ServiceAccount name; validated during JWT authentication.
	AgentSAName string

	// WorkflowName is set when this run is part of an AgentWorkflow.
	// Used to scope workflow-shared state keys.
	WorkflowName string
	// DeploymentName is set when this run is part of an AgentDeployment.
	// Used to scope deployment-shared state keys.
	DeploymentName string

	// DisableClarify prevents the auto-trigger clarify safety net from activating.
	// Set from Agent.Spec.DisableClarify for autonomous agents.
	DisableClarify bool

	// Providers is the ordered list of LLM providers resolved from the ModelSelector.
	Providers []ProviderConfig
	// Strategy is the routing strategy: "rule-based", "llm-meta", or "hybrid".
	Strategy string
	// CapabilityRouting maps capability tags to provider names.
	CapabilityRouting map[string]string
	// FallbackChain is the ordered list of provider names used on 429/5xx.
	FallbackChain []string

	// MetaRouterProviderName is the provider to use for LLM meta-routing decisions.
	MetaRouterProviderName string
	// MetaRouterThreshold is the minimum rule-router confidence below which meta-router is invoked.
	MetaRouterThreshold float64

	// BudgetPerRunUSD is the maximum USD spend for this run (0 = unlimited).
	BudgetPerRunUSD float64

	// ToolDefinitions are the OpenAI function definitions to inject into every chat completion.
	// Built by the operator from Tool CRDs, MCP tool lists, and agent-as-tool schemas.
	ToolDefinitions []ToolDefinition

	// MCPServers are the MCP servers to connect to at startup.
	// Populated by the operator from Tool CRDs with type=mcp.
	MCPServers []MCPServerConfig

	// CheckpointEvery is how many LLM turns between checkpoints.
	CheckpointEvery int
	// CheckpointKey is the storage key for conversation state.
	CheckpointKey string
	// ResumeCheckpointKey is the checkpoint key to load on startup (empty = fresh start).
	ResumeCheckpointKey string

	// StateConfig is the connection config for the conversation state store.
	StateConfig state.Config

	// KubeAPIURL is the Kubernetes API server URL used for TokenReview.
	// Defaults to https://kubernetes.default.svc.
	KubeAPIURL string

	// KnowledgeBases are the resolved KnowledgeBase configurations available to this agent.
	KnowledgeBases []KnowledgeBaseConfig

	// Safeguards configures behavioral guardrails for loop/cycle detection.
	Safeguards RouterSafeguards

	// EpisodicMemory configures periodic conversation summarization.
	EpisodicMemory EpisodicMemoryConfig

	// LongTermMemory configures a KnowledgeBase as persistent agent memory.
	LongTermMemory LongTermMemoryConfig

	// ContextWindowReserve is the fraction of a provider's context window to reserve
	// for the response and system overhead when performing pre-send truncation.
	// Range 0.0–1.0. Default: 0.20 (80% usable for conversation history).
	ContextWindowReserve float64 `json:"contextWindowReserve,omitempty"`

	// MaxToolResultTokens caps the estimated token count of any single tool result
	// before it enters conversation history. Results exceeding this are truncated
	// with a marker. Default: 4000 (~16K chars). 0 after defaulting = unlimited.
	MaxToolResultTokens int `json:"maxToolResultTokens,omitempty"`

	// Guardrails configures content filtering and validation for agent inputs/outputs.
	Guardrails *GuardrailConfig `json:"guardrails,omitempty"`

	// ConfirmRequiredKBs lists KnowledgeBase names that require user confirmation
	// before ingesting fixes. For these KBs, _rag_ingest is replaced with
	// _propose_fix and _confirm_fix built-in tools.
	ConfirmRequiredKBs []string `json:"confirmRequiredKBs,omitempty"`

	// HTTPInput configures http input mode delivery of run input to the agent.
	HTTPInput HTTPInputConfig `json:"httpInput,omitempty"`

	// ChatMode is true when the agent is running in interactive chat mode (inputMode: http or chat).
	// Controls the tone of built-in system hints — chat agents get conversational guidance
	// rather than the automation-pipeline framing used for batch/workflow agents.
	ChatMode bool

	// WarmMode is true when the model-router starts in pre-warmed idle mode.
	// In warm mode the router initializes fully (MCP servers, state store) but does NOT
	// start processing immediately. Instead it waits for POST /v1/claim-run to receive
	// the run-specific fields (RunName, input) before beginning.
	WarmMode bool

	// WarmLocalCacheDir is the on-disk directory used as an L1 cache that
	// supplements the Redis state store on warm pods. The model-router
	// write-throughs each checkpoint to this directory AND to Redis, then reads
	// from the local directory first (falling back to Redis on a miss). Non-empty
	// only on warm pods (set by the operator from AgentDeployment.spec.warmLocalCache).
	// Empty means the local cache is disabled.
	WarmLocalCacheDir string `json:"warmLocalCacheDir,omitempty"`

	// WarmLocalCacheSizeMi is the SizeLimit for the warm-pod local cache emptyDir,
	// mirrored here for observability/debugging. 0 = unlimited.
	WarmLocalCacheSizeMi int `json:"warmLocalCacheSizeMi,omitempty"`

	// SystemPrompt is the agent's system-level instruction, prepended to every conversation.
	// Injected by the operator from Agent.spec.systemPrompt.
	SystemPrompt string

	// OperatorAPIURL is the base URL of the operator's internal API server.
	// Injected by the operator via OPERATOR_API_URL env var (e.g. http://agent-orca-internal-api.svc:8082).
	OperatorAPIURL string

	// SATokenFile is the path to the projected ServiceAccount token used to authenticate
	// requests to the operator's internal API.
	// Defaults to /var/run/secrets/agentorca/token.
	SATokenFile string

	// LLMRequestTimeout is the maximum time the model-router waits for a single LLM
	// provider response (chat completion). Decoupled from the agent's inbound request
	// context so a short agent-side timeout doesn't cancel an in-flight LLM call.
	// Defaults to 1h. Set by the operator from the LLM_REQUEST_TIMEOUT env var
	// (chart value modelRouter.llmRequestTimeout), configurable per deployment.
	LLMRequestTimeout time.Duration `json:"llmRequestTimeout,omitempty"`

	// ProactiveTruncationThreshold is the fraction of the live buffer budget at which
	// truncation is triggered proactively (before the 80% budget is reached). This
	// prevents large spikes in latency when the buffer is near full capacity.
	// Range 0.0–1.0. Default: 0.6 (trigger at 60% of budget).
	// Set to 0.0 to disable proactive truncation (only truncate when over budget).
	ProactiveTruncationThreshold float64 `json:"proactiveTruncationThreshold,omitempty"`

	// ContextManagementStrategy controls how aggressively the router manages context:
	//   - "balanced": proactive truncation at 60% budget, episodic summaries if enabled
	//   - "aggressive": proactive truncation at 40% budget, more frequent compaction
	//   - "max-fidelity": minimal truncation, only when hard limit is reached
	// Default: "balanced"
	ContextManagementStrategy string `json:"contextManagementStrategy,omitempty"`

	// ContextCompactionRatio is the target fraction of the context window to
	// compact the in-memory buffer down to when truncation fires. The checkpoint
	// budget (80%) remains the safety ceiling — buffers exceeding it trigger
	// compaction, which then reduces to this target rather than to the ceiling.
	// Lower values retain less recent history in memory (reducing per-turn
	// token-counting cost and checkpoint size) while the compaction summary
	// carries the dropped context. Default 0.5; set to 0.1 for aggressive
	// compaction down to ~10% of the window (issue #54).
	ContextCompactionRatio float64 `json:"contextCompactionRatio,omitempty"`

	// Hindsight configures the hindsight memory system integration.
	// When enabled, every LLM turn will recall relevant memories before the call
	// and retain the conversation after the call.
	Hindsight HindsightConfig `json:"hindsight,omitempty"`
}

Config holds all configuration injected into the model-router sidecar via environment variables or a mounted ConfigMap. The operator populates this at pod creation time.

func ConfigFromEnv

func ConfigFromEnv() (*Config, error)

ConfigFromEnv builds a Config from environment variables and the mounted config file. The operator writes a JSON config file to /etc/agentorca/router-config.json at pod creation.

type EnvFileMapping

type EnvFileMapping struct {
	// Name is the environment variable name.
	Name string `json:"name"`
	// FilePath is the absolute path to the mounted secret file.
	FilePath string `json:"filePath"`
}

EnvFileMapping maps an environment variable name to a file containing its secret value.

type EpisodicMemoryConfig

type EpisodicMemoryConfig struct {
	// SummaryEvery is the number of LLM turns between summarization calls. 0 = disabled.
	SummaryEvery int `json:"summaryEvery,omitempty"`
	// SummaryProviderName is the ModelProvider name to use for the summarization call.
	SummaryProviderName string `json:"summaryProviderName,omitempty"`
	// SummaryModel is the LiteLLM model identifier for summarization.
	SummaryModel string `json:"summaryModel,omitempty"`
}

EpisodicMemoryConfig enables periodic summarization of the conversation to keep the active context window compact across long-running runs.

type Filter

type Filter interface {
	// Apply processes the text and returns the result.
	Apply(text string) FilterResult
	// Name returns the filter's human-readable name.
	Name() string
}

Filter is the interface all filter implementations must satisfy.

type FilterConfig

type FilterConfig struct {
	Name          string          `json:"name"`
	Type          string          `json:"type"`   // "regex", "keyword-blocklist", "topic-validation"
	Action        string          `json:"action"` // "redact", "block", "warn"
	Patterns      []PatternConfig `json:"patterns,omitempty"`
	Keywords      []string        `json:"keywords,omitempty"`
	AllowedTopics []string        `json:"allowedTopics,omitempty"`
	BlockMessage  string          `json:"blockMessage,omitempty"`
	Replacement   string          `json:"replacement,omitempty"`
}

FilterConfig is a single filter rule in the serialized config.

type FilterResult

type FilterResult struct {
	// Blocked is true if the filter blocked the content entirely.
	Blocked bool
	// BlockMessage is the message to return when blocked.
	BlockMessage string
	// FilteredText is the text after redaction (if any).
	FilteredText string
	// Warnings are warning messages for "warn" action filters.
	Warnings []string
}

FilterResult describes what happened when a filter was applied.

type FunctionCall

type FunctionCall struct {
	Name      string `json:"name"`
	Arguments string `json:"arguments"` // JSON-encoded arguments
}

FunctionCall holds the name and arguments of a tool call.

type GuardrailConfig

type GuardrailConfig struct {
	// OutputFilters are applied to LLM responses before returning to the agent.
	OutputFilters []FilterConfig `json:"outputFilters,omitempty"`
	// InputFilters are applied to user messages before sending to the LLM.
	InputFilters []FilterConfig `json:"inputFilters,omitempty"`
	// MaxOutputTokens limits output length. 0 = no limit.
	MaxOutputTokens int `json:"maxOutputTokens,omitempty"`
}

GuardrailConfig is the serialized guardrail policy injected into the router config.

type GuardrailPipeline

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

GuardrailPipeline evaluates content against a set of filters.

func NewGuardrailPipeline

func NewGuardrailPipeline(cfg *GuardrailConfig) *GuardrailPipeline

NewGuardrailPipeline builds a pipeline from the serialized config. Returns nil if the config has no filters.

func (*GuardrailPipeline) ApplyInput

func (p *GuardrailPipeline) ApplyInput(text string) FilterResult

ApplyInput runs input filters on user messages.

func (*GuardrailPipeline) ApplyOutput

func (p *GuardrailPipeline) ApplyOutput(text string) FilterResult

ApplyOutput runs output filters on LLM responses.

type HTTPInputConfig

type HTTPInputConfig struct {
	Enabled bool   `json:"enabled"`
	Input   string `json:"input"`
	Port    int    `json:"port"`
	Path    string `json:"path"`
	RunName string `json:"runName"`
	// TimeoutSeconds is the maximum time the model-router waits for the agent's
	// HTTP handler to respond to a single run input. Defaults to 1800 (30m) when 0.
	// This must comfortably exceed a single agent turn — which may perform many
	// sequential LLM/tool calls — so that long-trajectory tasks are not killed
	// mid-flight. The old hard-coded 60s cap routinely terminated in-progress
	// sessions and then (before cleanup) deleted the warm pod.
	TimeoutSeconds int `json:"timeoutSeconds,omitempty"`
}

HTTPInputConfig describes how the model-router delivers run input to an HTTP agent.

type HindsightConfig

type HindsightConfig struct {
	// Enabled is true when hindsight is active.
	Enabled bool `json:"enabled,omitempty"`
	// URL is the hindsight API endpoint (e.g. http://hindsight.hindsight.svc.cluster.local:8888).
	URL string `json:"url,omitempty"`
	// BankIDTemplate is the template for the hindsight bank ID. Can use placeholders like
	// {namespace}, {runName}, {agentName} which will be replaced at runtime.
	BankIDTemplate string `json:"bankIdTemplate,omitempty"`
	// RecallBudget is the maximum number of memories to retrieve per turn.
	RecallBudget int `json:"recallBudget,omitempty"`
	// RetainOnEveryTurn is true to retain conversation after every LLM turn.
	RetainOnEveryTurn bool `json:"retainOnEveryTurn,omitempty"`
}

HindsightConfig configures the hindsight memory system integration.

type KnowledgeBaseConfig

type KnowledgeBaseConfig struct {
	// Name is the KnowledgeBase CR name.
	Name string
	// VectorStoreURL is the Qdrant gRPC endpoint.
	VectorStoreURL string
	// CollectionName is the Qdrant collection to search.
	CollectionName string
	// EmbeddingProviderName is the ModelProvider to use for embedding queries.
	EmbeddingProviderName string
	// EmbeddingModel is the LiteLLM model identifier for embeddings.
	EmbeddingModel string
	// Dimensions is the embedding vector size.
	Dimensions int
}

KnowledgeBaseConfig is a resolved KnowledgeBase ready for RAG queries.

type LongTermMemoryConfig

type LongTermMemoryConfig struct {
	// Enabled is true when long-term memory is active.
	Enabled bool `json:"enabled,omitempty"`
	// KBName is the KnowledgeBase CR name used for long-term memory.
	KBName string `json:"kbName,omitempty"`
	// VectorStoreURL is the Qdrant gRPC endpoint.
	VectorStoreURL string `json:"vectorStoreURL,omitempty"`
	// CollectionName is the Qdrant collection.
	CollectionName string `json:"collectionName,omitempty"`
	// EmbeddingProviderName is the ModelProvider for embedding queries.
	EmbeddingProviderName string `json:"embeddingProviderName,omitempty"`
	// EmbeddingModel is the LiteLLM model identifier for embeddings.
	EmbeddingModel string `json:"embeddingModel,omitempty"`
	// Dimensions is the embedding vector size.
	Dimensions int `json:"dimensions,omitempty"`
	// TopK is how many memories to retrieve per turn. Defaults to 5.
	TopK int `json:"topK,omitempty"`
}

LongTermMemoryConfig wires a KnowledgeBase as the agent's persistent long-term memory.

type MCPServerConfig

type MCPServerConfig struct {
	// Name is the Tool CRD name this server corresponds to.
	Name string `json:"name"`
	// Transport is the MCP transport: "stdio", "http", or "sse".
	Transport string `json:"transport"`
	// URL is the server URL for http/sse transports.
	URL string `json:"url,omitempty"`
	// Cmd is the executable for stdio transport.
	Cmd string `json:"cmd,omitempty"`
	// Args are the command arguments for stdio transport.
	Args []string `json:"args,omitempty"`
	// Env is a list of "KEY=VALUE" env vars for stdio transport.
	Env []string `json:"env,omitempty"`
	// EnvFiles maps env var names to file paths containing secret values.
	// The model-router reads these files at MCP server startup and injects the values.
	EnvFiles []EnvFileMapping `json:"envFiles,omitempty"`
	// AuthHeaderFiles maps HTTP header names to file paths containing secret values.
	// Used for HTTP/SSE transport authentication.
	AuthHeaderFiles []AuthHeaderFile `json:"authHeaderFiles,omitempty"`
	// OAuth, when set, makes the model-router perform the OAuth 2.0 exchange itself
	// (in-memory bearer) instead of a static authHeaderFile. Used for OAuth-only MCP
	// servers (e.g. Slack); no access-token Secret is mounted — only the credentials
	// Secret (see MCPOAuthConfig on the MCPServer CRD).
	OAuth *mcp.OAuthConfig `json:"oauth,omitempty"`
	// AllowApps enables MCP App iframe rendering for tools from this server.
	AllowApps bool `json:"allowApps,omitempty"`
	// IncludePatterns is a list of glob patterns used to filter the tools the
	// model-router discovers at runtime (mirrors MCPServer.spec.includePatterns).
	// When non-empty, only discovered tools whose name matches at least one pattern
	// are exposed to the LLM. Empty means "all discovered tools" (no filtering).
	// +optional
	IncludePatterns []string `json:"includePatterns,omitempty"`
	// ExcludePatterns hides discovered tools matching any glob, even if they
	// matched IncludePatterns (mirrors MCPServer.spec.excludePatterns).
	// +optional
	ExcludePatterns []string `json:"excludePatterns,omitempty"`
}

MCPServerConfig describes an MCP server the model-router should connect to at startup.

type Message

type Message struct {
	Role       string     `json:"role"`
	Content    any        `json:"content"` // string or []ContentPart
	Name       string     `json:"name,omitempty"`
	ToolCallID string     `json:"tool_call_id,omitempty"`
	ToolCalls  []ToolCall `json:"tool_calls,omitempty"`
}

Message represents a single message in an LLM conversation.

type MetaRouter

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

MetaRouter uses a cheap/fast LLM to decide which model to use for ambiguous requests. It is only invoked when the rule-router's confidence falls below the configured threshold.

func NewMetaRouter

func NewMetaRouter(cfg *Config) *MetaRouter

NewMetaRouter creates a MetaRouter from the given configuration.

func (*MetaRouter) Route

func (m *MetaRouter) Route(ctx context.Context, messages []Message) (string, TokenUsage, error)

Route asks the meta-router LLM to select the best provider for the given messages. Returns the provider name or an error; callers fall back to rule-based routing on error.

type Metrics

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

Metrics holds the Prometheus counters/gauges/histograms that the model-router exposes on its :9091/metrics endpoint. They are registered into the router's own registry (created in cmd/model-router/main.go), NOT the process default registry, so they are scoped to this model-router pod.

func NewMetrics

func NewMetrics(reg prometheus.Registerer) *Metrics

NewMetrics builds a Metrics set and registers it into reg. If reg is nil the counters are still allocated (no-ops at the registry level) so call sites can never nil-deref; they simply aren't exposed.

func (*Metrics) AddTokens

func (m *Metrics) AddTokens(n int)

AddTokens records n streamed output tokens. Nil-safe.

func (*Metrics) IncToolCall

func (m *Metrics) IncToolCall()

IncToolCall records one dispatched tool call. Nil-safe.

func (*Metrics) ObserveStreamDuration

func (m *Metrics) ObserveStreamDuration(seconds float64)

ObserveStreamDuration records a streaming response duration (seconds). Nil-safe.

type PatternConfig

type PatternConfig struct {
	Name        string `json:"name"`
	Pattern     string `json:"pattern"`
	Replacement string `json:"replacement,omitempty"`
}

PatternConfig is a named regex pattern.

type ProviderConfig

type ProviderConfig struct {
	// Name is the ModelProvider CRD name.
	Name string
	// LiteLLMModel is the LiteLLM model identifier (e.g. "anthropic/claude-sonnet-4-6").
	LiteLLMModel string
	// APIKeyFile is the path to the mounted secret file containing the API key.
	APIKeyFile string
	// Weight is used for weighted random selection.
	Weight int
	// Capabilities tags for this model.
	Capabilities []string
	// ContextWindow in tokens.
	ContextWindow int
	// MaxRequestTokens is the effective per-request token limit.
	// When > 0, the router skips this provider for requests exceeding this size.
	MaxRequestTokens int
	// LatencyProfile: "fast", "medium", or "slow".
	LatencyProfile string
	// CostPerInputToken is the USD cost per single input token (converted from per-million at config load).
	CostPerInputToken float64
	// CostPerOutputToken is the USD cost per single output token (converted from per-million at config load).
	CostPerOutputToken float64
	// BaseURL is an optional API endpoint override for self-hosted models (e.g. "http://ollama.svc:11434").
	BaseURL string
	// RoutingHint is passed to the LLM meta-router to help it decide when to select this provider.
	RoutingHint string
	// TokenEncoding is the tiktoken encoding name for this provider (e.g. "cl100k_base", "o200k_base").
	// Defaults to "cl100k_base" when empty.
	TokenEncoding string
}

ProviderConfig is a fully-resolved LLM provider ready for dispatch.

type RouteResult

type RouteResult struct {
	// ProviderName is the selected ModelProvider CRD name.
	ProviderName string
	// Confidence is a 0.0–1.0 score indicating how certain the rule-based router is.
	// Low confidence triggers the LLM meta-router in hybrid mode.
	Confidence float64
	// Reason is a human-readable explanation for the routing decision.
	Reason string
}

RouteResult is the output of the rule-based router.

type Router

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

Router is the core model-router sidecar. It is created once at startup and handles all LLM proxy requests for the lifetime of the agent pod.

func New

func New(cfg *Config, store state.Store, initialMessages []json.RawMessage, exec *executor.Executor) (*Router, error)

New creates a Router with the given configuration. initialMessages is the conversation history loaded from a checkpoint (may be nil). exec may be nil if in-cluster Kubernetes is unavailable; tool dispatch for regular/agent backends is then disabled.

func (*Router) Cancel

func (r *Router) Cancel()

Cancel terminates all in-flight LLM requests and marks the router as failed. It is safe to call multiple times.

func (*Router) ClaimRun

func (r *Router) ClaimRun(input WarmRunInput)

ClaimRun assigns a run to this warm-mode router instance. It sets the mutable per-run fields on the config and resets all per-run state so the router is ready to process the run as if it just started. Must only be called when r.cfg.WarmMode is true.

func (*Router) CloseMCPClient

func (r *Router) CloseMCPClient()

CloseMCPClient disconnects from all MCP servers and terminates any stdio subprocesses.

func (*Router) CloseTokenBroadcaster

func (r *Router) CloseTokenBroadcaster()

CloseTokenBroadcaster shuts down the token broadcaster, closing all subscriber channels.

func (*Router) Finalize

func (r *Router) Finalize()

Finalize emits a terminal trace-event release decision if no explicit terminal was reached. Call this from the shutdown path before HTTP servers are closed so the operator POST can still reach the apiserver.

Heuristic: if the router served at least one LLM turn, the run completed without _done — the run completed normally. If zero turns were served, the pod was killed before any work happened — emit a terminal event and exit.

func (*Router) HandleChatCompletions

func (r *Router) HandleChatCompletions(w http.ResponseWriter, req *http.Request)

HandleChatCompletions handles POST /v1/chat/completions. This is the primary endpoint called by all OpenAI-compatible agent frameworks.

func (*Router) HandleGemini

func (r *Router) HandleGemini(w http.ResponseWriter, req *http.Request)

HandleGemini handles Gemini API format requests on :8082. Used by native Google ADK agents that target generativelanguage.googleapis.com. Translates to OpenAI format and proxies through the same routing logic.

func (*Router) HandleInternalStream

func (r *Router) HandleInternalStream(w http.ResponseWriter, req *http.Request)

HandleInternalStream serves GET /internal/stream — an SSE endpoint that streams LLM tokens in real-time to the operator. This bypasses Kubernetes pod log buffering by reading directly from the model-router's token broadcaster.

func (*Router) HandleModels

func (r *Router) HandleModels(w http.ResponseWriter, req *http.Request)

HandleModels handles GET /v1/models — returns the list of available providers.

func (*Router) HandleState

func (r *Router) HandleState(w http.ResponseWriter, req *http.Request)

HandleState handles the REST API for agent-code state access:

PUT    /v1/state/{scope}/{key}  — write a value
GET    /v1/state/{scope}/{key}  — read a value
DELETE /v1/state/{scope}/{key}  — delete a value
GET    /v1/state/{scope}        — list keys

func (*Router) InitMCPServers

func (r *Router) InitMCPServers()

InitMCPServers connects to all configured MCP servers and merges their tool schemas into the router's tool definitions. It is designed to be called in a goroutine after the HTTP server is already listening so MCP subprocess startup does not delay the startup probe.

func (*Router) SetExecutor

func (r *Router) SetExecutor(exec *executor.Executor)

SetExecutor binds the in-process tool executor (warm mode: call after ClaimRun when run name is known).

func (*Router) SetMetrics

func (r *Router) SetMetrics(m *Metrics)

SetMetrics attaches a Prometheus metrics set so the streaming hot path can record token/tool/latency counters exposed on :9091. Safe to call (or skip) after New; metrics is nil-safe for all increments.

func (*Router) SpendUSD

func (r *Router) SpendUSD() float64

SpendUSD returns the total USD spend accumulated during this router's lifetime.

type RouterSafeguards

type RouterSafeguards struct {
	// MaxConsecutiveNoopTurns halts the run if the LLM produces this many consecutive
	// turns with no tool calls and fewer than MinSubstantiveTokens output tokens.
	// 0 disables the check.
	MaxConsecutiveNoopTurns int `json:"maxConsecutiveNoopTurns,omitempty"`
	// MinSubstantiveTokens is the minimum output token count for a turn to count as substantive.
	// Defaults to 20 when MaxConsecutiveNoopTurns is set.
	MinSubstantiveTokens int `json:"minSubstantiveTokens,omitempty"`
	// MaxRepeatedToolCalls halts the run if the same tool+args hash is seen this many times.
	// 0 disables the check.
	MaxRepeatedToolCalls int `json:"maxRepeatedToolCalls,omitempty"`
	// ToolFrequencyCap halts the run if any single tool is called this many times total.
	// 0 disables the check.
	ToolFrequencyCap int `json:"toolFrequencyCap,omitempty"`
	// ToolExecutionTimeoutSec is the maximum seconds a single tool call may take
	// before being cancelled and returning a structured error to the LLM.
	// 0 means use the default (60s).
	ToolExecutionTimeoutSec int `json:"toolExecutionTimeoutSec,omitempty"`
}

RouterSafeguards configures behavioral guardrails applied by the model-router per run.

type RuleRouter

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

RuleRouter selects a model provider using deterministic rules. It is the fast path in both "rule-based" and "hybrid" strategies.

func NewRuleRouter

func NewRuleRouter(cfg *Config) *RuleRouter

NewRuleRouter creates a RuleRouter for a given configuration.

func (*RuleRouter) IncrementTurn

func (r *RuleRouter) IncrementTurn()

IncrementTurn increments the turn counter.

func (*RuleRouter) Route

func (r *RuleRouter) Route(messages []Message, estimatedTokens int, remainingTimeoutSec float64) RouteResult

Route selects a provider for the given chat completion request. Decision tree (evaluated in priority order):

  1. Token limit filter: exclude providers whose MaxRequestTokens < estimated tokens
  2. Vision capability: request contains image attachments → vision-capable model
  3. Context length: estimated tokens > 80% of model context window → long-context model
  4. Budget: remaining budget < 20% → cheapest model
  5. Timeout proximity: elapsed > 70% of run timeout → fastest model
  6. Capability routing map: check configured tag hints
  7. Default: weighted random selection

func (*RuleRouter) TurnCount

func (r *RuleRouter) TurnCount() int

TurnCount returns the number of LLM turns completed.

func (*RuleRouter) UpdateSpend

func (r *RuleRouter) UpdateSpend(usd float64)

UpdateSpend records additional spend so budget rules stay accurate.

type StreamOptions

type StreamOptions struct {
	IncludeUsage bool `json:"include_usage"`
}

StreamOptions controls streaming behavior.

type TokenBroadcaster

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

TokenBroadcaster fans out streaming LLM tokens to multiple SSE subscribers. It maintains a replay buffer so that late subscribers (e.g. the operator connecting after streaming has already started) receive all prior tokens.

func NewTokenBroadcaster

func NewTokenBroadcaster() *TokenBroadcaster

NewTokenBroadcaster creates a broadcaster with no initial subscribers.

func (*TokenBroadcaster) Close

func (b *TokenBroadcaster) Close()

Close closes all subscriber channels and prevents further sends.

func (*TokenBroadcaster) Send

func (b *TokenBroadcaster) Send(token string)

Send broadcasts a token to all current subscribers and appends it to the replay buffer for future subscribers.

func (*TokenBroadcaster) Subscribe

func (b *TokenBroadcaster) Subscribe() (<-chan string, func())

Subscribe returns a channel that receives all tokens (past and future) and an unsubscribe function. Past tokens are replayed from the buffer immediately. The channel is closed when the broadcaster is closed.

This is called under the lock so no tokens are missed between the replay and the subscription becoming active.

type TokenUsage

type TokenUsage struct {
	PromptTokens     int `json:"prompt_tokens"`
	CompletionTokens int `json:"completion_tokens"`
	TotalTokens      int `json:"total_tokens"`
}

TokenUsage holds token count metadata.

type ToolCall

type ToolCall struct {
	ID       string       `json:"id"`
	Type     string       `json:"type"`            // always "function"
	Index    int          `json:"index,omitempty"` // used in streaming deltas to identify which call a chunk belongs to
	Function FunctionCall `json:"function"`
}

ToolCall represents an LLM-requested function invocation.

type ToolDefinition

type ToolDefinition struct {
	// Name is the function name.
	Name string
	// Description is the human-readable description shown to the LLM.
	Description string
	// Parameters is the JSON Schema for the function parameters.
	Parameters json.RawMessage
	// BackendType indicates how to execute this tool: "regular", "agent", "mcp", "builtin".
	BackendType string
	// BackendRef is the Tool/Agent/MCP name to call.
	BackendRef string
	// SecretRefs are Kubernetes Secrets to mount into tool pods.
	// Used by the executor when spawning regular (pod) tool invocations.
	SecretRefs []ToolSecretRef `json:"secretRefs,omitempty"`
	// Command overrides the container entrypoint for pod-type tools.
	Command []string `json:"command,omitempty"`
	// Args overrides the container command arguments for pod-type tools.
	Args []string `json:"args,omitempty"`
	// AppResourceURI is the ui:// resource URI from _meta.ui.resourceUri, if present.
	// Only set when AllowApps is true on the owning MCPServer.
	AppResourceURI string `json:"appResourceUri,omitempty"`
	// AllowApps enables MCP App iframe rendering for this tool.
	AllowApps bool `json:"allowApps,omitempty"`
}

ToolDefinition is an OpenAI function definition injected into every chat completion.

func ConfirmFixToolDefinition

func ConfirmFixToolDefinition() ToolDefinition

ConfirmFixToolDefinition returns the OpenAI function schema for _confirm_fix.

func ProposedFixToolDefinition

func ProposedFixToolDefinition() ToolDefinition

ProposedFixToolDefinition returns the OpenAI function schema for _propose_fix.

type ToolSecretRef

type ToolSecretRef struct {
	// SecretName is the Kubernetes Secret name.
	SecretName string `json:"secretName"`
	// MountPath is where to mount the Secret. Empty means inject as env vars.
	MountPath string `json:"mountPath,omitempty"`
}

ToolSecretRef describes a Secret to inject into a tool pod.

type WarmRunInput

type WarmRunInput struct {
	// RunName is the AgentRun name to assign to this warm pod.
	RunName string `json:"runName"`
	// Input is the initial message to deliver to the agent.
	Input string `json:"input"`
	// PriorRunRef is the previous AgentRun name whose checkpoint should be loaded (empty = fresh).
	PriorRunRef string `json:"priorRunRef,omitempty"`
}

WarmRunInput is the payload sent to POST /v1/claim-run when a warm pod is assigned to a run.

Jump to

Keyboard shortcuts

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