Leonard is an AI agent pair-programming orchestrator that coordinates two coding agents in a collaborative loop: a Driver (Claude Code) that writes code, and a Navigator (Codex) that reviews and provides guidance. Leonard runs each agent as a subprocess, parses their JSON output to extract text, and forwards it between them in a turn-based cycle.
For macOS users (if you don't have Homebrew, install it from brew.sh):
# Install rustup-init (Rust toolchain installer)
brew install rustup-init
# Run the installer to set up Rust (will offer to update your PATH)
rustup-init
# Load Rust into your PATH if needed (or open a new terminal)
source "$HOME/.cargo/env"
# Verify installation
rustc --version
cargo --version
# Clone and build Leonard
git clone https://github.com/rsdouglas/leonard
cd leonard
cargo build --releaseYou'll also need the claude and codex CLI tools (see Prerequisites below).
Leonard implements a simple relay pattern for AI pair-programming:
- Driver receives a task and produces code changes
- Navigator reviews the Driver's output and provides feedback
- Driver receives the feedback via
--continueand iterates - Repeat until
--max-turnsreached
┌─────────────────────────────────────────────────────────────┐
│ Leonard │
│ │
│ Task → Driver (claude) → Navigator (codex) → Driver → ... │
│ └─ writes code └─ reviews/guides └─ iterates │
└─────────────────────────────────────────────────────────────┘
Leonard requires:
-
Rust toolchain (1.70+) - for building Leonard itself
- Recommended: Install via
rustup-initfor version management:brew install rustup-init rustup-init source "$HOME/.cargo/env" # or open a new terminal
- Alternative: Install directly via Homebrew (simpler, but no toolchain management):
brew install rust
- Recommended: Install via
-
claudeCLI - Anthropic's Claude Code CLI tool- Used as the Driver agent (writes code)
- Must support
-p,--continue,--permission-mode,--output-format stream-json - Requires
ANTHROPIC_API_KEYenvironment variable
-
codexCLI - OpenAI Codex CLI tool- Used as the Navigator agent (reviews and guides)
- Must support
exec,--json, andresume --last - Requires
OPENAI_API_KEYenvironment variable
If you already completed the Quick Start, you can skip the Rust installation steps.
# Clone the repository
git clone https://github.com/rsdouglas/leonard
cd leonard
# Copy environment template and add your API keys
cp .env.example .envrc
# Edit .envrc with your actual keys
# Build the binary
cargo build --release
# Binary will be at target/release/leonardRun without --task to enter an interactive prompt. Leonard asks for a task, runs the pair-programming loop until the agents agree it's done, then asks for the next task. Both agents carry their full session history across tasks.
leonard --cwd /path/to/repoAt the prompt:
- Enter — submit the task
- Shift+Enter or Alt+Enter — insert a newline (for multi-line tasks or pasting context)
- Ctrl+C / Ctrl+D / empty input — exit
Use -c / --continue to resume your previous agent sessions from a prior run:
leonard -c --cwd /path/to/repoPass --task to run one loop and exit:
leonard --cwd /path/to/repo --task "Add error handling to the login function" --max-turns 5| Flag | Description | Default |
|---|---|---|
--cwd <path> |
Working directory for both agents | current directory |
--task <string> |
Task prompt — omit for interactive mode | (none) |
--max-turns <n> |
Maximum relay turns per task (0 = unlimited) | 10 |
--strip-ansi |
Strip ANSI escape codes from output | true |
--max-forward-bytes <n> |
Max bytes forwarded between agents | 100000 |
-c, --continue |
Resume previous agent sessions | false |
--log-file <path> |
Log prompts and responses to file | (none) |
--output-format <format> |
Output format: pretty or jsonl |
pretty |
Required:
ANTHROPIC_API_KEY- API key for Claude (Driver)OPENAI_API_KEY- API key for Codex (Navigator)
Optional:
- Use
.envrcwith direnv for automatic loading - Or export manually:
export ANTHROPIC_API_KEY=...
Leonard automatically loads a leonard.md file from --cwd if provided, otherwise the current working directory. This file provides context and guidance to both agents about the specific repository they're working in.
Use leonard.md to document:
- Project-specific conventions and patterns
- Architecture decisions and constraints
- Testing requirements and procedures
- Build and deployment instructions
- Common gotchas or known issues
- Preferred libraries or approaches for the codebase
The contents of leonard.md are included in the initial prompts to both agents, giving them shared context about the project from the start.
Example leonard.md:
# Project Context
This is a React + TypeScript project using Vite.
## Conventions
- Use functional components with hooks, not class components
- All API calls go through `src/lib/api.ts`
- Tests use Vitest and React Testing Library
## Before submitting code
- Run `npm test` to ensure tests pass
- Run `npm run typecheck` to verify TypeScriptLeonard runs preflight checks at startup to validate that claude and codex binaries are available and warn if API keys are missing.
- Driver turn: Leonard spawns
claude -pwith the task, captures stdout and parses JSON events to extract text - Navigator turn: Extracted Driver text is forwarded to
codex exec --json(first turn) orcodex exec resume --last --json(continuation) - Driver continuation: Navigator feedback is sent to
claude -p --continue - Repeat: Steps 2-3 repeat until the Navigator signals
ALL_DONEor max-turns is reached
Output is streamed to stdout with section headers (=== DRIVER ===, === NAVIGATOR (turn N) ===). Logs with timestamps go to stderr.
Leonard spawns both agents as child processes and uses stdout pipes (Stdio::piped()) to capture their output. Stderr is also captured and displayed if a process exits with non-zero status.
Contributions welcome. Before opening a PR:
- Run
cargo testto ensure tests pass - Run
cargo clippyto check for lint warnings - Run
cargo fmtto format code
MIT License - see LICENSE file for details.
Leonard can be used as a subprocess with structured output for integration into other tools:
const { spawn } = require('child_process');
const readline = require('readline');
const leonard = spawn('leonard', [
'--cwd', './my-project',
'--task', 'Fix the bug in auth.rs',
'--output-format', 'jsonl'
]);
// Use readline to properly handle line buffering
const rl = readline.createInterface({
input: leonard.stdout,
crlfDelay: Infinity
});
rl.on('line', (line) => {
if (!line.trim()) return;
const event = JSON.parse(line);
console.log(`[${event.agent}] ${event.type}`);
});See STREAMING_DESIGN.md for full API documentation.
- CLI Tool Availability: The
claudeandcodexCLI tools are currently required dependencies. Configuration options to override these may be added in the future. - Text Extraction: Leonard parses JSON/JSONL output from both agents to extract text content, then forwards the extracted text between them.
- Truncation: If output exceeds
--max-forward-bytes, the end of the text is kept with a[...truncated...]prefix.