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
105impl Default for PoolConfig {
106 fn default() -> Self {
107 Self {
108 model: None,
109 permission_mode: Some(PermissionMode::Plan),
110 max_turns: None,
111 system_prompt: None,
112 allowed_tools: Vec::new(),
113 mcp_servers: HashMap::new(),
114 effort: None,
115 budget_microdollars: None,
116 slot_mode: SlotMode::default(),
117 max_restarts: 3,
118 worktree_isolation: false,
119 slot_assignment_timeout_secs: 300,
120 scaling: ScalingConfig::default(),
121 unattended_mode: false,
122 detect_permission_prompts: true,
123 }
124 }
125}
126
127/// Per-slot configuration overrides.
128///
129/// Any `Some` field here takes precedence over the corresponding field
130/// in [`PoolConfig`].
131#[derive(Debug, Clone, Default, Serialize, Deserialize)]
132pub struct SlotConfig {
133 /// Override model for this slot.
134 pub model: Option<String>,
135
136 /// Override permission mode for this slot.
137 pub permission_mode: Option<PermissionMode>,
138
139 /// Override max turns for this slot.
140 pub max_turns: Option<u32>,
141
142 /// Override system prompt for this slot.
143 pub system_prompt: Option<String>,
144
145 /// Additional allowed tools (merged with global).
146 pub allowed_tools: Option<Vec<String>>,
147
148 /// Additional MCP servers (merged with global).
149 pub mcp_servers: Option<HashMap<String, serde_json::Value>>,
150
151 /// Override effort level for this slot.
152 pub effort: Option<Effort>,
153
154 /// Optional name/role for this slot (e.g. "reviewer", "coder").
155 pub role: Option<String>,
156
157 /// Optional human-readable name for the slot (e.g. "reviewer", "writer").
158 pub name: Option<String>,
159
160 /// Optional description of the slot's purpose or responsibilities.
161 pub description: Option<String>,
162
163 /// Override slot assignment timeout (in seconds).
164 pub slot_assignment_timeout_secs: Option<u64>,
165}
166
167/// Current state of a slot.
168#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
169#[serde(rename_all = "snake_case")]
170pub enum SlotState {
171 /// Slot is ready to accept a task.
172 Idle,
173 /// Slot is currently executing a task.
174 Busy,
175 /// Slot process has exited or been stopped.
176 Stopped,
177 /// Slot encountered an error and needs attention.
178 Errored,
179}
180
181/// Record of a slot in the pool.
182#[derive(Debug, Clone, Serialize, Deserialize)]
183pub struct SlotRecord {
184 /// Unique slot identifier.
185 pub id: SlotId,
186
187 /// Current state.
188 pub state: SlotState,
189
190 /// Per-slot config overrides.
191 pub config: SlotConfig,
192
193 /// The task currently being executed, if any.
194 pub current_task: Option<TaskId>,
195
196 /// Claude session ID for session resumption.
197 pub session_id: Option<String>,
198
199 /// Number of tasks completed by this slot.
200 pub tasks_completed: u64,
201
202 /// Cumulative cost in microdollars.
203 pub cost_microdollars: u64,
204
205 /// Number of times this slot has been restarted.
206 pub restart_count: u32,
207
208 /// Git worktree path, if worktree isolation is enabled.
209 pub worktree_path: Option<String>,
210}
211
212// ── Task types ───────────────────────────────────────────────────────
213
214/// Current state of a task.
215#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
216#[serde(rename_all = "snake_case")]
217pub enum TaskState {
218 /// Task is waiting for a slot.
219 Pending,
220 /// Task is being executed by a slot.
221 Running,
222 /// Task completed successfully.
223 Completed,
224 /// Task failed.
225 Failed,
226 /// Task was cancelled.
227 Cancelled,
228}
229
230/// A task submitted to the pool.
231#[derive(Debug, Clone, Serialize, Deserialize)]
232pub struct TaskRecord {
233 /// Unique task identifier.
234 pub id: TaskId,
235
236 /// The prompt/instruction for the task.
237 pub prompt: String,
238
239 /// Current state.
240 pub state: TaskState,
241
242 /// Slot assigned to this task.
243 pub slot_id: Option<SlotId>,
244
245 /// Task result, available when state is `Completed` or `Failed`.
246 pub result: Option<TaskResult>,
247
248 /// Optional tags for filtering and grouping.
249 pub tags: Vec<String>,
250
251 /// Per-task config overrides (takes precedence over slot and global config).
252 pub config: Option<SlotConfig>,
253}
254
255/// The result of a completed task.
256#[derive(Debug, Clone, Serialize, Deserialize)]
257pub struct TaskResult {
258 /// The text output from Claude.
259 pub output: String,
260
261 /// Whether the task succeeded.
262 pub success: bool,
263
264 /// Cost in microdollars.
265 pub cost_microdollars: u64,
266
267 /// Number of turns used.
268 pub turns_used: u32,
269
270 /// Session ID from the execution.
271 pub session_id: Option<String>,
272}
273
274/// Filter criteria for listing tasks.
275#[derive(Debug, Clone, Default, Serialize, Deserialize)]
276pub struct TaskFilter {
277 /// Filter by state.
278 pub state: Option<TaskState>,
279
280 /// Filter by slot.
281 pub slot_id: Option<SlotId>,
282
283 /// Filter by tags (any match).
284 pub tags: Option<Vec<String>>,
285}