collet 0.1.1

Relentless agentic coding orchestrator with zero-drop agent loops
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
use serde::{Deserialize, Serialize};

/// A stored provider credential — credentials only, no model/behavior config.
///
/// Model selection and behavior overrides belong in `[[agents.list]]`.
/// Existing configs with extra fields (supports_tools, max_iterations, …) are
/// silently ignored during deserialization.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProviderEntry {
    #[serde(default)]
    pub name: String,
    #[serde(default, skip_serializing_if = "String::is_empty")]
    pub base_url: String,
    pub api_key_enc: Option<String>,
    /// All models available for this provider (used for model-picker UI).
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub models: Vec<String>,
    /// CLI binary name (e.g. "claude", "codex"). Mutually exclusive with
    /// `base_url` — if both are set, `base_url` takes priority.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub cli: Option<String>,
    /// Default arguments for the CLI binary (e.g. ["-p"] for headless mode).
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub cli_args: Vec<String>,
    /// Extra arguments appended when collet runs in yolo (auto-approve) mode.
    /// These are injected after `cli_args` when the user starts with `--yolo`.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub cli_yolo_args: Vec<String>,
    /// If set, passes the model via this environment variable instead of `--model`.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub cli_model_env: Option<String>,
    /// If true, model selection is not passed to the CLI at all.
    #[serde(default)]
    pub cli_skip_model: bool,
    /// Environment variables to set in yolo mode, as "KEY=VALUE" strings.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub cli_yolo_env: Vec<String>,
    /// CLI flag for passing max turns/iterations (e.g. "--max-turns"). None = not supported.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub cli_max_turns_flag: Option<String>,
}

/// Role used to select a model from a provider's ordered `models` list.
///
/// Convention: models are listed from most capable (index 0) to cheapest/fastest (last).
/// - `Coordinator` → `models[0]` (most capable)
/// - `Worker`      → `models[last]` (cheapest/fastest)
/// - Single model  → used for all roles
#[derive(Debug, Clone, Copy)]
pub enum ModelRole {
    Coordinator,
    Worker,
}

impl ProviderEntry {
    /// All models registered for this provider.
    pub fn all_models(&self) -> Vec<&str> {
        self.models.iter().map(|m| m.as_str()).collect()
    }

    /// Resolve a model for the given role from the ordered `models` array.
    ///
    /// Returns `None` if no models are registered.
    pub fn model_for_role(&self, role: ModelRole) -> Option<&str> {
        match self.models.as_slice() {
            [] => None,
            [only] => Some(only.as_str()),
            models => match role {
                ModelRole::Coordinator => models.first().map(String::as_str),
                ModelRole::Worker => models.last().map(String::as_str),
            },
        }
    }

    /// True if this is a CLI-only provider (no HTTP base_url).
    pub fn is_cli(&self) -> bool {
        self.cli.is_some() && self.base_url.is_empty()
    }

    /// Check if the CLI binary is available on PATH.
    pub fn cli_available(&self) -> bool {
        self.cli.as_ref().is_some_and(|bin| {
            std::process::Command::new("which")
                .arg(bin)
                .stdout(std::process::Stdio::null())
                .stderr(std::process::Stdio::null())
                .status()
                .map(|s| s.success())
                .unwrap_or(false)
        })
    }
}

/// Per-model overrides — the middle layer in the global < model < agent chain.
///
/// ```toml
/// [[models]]
/// name = "glm-4.7"
/// temperature = 0.3
/// thinking_budget_tokens = 4096
/// ```
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ModelOverride {
    #[serde(default)]
    pub name: String,
    #[serde(default)]
    pub temperature: Option<f32>,
    #[serde(default)]
    pub thinking_budget_tokens: Option<u32>,
    /// Reasoning effort level: "low", "medium", "high".
    /// Ignored if `thinking_budget_tokens` is set.
    #[serde(default)]
    pub reasoning_effort: Option<String>,
    #[serde(default)]
    pub supports_tools: Option<bool>,
    #[serde(default)]
    pub supports_reasoning: Option<bool>,
    #[serde(default)]
    pub context_window: Option<usize>,
    #[serde(default)]
    pub max_output_tokens: Option<u32>,
    #[serde(default)]
    pub max_iterations: Option<u32>,
    #[serde(default)]
    pub iteration_delay_ms: Option<u64>,
    /// Maximum concurrent requests allowed for this model (per-model rate limit).
    /// When omitted, a built-in default based on cost tier is used.
    #[serde(default)]
    pub concurrency_limit: Option<u32>,
}

/// A named agent definition — model selection + all behavior/capability overrides.
///
/// Providers supply only credentials (base_url + api_key). Everything that
/// affects *how* the agent runs lives here.
///
/// ```toml
/// [[agents.list]]
/// name     = "code"
/// provider = "openai"
/// model    = "gpt-4o"
/// max_iterations = 30
/// temperature    = 0.2
///
/// [[agents.list]]
/// name                  = "architect"
/// provider              = "anthropic"
/// model                 = "sonnet"
/// thinking_budget_tokens = 10000
/// ```
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AgentDef {
    pub name: String,
    pub model: String,
    /// One-line description of what this agent does (from `description:` frontmatter).
    /// Used for agent discovery UI and sub-agent routing.
    #[serde(default)]
    pub description: Option<String>,
    /// Model tier hint declared by the agent: "heavy" | "medium" | "light".
    /// Used during provider remap to assign the best-fit model from each tier.
    /// `heavy` = complex reasoning, `medium` = balanced, `light` = fast Q&A.
    #[serde(default)]
    pub tier: Option<String>,
    /// Reference to a `[[providers]]` entry name. When set, switching to this
    /// agent also switches base_url/api_key to the referenced provider.
    /// If None, inherits the currently active provider.
    #[serde(default)]
    pub provider: Option<String>,
    /// Ordered fallback chain of provider names. First available provider wins.
    /// Takes priority over `provider` when non-empty.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub providers: Vec<String>,
    /// Discovery tags for BM25 search routing.
    /// Supports comma-separated, inline array `[a, b]`, or block YAML `- item` format.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub tags: Vec<String>,
    /// Agent behavior prompt from .md body (empty for TOML-only defs).
    #[serde(default)]
    pub system_prompt: String,

    // ── Behavior overrides (None = fall back to [agent] global) ──────────
    /// Max agent iterations (None = `[agent]` max_iterations).
    #[serde(default)]
    pub max_iterations: Option<u32>,
    /// Delay between iterations in ms (None = `[agent]` iteration_delay_ms).
    #[serde(default)]
    pub iteration_delay_ms: Option<u64>,
    /// Sampling temperature sent in the API request (None = omit field).
    /// Automatically dropped for reasoning models (supports_reasoning = true).
    #[serde(default)]
    pub temperature: Option<f32>,
    /// Thinking/reasoning budget tokens (None = omit field).
    /// Only applied when the model profile has supports_reasoning = true.
    /// Takes priority over `reasoning_effort`.
    #[serde(default)]
    pub thinking_budget_tokens: Option<u32>,
    /// Reasoning effort level: "low", "medium", "high" (None = omit field).
    /// Ignored if `thinking_budget_tokens` is set.
    #[serde(default)]
    pub reasoning_effort: Option<String>,

    // ── Model-capability overrides (None = fall back to model_profile) ───
    /// Override tool-call support detection.
    #[serde(default)]
    pub supports_tools: Option<bool>,
    /// Override reasoning/thinking support detection.
    #[serde(default)]
    pub supports_reasoning: Option<bool>,
    /// Override context window size reported by the model profile.
    #[serde(default)]
    pub context_window: Option<usize>,
    /// Override max output tokens reported by the model profile.
    #[serde(default)]
    pub max_output_tokens: Option<u32>,

    // ── Soul ──
    /// Per-agent soul override (None = fall back to [soul] global enabled).
    #[serde(default)]
    pub soul: Option<bool>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct AgentsSection {
    /// Named agent definitions (used for Tab/Shift+Tab switching).
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub list: Vec<AgentDef>,

    /// Core role model assignments, written by `collet setup`.
    /// Format: "provider/model" — edit directly to override.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub arbor: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub code: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub architect: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ask: Option<String>,
}

impl Default for AgentsSection {
    fn default() -> Self {
        Self {
            list: vec![
                AgentDef {
                    name: "arbor".to_string(),
                    model: "default".to_string(),
                    provider: None,
                    description: Some("Autonomous orchestrator. Analyzes each task and selects the optimal agent(s) and execution mode automatically.".to_string()),
                    tier: Some("heavy".to_string()),
                    tags: vec!["autonomous".to_string(), "orchestration".to_string(),
                               "multi-agent".to_string(), "swarm".to_string()],
                    system_prompt: r#"You are Arbor — an autonomous orchestration agent for Collet.

## Role
You decide how every task should be executed. You have full authority over:
- Which collaboration mode to use (Fork / Hive / Flock / single-agent)
- Which specialist agents handle which subtasks
- How to decompose, parallelize, and merge work

## Decision Rules
- **Simple task** (single file, direct question, quick fix) → single-agent, execute immediately
- **Parallel independent tasks** (multiple files/modules, "and also", clearly separable) → Fork
- **Design / review / consensus** (architecture, planning, pros/cons, validation) → Hive
- **Cross-domain coordination** (frontend + backend, test + implement, real-time shared state) → Flock

## Behavior
- Do not ask the user which mode or agent to use — you decide.
- Briefly announce your routing decision at the start (one line), then execute.
- Delegate implementation to the appropriate specialist (code, architect, ask, etc.).
- Prefer the lightest mode that safely handles the task.
- After completion, summarize what was done and which agents participated.

## Example Routing Announcements
- "→ Single agent: quick fix in one file."
- "→ Fork: three independent modules — running in parallel."
- "→ Hive: architecture review — agents will reach consensus first."
- "→ Flock: frontend and backend changes — coordinating in real time."
"#.to_string(),
                    ..Default::default()
                },
                AgentDef {
                    name: "architect".to_string(),
                    model: "default".to_string(),
                    provider: None,
                    description: Some("Planning and design specialist. Breaks down tasks, creates implementation plans, coordinates multi-agent workflows.".to_string()),
                    tier: Some("heavy".to_string()),
                    tags: vec!["planning".to_string(), "design".to_string(), "architecture".to_string(),
                               "coordination".to_string()],
                    system_prompt: r#"You are an architect and technical lead. Your role is to:

## Primary Responsibilities
- Analyze user requests and break them down into clear, implementable tasks
- Create detailed implementation plans with step-by-step instructions
- Identify dependencies between tasks and suggest optimal execution order
- Coordinate multiple agents when parallel work is beneficial
- Review code changes for consistency with architectural decisions

## Behavior
- Start by understanding the full scope of the requested feature/change
- Create a structured plan before any implementation begins
- Consider edge cases, error handling, and testing requirements
- Suggest file organization and code structure improvements
- Flag potential architectural issues or technical debt

## Planning Format
When creating plans, use this structure:
1. **Analysis**: Current state, requirements gathering
2. **Design**: Architecture, data flow, component interaction
3. **Implementation**: Step-by-step tasks with dependencies
4. **Validation**: Testing approach, acceptance criteria

## Skills — use proactively
- /plan — for creating detailed implementation plans
- /review-code — for architectural reviews
- /analyze — for understanding existing codebases
- /refactor — for suggesting structural improvements

## Important Notes
- You do NOT write code yourself. Your job is planning and coordination.
- Delegate implementation tasks to the "code" agent.
- Always consider maintainability, performance, and security.
- Ask clarifying questions when requirements are ambiguous.
"#.to_string(),
                    ..Default::default()
                },
                AgentDef {
                    name: "code".to_string(),
                    model: "default".to_string(),
                    provider: None,
                    description: Some("Primary coding agent. Implements, tests, and verifies.".to_string()),
                    tier: Some("medium".to_string()),
                    tags: vec!["implementation".to_string(), "coding".to_string(), "testing".to_string(),
                               "debugging".to_string(), "build".to_string(), "refactoring".to_string(),
                               "compilation".to_string()],
                    system_prompt: r#"You are a senior software engineer working in an interactive coding session.

## Behavior
- Understand the request, then act immediately. Do not ask for permission to start.
- Briefly state your plan (1-2 sentences), then execute it with tools.
- Read relevant files before editing. Follow existing conventions.
- After changes: run build and tests to verify correctness.
- Keep changes minimal and focused. Avoid scope creep.

## Skills — use proactively
- /review-code — before marking any task complete
- /test-gen — when adding new public functions or fixing bugs
- /debug — when diagnosing failures or unexpected output
- /refactor — when asked to clean up or restructure code
- /commit-msg — when the user asks to commit or stage changes

## Core Principles
- **Read First**: Always read the file you're editing before making changes
- **Test Changes**: Run `cargo test`, `pytest`, `npm test`, etc. after code changes
- **Follow Conventions**: Match the existing code style and patterns
- **Be Minimal**: Change only what's needed for the current task
- **Verify**: Build and test to ensure changes work correctly

## Error Handling
- If tests fail: read the error, fix the issue, re-run tests
- If build fails: fix compilation errors, re-build
- If uncertain: ask clarifying questions rather than guessing
"#.to_string(),
                    ..Default::default()
                },
                AgentDef {
                    name: "ask".to_string(),
                    model: "default".to_string(),
                    provider: None,
                    description: Some("Q&A specialist - read-only, no code changes.".to_string()),
                    tier: Some("light".to_string()),
                    tags: vec!["qa".to_string(), "question".to_string(), "read-only".to_string()],
                    system_prompt: r#"You are a helpful Q&A assistant. Your role is to answer questions clearly and concisely.

## Behavior
- Provide accurate, focused answers to technical questions
- Explain concepts at the appropriate level of detail
- Reference official documentation when applicable
- Do NOT make code changes or suggest modifications
- If the question requires code changes, suggest using @code or an appropriate agent

## What You CAN Do
- Answer questions about code, architecture, and best practices
- Explain how things work
- Provide examples and documentation references
- Clarify ambiguities
- Suggest better approaches when appropriate

## What You CANNOT Do
- Write or modify code
- Execute commands that change files
- Run tests or builds
- Make assumptions about system state

## When to Delegate
- Code review requests → @security, @analyst, or @review
- Implementation tasks → @code
- Architecture questions → @architect
- Performance analysis → @analyst
"#.to_string(),
                    ..Default::default()
                },
            ],
            arbor: None,
            code: None,
            architect: None,
            ask: None,
        }
    }
}