Skip to content

Commit dcebf09

Browse files
nycjayJason Knaster
andauthored
docs: add guide and skill for adding new agent support (#959)
* docs: add guide and skill for adding new agent support - docs/development/adding-agents.md: comprehensive reference covering levels of support, step-by-step instructions, hook format reference, and common pitfalls - .claude/skills/add-agent/SKILL.md: interactive skill that walks through the agent addition workflow with verification checkpoints; includes a self-update section so the skill stays current - .kiro/skills/add-agent -> symlink to .claude/skills/add-agent so the skill works for both Claude Code and Kiro CLI users * docs: incorporate lessons from PR review Add pitfalls discovered during #958 review: - Sandbox hooks need separate wiring in build_container_config - Keep install_*_hooks() as pure file IO (no subprocess calls) - Use --format json when parsing agent CLIs - Document missing Waiting status as a limitation * fix: address PR review feedback - Remove .claude/skills/ and .kiro/skills/ (skill precedent decision deferred per maintainer) - Add website sync entries (PAGES, URL_MAP in sync-docs.mjs, nav in docsNav.ts) - Add cross-link from AGENTS.md to docs/development/adding-agents.md --------- Co-authored-by: Jason Knaster <jason.knaster@infor.com>
1 parent 08be017 commit dcebf09

4 files changed

Lines changed: 270 additions & 0 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
- `tests/`: integration tests (`tests/*.rs`).
2121
- `tests/e2e/`: end-to-end tests exercising the full `aoe` binary (see E2E Tests below).
2222
- `docs/`: user-facing documentation and guides.
23+
- `docs/development/adding-agents.md`: guide for adding a new agent to AoE.
2324
- `scripts/`: installation and utility scripts.
2425
- `xtask/`: build automation workspace.
2526

docs/development/adding-agents.md

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
# Adding a New Agent
2+
3+
This guide walks through adding support for a new AI coding agent to AoE.
4+
5+
## Overview
6+
7+
Adding an agent involves these files:
8+
9+
| File | Purpose |
10+
|------|---------|
11+
| `src/agents.rs` | Agent registry entry (name, binary, detection, flags) |
12+
| `src/tmux/status_detection.rs` | Status detection function (pane parsing or stub) |
13+
| `src/hooks/mod.rs` | Hook installer (if the agent supports hooks) |
14+
| `src/session/instance.rs` | Wire hook installation + env prefix |
15+
| `src/session/container_config.rs` | Config mount for Docker sandbox |
16+
| `docker/Dockerfile` | Install agent in sandbox image |
17+
| `README.md`, `docs/` | Documentation updates |
18+
19+
## Levels of Support
20+
21+
Not all agents need everything. Here's what each level provides:
22+
23+
### Level 1: Basic (minimum viable)
24+
25+
- Agent appears in `aoe agents` list
26+
- Sessions can be created and launched
27+
- Status always shows "Idle"
28+
29+
Requires: `AgentDef` entry + stub `detect_status` function.
30+
31+
### Level 2: Status Detection via Pane Parsing
32+
33+
- AoE reads the terminal output and infers running/waiting/idle
34+
- No agent-side configuration needed
35+
- Can be brittle if the agent's UI changes
36+
37+
Requires: a `detect_<agent>_status(content: &str) -> Status` function that parses tmux pane content.
38+
39+
Examples: OpenCode, Codex, Vibe, Copilot, Pi, Droid.
40+
41+
### Level 3: Status Detection via Hooks
42+
43+
- AoE installs hooks into the agent's config that write status to a file
44+
- Most reliable; survives UI changes
45+
- Requires the agent to support a hook/event system
46+
47+
Requires: either using the generic `hook_config` (if the agent uses the same JSON format as Claude/Cursor/Gemini) or a custom `install_<agent>_hooks()` function.
48+
49+
Examples: Claude, Cursor, Gemini (generic), Hermes (custom YAML), Kiro (custom JSON).
50+
51+
### Level 4: Session Resume
52+
53+
- AoE can restart a session and resume the prior conversation
54+
- Requires the agent to support a resume flag or session ID
55+
56+
Requires: setting `resume_strategy` in the `AgentDef`.
57+
58+
### Level 5: Docker Sandbox
59+
60+
- Agent runs in an isolated container
61+
- Config is synced from host to container
62+
63+
Requires: `AgentConfigMount` entry + Dockerfile installation.
64+
65+
## Step-by-Step: Adding a New Agent
66+
67+
### 1. Research the Agent
68+
69+
Before writing code, gather:
70+
71+
- **Binary name**: What command launches it? (e.g., `kiro-cli`, `claude`, `codex`)
72+
- **Detection**: How to check if it's installed? Usually `which <binary>`.
73+
- **YOLO/auto-approve flag**: Does it have a flag to skip permission prompts?
74+
- **Resume flag**: Can it resume a prior session? What flag?
75+
- **Hook support**: Does it have lifecycle hooks (preToolUse, stop, etc.)?
76+
- **Hook format**: If hooks exist, what's the config format? (JSON, YAML, TOML?)
77+
- **Config directory**: Where does it store config? (e.g., `~/.kiro`, `~/.claude`)
78+
- **Install command**: How do users install it?
79+
80+
### 2. Add the Agent Definition (`src/agents.rs`)
81+
82+
Add an entry to the `AGENTS` array:
83+
84+
```rust
85+
AgentDef {
86+
name: "myagent", // canonical name
87+
binary: "myagent-cli", // binary to invoke
88+
aliases: &["my-agent"], // alternative names for resolve_tool_name
89+
detection: DetectionMethod::Which("myagent-cli"),
90+
yolo: Some(YoloMode::CliFlag("--auto-approve")),
91+
instruction_flag: None, // or Some("--system-prompt {}")
92+
set_default_command: false, // true if binary must be explicit in instance.command
93+
detect_status: status_detection::detect_myagent_status,
94+
container_env: &[], // env vars for container sessions
95+
hook_config: None, // or Some(AgentHookConfig { ... }) for Claude-format hooks
96+
resume_strategy: ResumeStrategy::Flag("--resume"),
97+
host_only: false, // true if sandbox/worktree not supported
98+
send_keys_enter_delay_ms: 0,
99+
install_hint: "npm install -g myagent-cli",
100+
},
101+
```
102+
103+
### 3. Add Status Detection (`src/tmux/status_detection.rs`)
104+
105+
For hook-based agents, add a stub:
106+
107+
```rust
108+
/// MyAgent status is detected via hooks, not tmux pane parsing.
109+
pub fn detect_myagent_status(_content: &str) -> Status {
110+
Status::Idle
111+
}
112+
```
113+
114+
For pane-parsing agents, write a function that examines the terminal content:
115+
116+
```rust
117+
pub fn detect_myagent_status(raw_content: &str) -> Status {
118+
let content = raw_content.to_lowercase();
119+
if content.contains("waiting for input") {
120+
Status::Waiting
121+
} else if content.contains("processing") {
122+
Status::Running
123+
} else {
124+
Status::Idle
125+
}
126+
}
127+
```
128+
129+
### 4. Add Hook Installation (if applicable)
130+
131+
If the agent supports hooks but uses a different format than Claude/Cursor/Gemini, add a custom installer in `src/hooks/mod.rs`. See `install_hermes_hooks` (YAML) or `install_kiro_hooks` (JSON) as examples.
132+
133+
Then wire it into `install_agent_status_hooks()` in `src/session/instance.rs`:
134+
135+
```rust
136+
} else if self.tool == "myagent" && !self.is_sandboxed() {
137+
if let Some(home) = dirs::home_dir() {
138+
let config_path = home.join(".myagent/hooks.json");
139+
if let Err(e) = crate::hooks::install_myagent_hooks(&config_path) {
140+
tracing::warn!("Failed to install myagent hooks: {}", e);
141+
}
142+
}
143+
}
144+
```
145+
146+
And add the tool name to `status_hook_env_prefix()` so `AOE_INSTANCE_ID` is passed:
147+
148+
```rust
149+
let has_hooks = agent.and_then(|a| a.hook_config.as_ref()).is_some()
150+
|| tool == "settl"
151+
|| tool == "hermes"
152+
|| tool == "kiro"
153+
|| tool == "myagent";
154+
```
155+
156+
### 5. Add Container Config Mount (`src/session/container_config.rs`)
157+
158+
```rust
159+
AgentConfigMount {
160+
tool_name: "myagent",
161+
host_rel: ".myagent",
162+
container_suffix: ".myagent",
163+
skip_entries: &["sandbox", "sessions", "cache"],
164+
seed_files: &[],
165+
copy_dirs: &[],
166+
keychain_credential: None,
167+
home_seed_files: &[],
168+
preserve_files: &[],
169+
clean_files: &[],
170+
},
171+
```
172+
173+
### 6. Add to Dockerfile (`docker/Dockerfile`)
174+
175+
```dockerfile
176+
# Install MyAgent
177+
RUN curl -fsSL https://myagent.dev/install | bash
178+
```
179+
180+
And add the config directory to the `mkdir -p` block.
181+
182+
### 7. Update Tests
183+
184+
In `src/agents.rs`, update:
185+
- `test_get_agent_known`
186+
- `test_agent_names`
187+
- `test_resolve_tool_name`
188+
- `test_settings_index_roundtrip`
189+
- `test_send_keys_enter_delay`
190+
- `test_install_hint_lookup`
191+
192+
In `src/tmux/status_detection.rs`, add a test for your detection function.
193+
194+
In `src/session/instance.rs`, add your agent to `test_status_hook_env_prefix_includes_hermes` (if hook-based).
195+
196+
### 8. Update Documentation
197+
198+
- `README.md`: features list + FAQ
199+
- `docs/index.md`: supported agents list
200+
- `docs/guides/sandbox.md`: sandbox overview + image table
201+
- `docker/Dockerfile.dev`: comment listing inherited agents
202+
203+
### 9. Verify
204+
205+
```bash
206+
cargo fmt
207+
cargo clippy -- -D warnings
208+
cargo test --lib agents
209+
cargo test --lib <youragent>
210+
cargo test --lib container_config
211+
cargo build
212+
./target/debug/aoe agents # verify detection works
213+
```
214+
215+
## Hook Format Reference
216+
217+
### Claude/Cursor/Gemini (generic `hook_config`)
218+
219+
```json
220+
{
221+
"hooks": {
222+
"PreToolUse": [{"hooks": [{"type": "command", "command": "sh -c '...'"}]}],
223+
"Stop": [{"hooks": [{"type": "command", "command": "sh -c '...'"}]}]
224+
}
225+
}
226+
```
227+
228+
Set `hook_config: Some(AgentHookConfig { ... })` in the agent def; the generic `install_hooks()` handles it.
229+
230+
### Hermes (custom YAML)
231+
232+
```yaml
233+
hooks:
234+
pre_tool_call:
235+
- command: "sh -c '...'"
236+
```
237+
238+
### Kiro CLI (custom JSON agent config)
239+
240+
```json
241+
{
242+
"name": "aoe-hooks",
243+
"tools": ["*"],
244+
"hooks": {
245+
"preToolUse": [{"command": "sh -c '...'"}],
246+
"stop": [{"command": "sh -c '...'"}]
247+
}
248+
}
249+
```
250+
251+
## Common Pitfalls
252+
253+
- **Forgetting `status_hook_env_prefix`**: Without `AOE_INSTANCE_ID`, hooks write nothing. Add your tool name to the check in `src/session/instance.rs`.
254+
- **Wrong hook format**: Each agent has its own schema; test that hooks actually fire by sending a message and checking `/tmp/aoe-hooks/*/status`.
255+
- **Sandbox hooks are separate**: Host hook installation (`install_agent_status_hooks`) doesn't cover sandbox sessions. You also need to wire hooks into `build_container_config` in `src/session/container_config.rs` so the sidecar volume is mounted and the agent config is materialized inside the container.
256+
- **Don't shell out in `install_*_hooks()`**: Keep hook installation as pure file IO. Any subprocess calls (like setting a default agent) should be in a separate function so `cargo test` doesn't mutate the developer's real environment.
257+
- **Use structured output when parsing agent CLIs**: If the agent CLI has `--format json` or similar, use it instead of substring matching on human-readable output. Human-readable formats change between versions.
258+
- **Waiting status needs a dedicated event**: Not all agents have an approval/permission event. If the agent doesn't expose one, document it as a limitation and consider filing upstream.
259+
- **Redundant Dockerfile ENV**: Check if PATH is already set before adding another layer.
260+
- **`set_default_command`**: Only needed for agents where the binary name alone isn't sufficient (e.g., opencode needs explicit command storage).

website/scripts/sync-docs.mjs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,13 @@ const PAGES = [
111111
title: "Development",
112112
description: "Build, run, and test Agent of Empires from source.",
113113
},
114+
{
115+
source: "docs/development/adding-agents.md",
116+
dest: "docs/development/adding-agents.md",
117+
title: "Adding a New Agent",
118+
description:
119+
"Step-by-step guide for adding support for a new AI coding agent to AoE.",
120+
},
114121
{
115122
source: "docs/sounds.md",
116123
dest: "docs/sounds.md",
@@ -149,6 +156,7 @@ const URL_MAP = {
149156
"docs/quick-start.md": "/docs/quick-start/",
150157
"docs/sounds.md": "/docs/sounds/",
151158
"docs/development.md": "/docs/development/",
159+
"docs/development/adding-agents.md": "/docs/development/adding-agents/",
152160
"docs/guides/configuration.md": "/docs/guides/configuration/",
153161
"docs/cli/reference.md": "/docs/cli/reference/",
154162
"docs/api.md": "/docs/api/",

website/src/data/docsNav.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export const docsNav: NavSection[] = [
4444
title: "Contributing",
4545
items: [
4646
{ title: "Development", href: "/docs/development/" },
47+
{ title: "Adding a New Agent", href: "/docs/development/adding-agents/" },
4748
],
4849
},
4950
];

0 commit comments

Comments
 (0)