control

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: 12 Imported by: 0

Documentation

Overview

Package control provides control flow hardening for the agent loop.

This package implements 5 layers of hardening to prevent:

  • Premature completion (intent detection)
  • Raw tool output leakage (parser hardening)
  • Step exhaustion without synthesis (synthesis budget)
  • Leaked markup in responses (output sanitization)
  • Empty responses despite gathered information (buffer extraction)

Index

Constants

View Source
const (
	// DefaultMaxHistory is the maximum number of violations to track.
	DefaultMaxHistory = 10

	// DefaultStreakThreshold triggers stuck on N same-type violations.
	DefaultStreakThreshold = 3

	// DefaultCategoryThreshold triggers stuck on N same-category violations.
	DefaultCategoryThreshold = 4

	// DefaultOscillationWindow detects A-B-A-B patterns in last N violations.
	DefaultOscillationWindow = 6
)

Default configuration values.

Variables

This section is empty.

Functions

func QuickSanitize

func QuickSanitize(content string) string

QuickSanitize is a package-level function for one-off sanitization.

Description:

Uses a cached sanitizer with default config for efficient repeated use.
For model-specific sanitization, use QuickSanitizeForModel instead.

Inputs:

content - Response text to sanitize.

Outputs:

string - Sanitized text.

Thread Safety: Safe for concurrent use.

func QuickSanitizeForModel

func QuickSanitizeForModel(content string, model agent.ModelType) string

QuickSanitizeForModel sanitizes with model awareness.

Inputs:

content - Response text to sanitize.
model - The LLM model type.

Outputs:

string - Sanitized text.

func RecordStuckMetric

func RecordStuckMetric(result StuckResult)

RecordStuckMetric records when stuck state is entered.

Types

type AgentStateInput

type AgentStateInput struct {
	// FinalResponse is the synthesized response (if any).
	FinalResponse string

	// ToolResults are the tool execution results.
	ToolResults []ToolResultInput

	// Query is the original user query.
	Query string
}

AgentStateInput provides the agent state for extraction.

type AmbiguousRule

type AmbiguousRule struct {
	// FollowedByColon: if true, phrase + colon = intent; false = answer
	FollowedByColon bool
}

AmbiguousRule defines how to handle ambiguous phrases.

type BudgetConfig

type BudgetConfig struct {
	// TotalSteps is the total step budget (default: 15).
	TotalSteps int

	// DefaultSynthesisRatio is default synthesis reserve (default: 0.20).
	DefaultSynthesisRatio float64

	// SimpleQueryRatio is exploration ratio for simple queries (default: 0.50).
	SimpleQueryRatio float64

	// MediumQueryRatio is exploration ratio for medium queries (default: 0.70).
	MediumQueryRatio float64

	// ComplexQueryRatio is exploration ratio for complex queries (default: 0.85).
	ComplexQueryRatio float64
}

BudgetConfig allows configuring budget behavior.

func DefaultBudgetConfig

func DefaultBudgetConfig() BudgetConfig

DefaultBudgetConfig returns production defaults.

type BudgetStatus

type BudgetStatus struct {
	// CurrentStep is the current step number.
	CurrentStep int

	// TotalSteps is the total step budget.
	TotalSteps int

	// ExplorationBudget is steps available for exploration.
	ExplorationBudget int

	// SynthesisBudget is steps reserved for synthesis.
	SynthesisBudget int

	// InSynthesisPhase indicates if we're in synthesis phase.
	InSynthesisPhase bool

	// Complexity is the detected query complexity.
	Complexity QueryComplexity

	// RemainingSteps is steps remaining in current phase.
	RemainingSteps int
}

BudgetStatus contains budget state for logging/debugging.

type BufferExtractor

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

BufferExtractor generates fallback responses from accumulated state.

Description:

When the agent gathers information during exploration but fails to
synthesize a response (due to step exhaustion, timeout, or other issues),
the BufferExtractor constructs a fallback response from accumulated
tool results to avoid returning empty responses.

Thread Safety: Safe for concurrent use (stateless).

func NewBufferExtractor

func NewBufferExtractor(config ExtractionConfig, logger *slog.Logger) *BufferExtractor

NewBufferExtractor creates a new extractor.

Inputs:

config - Configuration for extraction behavior.
logger - Logger for diagnostic output (nil for default).

Outputs:

*BufferExtractor - The configured extractor.

func (*BufferExtractor) Extract

Extract generates a response from accumulated state.

Description:

First checks if there's a valid final response. If not, generates
a fallback response from accumulated tool results. If no tool results
exist, returns a "no information" message.

Inputs:

state - Current agent state with tool results.

Outputs:

ExtractionResult - Generated response with metadata.

Thread Safety: Safe for concurrent use.

func (*BufferExtractor) HasUsableContent

func (e *BufferExtractor) HasUsableContent(state AgentStateInput) bool

HasUsableContent checks if there's enough content for extraction.

Description:

Quick check to determine if extraction would produce meaningful
output. Useful for deciding whether to attempt fallback extraction.

Inputs:

state - Agent state to check.

Outputs:

bool - True if meaningful extraction is possible.

Thread Safety: Safe for concurrent use.

type ExtractionConfig

type ExtractionConfig struct {
	// MinFinalResponseLength is the minimum chars for a valid final response.
	MinFinalResponseLength int

	// MaxSummaryLength is the max chars per finding summary.
	MaxSummaryLength int

	// MaxFindings is the max number of findings to include in fallback.
	MaxFindings int

	// IncludeDisclaimer adds a disclaimer to fallback responses.
	IncludeDisclaimer bool
}

ExtractionConfig configures buffer extraction behavior.

func DefaultExtractionConfig

func DefaultExtractionConfig() ExtractionConfig

DefaultExtractionConfig returns production defaults.

type ExtractionResult

type ExtractionResult struct {
	// Response is the generated response text.
	Response string

	// IsFallback indicates if this is a fallback response.
	IsFallback bool

	// SourceCount is the number of tool results used.
	SourceCount int

	// FileCount is the number of unique files referenced.
	FileCount int
}

ExtractionResult contains the extracted response.

type FrustrationConfig

type FrustrationConfig struct {
	// MaxHistory is how many violations to remember (default: 10).
	MaxHistory int

	// StreakThreshold triggers STUCK on N same-type violations (default: 3).
	StreakThreshold int

	// CategoryThreshold triggers STUCK on N same-category violations (default: 4).
	CategoryThreshold int

	// OscillationWindow detects A-B-A-B patterns in last N (default: 6).
	OscillationWindow int
}

FrustrationConfig configures detection thresholds.

func DefaultFrustrationConfig

func DefaultFrustrationConfig() FrustrationConfig

DefaultFrustrationConfig returns sensible defaults.

type FrustrationTracker

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

FrustrationTracker detects behavioral deadlocks.

Description:

Tracks violations and detects patterns that indicate the agent is stuck
in an unresolvable loop. Uses three detection rules:
1. Same-type streak: N consecutive violations of the same type
2. Category saturation: N consecutive violations in the same category
3. Oscillation: Alternating between two violation types

Thread Safety: Safe for concurrent use via mutex.

func NewFrustrationTracker

func NewFrustrationTracker(config FrustrationConfig) *FrustrationTracker

NewFrustrationTracker creates a tracker with the given configuration.

Description:

Creates a FrustrationTracker with configured thresholds. Uses defaults
for any zero values in the config.

Inputs:

config - Configuration options. Uses defaults if zero values.

Outputs:

*FrustrationTracker - The configured tracker.

func (*FrustrationTracker) GetRecentViolations

func (f *FrustrationTracker) GetRecentViolations(n int) []Violation

GetRecentViolations returns the last N violations.

Description:

Returns a copy of the most recent violations for inspection.
Returns fewer than N if not enough violations recorded.

Inputs:

n - Maximum number of violations to return.

Outputs:

[]Violation - Copy of recent violations (newest last).

Thread Safety: This method is safe for concurrent use.

func (*FrustrationTracker) IsStuck

func (f *FrustrationTracker) IsStuck() bool

IsStuck checks current state without adding a violation.

Description:

Checks if the current violation history indicates a stuck state.
Does not modify the violation history.

Outputs:

bool - True if the agent is stuck.

Thread Safety: This method is safe for concurrent use.

func (*FrustrationTracker) RecordViolation

func (f *FrustrationTracker) RecordViolation(v Violation) StuckResult

RecordViolation adds a violation and checks for deadlock.

Description:

Records the violation, trims history if needed, checks all detection
rules, and returns a StuckResult indicating whether the agent is stuck.

Inputs:

v - The violation to record.

Outputs:

StuckResult - The detection result.

Thread Safety: This method is safe for concurrent use.

func (*FrustrationTracker) Reset

func (f *FrustrationTracker) Reset()

Reset clears violation history.

Description:

Clears all recorded violations. Call this after user provides
clarification to give the agent a fresh start.

Thread Safety: This method is safe for concurrent use.

func (*FrustrationTracker) ViolationCount

func (f *FrustrationTracker) ViolationCount() int

ViolationCount returns the number of recorded violations.

Thread Safety: This method is safe for concurrent use.

type HelpRequest

type HelpRequest struct {
	// Title is the help request title.
	Title string

	// WhatITried lists attempted actions.
	WhatITried []string

	// TheProblem explains the issue.
	TheProblem string

	// Suggestions lists how the user can help.
	Suggestions []string

	// CanSkip indicates if this step is optional.
	CanSkip bool

	// SkipMessage explains what happens if skipped.
	SkipMessage string
}

HelpRequest is a structured request for user assistance.

func (HelpRequest) String

func (h HelpRequest) String() string

String returns a formatted help request message.

type IntentCache

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

IntentCache caches classification results for unchanged responses.

Thread Safety: Safe for concurrent use via RWMutex.

type IntentClassifier

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

IntentClassifier detects when LLM outputs intent rather than answer.

Description:

Analyzes LLM response text to determine if it represents a statement
of future intent ("I will explore...") vs an actual answer. Intent
statements should trigger continuation, not completion.

Thread Safety: Safe for concurrent use (stateless methods, thread-safe cache).

func NewIntentClassifier

func NewIntentClassifier(config IntentConfig) *IntentClassifier

NewIntentClassifier creates a classifier with default indicators.

Inputs:

config - Configuration for intent detection behavior.

Outputs:

*IntentClassifier - The configured classifier.

func (*IntentClassifier) CacheStats

func (c *IntentClassifier) CacheStats() (size int, capacity int)

CacheStats returns cache statistics.

Thread Safety: Safe for concurrent use.

func (*IntentClassifier) ClearCache

func (c *IntentClassifier) ClearCache()

ClearCache clears the intent cache.

Thread Safety: Safe for concurrent use.

func (*IntentClassifier) IsIntent

func (c *IntentClassifier) IsIntent(content string) IntentResult

IsIntent returns true if content appears to be intent, not answer.

Inputs:

content - LLM response text to classify.

Outputs:

IntentResult - Classification result with score and reason.

Thread Safety: Safe for concurrent use.

type IntentConfig

type IntentConfig struct {
	// MaxIntentLength is max chars for content to be considered intent (default: 500).
	MaxIntentLength int

	// HighScoreThreshold is score for high confidence (default: 3).
	HighScoreThreshold int

	// ShortResponseLen is "short" response threshold (default: 300).
	ShortResponseLen int

	// EnableCache enables caching of classification results.
	EnableCache bool

	// CacheTTL is TTL for cache entries.
	CacheTTL time.Duration

	// CacheMaxSize is max entries in cache (LRU eviction).
	CacheMaxSize int
}

IntentConfig allows tuning intent detection behavior.

func DefaultIntentConfig

func DefaultIntentConfig() IntentConfig

DefaultIntentConfig returns production defaults.

type IntentResult

type IntentResult struct {
	// IsIntent is true if content is an intent statement, not an answer.
	IsIntent bool

	// Score is the confidence score (0-10).
	Score int

	// Reason explains the classification.
	Reason string

	// Cached indicates if result came from cache.
	Cached bool
}

IntentResult contains the result of intent classification.

type OutputSanitizer

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

OutputSanitizer removes leaked tool markup from responses.

Description:

Provides a final defense layer that strips leaked tool markup
before returning responses to the user. Model-aware to avoid
stripping native formats for their respective models.

Thread Safety: Safe for concurrent use (stateless, compiled regex).

func NewOutputSanitizer

func NewOutputSanitizer(config SanitizeConfig) *OutputSanitizer

NewOutputSanitizer creates a sanitizer for the given model.

Description:

Compiles combined regex at construction time for O(n) performance.
The sanitizer is model-aware to preserve native formats.

Inputs:

config - Configuration for sanitization behavior.

Outputs:

*OutputSanitizer - The configured sanitizer.

func (*OutputSanitizer) ContainsLeakedMarkup

func (s *OutputSanitizer) ContainsLeakedMarkup(content string) bool

ContainsLeakedMarkup checks if content contains leaked tool markup.

Description:

Quick check to determine if sanitization is needed. Useful for
metrics and logging without performing full sanitization.

Inputs:

content - Response text to check.

Outputs:

bool - True if leaked markup is detected.

Thread Safety: Safe for concurrent use.

func (*OutputSanitizer) GetModel

func (s *OutputSanitizer) GetModel() agent.ModelType

GetModel returns the configured model type.

func (*OutputSanitizer) Sanitize

func (s *OutputSanitizer) Sanitize(content string) SanitizeResult

Sanitize removes leaked tool markup while preserving legitimate content.

Description:

Removes tool markup patterns from the response while preserving
markdown code blocks and inline code. Uses a single-pass approach
for O(n) performance.

Inputs:

content - Response text potentially containing leaked markup.

Outputs:

SanitizeResult - Sanitized text and metadata.

Thread Safety: Safe for concurrent use.

func (*OutputSanitizer) SanitizeString

func (s *OutputSanitizer) SanitizeString(content string) string

SanitizeString is a convenience method that returns just the sanitized string.

Inputs:

content - Response text to sanitize.

Outputs:

string - Sanitized text.

Thread Safety: Safe for concurrent use.

type QueryComplexity

type QueryComplexity int

QueryComplexity categorizes query complexity for budget allocation.

const (
	// ComplexitySimple is for direct, single-entity questions.
	ComplexitySimple QueryComplexity = iota

	// ComplexityMedium is for multi-entity or explanation questions.
	ComplexityMedium

	// ComplexityComplex is for architecture or cross-cutting questions.
	ComplexityComplex
)

func (QueryComplexity) String

func (c QueryComplexity) String() string

String returns the string representation of complexity.

type SanitizeConfig

type SanitizeConfig struct {
	// Model is the LLM model type for model-aware sanitization.
	Model agent.ModelType

	// PreserveCodeBlocks preserves markdown code blocks during sanitization.
	PreserveCodeBlocks bool

	// PreserveInlineCode preserves inline code during sanitization.
	PreserveInlineCode bool

	// StripThinkTags removes <think> tags.
	StripThinkTags bool

	// StripReasoningTags removes <thought>, <reasoning>, <reflection> tags.
	StripReasoningTags bool
}

SanitizeConfig configures output sanitization behavior.

func DefaultSanitizeConfig

func DefaultSanitizeConfig() SanitizeConfig

DefaultSanitizeConfig returns production defaults.

type SanitizeResult

type SanitizeResult struct {
	// Content is the sanitized text.
	Content string

	// Stripped indicates if any content was stripped.
	Stripped bool

	// StrippedCount is the number of patterns stripped.
	StrippedCount int
}

SanitizeResult contains the result of sanitization.

type StepBudget

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

StepBudget manages step allocation between exploration and synthesis.

Description:

Reserves a configurable percentage of the step budget for the synthesis
phase, ensuring the agent has steps available to synthesize a response
even when exploration is exhaustive.

Thread Safety: Safe for concurrent use via mutex.

func NewStepBudget

func NewStepBudget(config BudgetConfig) *StepBudget

NewStepBudget creates a new budget with the given config.

Inputs:

config - Configuration for budget behavior.

Outputs:

*StepBudget - The configured budget manager.

func (*StepBudget) CanExplore

func (b *StepBudget) CanExplore() bool

CanExplore returns true if exploration steps remain.

Description:

Checks if the current step is within the exploration budget.
Once exploration budget is exhausted, the agent must synthesize.

Outputs:

bool - True if more exploration steps are available.

Thread Safety: Safe for concurrent use.

func (*StepBudget) DetectComplexity

func (b *StepBudget) DetectComplexity(query string)

DetectComplexity analyzes the query to determine complexity.

Description:

Examines the query for complexity indicators and adjusts the
synthesis budget accordingly. More complex queries get more
exploration budget.

Inputs:

query - The user's query string.

Thread Safety: Safe for concurrent use.

func (*StepBudget) EnterSynthesis

func (b *StepBudget) EnterSynthesis()

EnterSynthesis forces entry into synthesis phase.

Description:

Explicitly enters synthesis phase regardless of current step.
Called when the agent decides it has enough information to synthesize.

Thread Safety: Safe for concurrent use.

func (*StepBudget) GetComplexity

func (b *StepBudget) GetComplexity() QueryComplexity

GetComplexity returns the detected query complexity.

Thread Safety: Safe for concurrent use.

func (*StepBudget) IncrementStep

func (b *StepBudget) IncrementStep()

IncrementStep increments the step counter.

Thread Safety: Safe for concurrent use.

func (*StepBudget) IsInSynthesis

func (b *StepBudget) IsInSynthesis() bool

IsInSynthesis returns true if explicitly in synthesis phase.

Thread Safety: Safe for concurrent use.

func (*StepBudget) MustSynthesize

func (b *StepBudget) MustSynthesize() bool

MustSynthesize returns true if we're in the synthesis phase.

Description:

Returns true when exploration budget is exhausted and the agent
must synthesize a response from gathered information.

Outputs:

bool - True if synthesis is required.

Thread Safety: Safe for concurrent use.

func (*StepBudget) RemainingExplorationSteps

func (b *StepBudget) RemainingExplorationSteps() int

RemainingExplorationSteps returns steps remaining for exploration.

Thread Safety: Safe for concurrent use.

func (*StepBudget) RemainingSteps

func (b *StepBudget) RemainingSteps() int

RemainingSteps returns steps remaining in total budget.

Thread Safety: Safe for concurrent use.

func (*StepBudget) Reset

func (b *StepBudget) Reset()

Reset resets the budget for a new session.

Thread Safety: Safe for concurrent use.

func (*StepBudget) SetTotalSteps

func (b *StepBudget) SetTotalSteps(steps int)

SetTotalSteps updates the total step budget.

Thread Safety: Safe for concurrent use.

func (*StepBudget) Status

func (b *StepBudget) Status() BudgetStatus

Status returns current budget status for logging.

Thread Safety: Safe for concurrent use.

type StuckResult

type StuckResult struct {
	// IsStuck indicates if the agent is in a deadlock.
	IsStuck bool

	// ViolationType is the primary violation type.
	ViolationType ViolationType

	// ViolationCategory is the primary violation category.
	ViolationCategory ViolationCategory

	// Streak is the number of consecutive same-type violations.
	Streak int

	// DetectionRule is which rule triggered stuck ("same_type", "category_saturation", "oscillation").
	DetectionRule string

	// Violations are the recent violations for context.
	Violations []Violation

	// HelpRequest is the generated help message.
	HelpRequest HelpRequest
}

StuckResult is returned when frustration threshold is exceeded.

type ToolResultInput

type ToolResultInput struct {
	// ToolName is the name of the tool that was executed.
	ToolName string

	// Content is the output from the tool.
	Content string

	// Success indicates if the tool execution succeeded.
	Success bool
}

ToolResultInput represents a tool execution result for extraction.

type Violation

type Violation struct {
	// Type is the specific violation type.
	Type ViolationType

	// Category is the violation category (derived from Type).
	Category ViolationCategory

	// Message is a human-readable description.
	Message string

	// Context contains additional details (e.g., {"path": "/config.yaml"}).
	Context map[string]string

	// Timestamp is when the violation occurred.
	Timestamp time.Time

	// Attempt is which retry attempt this was.
	Attempt int
}

Violation records a single failure event.

func NewViolation

func NewViolation(vt ViolationType, message string, context map[string]string) Violation

NewViolation creates a violation with auto-derived category.

Description:

Creates a new Violation with the category automatically derived
from the violation type.

Inputs:

vt - The violation type.
message - Human-readable description.
context - Additional context (can be nil).

Outputs:

Violation - The created violation.

type ViolationCategory

type ViolationCategory string

ViolationCategory groups violation types for category-based detection.

const (
	// CategoryResource covers file, network, and resource access issues.
	CategoryResource ViolationCategory = "resource"

	// CategoryValidation covers safety layer rejections.
	CategoryValidation ViolationCategory = "validation"

	// CategorySemantic covers understanding and context issues.
	CategorySemantic ViolationCategory = "semantic"

	// CategoryUnknown for unclassified violations.
	CategoryUnknown ViolationCategory = "unknown"
)

func CategoryForType

func CategoryForType(vt ViolationType) ViolationCategory

CategoryForType returns the category for a violation type.

type ViolationType

type ViolationType string

ViolationType represents categories of agent failures.

const (
	// Resource violations - external dependencies unavailable.
	ViolationFileNotFound      ViolationType = "file_not_found"
	ViolationPermissionDenied  ViolationType = "permission_denied"
	ViolationNetworkError      ViolationType = "network_error"
	ViolationResourceExhausted ViolationType = "resource_exhausted"

	// Validation violations - safety layers rejecting output.
	ViolationIntentLoop      ViolationType = "intent_loop"
	ViolationMalformedTool   ViolationType = "malformed_tool"
	ViolationInvalidCitation ViolationType = "invalid_citation"
	ViolationConstraint      ViolationType = "constraint_violation"

	// Semantic violations - understanding issues.
	ViolationAmbiguous      ViolationType = "ambiguous_request"
	ViolationConflicting    ViolationType = "conflicting_info"
	ViolationMissingContext ViolationType = "missing_context"
	ViolationScopeUnclear   ViolationType = "scope_unclear"

	// Unknown violation type.
	ViolationUnknown ViolationType = "unknown"
)

Jump to

Keyboard shortcuts

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