Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,49 @@ schedule:
- release/*
```

### Engine Configuration

The `engine` field specifies which AI model to use and optional execution parameters. It accepts both a simple string format (model name only) and an object format with additional options.

```yaml
# Simple string format (just a model name)
engine: claude-opus-4.5

# Object format with additional options
engine:
model: claude-opus-4.5
max-turns: 50
timeout-minutes: 30
```

#### Fields

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `model` | string | `claude-opus-4.5` | AI model to use. Options include `claude-sonnet-4.5`, `gpt-5.2-codex`, `gemini-3-pro-preview`, etc. |
| `max-turns` | integer | *(none)* | Maximum number of agentic turns (tool-use iterations) the model is allowed per run. Maps to the `--max-turns` Copilot CLI argument. Use this to cap compute and prevent runaway loops. |
| `timeout-minutes` | integer | *(none)* | Maximum time in minutes the agent workflow is allowed to run. Maps to the `--max-timeout` Copilot CLI argument. Use this to cap long-running agent sessions. |

#### `max-turns`

Each "turn" is one iteration of the model calling a tool and receiving its output. Setting `max-turns` places an upper bound on how many such iterations the agent can perform in a single pipeline run. This is useful for:

- **Cost control** — limiting expensive model invocations.
- **Safety** — preventing infinite loops where the agent repeatedly calls tools without converging on a result.
- **Predictability** — ensuring the pipeline completes within a reasonable time frame.

When omitted, the Copilot CLI uses its built-in default. When set, the compiler emits `--max-turns <value>` in the generated pipeline's copilot params.

#### `timeout-minutes`

The `timeout-minutes` field sets a wall-clock limit (in minutes) for the entire agent session. It maps to the `--max-timeout` Copilot CLI argument. This is useful for:

- **Budget enforcement** — hard-capping the total runtime of an agent to control compute costs.
- **Pipeline hygiene** — preventing agents from occupying a runner indefinitely if they stall or enter long retry loops.
- **SLA compliance** — ensuring scheduled agents complete within a known window.

When omitted, the Copilot CLI uses its built-in default. When set, the compiler emits `--max-timeout <value>` in the generated pipeline's copilot params.

### Tools Configuration

The `tools` field controls which tools are available to the agent. Both sub-fields are optional and have sensible defaults.
Expand Down Expand Up @@ -439,6 +482,8 @@ Should be replaced with the human-readable name from the front matter (e.g., "Da

Additional params provided to agency CLI. The compiler generates:
- `--model <model>` - AI model from `engine` front matter field (default: claude-opus-4.5)
- `--max-turns <n>` - Maximum agentic turns from `engine.max-turns` (omitted when not set)
- `--max-timeout <n>` - Workflow timeout in minutes from `engine.timeout-minutes` (omitted when not set)
- `--disable-builtin-mcps` - Disables all built-in MCPs initially
- `--no-ask-user` - Prevents interactive prompts
- `--allow-tool <tool>` - Explicitly allows specific tools (github, safeoutputs, write, shell commands like cat, date, echo, grep, head, ls, pwd, sort, tail, uniq, wc, yq)
Expand Down
76 changes: 76 additions & 0 deletions src/compile/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,28 @@ pub fn generate_copilot_params(front_matter: &FrontMatter) -> String {
let mut params = Vec::new();

params.push(format!("--model {}", front_matter.engine.model()));
if let Some(max_turns) = front_matter.engine.max_turns() {
if max_turns == 0 {
eprintln!(
"Warning: Agent '{}' has max-turns: 0, which means zero turns allowed. \
The agent will not be able to perform any tool calls. \
Consider setting max-turns to at least 1.",
front_matter.name
);
}
params.push(format!("--max-turns {}", max_turns));
}
if let Some(timeout_minutes) = front_matter.engine.timeout_minutes() {
if timeout_minutes == 0 {
eprintln!(
"Warning: Agent '{}' has timeout-minutes: 0, which means no time is allowed. \
The agent session will time out immediately. \
Consider setting timeout-minutes to at least 1.",
front_matter.name
);
}
params.push(format!("--max-timeout {}", timeout_minutes));
}
params.push("--disable-builtin-mcps".to_string());
params.push("--no-ask-user".to_string());

Expand Down Expand Up @@ -894,6 +916,60 @@ mod tests {
assert!(params.contains("--mcp ado"));
}

#[test]
fn test_copilot_params_max_turns() {
let (fm, _) = parse_markdown(
"---\nname: test\ndescription: test\nengine:\n model: claude-opus-4.5\n max-turns: 50\n---\n",
)
.unwrap();
let params = generate_copilot_params(&fm);
assert!(params.contains("--max-turns 50"));
}

#[test]
fn test_copilot_params_no_max_turns_when_simple_engine() {
let fm = minimal_front_matter();
let params = generate_copilot_params(&fm);
assert!(!params.contains("--max-turns"));
}

#[test]
fn test_copilot_params_max_timeout() {
let (fm, _) = parse_markdown(
"---\nname: test\ndescription: test\nengine:\n model: claude-opus-4.5\n timeout-minutes: 30\n---\n",
)
.unwrap();
let params = generate_copilot_params(&fm);
assert!(params.contains("--max-timeout 30"));
}

#[test]
fn test_copilot_params_no_max_timeout_when_simple_engine() {
let fm = minimal_front_matter();
let params = generate_copilot_params(&fm);
assert!(!params.contains("--max-timeout"));
}

#[test]
fn test_copilot_params_max_turns_zero_still_emitted() {
let (fm, _) = parse_markdown(
"---\nname: test\ndescription: test\nengine:\n model: claude-opus-4.5\n max-turns: 0\n---\n",
)
.unwrap();
let params = generate_copilot_params(&fm);
assert!(params.contains("--max-turns 0"));
}

#[test]
fn test_copilot_params_max_timeout_zero_still_emitted() {
let (fm, _) = parse_markdown(
"---\nname: test\ndescription: test\nengine:\n model: claude-opus-4.5\n timeout-minutes: 0\n---\n",
)
.unwrap();
let params = generate_copilot_params(&fm);
assert!(params.contains("--max-timeout 0"));
}

// ─── sanitize_filename ────────────────────────────────────────────────────

#[test]
Expand Down