oxi_agent/agent_loop/config.rs
1//! Agent loop configuration types
2
3/// Configuration for an [`crate::AgentLoop`] instance.
4#[derive(Clone)]
5pub struct AgentLoopConfig {
6 /// Model identifier in `provider/model` format.
7 pub model_id: String,
8 /// Optional system prompt prepended to every request.
9 pub system_prompt: Option<String>,
10 /// Sampling temperature (0.0 – 2.0).
11 pub temperature: f32,
12 /// Maximum tokens the model may generate per request.
13 pub max_tokens: u32,
14 /// Whether tool calls run in parallel or sequentially.
15 pub tool_execution: ToolExecutionMode,
16 /// Compaction strategy for managing context window usage.
17 pub compaction_strategy: oxi_ai::CompactionStrategy,
18 /// Approximate context window size in tokens.
19 pub context_window: usize,
20 /// Optional instruction injected into the compaction prompt.
21 pub compaction_instruction: Option<String>,
22 /// Optional session identifier for logging and tracing.
23 pub session_id: Option<String>,
24 /// Optional transport override (e.g. "sse", "stdio").
25 pub transport: Option<String>,
26 /// Whether to trigger compaction before the first turn.
27 pub compact_on_start: bool,
28 /// Optional cap on retry back-off delay (milliseconds).
29 pub max_retry_delay_ms: Option<u64>,
30 /// Enable automatic retry on retryable assistant errors.
31 pub auto_retry_enabled: bool,
32 /// Maximum number of auto-retry attempts.
33 pub auto_retry_max_attempts: usize,
34 /// Base delay in milliseconds for auto-retry exponential back-off.
35 pub auto_retry_base_delay_ms: u64,
36 /// API key override for the provider.
37 ///
38 /// When set, this is injected into [`oxi_ai::StreamOptions`] so the
39 /// provider uses it instead of an environment variable.
40 pub api_key: Option<String>,
41 /// Working directory for file tools. Defaults to current directory if None.
42 pub workspace_dir: Option<std::path::PathBuf>,
43 /// Per-provider options for fine-grained control.
44 ///
45 /// Passed through to [`oxi_ai::StreamOptions::provider_options`] so the
46 /// provider can read provider-specific settings.
47 pub provider_options: Option<oxi_ai::ProviderOptions>,
48
49 /// Async hook invoked after context compaction completes.
50 ///
51 /// Unlike the `Compaction` event in the `Fn` callback, this hook is
52 /// async and its future is awaited within the agent loop. Errors are
53 /// logged at WARN level but don't fail the loop.
54 ///
55 /// Use this for side effects that require async I/O (e.g., persisting
56 /// compaction summaries to a memory store) without resorting to
57 /// `tokio::spawn` fire-and-forget.
58 pub on_compaction: Option<CompactionHook>,
59 /// Snapshot store for hashline edit mode.
60 pub snapshot_store: Option<Arc<dyn oxi_hashline::SnapshotStore>>,
61 /// Memory backend for memory tools.
62 pub memory: Option<Arc<dyn crate::tools::MemoryBackend>>,
63 /// URL resolver for internal protocol schemes.
64 pub url_resolver: Option<Arc<dyn crate::tools::UrlResolver>>,
65 /// Todo state provider for the `todo` tool.
66 pub todo: Option<Arc<dyn crate::tools::TodoStateProvider>>,
67 /// Agent pool for Hub display.
68 pub agent_pool: Option<Arc<dyn crate::tools::AgentPoolProvider>>,
69 /// LSP provider for the `lsp` tool.
70 pub lsp: Option<Arc<dyn crate::tools::LspProvider>>,
71 /// TTSR engine for stream rule checking.
72 pub ttsr_engine: Option<Arc<crate::agent_loop::ttsr::TtsrEngine>>,
73 /// In-process sub-agent runner (issue #28 gap 3).
74 /// When `Some`, the `subagent` tool prefers an in-process isolated
75 /// run over shelling out to the CLI. Library consumers set this so
76 /// delegation works without an `oxi` subprocess.
77 pub subagent_runner: Option<Arc<dyn crate::tools::SubagentRunner>>,
78 /// Current sub-agent nesting depth (issue #28 gap 3).
79 ///
80 /// The CLI backend uses env vars (`OXI_SUBAGENT_DEPTH`) for this,
81 /// which is safe because each subprocess has its own env. The
82 /// in-process backend **cannot** use env vars (concurrent
83 /// `set_var` is UB; state leaks between forks), so it reads this
84 /// field instead. Default 0 (top-level). The `subagent` tool
85 /// increments this when creating a forked `AgentLoopConfig`, and
86 /// the fork checks it against the agent definition's
87 /// `max_subagent_depth` to cap recursion.
88 pub subagent_depth: u8,
89 /// Maximum size (in bytes) of a single tool result's text content
90 /// before it is truncated (issue #28 gap 1).
91 ///
92 /// When set, tool results exceeding this limit are truncated to
93 /// the limit and a marker is appended:
94 /// `"... [truncated: N bytes omitted]"`. This prevents a single
95 /// large tool output (e.g. reading a huge file, verbose bash
96 /// output) from consuming the entire context window.
97 ///
98 /// `None` (default) = no limit. Opt-in — existing behavior is
99 /// preserved.
100 pub max_tool_result_bytes: Option<usize>,
101}
102
103impl Default for AgentLoopConfig {
104 fn default() -> Self {
105 Self {
106 model_id: String::new(),
107 system_prompt: None,
108 temperature: 0.7,
109 max_tokens: 4096,
110 tool_execution: ToolExecutionMode::Parallel,
111 compaction_strategy: oxi_ai::CompactionStrategy::default(),
112 context_window: 128_000,
113 compaction_instruction: None,
114 session_id: None,
115 transport: None,
116 compact_on_start: false,
117 max_retry_delay_ms: None,
118 auto_retry_enabled: false,
119 auto_retry_max_attempts: 3,
120 auto_retry_base_delay_ms: 2000,
121 api_key: None,
122 workspace_dir: None,
123 provider_options: None,
124 on_compaction: None,
125 snapshot_store: None,
126 memory: None,
127 url_resolver: None,
128 todo: None,
129 agent_pool: None,
130 lsp: None,
131 ttsr_engine: None,
132 subagent_runner: None,
133 subagent_depth: 0,
134 max_tool_result_bytes: None,
135 }
136 }
137}
138
139// Re-export ToolExecutionMode from crate::config to avoid duplicate definitions.
140pub use crate::config::ToolExecutionMode;
141
142use crate::AgentToolResult;
143use crate::compaction::CompactedContext;
144use anyhow::{Error, Result};
145use serde_json::Value;
146use std::future::Future;
147use std::pin::Pin;
148use std::sync::Arc;
149
150/// Async hook invoked after context compaction completes.
151///
152/// Receives the [`CompactedContext`] and returns a `Result<()>` future.
153/// The future is awaited within the agent loop, so async operations
154/// (memory storage, logging, etc.) are safe here.
155///
156/// # Example
157///
158/// ```ignore
159/// let config = AgentLoopConfig {
160/// on_compaction: Some(Arc::new(|ctx: CompactedContext| {
161/// let summary = ctx.summary.clone();
162/// Box::pin(async move {
163/// memory_store.save(summary).await
164/// })
165/// })),
166/// ..Default::default()
167/// };
168/// ```
169pub type CompactionHook =
170 Arc<dyn Fn(CompactedContext) -> Pin<Box<dyn Future<Output = Result<()>> + Send>> + Send + Sync>;
171
172/// Hook invoked before each tool call; may return an override result.
173pub type BeforeToolCallHook = Arc<
174 dyn Fn(
175 &str,
176 &Value,
177 ) -> Pin<Box<dyn Future<Output = Result<Option<AgentToolResult>, Error>> + Send>>
178 + Send
179 + Sync,
180>;
181
182/// Hook invoked after each tool call; may return a modified result.
183pub type AfterToolCallHook = Arc<
184 dyn Fn(
185 &str,
186 &AgentToolResult,
187 ) -> Pin<Box<dyn Future<Output = Result<Option<AgentToolResult>, Error>> + Send>>
188 + Send
189 + Sync,
190>;
191
192// MAX_RETRIES and BACKOFF_BASE_SECS are now defined in crate::stream_retry
193// and re-exported from crate::agent_loop::retry.
194pub use crate::stream_retry::{BACKOFF_BASE_SECS, MAX_RETRIES};