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
- func QuickSanitize(content string) string
- func QuickSanitizeForModel(content string, model agent.ModelType) string
- func RecordStuckMetric(result StuckResult)
- type AgentStateInput
- type AmbiguousRule
- type BudgetConfig
- type BudgetStatus
- type BufferExtractor
- type ExtractionConfig
- type ExtractionResult
- type FrustrationConfig
- type FrustrationTracker
- type HelpRequest
- type IntentCache
- type IntentClassifier
- type IntentConfig
- type IntentResult
- type OutputSanitizer
- type QueryComplexity
- type SanitizeConfig
- type SanitizeResult
- type StepBudget
- func (b *StepBudget) CanExplore() bool
- func (b *StepBudget) DetectComplexity(query string)
- func (b *StepBudget) EnterSynthesis()
- func (b *StepBudget) GetComplexity() QueryComplexity
- func (b *StepBudget) IncrementStep()
- func (b *StepBudget) IsInSynthesis() bool
- func (b *StepBudget) MustSynthesize() bool
- func (b *StepBudget) RemainingExplorationSteps() int
- func (b *StepBudget) RemainingSteps() int
- func (b *StepBudget) Reset()
- func (b *StepBudget) SetTotalSteps(steps int)
- func (b *StepBudget) Status() BudgetStatus
- type StuckResult
- type ToolResultInput
- type Violation
- type ViolationCategory
- type ViolationType
Constants ¶
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 ¶
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 ¶
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 ¶
func (e *BufferExtractor) Extract(state AgentStateInput) ExtractionResult
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" )