feat: overhaul orchestrator prompt with comprehensive CLI reference (#90)
* feat: overhaul orchestrator prompt with comprehensive CLI reference
The generated CLAUDE.orchestrator.md now teaches the orchestrator agent
everything it needs out of the box: identity/role, complete CLI reference
for every `ao` command with flags/options, behavioral guidelines (do/don't),
session lifecycle, workflows, and anti-patterns.
- Rewrite generateOrchestratorPrompt() with detailed CLI docs
- Add "Never Do" section (no legacy scripts, no raw tmux, no coding)
- Add session lifecycle ASCII diagram adapted to project config
- Document ao send flags (--no-wait, --timeout, -f) and mechanics
- Update static CLAUDE.orchestrator.md to use ao CLI exclusively
- Add 35 unit tests for orchestrator prompt generation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: move orchestrator prompt injection into Agent plugin interface
The orchestrator prompt was being injected by directly writing
CLAUDE.local.md and CLAUDE.orchestrator.md in start.ts — Claude Code-
specific logic that doesn't work for other agents (Codex, Aider, OpenCode).
- Add `injectSystemPrompt()` to the Agent interface in types.ts
- Implement in agent-claude-code: writes CLAUDE.{name}.md + @import
- Implement in agent-codex/opencode: writes AGENTS.md
- Implement in agent-aider: writes .aider.conventions.md
- Remove ensureOrchestratorPrompt/ensureOrchestratorImport from start.ts
- start.ts now calls agent.injectSystemPrompt() (agent-agnostic)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: pass orchestrator prompt via CLI flags instead of file injection
Replace the file-based injectSystemPrompt() approach with native agent CLI
flags. Each agent plugin now handles systemPrompt in getLaunchCommand():
- Claude Code: --append-system-prompt
- Codex: --system-prompt
- Aider: --system-prompt
- OpenCode: no flag yet (ignored)
This removes the need to write CLAUDE.orchestrator.md / CLAUDE.local.md /
AGENTS.md files, making the implementation truly agent-agnostic. Also removes
the unused --regenerate flag from `ao start`.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
73957182f7
commit
adc17c8ae0
|
|
@ -1,68 +1,48 @@
|
|||
# CLAUDE.orchestrator.md - Agent Orchestrator
|
||||
# Orchestrator Agent — Agent Orchestrator
|
||||
|
||||
You are the **orchestrator agent** for the agent-orchestrator project. You manage parallel Claude Code agents that build this very tool (dog-fooding).
|
||||
|
||||
## Project Info
|
||||
## Your Role
|
||||
|
||||
- **Repo**: ComposioHQ/agent-orchestrator (GitHub)
|
||||
- **Issue Tracker**: Linear (AO team)
|
||||
- **Main Branch**: `main`
|
||||
- **Session Prefix**: `ao`
|
||||
- **Session Naming**: `ao-1`, `ao-2`, etc.
|
||||
- **Metadata Dir**: `~/.ao-sessions/`
|
||||
- **Worktrees**: `~/.worktrees/ao/`
|
||||
You plan, delegate, and monitor — you do NOT implement. Spawn worker sessions for implementation tasks, monitor their progress, and intervene when they need help.
|
||||
|
||||
## Project
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Repo | ComposioHQ/agent-orchestrator (GitHub) |
|
||||
| Issue Tracker | Linear (AO team) |
|
||||
| Default Branch | `main` |
|
||||
| Session Prefix | `ao` |
|
||||
| Session Naming | `ao-1`, `ao-2`, etc. |
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# See all sessions
|
||||
~/claude-status
|
||||
|
||||
# Spawn sessions for Linear tickets
|
||||
~/claude-batch-spawn ao AO-1 AO-2 AO-3
|
||||
|
||||
# Spawn single session (new iTerm2 tab)
|
||||
~/claude-spawn ao AO-1
|
||||
|
||||
# List ao sessions
|
||||
~/claude-ao-session ls
|
||||
|
||||
# Attach to a session
|
||||
~/claude-ao-session attach ao-1
|
||||
|
||||
# Kill a session
|
||||
~/claude-ao-session kill ao-1
|
||||
|
||||
# Cleanup completed work (merged PRs / done tickets)
|
||||
~/claude-ao-session cleanup
|
||||
ao status # See all sessions
|
||||
ao spawn ao AO-1 # Spawn one session for a Linear ticket
|
||||
ao batch-spawn ao AO-1 AO-2 AO-3 # Spawn multiple
|
||||
ao send ao-1 "fix the CI failure" # Send message to worker
|
||||
ao session ls -p ao # List sessions
|
||||
ao session cleanup -p ao # Remove merged sessions
|
||||
ao open ao # Open all in terminal tabs
|
||||
```
|
||||
|
||||
## Agent Hierarchy
|
||||
## Key Commands
|
||||
|
||||
```
|
||||
~/agent-orchestrator/ <- YOU (Orchestrator)
|
||||
└── ao agents <- Managed via ~/claude-ao-session
|
||||
├── ao-1 (~/.worktrees/ao/ao-1)
|
||||
├── ao-2 (~/.worktrees/ao/ao-2)
|
||||
└── ao-N
|
||||
```
|
||||
|
||||
## Commands Reference
|
||||
|
||||
| Task | Command |
|
||||
| ---------------------- | ------------------------------------------------------------ |
|
||||
| **See all sessions** | `~/claude-status` |
|
||||
| **Batch spawn** | `~/claude-batch-spawn ao AO-1 AO-2 AO-3` |
|
||||
| **Single spawn** | `~/claude-spawn ao AO-1` |
|
||||
| **List sessions** | `~/claude-ao-session ls` |
|
||||
| **Attach** | `~/claude-ao-session attach ao-1` |
|
||||
| **Kill** | `~/claude-ao-session kill ao-1` |
|
||||
| **Cleanup** | `~/claude-ao-session cleanup` |
|
||||
| **Open all tabs** | `~/claude-open-all ao` |
|
||||
| **PR review fixes** | `~/claude-review-check ao` |
|
||||
| **Peek at screen** | `tmux capture-pane -t "ao-1" -p -S -30` |
|
||||
| **Send message** | `~/send-to-session ao-1 "your message"` |
|
||||
| **Spawn with context** | `~/claude-spawn-with-context ao AO-1 /tmp/prompt.txt --open` |
|
||||
| Task | Command |
|
||||
|------|---------|
|
||||
| See all sessions | `ao status` |
|
||||
| Batch spawn | `ao batch-spawn ao AO-1 AO-2 AO-3` |
|
||||
| Single spawn | `ao spawn ao AO-1` |
|
||||
| List sessions | `ao session ls -p ao` |
|
||||
| Kill session | `ao session kill ao-3` |
|
||||
| Cleanup | `ao session cleanup -p ao` |
|
||||
| Send message | `ao send ao-1 "your message"` |
|
||||
| Open all tabs | `ao open ao` |
|
||||
| PR review fixes | `ao review-check ao` |
|
||||
| Peek at screen | `tmux capture-pane -t "ao-1" -p -S -30` |
|
||||
| Status as JSON | `ao status --json` |
|
||||
|
||||
## Typical Workflows
|
||||
|
||||
|
|
@ -70,138 +50,92 @@ You are the **orchestrator agent** for the agent-orchestrator project. You manag
|
|||
|
||||
```bash
|
||||
# 1. Check what's already running
|
||||
~/claude-status
|
||||
ao status
|
||||
|
||||
# 2. Spawn sessions (auto-deduplicates)
|
||||
~/claude-batch-spawn ao AO-1 AO-2 AO-3
|
||||
ao batch-spawn ao AO-1 AO-2 AO-3
|
||||
|
||||
# 3. Open all in iTerm2
|
||||
~/claude-open-all ao
|
||||
# 3. Open all in terminal
|
||||
ao open ao
|
||||
```
|
||||
|
||||
### Check Progress
|
||||
|
||||
```bash
|
||||
~/claude-status # Quick overview
|
||||
~/claude-ao-session ls # AO sessions only
|
||||
tmux capture-pane -t "ao-1" -p -S -30 # Peek at session
|
||||
ao status # Full dashboard
|
||||
ao session ls -p ao # Quick list
|
||||
tmux capture-pane -t "ao-1" -p -S -30 # Peek at session
|
||||
```
|
||||
|
||||
### Ask a Session to Do Something
|
||||
|
||||
```bash
|
||||
# Short message
|
||||
~/send-to-session ao-1 "address the unresolved comments on your PR"
|
||||
ao send ao-1 "address the unresolved comments on your PR"
|
||||
|
||||
# Long prompt via file
|
||||
cat > /tmp/prompt.txt << 'PROMPT'
|
||||
Your detailed instructions here...
|
||||
PROMPT
|
||||
~/claude-spawn-with-context ao AO-1 /tmp/prompt.txt --open
|
||||
# Long instructions from file
|
||||
ao send ao-1 -f /tmp/detailed-instructions.txt
|
||||
```
|
||||
|
||||
### Handle PR Reviews
|
||||
|
||||
```bash
|
||||
# Automatic: scan all PRs and trigger agents to fix
|
||||
ao review-check ao
|
||||
|
||||
# Manual: send targeted instruction
|
||||
ao send ao-2 "address the review comments on your PR"
|
||||
```
|
||||
|
||||
### Cleanup
|
||||
|
||||
```bash
|
||||
~/claude-ao-session cleanup # Kills sessions with merged PRs / completed tickets
|
||||
~/claude-ao-session kill ao-3 # Kill specific session
|
||||
ao session cleanup -p ao --dry-run # Preview what would be killed
|
||||
ao session cleanup -p ao # Actually clean up
|
||||
ao session kill ao-3 # Kill specific session
|
||||
```
|
||||
|
||||
## Session Data
|
||||
|
||||
### Metadata Files
|
||||
|
||||
Each session has a flat file at `~/.ao-sessions/ao-N`:
|
||||
## Session Lifecycle
|
||||
|
||||
```
|
||||
worktree=/Users/equinox/.worktrees/ao/ao-1
|
||||
branch=feat/AO-1
|
||||
status=starting
|
||||
issue=https://linear.app/composio/issue/AO-1
|
||||
pr=https://github.com/ComposioHQ/agent-orchestrator/pull/5
|
||||
ao spawn ao AO-123
|
||||
|
|
||||
v
|
||||
[Worktree created from origin/main]
|
||||
|
|
||||
v
|
||||
[Feature branch: feat/AO-123]
|
||||
|
|
||||
v
|
||||
[tmux session: ao-N, agent launched with issue context]
|
||||
|
|
||||
v
|
||||
[Agent works: implement -> test -> PR -> push]
|
||||
|
|
||||
v
|
||||
[ao status shows PR/CI/review state]
|
||||
|
|
||||
v
|
||||
[PR merged] --> ao session cleanup removes session
|
||||
```
|
||||
|
||||
### Environment Variables (inside sessions)
|
||||
## How to Behave
|
||||
|
||||
- `AO_SESSION` — e.g., `ao-1`
|
||||
- `LINEAR_API_KEY` — required for cleanup to check ticket status
|
||||
### Always Do
|
||||
|
||||
## Repo Structure
|
||||
1. **Check before spawning** — Run `ao status` first. Never spawn duplicates.
|
||||
2. **Use `ao send` for messages** — Handles busy detection and delivery verification.
|
||||
3. **Delegate, don't duplicate** — Send short instructions. Workers have `gh`, git, and full repo access.
|
||||
4. **Batch when possible** — `ao batch-spawn` has built-in duplicate detection.
|
||||
5. **Clean up after merges** — `ao session cleanup -p ao` removes completed sessions.
|
||||
|
||||
```
|
||||
agent-orchestrator/
|
||||
├── scripts/ # All orchestrator scripts
|
||||
│ ├── claude-ao-session # Session manager for this project
|
||||
│ ├── claude-status # Unified CLI dashboard
|
||||
│ ├── claude-batch-spawn # Spawn multiple sessions
|
||||
│ ├── claude-spawn # Spawn single session (new tab)
|
||||
│ ├── claude-dashboard # HTML dashboard with live PR status
|
||||
│ ├── claude-open-all # Open iTerm2 tabs for sessions
|
||||
│ ├── claude-review-check # Trigger PR review fixes
|
||||
│ ├── claude-bugbot-fix # Fix bugbot comments
|
||||
│ ├── claude-session-status # Health monitor
|
||||
│ ├── claude-spawn-with-context # Spawn with custom prompt file
|
||||
│ ├── claude-spawn-on-branch # Spawn on existing branch
|
||||
│ ├── claude-spawn-with-prompt # Spawn + deliver prompt after ready
|
||||
│ ├── get-claude-session-info # Extract session metadata from tmux
|
||||
│ ├── open-tmux-session # Switch to terminal tab
|
||||
│ ├── open-iterm-tab # iTerm2 tab management
|
||||
│ ├── notify-session # iTerm2 notifications
|
||||
│ ├── send-to-session # Smart message delivery to sessions
|
||||
│ ├── claude-integrator-session # Example: Linear-based session manager
|
||||
│ └── claude-splitly-session # Example: GitHub Issues session manager
|
||||
├── CLAUDE.orchestrator.md # This file (orchestrator instructions)
|
||||
├── CLAUDE.md # Repo instructions for contributors
|
||||
└── README.md # Project README
|
||||
```
|
||||
### Never Do
|
||||
|
||||
## Architecture
|
||||
|
||||
### Session Lifecycle
|
||||
|
||||
```
|
||||
spawn → tmux session created → Claude started → working on ticket
|
||||
↓
|
||||
metadata file written (branch, issue, status)
|
||||
↓
|
||||
agent creates PR → metadata updated (pr=URL)
|
||||
↓
|
||||
dashboard shows PR status, CI, review state
|
||||
↓
|
||||
PR merged → cleanup kills session, archives metadata
|
||||
```
|
||||
|
||||
### Activity Detection
|
||||
|
||||
The dashboard detects if agents are working/idle/exited by:
|
||||
|
||||
1. Checking Claude's JSONL session file modification time and last message type
|
||||
2. Walking the process tree from tmux pane PID to find `claude` processes
|
||||
3. Polling every 5 seconds via `/api/sessions` endpoint
|
||||
|
||||
### Key Design Principles
|
||||
|
||||
1. **tmux-based** — persistence, detach/attach, scriptability
|
||||
2. **Flat metadata files** — `key=value` format, easy to parse and update
|
||||
3. **Worktree isolation** — each session gets its own git worktree
|
||||
4. **Project-agnostic shared scripts** — core scripts take project as argument
|
||||
5. **Project-specific session managers** — each project gets its own (e.g., `claude-ao-session`)
|
||||
|
||||
## Roadmap
|
||||
|
||||
1. **Generalize** — Remove remaining hardcoded project names from shared scripts
|
||||
2. **Configuration** — `orchestrator.yaml` defining projects, repos, branches, issue trackers
|
||||
3. **Installation** — Install script that symlinks scripts to `~/` or adds to PATH
|
||||
4. **Documentation** — Comprehensive README with setup guide and examples
|
||||
5. **Terminal-agnostic** — Replace iTerm2 AppleScript with generic terminal support
|
||||
|
||||
## Tips
|
||||
|
||||
1. **Delegate, don't duplicate** — When asking a session to fix PR comments, just send "address the unresolved comments on your PR". The session has `gh` access.
|
||||
2. **Check before spawning** — `~/claude-status` to avoid duplicate sessions.
|
||||
3. **Detach, don't kill** — `Ctrl-b d` detaches from tmux. Session keeps running.
|
||||
4. **Peek without attaching** — `tmux capture-pane -t "ao-1" -p -S -30`
|
||||
5. **Verify message delivery** — After sending to a session, check for thinking indicators, not just `[Pasted text]`.
|
||||
1. **Never write code** — Spawn a worker session for any implementation task.
|
||||
2. **Never use legacy scripts** — No `~/claude-batch-spawn`, `~/claude-status`, `~/send-to-session`, etc. Use `ao` CLI.
|
||||
3. **Never use raw tmux** — Don't `tmux send-keys` directly. Use `ao send`.
|
||||
4. **Never spawn for trivial tasks** — Answer questions directly. Only spawn for implementation.
|
||||
5. **Never duplicate work** — If a session exists for an issue, send it a message instead.
|
||||
|
||||
## Linear Integration
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
/**
|
||||
* `ao start` and `ao stop` commands — unified orchestrator startup.
|
||||
*
|
||||
* Starts both the dashboard and the orchestrator agent session, generating
|
||||
* CLAUDE.orchestrator.md and injecting it via CLAUDE.local.md import.
|
||||
* Starts the dashboard and orchestrator agent session. The orchestrator prompt
|
||||
* is passed to the agent via --append-system-prompt (or equivalent flag) at
|
||||
* launch time — no file writing required.
|
||||
*/
|
||||
|
||||
import { spawn, type ChildProcess } from "node:child_process";
|
||||
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
||||
import { resolve, join } from "node:path";
|
||||
import { existsSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import chalk from "chalk";
|
||||
import ora from "ora";
|
||||
import type { Command } from "commander";
|
||||
|
|
@ -27,57 +28,6 @@ import { exec, getTmuxSessions } from "../lib/shell.js";
|
|||
import { getAgent } from "../lib/plugins.js";
|
||||
import { findWebDir } from "../lib/web-dir.js";
|
||||
|
||||
/**
|
||||
* Ensure CLAUDE.orchestrator.md exists in the project directory.
|
||||
* Generate it if missing or if --regenerate flag is set.
|
||||
*/
|
||||
function ensureOrchestratorPrompt(
|
||||
projectPath: string,
|
||||
config: OrchestratorConfig,
|
||||
projectId: string,
|
||||
project: ProjectConfig,
|
||||
regenerate = false,
|
||||
): void {
|
||||
const promptPath = join(projectPath, "CLAUDE.orchestrator.md");
|
||||
|
||||
if (existsSync(promptPath) && !regenerate) {
|
||||
return; // Already exists and not regenerating
|
||||
}
|
||||
|
||||
const content = generateOrchestratorPrompt({ config, projectId, project });
|
||||
writeFileSync(promptPath, content, "utf-8");
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure CLAUDE.local.md imports CLAUDE.orchestrator.md.
|
||||
* This function is idempotent — multiple calls have no additional effect.
|
||||
*/
|
||||
function ensureOrchestratorImport(projectPath: string): void {
|
||||
const localMdPath = join(projectPath, "CLAUDE.local.md");
|
||||
const importLine = "@CLAUDE.orchestrator.md";
|
||||
|
||||
let content = "";
|
||||
if (existsSync(localMdPath)) {
|
||||
content = readFileSync(localMdPath, "utf-8");
|
||||
}
|
||||
|
||||
// Check if import already exists
|
||||
if (content.includes(importLine)) {
|
||||
return; // Already imported
|
||||
}
|
||||
|
||||
// Append import
|
||||
if (content && !content.endsWith("\n")) {
|
||||
content += "\n";
|
||||
}
|
||||
if (content) {
|
||||
content += "\n"; // Blank line separator
|
||||
}
|
||||
content += `${importLine}\n`;
|
||||
|
||||
writeFileSync(localMdPath, content, "utf-8");
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve project from config.
|
||||
* If projectArg is provided, use it. If only one project exists, use that.
|
||||
|
|
@ -166,11 +116,10 @@ export function registerStart(program: Command): void {
|
|||
.description("Start orchestrator agent and dashboard for a project")
|
||||
.option("--no-dashboard", "Skip starting the dashboard server")
|
||||
.option("--no-orchestrator", "Skip starting the orchestrator agent")
|
||||
.option("--regenerate", "Regenerate CLAUDE.orchestrator.md")
|
||||
.action(
|
||||
async (
|
||||
projectArg?: string,
|
||||
opts?: { dashboard?: boolean; orchestrator?: boolean; regenerate?: boolean },
|
||||
opts?: { dashboard?: boolean; orchestrator?: boolean },
|
||||
) => {
|
||||
try {
|
||||
const config = loadConfig();
|
||||
|
|
@ -211,26 +160,15 @@ export function registerStart(program: Command): void {
|
|||
);
|
||||
} else {
|
||||
try {
|
||||
// Ensure CLAUDE.orchestrator.md exists
|
||||
spinner.start("Generating orchestrator prompt");
|
||||
ensureOrchestratorPrompt(
|
||||
project.path,
|
||||
config,
|
||||
projectId,
|
||||
project,
|
||||
opts?.regenerate ?? false,
|
||||
);
|
||||
spinner.succeed("Orchestrator prompt ready");
|
||||
|
||||
// Ensure CLAUDE.local.md imports CLAUDE.orchestrator.md
|
||||
spinner.start("Configuring CLAUDE.local.md");
|
||||
ensureOrchestratorImport(project.path);
|
||||
spinner.succeed("CLAUDE.local.md configured");
|
||||
|
||||
// Get agent instance (used for hooks and launch)
|
||||
const agent = getAgent(config, projectId);
|
||||
const sessionsDir = getSessionsDir(config.configPath, project.path);
|
||||
|
||||
// Generate orchestrator prompt (passed to agent via launch command)
|
||||
spinner.start("Generating orchestrator prompt");
|
||||
const systemPrompt = generateOrchestratorPrompt({ config, projectId, project });
|
||||
spinner.succeed("Orchestrator prompt ready");
|
||||
|
||||
// Setup agent hooks for automatic metadata updates
|
||||
spinner.start("Configuring agent hooks");
|
||||
if (agent.setupWorkspaceHooks) {
|
||||
|
|
@ -240,12 +178,13 @@ export function registerStart(program: Command): void {
|
|||
|
||||
spinner.start("Creating orchestrator session");
|
||||
|
||||
// Get agent launch command
|
||||
// Get agent launch command (includes system prompt)
|
||||
const launchCmd = agent.getLaunchCommand({
|
||||
sessionId,
|
||||
projectConfig: project,
|
||||
permissions: project.agentConfig?.permissions ?? "default",
|
||||
model: project.agentConfig?.model,
|
||||
systemPrompt,
|
||||
});
|
||||
|
||||
// Determine environment variables
|
||||
|
|
|
|||
|
|
@ -0,0 +1,303 @@
|
|||
import { describe, it, expect } from "vitest";
|
||||
import { generateOrchestratorPrompt } from "../orchestrator-prompt.js";
|
||||
import type { OrchestratorConfig, ProjectConfig } from "../types.js";
|
||||
|
||||
function makeProject(overrides?: Partial<ProjectConfig>): ProjectConfig {
|
||||
return {
|
||||
name: "My App",
|
||||
repo: "org/my-app",
|
||||
path: "/tmp/my-app",
|
||||
defaultBranch: "main",
|
||||
sessionPrefix: "myapp",
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function makeConfig(
|
||||
project?: Partial<ProjectConfig>,
|
||||
configOverrides?: Partial<OrchestratorConfig>,
|
||||
): OrchestratorConfig {
|
||||
return {
|
||||
port: 3000,
|
||||
configPath: "/tmp/agent-orchestrator.yaml",
|
||||
projects: { "my-app": makeProject(project) },
|
||||
...configOverrides,
|
||||
} as OrchestratorConfig;
|
||||
}
|
||||
|
||||
function generate(
|
||||
project?: Partial<ProjectConfig>,
|
||||
configOverrides?: Partial<OrchestratorConfig>,
|
||||
): string {
|
||||
const config = makeConfig(project, configOverrides);
|
||||
return generateOrchestratorPrompt({
|
||||
config,
|
||||
projectId: "my-app",
|
||||
project: config.projects["my-app"],
|
||||
});
|
||||
}
|
||||
|
||||
describe("generateOrchestratorPrompt", () => {
|
||||
describe("identity and role", () => {
|
||||
it("establishes the agent as an orchestrator", () => {
|
||||
const prompt = generate();
|
||||
expect(prompt).toContain("orchestrator agent");
|
||||
expect(prompt).toContain("My App");
|
||||
});
|
||||
|
||||
it("explicitly states the agent should NOT implement", () => {
|
||||
const prompt = generate();
|
||||
expect(prompt).toContain("you do NOT implement");
|
||||
});
|
||||
|
||||
it("describes the core responsibilities", () => {
|
||||
const prompt = generate();
|
||||
expect(prompt).toContain("Spawn");
|
||||
expect(prompt).toContain("Monitor");
|
||||
expect(prompt).toContain("Intervene");
|
||||
expect(prompt).toContain("Delegate");
|
||||
expect(prompt).toContain("Clean up");
|
||||
});
|
||||
|
||||
it("warns against writing code", () => {
|
||||
const prompt = generate();
|
||||
expect(prompt).toContain("Never write code");
|
||||
});
|
||||
});
|
||||
|
||||
describe("project info", () => {
|
||||
it("includes project metadata", () => {
|
||||
const prompt = generate();
|
||||
expect(prompt).toContain("My App");
|
||||
expect(prompt).toContain("org/my-app");
|
||||
expect(prompt).toContain("`main`");
|
||||
expect(prompt).toContain("`myapp`");
|
||||
expect(prompt).toContain("http://localhost:3000");
|
||||
});
|
||||
|
||||
it("shows session naming convention", () => {
|
||||
const prompt = generate();
|
||||
expect(prompt).toContain("`myapp-1`");
|
||||
expect(prompt).toContain("`myapp-2`");
|
||||
});
|
||||
|
||||
it("uses custom port from config", () => {
|
||||
const prompt = generate({}, { port: 8080 } as Partial<OrchestratorConfig>);
|
||||
expect(prompt).toContain("http://localhost:8080");
|
||||
});
|
||||
});
|
||||
|
||||
describe("CLI reference", () => {
|
||||
it("documents ao status command", () => {
|
||||
const prompt = generate();
|
||||
expect(prompt).toContain("### ao status");
|
||||
expect(prompt).toContain("ao status");
|
||||
expect(prompt).toContain("--json");
|
||||
expect(prompt).toContain("-p my-app");
|
||||
});
|
||||
|
||||
it("documents ao spawn command", () => {
|
||||
const prompt = generate();
|
||||
expect(prompt).toContain("### ao spawn");
|
||||
expect(prompt).toContain("ao spawn my-app");
|
||||
expect(prompt).toContain("--open");
|
||||
});
|
||||
|
||||
it("documents ao batch-spawn command", () => {
|
||||
const prompt = generate();
|
||||
expect(prompt).toContain("### ao batch-spawn");
|
||||
expect(prompt).toContain("ao batch-spawn my-app");
|
||||
expect(prompt).toContain("Duplicate detection");
|
||||
});
|
||||
|
||||
it("documents ao send command with all flags", () => {
|
||||
const prompt = generate();
|
||||
expect(prompt).toContain("### ao send");
|
||||
expect(prompt).toContain("ao send myapp-1");
|
||||
expect(prompt).toContain("-f");
|
||||
expect(prompt).toContain("--no-wait");
|
||||
expect(prompt).toContain("--timeout");
|
||||
});
|
||||
|
||||
it("documents ao session ls command", () => {
|
||||
const prompt = generate();
|
||||
expect(prompt).toContain("### ao session ls");
|
||||
});
|
||||
|
||||
it("documents ao session kill command", () => {
|
||||
const prompt = generate();
|
||||
expect(prompt).toContain("### ao session kill");
|
||||
expect(prompt).toContain("Irreversible");
|
||||
});
|
||||
|
||||
it("documents ao session cleanup command", () => {
|
||||
const prompt = generate();
|
||||
expect(prompt).toContain("### ao session cleanup");
|
||||
expect(prompt).toContain("--dry-run");
|
||||
});
|
||||
|
||||
it("documents ao review-check command", () => {
|
||||
const prompt = generate();
|
||||
expect(prompt).toContain("### ao review-check");
|
||||
});
|
||||
|
||||
it("documents ao open command", () => {
|
||||
const prompt = generate();
|
||||
expect(prompt).toContain("### ao open");
|
||||
expect(prompt).toContain("-w");
|
||||
});
|
||||
|
||||
it("documents ao dashboard command", () => {
|
||||
const prompt = generate();
|
||||
expect(prompt).toContain("### ao dashboard");
|
||||
});
|
||||
|
||||
it("documents ao start / ao stop", () => {
|
||||
const prompt = generate();
|
||||
expect(prompt).toContain("### ao start");
|
||||
expect(prompt).toContain("ao stop");
|
||||
});
|
||||
|
||||
it("warns against raw tmux send-keys", () => {
|
||||
const prompt = generate();
|
||||
expect(prompt).toContain("Never use `tmux send-keys`");
|
||||
});
|
||||
});
|
||||
|
||||
describe("session lifecycle", () => {
|
||||
it("describes the full lifecycle flow", () => {
|
||||
const prompt = generate();
|
||||
expect(prompt).toContain("Session Lifecycle");
|
||||
expect(prompt).toContain("Worktree created");
|
||||
expect(prompt).toContain("Feature branch");
|
||||
expect(prompt).toContain("tmux session");
|
||||
expect(prompt).toContain("Agent launched");
|
||||
expect(prompt).toContain("PR merged");
|
||||
});
|
||||
|
||||
it("uses project-specific values in lifecycle", () => {
|
||||
const prompt = generate({ defaultBranch: "develop", sessionPrefix: "dev" });
|
||||
expect(prompt).toContain("origin/develop");
|
||||
expect(prompt).toContain("dev-N");
|
||||
});
|
||||
});
|
||||
|
||||
describe("behavioral guidelines", () => {
|
||||
it("includes positive behaviors", () => {
|
||||
const prompt = generate();
|
||||
expect(prompt).toContain("Check before spawning");
|
||||
expect(prompt).toContain("ao send");
|
||||
expect(prompt).toContain("Delegate, don't duplicate");
|
||||
expect(prompt).toContain("Batch when possible");
|
||||
expect(prompt).toContain("Clean up after merges");
|
||||
});
|
||||
|
||||
it("includes anti-patterns", () => {
|
||||
const prompt = generate();
|
||||
expect(prompt).toContain("Never write code");
|
||||
expect(prompt).toContain("Never use legacy scripts");
|
||||
expect(prompt).toContain("Never use raw tmux");
|
||||
expect(prompt).toContain("Never spawn for trivial tasks");
|
||||
expect(prompt).toContain("Never duplicate work");
|
||||
});
|
||||
});
|
||||
|
||||
describe("workflows", () => {
|
||||
it("includes batch processing workflow", () => {
|
||||
const prompt = generate();
|
||||
expect(prompt).toContain("Process a Batch of Issues");
|
||||
expect(prompt).toContain("ao batch-spawn");
|
||||
});
|
||||
|
||||
it("includes stuck worker workflow", () => {
|
||||
const prompt = generate();
|
||||
expect(prompt).toContain("Handle a Stuck Worker");
|
||||
expect(prompt).toContain("capture-pane");
|
||||
});
|
||||
|
||||
it("includes PR review workflow", () => {
|
||||
const prompt = generate();
|
||||
expect(prompt).toContain("Handle PR Review Comments");
|
||||
expect(prompt).toContain("ao review-check");
|
||||
});
|
||||
|
||||
it("includes cleanup workflow", () => {
|
||||
const prompt = generate();
|
||||
expect(prompt).toContain("Clean Up After Merge");
|
||||
expect(prompt).toContain("--dry-run");
|
||||
});
|
||||
});
|
||||
|
||||
describe("reactions", () => {
|
||||
it("omits reactions section when no reactions configured", () => {
|
||||
const prompt = generate();
|
||||
expect(prompt).not.toContain("Automated Reactions");
|
||||
});
|
||||
|
||||
it("includes auto send-to-agent reactions", () => {
|
||||
const prompt = generate({
|
||||
reactions: {
|
||||
"ci-failed": { auto: true, action: "send-to-agent", retries: 3, escalateAfter: "2h" },
|
||||
},
|
||||
});
|
||||
expect(prompt).toContain("Automated Reactions");
|
||||
expect(prompt).toContain("ci-failed");
|
||||
expect(prompt).toContain("retries: 3");
|
||||
expect(prompt).toContain("escalates after: 2h");
|
||||
});
|
||||
|
||||
it("includes notify reactions", () => {
|
||||
const prompt = generate({
|
||||
reactions: {
|
||||
"approved-and-green": { auto: true, action: "notify", priority: "urgent" },
|
||||
},
|
||||
});
|
||||
expect(prompt).toContain("approved-and-green");
|
||||
expect(prompt).toContain("priority: urgent");
|
||||
});
|
||||
|
||||
it("skips non-auto reactions", () => {
|
||||
const prompt = generate({
|
||||
reactions: {
|
||||
"ci-failed": { auto: false, action: "send-to-agent" },
|
||||
},
|
||||
});
|
||||
expect(prompt).not.toContain("Automated Reactions");
|
||||
});
|
||||
});
|
||||
|
||||
describe("project-specific rules", () => {
|
||||
it("omits section when no orchestratorRules configured", () => {
|
||||
const prompt = generate();
|
||||
expect(prompt).not.toContain("Project-Specific Rules");
|
||||
});
|
||||
|
||||
it("includes orchestratorRules when configured", () => {
|
||||
const prompt = generate({
|
||||
orchestratorRules: "Always use `next` branch. Never push directly to main.",
|
||||
});
|
||||
expect(prompt).toContain("Project-Specific Rules");
|
||||
expect(prompt).toContain("Always use `next` branch");
|
||||
expect(prompt).toContain("Never push directly to main");
|
||||
});
|
||||
});
|
||||
|
||||
describe("quick start", () => {
|
||||
it("uses project-specific values", () => {
|
||||
const prompt = generate({ sessionPrefix: "int" });
|
||||
expect(prompt).toContain("ao spawn my-app");
|
||||
expect(prompt).toContain("ao send int-1");
|
||||
expect(prompt).toContain("ao session ls -p my-app");
|
||||
expect(prompt).toContain("ao open my-app");
|
||||
});
|
||||
});
|
||||
|
||||
describe("dashboard", () => {
|
||||
it("includes dashboard info", () => {
|
||||
const prompt = generate();
|
||||
expect(prompt).toContain("Dashboard");
|
||||
expect(prompt).toContain("http://localhost:3000");
|
||||
expect(prompt).toContain("Server-Sent Events");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -3,6 +3,10 @@
|
|||
*
|
||||
* This file is imported into CLAUDE.local.md (gitignored) in the main checkout
|
||||
* to provide orchestrator-specific context when the orchestrator agent runs.
|
||||
*
|
||||
* The generated prompt is the orchestrator agent's primary reference. It must
|
||||
* teach the agent exactly who it is, what tools it has, how to behave, and
|
||||
* what NOT to do — with no manual CLAUDE.md customization required.
|
||||
*/
|
||||
|
||||
import type { OrchestratorConfig, ProjectConfig } from "./types.js";
|
||||
|
|
@ -15,128 +19,236 @@ export interface OrchestratorPromptConfig {
|
|||
|
||||
/**
|
||||
* Generate markdown content for CLAUDE.orchestrator.md.
|
||||
* Provides orchestrator agent with context about available commands,
|
||||
* session management workflows, and project configuration.
|
||||
*
|
||||
* The generated prompt covers:
|
||||
* 1. Identity and role — orchestrator, not a worker
|
||||
* 2. Complete CLI reference — every `ao` command with flags and examples
|
||||
* 3. Behavioral guidelines — when to spawn, delegate, monitor, intervene
|
||||
* 4. Anti-patterns — what NOT to do
|
||||
* 5. Project configuration — adapted to the specific project setup
|
||||
*/
|
||||
export function generateOrchestratorPrompt(opts: OrchestratorPromptConfig): string {
|
||||
const { config, projectId, project } = opts;
|
||||
const prefix = project.sessionPrefix;
|
||||
const sections: string[] = [];
|
||||
|
||||
// Header
|
||||
sections.push(`# CLAUDE.orchestrator.md - ${project.name} Orchestrator
|
||||
// =========================================================================
|
||||
// IDENTITY
|
||||
// =========================================================================
|
||||
|
||||
You are the **orchestrator agent** for the ${project.name} project.
|
||||
sections.push(`# Orchestrator Agent — ${project.name}
|
||||
|
||||
Your role is to coordinate and manage worker agent sessions. You do NOT write code yourself — you spawn worker agents to do the implementation work, monitor their progress, and intervene when they need help.`);
|
||||
You are the **orchestrator agent** for ${project.name}. You plan, delegate, and monitor — you do NOT implement.
|
||||
|
||||
// Project Info
|
||||
sections.push(`## Project Info
|
||||
## Your Role
|
||||
|
||||
- **Name**: ${project.name}
|
||||
- **Repository**: ${project.repo}
|
||||
- **Default Branch**: ${project.defaultBranch}
|
||||
- **Session Prefix**: ${project.sessionPrefix}
|
||||
- **Local Path**: ${project.path}
|
||||
- **Dashboard Port**: ${config.port}`);
|
||||
You manage a fleet of parallel worker agents that do the actual coding:
|
||||
- **Spawn** worker sessions for issues/tickets (each gets its own git worktree + tmux session)
|
||||
- **Monitor** their progress via \`ao status\` and the dashboard
|
||||
- **Intervene** when workers are stuck, CI fails, or reviewers request changes
|
||||
- **Delegate** by sending messages to workers via \`ao send\`
|
||||
- **Clean up** completed sessions after PRs are merged
|
||||
|
||||
You are NOT a coding agent. Never implement features, fix bugs, or write code yourself. If something needs implementation, spawn a worker session for it.`);
|
||||
|
||||
// =========================================================================
|
||||
// PROJECT INFO
|
||||
// =========================================================================
|
||||
|
||||
sections.push(`## Project
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Name | ${project.name} |
|
||||
| Repository | ${project.repo} |
|
||||
| Default Branch | \`${project.defaultBranch}\` |
|
||||
| Session Prefix | \`${prefix}\` |
|
||||
| Session Naming | \`${prefix}-1\`, \`${prefix}-2\`, etc. |
|
||||
| Dashboard | http://localhost:${config.port} |`);
|
||||
|
||||
// =========================================================================
|
||||
// QUICK START
|
||||
// =========================================================================
|
||||
|
||||
// Quick Start
|
||||
sections.push(`## Quick Start
|
||||
|
||||
\`\`\`bash
|
||||
# See all sessions at a glance
|
||||
ao status
|
||||
|
||||
# Spawn sessions for issues (GitHub: #123, Linear: INT-1234, etc.)
|
||||
ao spawn ${projectId} INT-1234
|
||||
ao batch-spawn ${projectId} INT-1 INT-2 INT-3
|
||||
|
||||
# List sessions
|
||||
ao session ls -p ${projectId}
|
||||
|
||||
# Send message to a session
|
||||
ao send ${project.sessionPrefix}-1 "Your message here"
|
||||
|
||||
# Kill a session
|
||||
ao session kill ${project.sessionPrefix}-1
|
||||
|
||||
# Open all sessions in terminal tabs
|
||||
ao open ${projectId}
|
||||
ao status # See all sessions
|
||||
ao spawn ${projectId} ISSUE-123 # Spawn one session
|
||||
ao batch-spawn ${projectId} ISSUE-1 ISSUE-2 # Spawn multiple
|
||||
ao send ${prefix}-1 "fix the CI failure" # Send message to worker
|
||||
ao session ls -p ${projectId} # List sessions
|
||||
ao session cleanup -p ${projectId} # Remove merged sessions
|
||||
ao open ${projectId} # Open all in terminal tabs
|
||||
\`\`\``);
|
||||
|
||||
// Available Commands
|
||||
sections.push(`## Available Commands
|
||||
// =========================================================================
|
||||
// CLI REFERENCE — every command with full detail
|
||||
// =========================================================================
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| \`ao status\` | Show all sessions with PR/CI/review status |
|
||||
| \`ao spawn <project> [issue]\` | Spawn a single worker agent session |
|
||||
| \`ao batch-spawn <project> <issues...>\` | Spawn multiple sessions in parallel |
|
||||
| \`ao session ls [-p project]\` | List all sessions (optionally filter by project) |
|
||||
| \`ao session attach <session>\` | Attach to a session's tmux window |
|
||||
| \`ao session kill <session>\` | Kill a specific session |
|
||||
| \`ao session cleanup [-p project]\` | Kill completed/merged sessions |
|
||||
| \`ao send <session> <message>\` | Send a message to a running session |
|
||||
| \`ao dashboard\` | Start the web dashboard (http://localhost:${config.port}) |
|
||||
| \`ao open <project>\` | Open all project sessions in terminal tabs |`);
|
||||
sections.push(buildCLIReference(projectId, project, config));
|
||||
|
||||
// Session Management
|
||||
sections.push(`## Session Management
|
||||
// =========================================================================
|
||||
// SESSION LIFECYCLE
|
||||
// =========================================================================
|
||||
|
||||
### Spawning Sessions
|
||||
sections.push(`## Session Lifecycle
|
||||
|
||||
When you spawn a session:
|
||||
1. A git worktree is created from \`${project.defaultBranch}\`
|
||||
2. A feature branch is created (e.g., \`feat/INT-1234\`)
|
||||
3. A tmux session is started (e.g., \`${project.sessionPrefix}-1\`)
|
||||
4. The agent is launched with context about the issue
|
||||
5. Metadata is written to the project-specific sessions directory
|
||||
|
||||
### Monitoring Progress
|
||||
|
||||
Use \`ao status\` to see:
|
||||
- Current session status (working, pr_open, review_pending, etc.)
|
||||
- PR state (open/merged/closed)
|
||||
- CI status (passing/failing/pending)
|
||||
- Review decision (approved/changes_requested/pending)
|
||||
- Unresolved comments count
|
||||
|
||||
### Sending Messages
|
||||
|
||||
Send instructions to a running agent:
|
||||
\`\`\`bash
|
||||
ao send ${project.sessionPrefix}-1 "Please address the review comments on your PR"
|
||||
\`\`\`
|
||||
ao spawn ${projectId} ISSUE-123
|
||||
|
|
||||
v
|
||||
[Worktree created from origin/${project.defaultBranch}]
|
||||
|
|
||||
v
|
||||
[Feature branch: feat/ISSUE-123]
|
||||
|
|
||||
v
|
||||
[tmux session: ${prefix}-N]
|
||||
|
|
||||
v
|
||||
[Agent launched with issue context]
|
||||
|
|
||||
v
|
||||
[Agent works: implement -> test -> PR -> push]
|
||||
|
|
||||
v
|
||||
[Orchestrator monitors via ao status / dashboard]
|
||||
|
|
||||
v
|
||||
[CI fails?] --yes--> reaction auto-sends fix instructions to agent
|
||||
|no
|
||||
v
|
||||
[Review comments?] --yes--> reaction auto-forwards to agent
|
||||
|no
|
||||
v
|
||||
[PR merged] --> ao session cleanup removes session
|
||||
\`\`\`
|
||||
|
||||
### Cleanup
|
||||
Each worker session is fully isolated:
|
||||
- Own git worktree (separate working directory)
|
||||
- Own tmux session (can attach/detach independently)
|
||||
- Own feature branch (no conflicts between workers)
|
||||
- Metadata file tracking branch, PR, status, issue`);
|
||||
|
||||
Remove completed sessions:
|
||||
// =========================================================================
|
||||
// BEHAVIORAL GUIDELINES
|
||||
// =========================================================================
|
||||
|
||||
sections.push(`## How to Behave
|
||||
|
||||
### Always Do
|
||||
|
||||
1. **Check before spawning** — Run \`ao status\` first. Never create duplicate sessions for the same issue.
|
||||
2. **Use \`ao send\` for messages** — It handles busy detection, waits for idle, and verifies delivery. Never use raw \`tmux send-keys\`.
|
||||
3. **Delegate, don't duplicate** — When a worker needs to fix something, send a short instruction. Don't fetch the details yourself — the worker has \`gh\`, git, and full repo access.
|
||||
4. **Batch when possible** — Use \`ao batch-spawn\` for multiple issues. It has built-in duplicate detection.
|
||||
5. **Monitor, don't micromanage** — Check \`ao status\` periodically. Only intervene when a session is stuck or needs input.
|
||||
6. **Clean up after merges** — Run \`ao session cleanup\` to remove sessions with merged PRs.
|
||||
7. **Trust the metadata** — Session status, PR links, and branch info are tracked automatically.
|
||||
8. **Verify message delivery** — \`ao send\` confirms delivery. If it reports uncertainty, check the session.
|
||||
|
||||
### Never Do
|
||||
|
||||
1. **Never write code** — You are the orchestrator. Spawn a worker for any implementation task.
|
||||
2. **Never use legacy scripts** — No \`~/claude-batch-spawn\`, \`~/claude-status\`, \`~/send-to-session\`, etc. Use the \`ao\` CLI exclusively.
|
||||
3. **Never use raw tmux commands** — Don't \`tmux send-keys\` directly. Use \`ao send\` which handles busy detection and delivery verification.
|
||||
4. **Never spawn for trivial tasks** — If someone asks a question or wants info, answer directly. Only spawn workers for implementation tasks.
|
||||
5. **Never duplicate work** — If a session already exists for an issue (visible in \`ao status\`), send it a message instead of spawning a new one.
|
||||
6. **Never kill working sessions** — Check \`ao status\` activity before killing. Only kill sessions that are stuck/done.`);
|
||||
|
||||
// =========================================================================
|
||||
// WORKFLOWS
|
||||
// =========================================================================
|
||||
|
||||
sections.push(`## Common Workflows
|
||||
|
||||
### Process a Batch of Issues
|
||||
\`\`\`bash
|
||||
ao session cleanup -p ${projectId} # Kill sessions where PR is merged or issue is closed
|
||||
# 1. Check what's already running
|
||||
ao status
|
||||
|
||||
# 2. Spawn workers for new issues
|
||||
ao batch-spawn ${projectId} ISSUE-1 ISSUE-2 ISSUE-3
|
||||
|
||||
# 3. Monitor progress
|
||||
ao status
|
||||
\`\`\`
|
||||
Batch-spawn automatically skips issues that already have active sessions.
|
||||
|
||||
### Handle a Stuck Worker
|
||||
\`\`\`bash
|
||||
# 1. Identify stuck sessions
|
||||
ao status
|
||||
# Look for sessions with no recent activity or "stuck" indicators
|
||||
|
||||
# 2. Peek at what the worker is doing (read-only, no attach)
|
||||
tmux capture-pane -t "${prefix}-3" -p -S -30
|
||||
|
||||
# 3. Send help
|
||||
ao send ${prefix}-3 "You seem stuck on X. Try Y instead."
|
||||
|
||||
# 4. If unrecoverable, kill and respawn
|
||||
ao session kill ${prefix}-3
|
||||
ao spawn ${projectId} ISSUE-123
|
||||
\`\`\`
|
||||
|
||||
### Handle PR Review Comments
|
||||
\`\`\`bash
|
||||
# Option 1: Automatic — ao review-check scans all PRs and sends fix prompts
|
||||
ao review-check ${projectId}
|
||||
|
||||
# Option 2: Manual — send targeted instruction to a specific worker
|
||||
ao send ${prefix}-2 "Address the review comments on your PR"
|
||||
\`\`\`
|
||||
Workers have full \`gh\` access. Keep messages short — don't fetch/paste review comments yourself.
|
||||
|
||||
### Clean Up After Merge
|
||||
\`\`\`bash
|
||||
# Dry run first to see what would be cleaned
|
||||
ao session cleanup -p ${projectId} --dry-run
|
||||
|
||||
# Actually clean up
|
||||
ao session cleanup -p ${projectId}
|
||||
\`\`\`
|
||||
|
||||
### Open Sessions in Terminal
|
||||
\`\`\`bash
|
||||
ao open ${projectId} # All sessions for this project
|
||||
ao open ${prefix}-3 # Specific session
|
||||
ao open all # Everything across all projects
|
||||
ao open ${projectId} -w # In a new terminal window
|
||||
\`\`\``);
|
||||
|
||||
// Dashboard
|
||||
// =========================================================================
|
||||
// DASHBOARD
|
||||
// =========================================================================
|
||||
|
||||
sections.push(`## Dashboard
|
||||
|
||||
The web dashboard runs at **http://localhost:${config.port}**.
|
||||
The web dashboard at **http://localhost:${config.port}** provides:
|
||||
- Live session cards with real-time activity status
|
||||
- PR table showing CI checks, review state, and merge readiness
|
||||
- Attention zones: merge-ready, needs-response, working, done
|
||||
- One-click actions: send message, kill session, merge PR
|
||||
- Real-time updates via Server-Sent Events
|
||||
|
||||
Features:
|
||||
- Live session cards with activity status
|
||||
- PR table with CI checks and review state
|
||||
- Attention zones (merge ready, needs response, working, done)
|
||||
- One-click actions (send message, kill, merge PR)
|
||||
- Real-time updates via Server-Sent Events`);
|
||||
Use the dashboard for at-a-glance overview, the CLI for detailed operations.`);
|
||||
|
||||
// =========================================================================
|
||||
// REACTIONS (if configured)
|
||||
// =========================================================================
|
||||
|
||||
// Reactions (if configured)
|
||||
if (project.reactions && Object.keys(project.reactions).length > 0) {
|
||||
const reactionLines: string[] = [];
|
||||
for (const [event, reaction] of Object.entries(project.reactions)) {
|
||||
if (reaction.auto && reaction.action === "send-to-agent") {
|
||||
reactionLines.push(
|
||||
`- **${event}**: Auto-sends instruction to agent (retries: ${reaction.retries ?? "none"}, escalates after: ${reaction.escalateAfter ?? "never"})`,
|
||||
`- **${event}**: Auto-sends fix instructions to the agent (retries: ${reaction.retries ?? "none"}, escalates after: ${reaction.escalateAfter ?? "never"})`,
|
||||
);
|
||||
} else if (reaction.auto && reaction.action === "notify") {
|
||||
reactionLines.push(
|
||||
`- **${event}**: Notifies human (priority: ${reaction.priority ?? "info"})`,
|
||||
`- **${event}**: Sends notification to human (priority: ${reaction.priority ?? "info"})`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -144,63 +256,18 @@ Features:
|
|||
if (reactionLines.length > 0) {
|
||||
sections.push(`## Automated Reactions
|
||||
|
||||
The system automatically handles these events:
|
||||
These events are handled automatically — you do NOT need to intervene unless the auto-handling fails:
|
||||
|
||||
${reactionLines.join("\n")}`);
|
||||
${reactionLines.join("\n")}
|
||||
|
||||
Reactions that auto-send to agents will retry and escalate to you if the agent doesn't fix the issue within the configured window.`);
|
||||
}
|
||||
}
|
||||
|
||||
// Workflows
|
||||
sections.push(`## Common Workflows
|
||||
// =========================================================================
|
||||
// PROJECT-SPECIFIC RULES (if any)
|
||||
// =========================================================================
|
||||
|
||||
### Bulk Issue Processing
|
||||
1. Get list of issues from tracker (GitHub/Linear/etc.)
|
||||
2. Use \`ao batch-spawn\` to spawn sessions for each issue
|
||||
3. Monitor with \`ao status\` or the dashboard
|
||||
4. Agents will fetch, implement, test, PR, and respond to reviews
|
||||
5. Use \`ao session cleanup\` when PRs are merged
|
||||
|
||||
### Handling Stuck Agents
|
||||
1. Check \`ao status\` for sessions in "stuck" or "needs_input" state
|
||||
2. Attach with \`ao session attach <session>\` to see what they're doing
|
||||
3. Send clarification or instructions with \`ao send <session> '...'\`
|
||||
4. Or kill and respawn with fresh context if needed
|
||||
|
||||
### PR Review Flow
|
||||
1. Agent creates PR and pushes
|
||||
2. CI runs automatically
|
||||
3. If CI fails: reaction auto-sends fix instructions to agent
|
||||
4. If reviewers request changes: reaction auto-sends comments to agent
|
||||
5. When approved + green: notify human to merge (unless auto-merge enabled)
|
||||
|
||||
### Manual Intervention
|
||||
When an agent needs human judgment:
|
||||
1. You'll get a notification (desktop/slack/webhook)
|
||||
2. Check the dashboard or \`ao status\` for details
|
||||
3. Attach to the session if needed: \`ao session attach <session>\`
|
||||
4. Send instructions: \`ao send <session> '...'\`
|
||||
5. Or handle it yourself (merge PR, close issue, etc.)`);
|
||||
|
||||
// Tips
|
||||
sections.push(`## Tips
|
||||
|
||||
1. **Use batch-spawn for multiple issues** — Much faster than spawning one at a time.
|
||||
|
||||
2. **Check status before spawning** — Avoid creating duplicate sessions for issues already being worked on.
|
||||
|
||||
3. **Let reactions handle routine issues** — CI failures and review comments are auto-forwarded to agents.
|
||||
|
||||
4. **Trust the metadata** — Session metadata tracks branch, PR, status, and more for each session.
|
||||
|
||||
5. **Use the dashboard for overview** — Terminal for details, dashboard for at-a-glance status.
|
||||
|
||||
6. **Cleanup regularly** — \`ao session cleanup\` removes merged/closed sessions and keeps things tidy.
|
||||
|
||||
7. **Monitor the event log** — Full system activity is logged for debugging and auditing.
|
||||
|
||||
8. **Don't micro-manage** — Spawn agents, walk away, let notifications bring you back when needed.`);
|
||||
|
||||
// Project-specific rules (if any)
|
||||
if (project.orchestratorRules) {
|
||||
sections.push(`## Project-Specific Rules
|
||||
|
||||
|
|
@ -209,3 +276,194 @@ ${project.orchestratorRules}`);
|
|||
|
||||
return sections.join("\n\n");
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// CLI REFERENCE BUILDER
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Build a comprehensive CLI reference section documenting every `ao` command
|
||||
* with all flags, options, and usage examples.
|
||||
*/
|
||||
function buildCLIReference(
|
||||
projectId: string,
|
||||
project: ProjectConfig,
|
||||
config: OrchestratorConfig,
|
||||
): string {
|
||||
const prefix = project.sessionPrefix;
|
||||
|
||||
return `## CLI Reference
|
||||
|
||||
### ao status
|
||||
|
||||
Show all sessions with branch, PR, CI, review status, and agent activity.
|
||||
|
||||
\`\`\`bash
|
||||
ao status # All projects
|
||||
ao status -p ${projectId} # Filter to this project
|
||||
ao status --json # Machine-readable JSON output
|
||||
\`\`\`
|
||||
|
||||
**Output columns**: Session, Branch, PR#, CI (pass/fail/pending), Review (approved/changes/pending), Threads (unresolved comment count), Activity (working/idle/waiting/exited), Age.
|
||||
|
||||
Each session also shows the agent's auto-generated summary of what it's working on.
|
||||
|
||||
---
|
||||
|
||||
### ao spawn
|
||||
|
||||
Spawn a single worker agent session for an issue.
|
||||
|
||||
\`\`\`bash
|
||||
ao spawn ${projectId} ISSUE-123 # Spawn with issue
|
||||
ao spawn ${projectId} # Spawn without issue (bare session)
|
||||
ao spawn ${projectId} ISSUE-123 --open # Also open in terminal tab
|
||||
\`\`\`
|
||||
|
||||
**What happens**: Creates git worktree from \`origin/${project.defaultBranch}\`, creates feature branch (\`feat/ISSUE-123\`), starts tmux session (\`${prefix}-N\`), launches agent with composed prompt, writes metadata.
|
||||
|
||||
**Issue format**: Accepts any identifier — GitHub issues (\`#42\`, \`42\`), Linear tickets (\`INT-1234\`), Jira keys (\`PROJ-567\`), etc.
|
||||
|
||||
---
|
||||
|
||||
### ao batch-spawn
|
||||
|
||||
Spawn sessions for multiple issues at once with duplicate detection.
|
||||
|
||||
\`\`\`bash
|
||||
ao batch-spawn ${projectId} ISSUE-1 ISSUE-2 ISSUE-3
|
||||
ao batch-spawn ${projectId} ISSUE-1 ISSUE-2 --open # Also open tabs
|
||||
\`\`\`
|
||||
|
||||
**Duplicate detection**: Skips issues that already have an active session (checks both existing sessions and within the current batch). Reports a summary of created/skipped/failed.
|
||||
|
||||
---
|
||||
|
||||
### ao send
|
||||
|
||||
Send a message to a running worker agent. Handles busy detection and delivery verification.
|
||||
|
||||
\`\`\`bash
|
||||
ao send ${prefix}-1 "Fix the failing test in auth.test.ts"
|
||||
ao send ${prefix}-1 -f /tmp/detailed-instructions.txt # From file
|
||||
ao send ${prefix}-1 --no-wait "Urgent: stop what you're doing" # Skip idle wait
|
||||
ao send ${prefix}-1 --timeout 120 "Take your time" # Custom timeout (seconds)
|
||||
\`\`\`
|
||||
|
||||
**How it works**:
|
||||
1. Waits for the session to become idle (default: up to 600s)
|
||||
2. Clears any partial input in the session
|
||||
3. Sends the message (multi-line messages use tmux buffer loading)
|
||||
4. Presses Enter to submit
|
||||
5. Verifies delivery by checking for agent activity indicators
|
||||
6. Retries Enter up to 3 times if delivery isn't confirmed
|
||||
|
||||
**Always use \`ao send\`** instead of raw \`tmux send-keys\`. It solves the hard problems: busy detection, input clearing, long message handling, delivery verification.
|
||||
|
||||
---
|
||||
|
||||
### ao session ls
|
||||
|
||||
List all sessions with metadata.
|
||||
|
||||
\`\`\`bash
|
||||
ao session ls # All projects
|
||||
ao session ls -p ${projectId} # Filter to this project
|
||||
\`\`\`
|
||||
|
||||
Shows: session name, age, branch, status, PR link.
|
||||
|
||||
---
|
||||
|
||||
### ao session kill
|
||||
|
||||
Kill a session, remove its worktree, and archive its metadata.
|
||||
|
||||
\`\`\`bash
|
||||
ao session kill ${prefix}-3
|
||||
\`\`\`
|
||||
|
||||
**Irreversible**: Removes the git worktree (uncommitted/unpushed work is lost). Only kill sessions that have pushed their work or are truly stuck.
|
||||
|
||||
---
|
||||
|
||||
### ao session cleanup
|
||||
|
||||
Automatically kill sessions where the PR has been merged.
|
||||
|
||||
\`\`\`bash
|
||||
ao session cleanup # All projects
|
||||
ao session cleanup -p ${projectId} # This project only
|
||||
ao session cleanup -p ${projectId} --dry-run # Preview what would be killed
|
||||
\`\`\`
|
||||
|
||||
Always run with \`--dry-run\` first when unsure.
|
||||
|
||||
---
|
||||
|
||||
### ao review-check
|
||||
|
||||
Scan all sessions with PRs for pending review comments and send fix instructions.
|
||||
|
||||
\`\`\`bash
|
||||
ao review-check # All projects
|
||||
ao review-check ${projectId} # This project only
|
||||
ao review-check ${projectId} --dry-run # Preview without sending
|
||||
\`\`\`
|
||||
|
||||
**What it does**: Finds PRs with unresolved review threads or "changes requested" decisions, then sends a fix prompt to the corresponding worker agent.
|
||||
|
||||
---
|
||||
|
||||
### ao open
|
||||
|
||||
Open session(s) in terminal tabs.
|
||||
|
||||
\`\`\`bash
|
||||
ao open ${prefix}-3 # Specific session
|
||||
ao open ${projectId} # All sessions for this project
|
||||
ao open all # All sessions across all projects
|
||||
ao open ${projectId} -w # Open in new terminal window
|
||||
\`\`\`
|
||||
|
||||
---
|
||||
|
||||
### ao dashboard
|
||||
|
||||
Start the web dashboard manually (usually started by \`ao start\`).
|
||||
|
||||
\`\`\`bash
|
||||
ao dashboard # Start on configured port (${config.port})
|
||||
ao dashboard -p 8080 # Custom port
|
||||
ao dashboard --no-open # Don't auto-open browser
|
||||
\`\`\`
|
||||
|
||||
---
|
||||
|
||||
### ao start / ao stop
|
||||
|
||||
Start or stop the orchestrator agent and dashboard.
|
||||
|
||||
\`\`\`bash
|
||||
ao start ${projectId} # Start everything
|
||||
ao start ${projectId} --no-dashboard # Skip dashboard
|
||||
ao start ${projectId} --no-orchestrator # Skip orchestrator agent
|
||||
ao stop ${projectId} # Stop everything
|
||||
\`\`\`
|
||||
|
||||
---
|
||||
|
||||
### tmux (read-only inspection)
|
||||
|
||||
For read-only inspection of sessions, you can use tmux directly:
|
||||
|
||||
\`\`\`bash
|
||||
# Peek at last 30 lines of a session (read-only, no attach)
|
||||
tmux capture-pane -t "${prefix}-3" -p -S -30
|
||||
|
||||
# List all tmux sessions
|
||||
tmux ls
|
||||
\`\`\`
|
||||
|
||||
Never use \`tmux send-keys\` — use \`ao send\` instead.`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -254,6 +254,7 @@ export interface Agent {
|
|||
* run git/gh commands. Without this, PRs created by agents never show up.
|
||||
*/
|
||||
setupWorkspaceHooks?(workspacePath: string, config: WorkspaceHooksConfig): Promise<void>;
|
||||
|
||||
}
|
||||
|
||||
export interface AgentLaunchConfig {
|
||||
|
|
@ -263,6 +264,18 @@ export interface AgentLaunchConfig {
|
|||
prompt?: string;
|
||||
permissions?: "skip" | "default";
|
||||
model?: string;
|
||||
|
||||
/**
|
||||
* System-level instructions to inject into the agent's context.
|
||||
* Used by `ao start` to give the orchestrator agent its instructions.
|
||||
*
|
||||
* Each agent plugin handles this in getLaunchCommand():
|
||||
* - Claude Code: --append-system-prompt
|
||||
* - Codex: --system-prompt or AGENTS.md
|
||||
* - Aider: --read flag or conventions
|
||||
* - OpenCode: equivalent mechanism
|
||||
*/
|
||||
systemPrompt?: string;
|
||||
}
|
||||
|
||||
export interface WorkspaceHooksConfig {
|
||||
|
|
|
|||
|
|
@ -82,6 +82,10 @@ function createAiderAgent(): Agent {
|
|||
parts.push("--model", shellEscape(config.model));
|
||||
}
|
||||
|
||||
if (config.systemPrompt) {
|
||||
parts.push("--system-prompt", shellEscape(config.systemPrompt));
|
||||
}
|
||||
|
||||
if (config.prompt) {
|
||||
parts.push("--message", shellEscape(config.prompt));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -562,6 +562,10 @@ function createClaudeCodeAgent(): Agent {
|
|||
parts.push("--model", shellEscape(config.model));
|
||||
}
|
||||
|
||||
if (config.systemPrompt) {
|
||||
parts.push("--append-system-prompt", shellEscape(config.systemPrompt));
|
||||
}
|
||||
|
||||
if (config.prompt) {
|
||||
parts.push("-p", shellEscape(config.prompt));
|
||||
}
|
||||
|
|
@ -726,6 +730,7 @@ function createClaudeCodeAgent(): Agent {
|
|||
const hookScriptPath = join(session.workspacePath, ".claude", "metadata-updater.sh");
|
||||
await setupHookInWorkspace(session.workspacePath, hookScriptPath);
|
||||
},
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,10 @@ function createCodexAgent(): Agent {
|
|||
parts.push("--model", shellEscape(config.model));
|
||||
}
|
||||
|
||||
if (config.systemPrompt) {
|
||||
parts.push("--system-prompt", shellEscape(config.systemPrompt));
|
||||
}
|
||||
|
||||
if (config.prompt) {
|
||||
// Use `--` to end option parsing so prompts starting with `-` aren't
|
||||
// misinterpreted as flags.
|
||||
|
|
|
|||
Loading…
Reference in New Issue