use std::time::Duration;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum LoopState {
Initialized,
Generating,
Detecting,
Executing,
Integrating,
Completed,
Stuck,
Yielded,
}
impl LoopState {
pub fn is_terminal(self) -> bool {
matches!(self, Self::Completed | Self::Stuck | Self::Yielded)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum LoopStatus {
Progressing,
Exploring,
Struggling,
Stuck,
Completed,
Terminated,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoopConfig {
pub max_iterations: u32,
pub max_tool_calls: u32,
pub max_wall_time: Duration,
pub max_tokens: u32,
pub context_compression: bool,
pub allow_uncertainty: bool,
pub allow_yield: bool,
pub preserve_exploration: bool,
pub detect_implicit_signals: bool,
pub approval_timeout: Duration,
}
impl Default for LoopConfig {
fn default() -> Self {
Self {
max_iterations: 10,
max_tool_calls: 50,
max_wall_time: Duration::from_secs(300),
max_tokens: 16384,
context_compression: true,
allow_uncertainty: true,
allow_yield: true,
preserve_exploration: true,
detect_implicit_signals: true,
approval_timeout: Duration::from_secs(300),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenBudget {
pub limit: u32,
pub used: u32,
}
impl TokenBudget {
pub fn new(limit: u32) -> Self {
Self { limit, used: 0 }
}
pub fn remaining(&self) -> u32 {
self.limit.saturating_sub(self.used)
}
pub fn is_exhausted(&self) -> bool {
self.used >= self.limit
}
pub fn consume(&mut self, tokens: u32) -> bool {
self.used = self.used.saturating_add(tokens);
!self.is_exhausted()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TerminationReason {
Natural(NaturalTermination),
Resource(ResourceTermination),
External(ExternalTermination),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum NaturalTermination {
AnswerProvided {
answer: String,
confidence: f32,
},
AgentYielded {
partial: Option<String>,
reason: String,
},
AgentStuck {
attempts: u32,
request: StuckRequest,
},
TaskComplete,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ResourceTermination {
MaxIterations {
completed: u32,
limit: u32,
},
TokenBudgetExhausted {
generated: u32,
budget: u32,
},
WallTimeExceeded {
elapsed: Duration,
limit: Duration,
},
ToolCallLimitReached {
calls: u32,
limit: u32,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ExternalTermination {
ClientCancelled,
OperatorTerminated {
reason: String,
},
SystemShutdown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MetaSignal {
Answer {
content: String,
confidence: f32,
caveats: Vec<String>,
},
Uncertain {
partial_answer: Option<String>,
missing_information: Vec<String>,
would_help: Vec<String>,
},
Stuck {
attempts: Vec<AttemptSummary>,
hypothesis: Option<String>,
request: StuckRequest,
},
Yield {
partial_progress: Option<String>,
suggested_expertise: Vec<String>,
},
Thinking {
direction: String,
estimated_steps: Option<u32>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AttemptSummary {
pub description: String,
pub outcome: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum StuckRequest {
Clarification(Vec<String>),
MoreContext {
about: String,
},
DifferentTools {
need: Vec<String>,
},
HumanIntervention {
reason: String,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DetectionConfig {
pub detect_implicit: bool,
pub implicit_threshold: f32,
}
impl Default for DetectionConfig {
fn default() -> Self {
Self {
detect_implicit: true,
implicit_threshold: 0.7,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AutonomyGrant {
pub auto_approve: Vec<ToolPattern>,
pub require_approval: Vec<ToolPattern>,
pub forbidden: Vec<ToolPattern>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ToolPattern {
Read(String),
Write(String),
Bash(String),
Tool(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Permission {
Allowed,
RequiresApproval,
Forbidden,
}
#[derive(Debug, Clone)]
pub enum ExecutionOutcome {
Completed(AgenticToolResult),
Denied {
reason: String,
},
PendingApproval {
call_id: String,
},
Failed(ToolError),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgenticToolResult {
pub call_id: String,
pub tool_name: String,
pub status: ResultStatus,
pub data: serde_json::Value,
pub confidence: Confidence,
pub latency_ms: u64,
pub truncated: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum ResultStatus {
Success,
PartialSuccess {
completed: u32,
failed: u32,
},
Empty,
Failed {
recoverable: bool,
},
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum Confidence {
Measured,
Estimated,
Uncertain,
Unknown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolError {
pub call_id: String,
pub error: String,
pub recoverable: bool,
pub suggestion: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExplorationBranch {
pub description: String,
pub tool_calls: Vec<String>,
pub productive: bool,
pub findings: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoopSummary {
pub termination: TerminationReason,
pub iterations_completed: u32,
pub tool_calls_made: u32,
pub tokens_generated: u32,
pub wall_time: Duration,
pub partial_answer: Option<String>,
pub exploration_summary: Vec<ExplorationBranch>,
pub tool_results_summary: Vec<AgenticToolResult>,
pub can_resume: bool,
pub continuation_token: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContextWindow {
pub messages: Vec<ContextMessage>,
pub system_state: LoopStateSnapshot,
pub original_token_count: u32,
pub current_token_count: u32,
pub compressions_applied: Vec<CompressionEvent>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContextMessage {
pub role: String,
pub content: String,
pub tool_call_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoopStateSnapshot {
pub iteration: u32,
pub max_iterations: u32,
pub token_budget_remaining: u32,
pub tools_available: Vec<String>,
pub context_pressure: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum CompressionStrategy {
SummarizeOldResults {
keep_recent: u32,
},
PruneDeadEnds,
CollapseExploration {
summary_tokens: u32,
},
AgentDirected,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompressionEvent {
pub strategy: CompressionStrategy,
pub tokens_saved: u32,
pub at_iteration: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "event", content = "data")]
#[serde(rename_all = "snake_case")]
pub enum LoopEvent {
LoopStarted {
session_id: String,
config: LoopConfig,
},
IterationStarted {
iteration: u32,
status: LoopStatus,
},
IterationCompleted {
iteration: u32,
outcome: IterationOutcome,
},
LoopCompleted {
summary: LoopSummary,
},
TokenGenerated {
token: String,
},
GenerationCompleted {
content: String,
tokens: u32,
},
ToolCallDetected {
call_id: String,
tool: String,
},
ToolExecutionStarted {
call_id: String,
tool: String,
},
ToolExecutionCompleted {
call_id: String,
result: AgenticToolResult,
},
ToolApprovalRequired {
call_id: String,
tool: String,
arguments: serde_json::Value,
timeout_secs: u64,
pending_count: usize,
},
MetaSignalDetected {
signal: MetaSignal,
},
ContextCompressed {
strategy: CompressionStrategy,
saved_tokens: u32,
},
Error {
message: String,
recoverable: bool,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum IterationOutcome {
ToolCallsExecuted {
count: u32,
},
AnswerProvided,
Stuck,
Yielded,
ResourceLimitReached,
}
#[derive(Debug, Clone, thiserror::Error)]
pub enum TransitionError {
#[error("invalid transition from {from:?}: {reason}")]
InvalidTransition {
from: LoopState,
reason: String,
},
#[error("resource limit: {0}")]
ResourceLimitReached(String),
#[error("loop already terminated")]
AlreadyTerminated,
}