claude_pool/types.rs
1//! Core types for claude-pool.
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6// Re-export shared types from claude-wrapper so consumers don't need
7// to depend on both crates for basic config.
8pub use claude_wrapper::types::{Effort, PermissionMode};
9
10// ── Identifiers ──────────────────────────────────────────────────────
11
12/// Unique identifier for a task.
13#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
14pub struct TaskId(pub String);
15
16/// Unique identifier for a slot.
17#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
18pub struct SlotId(pub String);
19
20// ── Slot types ─────────────────────────────────────────────────────
21
22/// Slot persistence mode.
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
24#[serde(rename_all = "snake_case")]
25pub enum SlotMode {
26 /// Persistent slots stay alive across tasks, resuming sessions.
27 #[default]
28 Persistent,
29 /// Ephemeral slots are created per task and destroyed after.
30 Ephemeral,
31}
32
33/// Configuration for dynamic slot pool scaling.
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct ScalingConfig {
36 /// Minimum number of slots (default: 1).
37 pub min_slots: usize,
38 /// Maximum number of slots (default: 16).
39 pub max_slots: usize,
40}
41
42impl Default for ScalingConfig {
43 fn default() -> Self {
44 Self {
45 min_slots: 1,
46 max_slots: 16,
47 }
48 }
49}
50
51/// Configuration that applies to all slots by default.
52///
53/// Individual slots can override any of these fields via [`SlotConfig`].
54#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct PoolConfig {
56 /// Claude model to use (e.g. "claude-haiku-4-5-20251001").
57 pub model: Option<String>,
58
59 /// Permission mode for slots.
60 pub permission_mode: Option<PermissionMode>,
61
62 /// Maximum turns per task.
63 pub max_turns: Option<u32>,
64
65 /// System prompt prepended to all slot tasks.
66 pub system_prompt: Option<String>,
67
68 /// Allowed tools for slots.
69 pub allowed_tools: Vec<String>,
70
71 /// MCP servers available to slots.
72 pub mcp_servers: HashMap<String, serde_json::Value>,
73
74 /// Default effort level for slots (maps to `--effort`).
75 pub effort: Option<Effort>,
76
77 /// Total budget cap for the pool in microdollars.
78 /// When cumulative spend across all slots reaches this limit,
79 /// new tasks are rejected with [`crate::Error::BudgetExhausted`].
80 pub budget_microdollars: Option<u64>,
81
82 /// Default slot mode.
83 pub slot_mode: SlotMode,
84
85 /// Maximum number of restarts per slot before marking as errored.
86 pub max_restarts: u32,
87
88 /// Enable git worktree isolation for slots.
89 pub worktree_isolation: bool,
90
91 /// Maximum time to wait for an idle slot before failing a task (in seconds).
92 pub slot_assignment_timeout_secs: u64,
93
94 /// Dynamic scaling configuration (min/max bounds).
95 pub scaling: ScalingConfig,
96
97 /// Enable unattended mode: use stricter permission defaults to prevent prompts.
98 /// When true, defaults to `DontAsk` permission mode if not explicitly set.
99 pub unattended_mode: bool,
100
101 /// If true, detect permission prompt patterns in stderr and provide actionable errors.
102 pub detect_permission_prompts: bool,
103
104 /// Enable the background supervisor loop for slot health monitoring.
105 ///
106 /// When enabled, the supervisor periodically checks for errored slots and
107 /// restarts them automatically (up to [`max_restarts`](Self::max_restarts)).
108 pub supervisor_enabled: bool,
109
110 /// Interval in seconds between supervisor health checks (default: 30).
111 ///
112 /// Only used when [`supervisor_enabled`](Self::supervisor_enabled) is true.
113 pub supervisor_interval_secs: u64,
114
115 /// Use `--strict-mcp-config` when passing MCP config to slots.
116 ///
117 /// Prevents slots from inheriting the coordinator's `.mcp.json`, which
118 /// avoids accidental recursive pool calls (a slot invoking `pool_run` on itself).
119 /// Default: `true`.
120 pub strict_mcp_config: bool,
121}
122
123impl Default for PoolConfig {
124 fn default() -> Self {
125 Self {
126 model: None,
127 permission_mode: Some(PermissionMode::Plan),
128 max_turns: None,
129 system_prompt: None,
130 allowed_tools: Vec::new(),
131 mcp_servers: HashMap::new(),
132 effort: None,
133 budget_microdollars: None,
134 slot_mode: SlotMode::default(),
135 max_restarts: 3,
136 worktree_isolation: false,
137 slot_assignment_timeout_secs: 300,
138 scaling: ScalingConfig::default(),
139 unattended_mode: false,
140 detect_permission_prompts: true,
141 supervisor_enabled: false,
142 supervisor_interval_secs: 30,
143 strict_mcp_config: true,
144 }
145 }
146}
147
148/// Per-slot configuration overrides.
149///
150/// Any `Some` field here takes precedence over the corresponding field
151/// in [`PoolConfig`].
152#[derive(Debug, Clone, Default, Serialize, Deserialize)]
153pub struct SlotConfig {
154 /// Override model for this slot.
155 pub model: Option<String>,
156
157 /// Override permission mode for this slot.
158 pub permission_mode: Option<PermissionMode>,
159
160 /// Override max turns for this slot.
161 pub max_turns: Option<u32>,
162
163 /// Override system prompt for this slot.
164 pub system_prompt: Option<String>,
165
166 /// Additional allowed tools (merged with global).
167 pub allowed_tools: Option<Vec<String>>,
168
169 /// Additional MCP servers (merged with global).
170 pub mcp_servers: Option<HashMap<String, serde_json::Value>>,
171
172 /// Override effort level for this slot.
173 pub effort: Option<Effort>,
174
175 /// Optional name/role for this slot (e.g. "reviewer", "coder").
176 pub role: Option<String>,
177
178 /// Optional human-readable name for the slot (e.g. "reviewer", "writer").
179 pub name: Option<String>,
180
181 /// Optional description of the slot's purpose or responsibilities.
182 pub description: Option<String>,
183
184 /// Override slot assignment timeout (in seconds).
185 pub slot_assignment_timeout_secs: Option<u64>,
186}
187
188/// Current state of a slot.
189#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
190#[serde(rename_all = "snake_case")]
191pub enum SlotState {
192 /// Slot is ready to accept a task.
193 Idle,
194 /// Slot is currently executing a task.
195 Busy,
196 /// Slot process has exited or been stopped.
197 Stopped,
198 /// Slot encountered an error and needs attention.
199 Errored,
200}
201
202/// Record of a slot in the pool.
203#[derive(Debug, Clone, Serialize, Deserialize)]
204pub struct SlotRecord {
205 /// Unique slot identifier.
206 pub id: SlotId,
207
208 /// Current state.
209 pub state: SlotState,
210
211 /// Per-slot config overrides.
212 pub config: SlotConfig,
213
214 /// The task currently being executed, if any.
215 pub current_task: Option<TaskId>,
216
217 /// Claude session ID for session resumption.
218 pub session_id: Option<String>,
219
220 /// Number of tasks completed by this slot.
221 pub tasks_completed: u64,
222
223 /// Cumulative cost in microdollars.
224 pub cost_microdollars: u64,
225
226 /// Number of times this slot has been restarted.
227 pub restart_count: u32,
228
229 /// Git worktree path, if worktree isolation is enabled.
230 pub worktree_path: Option<String>,
231
232 /// Path to the slot's temp `.mcp.json` file, if MCP servers are configured.
233 ///
234 /// Written once per slot (on first task that needs it) and reused across
235 /// subsequent tasks. Cleaned up on pool drain/shutdown.
236 #[serde(skip)]
237 pub mcp_config_path: Option<std::path::PathBuf>,
238}
239
240// ── Task types ───────────────────────────────────────────────────────
241
242/// Current state of a task.
243#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
244#[serde(rename_all = "snake_case")]
245pub enum TaskState {
246 /// Task is waiting for a slot.
247 Pending,
248 /// Task is being executed by a slot.
249 Running,
250 /// Task completed successfully.
251 Completed,
252 /// Task failed.
253 Failed,
254 /// Task was cancelled.
255 Cancelled,
256}
257
258/// A task submitted to the pool.
259#[derive(Debug, Clone, Serialize, Deserialize)]
260pub struct TaskRecord {
261 /// Unique task identifier.
262 pub id: TaskId,
263
264 /// The prompt/instruction for the task.
265 pub prompt: String,
266
267 /// Current state.
268 pub state: TaskState,
269
270 /// Slot assigned to this task.
271 pub slot_id: Option<SlotId>,
272
273 /// Task result, available when state is `Completed` or `Failed`.
274 pub result: Option<TaskResult>,
275
276 /// Optional tags for filtering and grouping.
277 pub tags: Vec<String>,
278
279 /// Per-task config overrides (takes precedence over slot and global config).
280 pub config: Option<SlotConfig>,
281}
282
283/// The result of a completed task.
284#[derive(Debug, Clone, Serialize, Deserialize)]
285pub struct TaskResult {
286 /// The text output from Claude.
287 pub output: String,
288
289 /// Whether the task succeeded.
290 pub success: bool,
291
292 /// Cost in microdollars.
293 pub cost_microdollars: u64,
294
295 /// Number of turns used.
296 pub turns_used: u32,
297
298 /// Session ID from the execution.
299 pub session_id: Option<String>,
300}
301
302/// Filter criteria for listing tasks.
303#[derive(Debug, Clone, Default, Serialize, Deserialize)]
304pub struct TaskFilter {
305 /// Filter by state.
306 pub state: Option<TaskState>,
307
308 /// Filter by slot.
309 pub slot_id: Option<SlotId>,
310
311 /// Filter by tags (any match).
312 pub tags: Option<Vec<String>>,
313}