Leerie is an autonomous task driver for Claude Code. One prompt. Finished, committed, validated code. No steering mid-run, no polishing when it's done.
Most tools that call themselves autonomous still require you: to confirm a direction, catch a hallucination, or clean up the result before it's usable. Leerie doesn't. It classifies the task, decomposes it, implements each piece in parallel isolated worktrees, validates the integrated result, and merges — beginning to end, unattended.
It runs entirely on the Claude Code CLI and your existing subscription — no Anthropic API key, no per-call billing. If you have Claude Code installed and logged in, you have everything it needs.
Why it actually finishes without you:
Most AI "orchestrators" let the model pilot: the model decides what to do next, declares when it's done, and judges whether it succeeded. That's where drift, hallucinated completion, and silent failures come from — and why you end up steering.
Leerie inverts the relationship. The model writes code. The program runs everything else. Phases, wave scheduling, retries, caps, merge logic, and success-criteria enforcement are ordinary Python — real loops and conditionals that cannot drift.
- No silent failures. Every worker output is JSON-schema-validated before the orchestrator acts on it. A worker cannot, by malformed output or confident hallucination, cause the system to do something undefined.
- Confidence is the only hard gate. The implementer self-gates on evidence-anchored confidence in
root_causeandsolution(≥9 on both, see DESIGN.md §8) — falsifiers tested, contradictions reconciled, gaps named with concrete artifacts. A worker that cannot justify the score exitsblockedwith the gap analysis. Everything else — tests passing, lint clean, build green, per-criterion satisfaction — is best-effort: surfaced as advisory warnings on the subtask result, never escalated tofailedorblockedby the orchestrator. The criteria file is the implementer's working note, not a gate. - Workers must justify confidence with evidence, not feelings. Before writing code, an implementer clears domain-specific evidence gates — file-and-line citations, reproductions, falsification attempts. A self-reported score without hard artifacts doesn't clear the bar.
- Parallel work that's actually safe. Each implementer gets an isolated git worktree. Parallel writes never collide. Conflicts surface one wave at a time, close to the work that caused them.
- Resumable by design. A reboot, network blip, budget cap, the Claude Code subscription rate-limit, Ctrl-C, or an external kill (SIGTERM from CI / systemd / a closed terminal) all lose nothing — the run branch is the durable record, worktrees are torn down, and
--resumepicks up from the last completed wave. When the subscription rate-limit hits and the reset time is unambiguously parseable, leerie even auto-resumes after the reset window without manual intervention. The explicit "throw this away" gesture isscripts/cleanup.sh --run-id <id> --branches, not Ctrl-C. - Parallel-safe across runs. Multiple
./leerieinvocations in the same repository each get a uniquerun_id(a derived branch + state directory). Their branches, worktrees, and per-run state directories never collide. Launch a fix and a feature in parallel without coordination.
The orchestrator is a Python program — not an in-session agent. It shells out
to claude -p (headless mode) for each unit of LLM work. Each call is a
separate process, so there is no subagent nesting anywhere. Control flow lives
in real Python: for loops, if statements, counters. It cannot drift.
leerie "<task>"
├─ Phase 1 Classify into 1..9 categories → 1 claude -p
│ ↓ derive run_id (category + slug + start-hex)
│ • Clarify — intent-only questions (optional; skipped for fully-specified tasks)
├─ Phase 2 Plan — one planner per category (parallel) → N×3 claude -p (multi-sampled)
│ • Decompose — repo-map-grounded recursive split into
│ right-sized leaf subtasks (P6 + P1) → fit_judge / splitter
│ • Reconcile — cross-domain capability-tag bridging (0 or 1 claude -p, when needed)
│ • Overlap-judge — cross-planner surface collisions (multi-planner runs only)
├─ Phase 3 Schedule — global dependency graph → topo waves (topo-sort pure Python;
│ a satisfied-probe drops already-done criterion-bearing subtasks → 1 claude -p each)
├─ Phase 4 Create leerie/runs/<run-id> branch + worktree (per-run unique)
├─ Phase 5 Per wave:
│ ├─ Implement each subtask (parallel, isolated worktrees) → claude -p each
│ │ • self-gates on evidence-anchored confidence — the only hard gate
│ │ • Conform — post-work doc/test/lint/build pass (advisory) → 1 conformer claude -p
│ ├─ Integrate the wave into the run branch; on conflict → 1 integrator claude -p
│ └─ Validate the integrated run branch (conflict-marker scan)
│ (after the final wave: one more conformer pass on the whole tree)
└─ Phase 6 Push run branch; open PR against working branch; cleanup
(working branch not modified locally)
For the full rationale — why the orchestrator is a script rather than a plugin
command, all architectural decisions, and the complete enforcement surface —
read docs/DESIGN.md.
claudeCLI onPATH, logged in interactivelygit- A git repository with
user.emailanduser.nameconfigured - A reasonably clean working tree
- A container runtime (one-time setup — see Install below)
ghCLI logged in (gh auth statussucceeds), or pass--no-pushto skip the finalize PR step
Leerie runs inside a container to give cleanup a hard kernel
guarantee: when you Ctrl-C, the Linux PID namespace is torn down and
every worker / build / test runner is reaped, even ones that detached
into their own POSIX sessions. See
docs/DESIGN.md §6 and
docs/IMPLEMENTATION.md §0.5 for the
reasoning and mechanics. Python is provisioned inside the container
by the image; you don't need it on the host.
curl -fsSL https://raw.githubusercontent.com/enricai/leerie/main/scripts/install.sh | bashThe installer auto-installs and starts the container runtime per OS
(Colima on macOS via brew; containerd + pinned nerdctl on
Debian/Ubuntu, Fedora/RHEL, and Arch via the distro package manager)
and then clones leerie into ~/.leerie + symlinks leerie into
~/.local/bin. Sudo prompts apply on Linux. Full per-OS details and
the rootless / unsupported-distro paths live in
docs/INSTALL.md.
/plugin marketplace add enricai/leerie
/plugin install leerie@enricai-leerie
Then in any Claude Code session:
/leerie Fix the login timeout bug and add a regression test
curl -fsSL https://raw.githubusercontent.com/enricai/leerie/main/scripts/install.sh -o install.sh
bash install.sh --dry-run # print actions without executing
bash install.sh # then run for realCustomize with --prefix DIR (default ~/.leerie), --bin-dir DIR
(default ~/.local/bin), or --ref REF (default main).
If you'd rather install the runtime yourself (CI, dotfiles managers,
or you want to pin a different nerdctl version), do the runtime
steps manually then pass --no-runtime-install (or set
LEERIE_NO_RUNTIME_INSTALL=1):
macOS (Colima manages a Linux VM):
brew install colima
# Size the VM at ~half your host's CPU/RAM (Colima's 2-CPU / 2-GB
# default OOMs under parallel leerie workloads — see docs/INSTALL.md
# for the auto-sizing the installer applies). On an 8/16 host:
colima start --runtime containerd --mount-type virtiofs --cpu 4 --memory 8
# Also add 4 GB of swap (paste the YAML block from docs/INSTALL.md
# "Memory pressure: swap configuration" into ~/.colima/default/colima.yaml,
# then colima stop && colima start). This step is optional but strongly
# recommended — without swap the VM OOMs under heavy parallel load.
curl -fsSL https://raw.githubusercontent.com/enricai/leerie/main/scripts/install.sh | bash -s -- --no-runtime-install(Do not brew install nerdctl — the formula requires Linux. Leerie
auto-installs the host-side nerdctl shim from Colima on first run.)
Linux (Debian/Ubuntu — see docs/INSTALL.md
for Fedora, Arch, and rootless setups):
sudo apt-get install -y containerd
NERDCTL_VERSION=2.3.1
ARCH="$(dpkg --print-architecture 2>/dev/null || uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')"
curl -L "https://github.com/containerd/nerdctl/releases/download/v${NERDCTL_VERSION}/nerdctl-${NERDCTL_VERSION}-linux-${ARCH}.tar.gz" \
| sudo tar -C /usr/local/bin -xz nerdctl
sudo systemctl enable --now containerd
curl -fsSL https://raw.githubusercontent.com/enricai/leerie/main/scripts/install.sh | bash -s -- --no-runtime-installIf you'd rather not run any installer at all:
git clone https://github.com/enricai/leerie.git
./leerie/leerie "your task" # or symlink onto PATHThe first invocation builds the container image (~60–120s); subsequent
runs reuse it. The container runtime must already be set up — see
docs/INSTALL.md for per-OS instructions.
# From the root of the target git repository:
leerie "Fix the login timeout bug and add a regression test"
# (substitute leerie if you used the manual install)
# Or pass a path to a .txt / .md file whose contents are the task —
# useful for multi-paragraph briefs that are awkward to quote on the shell:
leerie path/to/task.md
# Resume an interrupted or budget-capped run. Auto-picks if exactly one
# in-flight run exists; otherwise pass the run-id (see `--list`).
leerie --resume
leerie --resume fix-login-timeout-bug-b81e90
# List in-flight and completed runs in this repository:
leerie --list
# Skip the default push + PR at finalize (run completes with the run
# branch local-only; your working branch is unchanged):
leerie "task" --no-push
# Skip pre-push hooks at finalize (the user's explicit override; defaults
# off). Affects only the final `git push`; worker commits still run hooks.
leerie "task" --no-verify
# Opt into intent questions (default: no questions are surfaced).
leerie "task" --clarify
# Pre-supply clarification answers (JSON object):
# Keys are question ids from the classifier, plus "source_of_truth"
# set to "codebase", "research", or "both".
leerie "task" --answers answers.json
# Override caps (defaults: 200 total workers, 5 in parallel per wave).
# --max-workers also reads LEERIE_MAX_WORKERS or max_workers in
# leerie.toml; --max-parallel also reads LEERIE_MAX_PARALLEL or
# max_parallel in leerie.toml.
leerie "task" --max-workers 80 --max-parallel 4
export LEERIE_MAX_WORKERS=80
# Dial how persistent the planner and implementer are at building
# confidence before they exit blocked (default 8 evidence-gate rounds
# inside each worker; see DESIGN §8):
leerie "task" --confidence-rounds 12
export LEERIE_CONFIDENCE_ROUNDS=12
# Override the default source-of-truth preference (`both`) — pass
# --source-of-truth on the command line for a one-off, set
# LEERIE_SOURCE_OF_TRUTH for the session, or commit a leerie.toml
# at the repo root with the line `source_of_truth = codebase` (or
# research / both).
# Precedence (highest first): --source-of-truth > env > leerie.toml.
export LEERIE_SOURCE_OF_TRUTH=codebase # or: research, both
leerie "task" --source-of-truth codebase
# Choose the model. Without overrides, judgment workers (classifier /
# planner / reconciler / plan_overlap_judge / provision / integrator)
# default to opus and the acting
# workers (implementer, conformer) default to sonnet — see
# docs/IMPLEMENTATION.md §2 "Model selection" for the full env-var /
# CLI-flag / TOML-key table.
# Set LEERIE_MODEL=sonnet (or --model sonnet) to restore the
# pre-0.3 all-sonnet behavior in one knob.
export LEERIE_MODEL=sonnet # or: opus, haiku
leerie "task" --model opus
leerie "task" --model-implementer opus --model-classifier haiku
# Optional but recommended — lower the auto-compaction threshold
# for worker processes (default is 95%):
export CLAUDE_AUTOCOMPACT_PCT_OVERRIDE=70
# Chain orchestration: submit and track multi-run chains. A chain is
# N parallel `leerie --runtime fly` invocations per wave, with
# synth-merge between waves to build the next wave's base branch.
# The laptop is the sequencer; no Fly coordinator machine. No chain-
# specific env vars required — each per-job `--runtime fly`
# invocation has its own env requirements unchanged.
# Submit a new chain. Each --wave defines one wave (comma-separated
# prompt files). Waves execute in order; runs within a wave execute
# in parallel as separate Fly machines. N waves supported. The chain
# operates against $USER_REPO directly.
leerie --chain \
--wave prompts/fetch.md,prompts/lint.md \
--wave prompts/publish.md
# ID-dispatched verbs: UUID positional → chain scope (iterates
# run.json filtered by chain_id); Fly machine id → existing
# single-run scope.
leerie --status <chain-id> # render per-run states from run.json
leerie --attach <chain-id> # poll run.json files every 5s
leerie --stop <chain-id> # pause every running chain run
leerie --kill <chain-id> # destroy every chain run's machine
leerie --resume <chain-id> # resume every paused chain run
leerie --finalize <chain-id> # push + open PR for every unpushed run
leerie --list --chains # group runs by chain_id
# Deprecated --chain-* aliases (kept for backwards compat) shim to
# the new verbs above.
# Run-groups: launch N single-repo leerie runs together as a coordinated
# unit. Each member runs in its own state dir (basename-keyed), its own
# branch, and opens its own PR. Members share a group_id and read-only
# cross-repo visibility via --inspect-dir; the optional --brief file is
# prepended to every member's prompt.
leerie --group \
--repo ../api "add /volumes endpoint" \
--repo ../frontend "add-disk dialog" \
--brief group-brief.md # optional shared brief
# Resubmit with an existing group_id (keeps the same group):
leerie --group --group-id <prior-group-id> \
--repo ../api "add /volumes endpoint" \
--repo ../frontend "add-disk dialog"
# Group-scoped verbs (UUID → group scope across member state dirs):
leerie --status <group-id> # render per-member run states
leerie --stop <group-id> # pause every running member (Fly runtime only)
leerie --resume <group-id> # resume every paused member run
leerie --kill <group-id> # destroy every member run
leerie --finalize <group-id> # push + open PR for every unpushed member
leerie --list --groups # list all groups across state dirsInside Claude Code (after /plugin install leerie@enricai-leerie):
/leerie Fix the login timeout bug and add a regression test
Complete reference for every CLI flag, environment variable, and
leerie.toml key the orchestrator reads.
| Flag | Default | Description |
|---|---|---|
task (positional) |
— | The task description (literal string, or path to a .txt/.md file). Required unless --resume, --list, or --phase is given. |
--resume |
off | Resume an interrupted run. Auto-picks if exactly one run exists; pass the run-id if multiple. |
--run-id ID |
— | Select a specific run by id (e.g., for --resume or --phase when multiple runs are in flight). |
--list |
off | Enumerate in-flight and completed runs in this repository (run id, started, status, cost, branch). |
--no-push |
off | Skip the default push + PR at finalize. The run completes with the run branch local-only; your working branch is unchanged. Overrides LEERIE_NO_PUSH / leerie.toml. |
--no-verify |
off | Pass --no-verify to the finalize git push only (skips pre-push hooks). Worker commits inside worktrees still run all hooks. The user's explicit override per CLAUDE.md's hooks principle. |
--answers FILE |
— | JSON object of pre-supplied clarification answers (keyed by question id; may include source_of_truth). |
--clarify |
off | Opt into surfacing intent questions to the user. Default: questions are dropped after the classifier's codebase→research filter, and the implementer makes a documented best-effort decision. Also LEERIE_CLARIFY env var or clarify = true in leerie.toml. |
--max-workers N |
200 |
Cap on total claude -p invocations across the run. Also LEERIE_MAX_WORKERS env var or max_workers in leerie.toml. |
--max-parallel N |
5 |
Cap on concurrent workers within a wave. Per-worker cgroup containment keeps an OOM inside one worker's cgroup; users on smaller VMs can opt down. Also LEERIE_MAX_PARALLEL env var or max_parallel in leerie.toml. |
--worker-memory-max SIZE |
auto | Per-worker cgroup memory cap (e.g. 4G, 512M). Bounds RAM the worker subtree may consume; OOMs stay inside the worker cgroup rather than cascading to sshd / orchestrator. Auto-derived from /proc/meminfo when unset (VM RAM / max_parallel+1, floored at 8 GiB). Also LEERIE_WORKER_MEMORY_MAX or worker_memory_max in leerie.toml. |
--confidence-rounds N |
8 |
Evidence-gate rounds the planner and implementer may run before exiting blocked (DESIGN §8). Overrides LEERIE_CONFIDENCE_ROUNDS and leerie.toml. |
--skip-smoke |
off | Skip the live claude -p preflight smoke test. |
--source-of-truth VALUE |
both |
codebase / research / both. Overrides LEERIE_SOURCE_OF_TRUTH and leerie.toml. |
--runtime VALUE |
local |
local / fly / ec2. Execution backend for per-subtask worker containers. Overrides LEERIE_RUNTIME and leerie.toml. --runtime ec2 provisions an EC2 instance, seeds it, and runs the orchestrator on it, mirroring --runtime fly; see docs/INSTALL.md "EC2 runtime" (requires LEERIE_EC2_AMI to name an AMI with the orchestrator already baked in). |
--aws-region VALUE |
none | AWS region leerie itself uses when provisioning --runtime ec2 machines. Distinct from the AWS SDK's own AWS_REGION credential-chain var. Also LEERIE_AWS_REGION env var or aws_region in leerie.toml. |
--aws-profile VALUE |
none | AWS profile leerie itself uses when provisioning --runtime ec2 machines. Distinct from the AWS SDK's own AWS_PROFILE credential-chain var. Also LEERIE_AWS_PROFILE env var or aws_profile in leerie.toml. |
--ec2-ami VALUE |
none (required for --runtime ec2) |
AMI id for the RunInstances call. Also LEERIE_EC2_AMI env var or ec2_ami in leerie.toml. |
--ec2-instance-type VALUE |
none (required for --runtime ec2) |
EC2 instance type (e.g. t3.large). Also LEERIE_EC2_INSTANCE_TYPE env var or ec2_instance_type in leerie.toml. |
--ec2-key-name VALUE |
none (required for --runtime ec2) |
EC2 key-pair name for SSH access. Also LEERIE_EC2_KEY_NAME env var or ec2_key_name in leerie.toml. |
--ec2-security-group VALUE |
none (required for --runtime ec2) |
Security group id to attach. Also LEERIE_EC2_SECURITY_GROUP env var or ec2_security_group in leerie.toml. |
--ec2-subnet-id VALUE |
none (required for --runtime ec2) |
Subnet id to launch into. Also LEERIE_EC2_SUBNET_ID env var or ec2_subnet_id in leerie.toml. |
--inspect-dir PATH |
none | Extra directory the inspect-bucket workers (classifier, planner, reconciler, plan_overlap_judge, provision) may read; forwarded to claude -p as --add-dir. Repeatable. Also LEERIE_INSPECT_DIRS (colon-separated) or inspect_dirs in leerie.toml (comma-separated). |
--model ALIAS |
per-worker (judgment: opus; acting workers — implementer, conformer: sonnet) |
sonnet / opus / haiku. Sets every worker this run; without it the per-worker defaults apply. |
--model-<worker> ALIAS |
per-worker default (implementer, conformer → sonnet; everything else → opus) |
Per-worker override. <worker> is one of classifier, planner, reconciler, plan_overlap_judge, provision, implementer, integrator, conformer, fit_judge, splitter. Overrides --model, LEERIE_MODEL, and leerie.toml. |
--effort LEVEL |
per-worker (judgment: high; acting workers — implementer, conformer: inherit Claude default) |
low / medium / high / xhigh / max. Reasoning-depth dial forwarded to claude -p --effort. Pins judgment workers to a consistent depth across runs to reduce same-job variance (e.g. planner subtask-count drift). IMPLEMENTATION.md §2 "Effort selection". |
--effort-<worker> LEVEL |
per-worker default (judgment workers → high; acting workers → inherit Claude default) |
Per-worker override. <worker> is one of the orchestrator workers (same set as --model-<worker>). Overrides --effort, LEERIE_EFFORT, and leerie.toml. |
--judge-model ALIAS |
sonnet |
Model alias for the post-run judge skill. Also LEERIE_MODEL_JUDGE or model_judge in leerie.toml. |
--heal-model ALIAS |
sonnet |
Model alias for the post-run self-heal skill. Also LEERIE_MODEL_HEAL or model_heal in leerie.toml. |
--heal-max-rounds N |
10 |
Maximum heal-loop iterations per call_type. Also LEERIE_HEAL_MAX_ROUNDS or heal_max_rounds in leerie.toml. |
--heal-success-threshold RATE |
0.9 |
Pass-rate threshold for the heal-loop SUCCESS verdict. Also LEERIE_HEAL_SUCCESS_THRESHOLD or heal_success_threshold in leerie.toml. |
--verbosity LEVEL |
stream |
quiet / normal / stream / debug. Controls inline per-worker activity output; full per-worker stream is always saved to <state-root>/logs/<sid>.log (where <state-root> is the resolved state directory — default $HOME/.leerie/<basename>/). |
-v / -vv |
0 (off) |
Shortcuts that anchor to normal: -v = stream, -vv = debug. With no -v and no --verbosity, falls through to LEERIE_VERBOSITY / leerie.toml / default stream. |
-q / -qq |
0 (off) |
Shortcuts that anchor to normal: -q = normal (pre-streaming behavior), -qq = quiet. With no -q and no --verbosity, falls through to the same chain as -v. |
--judge-dir DIR |
judge-out |
Subdirectory name under the run dir for LLM judge output. Also LEERIE_JUDGE_DIR or judge_dir in leerie.toml. |
--heal-dir DIR |
heal-out |
Subdirectory name under the run dir for LLM self-heal output. Also LEERIE_HEAL_DIR or heal_dir in leerie.toml. |
--phase PHASE |
— | Run a post-run skill phase (judge or heal) against an existing run's captured LLM calls instead of starting a new run. Use --run-id to select when multiple runs exist. |
--report [RUN_ID] |
— | Print a read-only telemetry report for a run: per-call-type token/cost/latency/failure breakdown plus memory peak. Pass a run id, or omit to auto-pick when exactly one run exists. Exits without running orchestrate. |
--version |
— | Print leerie <version> and exit. |
--status STATE |
— | With --list, restrict the table to runs whose derived status matches STATE. One of: seed-failed, corrupt-sidecar, in-progress, done, done-pushed-no-pr, done-pushed-pr, push-failed, pr-failed, paused, killed, sync-failed. |
--skip-overlap-judge |
off | Skip the phase 2¾ plan-overlap judge (DESIGN §5). Auto-skipped on single-planner runs; this flag disables it on multi-planner runs. Also LEERIE_SKIP_OVERLAP_JUDGE or skip_overlap_judge in leerie.toml. |
--skip-budget-check |
off | Skip the post-schedule budget-feasibility preflight (DESIGN §13). The runtime backstop in State.bump_workers() still fires. Also LEERIE_SKIP_BUDGET_CHECK or skip_budget_check in leerie.toml. |
--skip-repo-map |
off | Skip the P6 repo-map structural context (DESIGN §5½ (P6)): suppresses build_repo_map() and the ranked subgraph injection into planner/splitter context; the planner degrades gracefully to the prior grep/glob-only path. Use on repos where tree-sitter cannot parse the primary language. Also LEERIE_SKIP_REPO_MAP or skip_repo_map in leerie.toml. |
--dangerously-skip-permissions |
off | Pass --dangerously-skip-permissions to every claude -p worker, including judgment workers that run in the real repo cwd. Waives DESIGN §12 read-only enforcement. Also LEERIE_DANGEROUSLY_SKIP_PERMISSIONS or dangerously_skip_permissions in leerie.toml. |
--pr-template NAME |
none | When the target repo has multiple PR templates in PULL_REQUEST_TEMPLATE/, pick this one by basename (with or without .md). Also LEERIE_PR_TEMPLATE or pr_template in leerie.toml. |
--pr-writer-model ALIAS |
sonnet |
Model alias for the finalize-time PR title + body writer. Also LEERIE_MODEL_PR_WRITER or model_pr_writer in leerie.toml. |
These flags are handled by the bash launcher before the container starts.
A summary appears in the leerie --help epilog; see below for full
details and sub-flags.
Per-repo configuration (no container required):
leerie config is a host-only fast path — it exits before nerdctl run and never starts a container.
| Verb | Description |
|---|---|
config |
Print the effective build/lint/test config for this repo, with [config] or [inference] provenance for each axis. Also shows leerie.toml operational knobs when present. |
config --init |
Create .leerie/config.toml with auto-detected BLT commands (uncommented) and a commented setup_packages example. Errors if the file already exists. Prints the path and suggests git add .leerie/. |
config --chat |
Open an interactive claude session with a config-generation system prompt and --add-dir pointing at the current repo. The model can read the repo and write .leerie/config.toml (and optionally .leerie/Dockerfile). |
config --recapture |
Host-only (no container). Consolidates across all finished runs' logs (not just the newest) and writes merged setup_packages / language-dep installs to .leerie/config.toml via the dep_capture LLM worker. Never-clobber union: already-captured runs (sentinel present) are skipped and only new packages/managers are added. |
config --recapture --force |
Re-runs the worker over runs already captured (drops the dep_capture.done sentinel) and wholesale-replaces the persisted setup_packages / language_installs from the fresh capture — deps no longer captured are dropped. An empty capture leaves the existing config untouched (never blanks a good config). Use to re-derive deps from current run history. |
Lifecycle (remote mode):
| Flag | Description |
|---|---|
--stop <run-id> [--runtime local|fly|ec2] |
Pause a run — a remote Fly machine, an EC2 instance (stop-instances, preserving the root EBS volume), or a local container. Resumable via --resume (EC2 --resume calls resume_instance() and re-resolves the reassigned public IP). |
--kill <run-id> [--force] |
Destroy a remote machine permanently. --force skips confirmation. Also accepts --machine-id <id> [--app <app>] for orphan cleanup. |
--finalize <run-id> [--force] [--no-verify] [--no-push] [--runtime fly] |
Post-detach finalization: collect un-integrated subtask branches on the machine, fetch the run branch, then push + open PR on the host. Without --force, requires the orchestrator to be dead. --force SIGTERMs a live orchestrator first, then collects and fetches. |
--re-seed <run-id> [--force] |
Mid-run host→machine re-rsync of dirty delta. --force bypasses the safety check that refuses to clobber machine-side uncommitted edits. |
Resume modifiers (used with --resume):
| Flag | Description |
|---|---|
--shell |
Drop into a bash shell at /work on the machine instead of tailing the orchestrator log. |
--auto-finalize |
On clean orchestrator exit, automatically run leerie --finalize. |
--no-re-seed |
Skip the automatic re-seed of dirty delta on resume. |
Build and runtime:
| Flag | Description |
|---|---|
--state-dir PATH |
Override the per-repo state directory. Also LEERIE_STATE_DIR env var or state_dir in leerie.toml. |
--fly-app NAME |
Fly.io app name (required for --runtime fly; globally unique). Also LEERIE_FLY_APP env var. |
--fly-disk-gb N |
Provision a Fly volume of N GB mounted at /home/leerie. Also FLY_VM_DISK_GB env var. |
--no-runtime-install |
Skip auto-install of container runtime (Colima / nerdctl / containerd). Also LEERIE_NO_RUNTIME_INSTALL. |
--no-auto-publish |
Skip the image-publish probe on startup. Also LEERIE_NO_AUTO_PUBLISH. |
--local-build |
Force local nerdctl build instead of the Fly remote builder. Also LEERIE_LOCAL_BUILD. |
| Env var | leerie.toml key |
Description |
|---|---|---|
LEERIE_STATE_DIR |
state_dir |
Override the per-repo run state directory. Unset → default $HOME/.leerie/<basename>/ (outside the repo; no .gitignore entry needed in target projects). Cross-repo basename collisions are caught at use time via an .owner sidecar inside the dir. Set once in your shell profile for a global directory across all repos. |
LEERIE_SOURCE_OF_TRUTH |
source_of_truth |
Sticky source-of-truth preference (codebase / research / both). Overridden by --source-of-truth. Unset → default both. |
LEERIE_RUNTIME |
runtime |
Execution backend for per-subtask worker containers (local / fly / ec2). Overridden by --runtime. Unset → default local. |
LEERIE_MODEL |
model |
Model alias applied to every worker. Overridden by --model and per-worker overrides. Unset → per-worker defaults (judgment workers opus, acting workers — implementer, conformer — sonnet). |
LEERIE_MODEL_<WORKER> |
model_<worker> |
Per-worker override (e.g. LEERIE_MODEL_IMPLEMENTER=opus). Overridden by --model-<worker>. <worker> ∈ classifier, planner, reconciler, plan_overlap_judge, satisfied_probe, provision, implementer, integrator, conformer, fit_judge, splitter. Unset → implementer, conformer, and satisfied_probe → sonnet; everything else → opus. |
LEERIE_EFFORT |
effort |
Reasoning-depth dial forwarded to claude -p --effort (low / medium / high / xhigh / max). Applies to every worker; overridden by --effort and per-worker overrides. Unset → judgment workers high, acting workers inherit Claude default. |
LEERIE_EFFORT_<WORKER> |
effort_<worker> |
Per-worker override (e.g. LEERIE_EFFORT_PLANNER=max). Overridden by --effort-<worker>. Same worker set as LEERIE_MODEL_<WORKER>. Unset → judgment workers high; acting workers (implementer, conformer) inherit Claude default. |
LEERIE_CONFIDENCE_ROUNDS |
confidence_rounds |
Evidence-gate rounds per worker (positive integer). Overridden by --confidence-rounds. Unset → default 8. |
LEERIE_INSPECT_DIRS |
inspect_dirs |
Extra directories the inspect-bucket workers (classifier, planner, reconciler, plan_overlap_judge, provision) may read; forwarded as --add-dir. Env value is colon-separated; TOML value is comma-separated. Overridden by --inspect-dir (repeatable). Unset → none. |
LEERIE_VERBOSITY |
verbosity |
Inline-output verbosity (quiet / normal / stream / debug). Overridden by --verbosity. -v / -vv / -q / -qq shortcuts override both. Unset → default stream. |
LEERIE_NO_PUSH |
no_push |
Sticky opt-out from push + PR at finalize (truthy → skip). Overridden by --no-push. --no-verify has no env/TOML mirror — it is a per-invocation override only. Unset → default false (push + PR happen). |
LEERIE_CLARIFY |
clarify |
Sticky opt-in to surfacing intent questions to the user (truthy → on). Overridden by --clarify. Unset → default false. |
LEERIE_MODEL_JUDGE |
model_judge |
Model alias for the post-run judge skill. Overridden by --judge-model. Unset → default sonnet. |
LEERIE_MODEL_HEAL |
model_heal |
Model alias for the post-run self-heal skill. Overridden by --heal-model. Unset → default sonnet. |
LEERIE_HEAL_MAX_ROUNDS |
heal_max_rounds |
Maximum heal-loop iterations per call_type. Overridden by --heal-max-rounds. Unset → default 10. |
LEERIE_HEAL_SUCCESS_THRESHOLD |
heal_success_threshold |
Pass-rate threshold for the heal-loop SUCCESS verdict. Overridden by --heal-success-threshold. Unset → default 0.9. |
LEERIE_JUDGE_DIR |
judge_dir |
Subdirectory name under the run dir for LLM judge output. Overridden by --judge-dir. Unset → default judge-out. |
LEERIE_HEAL_DIR |
heal_dir |
Subdirectory name under the run dir for LLM self-heal output. Overridden by --heal-dir. Unset → default heal-out. |
LEERIE_MAX_WORKERS |
max_workers |
Total worker-invocation budget. Overridden by --max-workers. Unset → default 200. |
LEERIE_MAX_PARALLEL |
max_parallel |
Concurrent workers per wave. Overridden by --max-parallel. Unset → default 5. |
LEERIE_WORKER_MEMORY_MAX |
worker_memory_max |
Per-worker cgroup memory cap (e.g. 4G, 512M). Overridden by --worker-memory-max. Unset → auto-derived from /proc/meminfo. |
LEERIE_DANGEROUSLY_SKIP_PERMISSIONS |
dangerously_skip_permissions |
Waive §12 read-only enforcement on judgment workers (truthy → on). Overridden by --dangerously-skip-permissions. Unset → default false. |
LEERIE_SKIP_OVERLAP_JUDGE |
skip_overlap_judge |
Skip the phase 2¾ plan-overlap judge on multi-planner runs (truthy → skip). Overridden by --skip-overlap-judge. Unset → default false. |
LEERIE_SKIP_BUDGET_CHECK |
skip_budget_check |
Skip the post-schedule budget-feasibility preflight (truthy → skip). Overridden by --skip-budget-check. Unset → default false. |
LEERIE_SKIP_REPO_MAP |
skip_repo_map |
Skip the P6 repo-map structural context injection (truthy → skip). Overridden by --skip-repo-map. Unset → default false. |
LEERIE_PR_TEMPLATE |
pr_template |
PR template basename for repos with multiple templates. Overridden by --pr-template. Unset → alphabetically first .md. |
LEERIE_MODEL_PR_WRITER |
model_pr_writer |
Model alias for the finalize-time PR writer. Overridden by --pr-writer-model. Unset → default sonnet. |
LEERIE_MODEL_DEP_CAPTURE |
(none) | Model alias for the finalize-time dep_capture worker. Env var only — no per-worker CLI flag and no leerie.toml key (it still honors the global model key / --model). Unset → default opus. |
LEERIE_CAPTURE_DEPS |
capture_deps (.leerie/config.toml only — not leerie.toml) |
Enable finalize-time dependency capture (truthy → on). Precedence: LEERIE_CAPTURE_DEPS > .leerie/config.toml > default true. Set to false / 0 to disable entirely. |
LEERIE_BAKE_LANGUAGE_DEPS |
bake_language_deps |
Include a language-dep COPY+RUN layer in the auto-generated .leerie/Dockerfile (truthy → on). Precedence: LEERIE_BAKE_LANGUAGE_DEPS > leerie.toml > .leerie/config.toml > default true. Set to false for an apt-only bake. |
LEERIE_WORKER_DEBUG |
— | Enable debug-level logging injection (DEBUG=*, ANTHROPIC_LOG=debug) into worker processes. Truthy → on. |
LEERIE_FLY_APP |
— | Fly.io app name (globally unique). Required when --runtime fly. Set via env or --fly-app. Launcher-only. |
LEERIE_REGION |
— | Fly region used by per-job --runtime fly machines (including those spawned by leerie --chain). Unset → default iad. Launcher-only. |
LEERIE_AWS_REGION |
aws_region |
AWS region leerie itself uses when provisioning --runtime ec2 machines — distinct from the AWS SDK's own AWS_REGION credential-chain env var. Overridden by --aws-region. Unset → default None (region selection left to the AWS credential chain). |
LEERIE_AWS_PROFILE |
aws_profile |
AWS profile leerie itself uses when provisioning --runtime ec2 machines — distinct from the AWS SDK's own AWS_PROFILE credential-chain env var. Overridden by --aws-profile. Unset → default None. |
LEERIE_EC2_AMI |
ec2_ami |
AMI id for the --runtime ec2 RunInstances call. Overridden by --ec2-ami. Required for --runtime ec2, no default. Launcher-only. |
LEERIE_EC2_INSTANCE_TYPE |
ec2_instance_type |
EC2 instance type (e.g. t3.large). Overridden by --ec2-instance-type. Required for --runtime ec2, no default. Launcher-only. |
LEERIE_EC2_KEY_NAME |
ec2_key_name |
EC2 key-pair name for SSH access. Overridden by --ec2-key-name. Required for --runtime ec2, no default. Launcher-only. |
LEERIE_EC2_SECURITY_GROUP |
ec2_security_group |
Security group id to attach. Overridden by --ec2-security-group. Required for --runtime ec2, no default. Launcher-only. |
LEERIE_EC2_SUBNET_ID |
ec2_subnet_id |
Subnet id to launch into. Overridden by --ec2-subnet-id. Required for --runtime ec2, no default. Launcher-only. |
LEERIE_SEED_TIMEOUT_S |
— | Timeout in seconds for seed_auth / seed_repo bulk transfers over flyctl ssh console. Unset → default 600 (10 min). Launcher-only. |
LEERIE_PROGRESS_INTERVAL_S |
— | Heartbeat cadence in seconds for "still streaming" lines during bulk transfers. Set to 0 to suppress. Unset → default 10. Launcher-only. |
LEERIE_MACHINE_START_TIMEOUT |
— | Timeout in seconds for Fly machine start. Unset → default 120. Launcher-only. |
LEERIE_PAUSE_NOTIFY_CMD |
— | Shell command to eval when a Fly machine pauses on failure. Unset → no notification. Launcher-only. |
LEERIE_NO_RUNTIME_INSTALL |
— | Skip auto-install of container runtime (truthy → skip). Also --no-runtime-install. Launcher-only. |
LEERIE_NO_AUTO_PUBLISH |
— | Skip image publish probe (truthy → skip). Also --no-auto-publish. Launcher-only. |
LEERIE_LOCAL_BUILD |
— | Force local image build instead of Fly remote builder (truthy → local). Also --local-build. Launcher-only. |
LEERIE_NONINTERACTIVE |
— | Suppress interactive prompts in runtime-install and auth flows (truthy → non-interactive). Launcher-only. |
FLY_VM_DISK_GB |
— | Provision a Fly volume of this many GB. Also --fly-disk-gb. Launcher-only. |
CLAUDE_AUTOCOMPACT_PCT_OVERRIDE |
— | Claude Code CLI variable, not consumed by leerie. Set to 70 to backstop worker auto-compaction. |
- Source-of-truth (highest first):
--source-of-truth→LEERIE_SOURCE_OF_TRUTH→leerie.toml→ defaultboth. - Model (per worker, highest first):
--model-<worker>→--model→LEERIE_MODEL_<WORKER>→LEERIE_MODEL→model_<worker>inleerie.toml→modelinleerie.toml→ per-worker default (implementer,conformer,satisfied_probe→sonnet; everything else →opus). The judgment-vs-acting split keeps the most-frequently-invoked workers on the lower-cost model while every judgment step gets Opus-grade reasoning. To restore the pre-0.3 all-sonnet behavior in one knob, setLEERIE_MODEL=sonnetor pass--model sonnet. - Confidence rounds (highest first):
--confidence-rounds→LEERIE_CONFIDENCE_ROUNDS→confidence_roundsinleerie.toml→ default8. - Verbosity (highest first):
--verbosity→-v/-vv/-q/-qqshortcuts (anchored tonormal, not to the resolved default) →LEERIE_VERBOSITY→verbosityinleerie.toml→ defaultstream.
See docs/IMPLEMENTATION.md §2 for the
rationale behind these orders and the full validation contract.
Leerie spawns eleven kinds of claude -p worker. Each is a separate
subprocess; there is no in-session agent nesting.
| Worker | Prompt source | Default model | Runs per task | Returns |
|---|---|---|---|---|
classifier |
prompts/classifier.md |
opus | 1 | category set + intent questions |
planner |
prompts/planner.md |
opus | one per category (parallel) | subtask list with deps |
reconciler |
prompts/reconciler.md |
opus | 0, 1, or up to 3 (retried up to twice when its first attempt closes a dependency cycle or leaves unresolved tags) | eight arrays — renames / added_provides / added_subtasks / conditional_drops / dropped_requires (resolution; conditional_drops drops a planner-emitted consumer subtask whose own intent declares it conditional on an unresolvable in_plan precondition; dropped_requires removes an over-specified requires entry — an aggregate or coarser synonym of what the consumer itself provides — and ALSO plays a cycle-breaking role on retry); dependency_edges / merged_subtasks (cycle-breaking-only, used on retry when leerie's gates detect a cycle); unresolvable (escape hatch). DESIGN §5 |
plan_overlap_judge |
prompts/plan_overlap_judge.md |
opus | 0 or 1 (phase 2¾, multi-planner runs only; auto-skipped on single-planner runs) | cross-domain surface overlap analysis. DESIGN §5 |
satisfied_probe |
prompts/satisfied_probe.md |
sonnet | 0 or 1 per subtask (phase 3, before scheduling; skipped when --skip-satisfied-check) |
{satisfied: bool, evidence: str} — soft-drops subtasks already met on the base tree. DESIGN §8 |
provision |
prompts/provision.md |
opus | 0 or 1 (spawned only when the deterministic lockfile-detection table abstains — Java/Gradle, bare pyproject.toml, polyglot Makefile) |
install recipe (argv-allowlisted) executed via mise exec --. See DESIGN §6½ |
implementer |
prompts/implementer.md |
sonnet | one per subtask (per wave, parallel) | commits on a leerie/subtasks/<run-id>/<subtask-id> branch |
conformer |
prompts/conformer.md |
sonnet | one per subtask, only on the implementer's success path | advisory conformance_warnings on the subtask result; doc/test/rule-fix commits prefixed conformer: on the same branch (DESIGN §9 Post-work conformance) |
integrator |
prompts/integrator.md |
opus | on conflict during wave integration | resolved merge commit on leerie/runs/<run-id> |
fit_judge |
prompts/fit_judge.md |
opus | 0 or more per subtask (P1 recursive decomposition — one per recursive_decompose() call) |
P1 Task-Context Fit score (0–1) with rationale and diffuse analysis. DESIGN §5½ (P1) |
splitter |
prompts/splitter.md |
opus | 0 or more per subtask (P1 recursive decomposition — coupled-minority path only; migration sweeps use deterministic partition_files()) |
child subtask list with ids, titles, and success criteria. DESIGN §5½ (P1) |
Additionally, two post-run workers run outside the main orchestrate loop and are not in WORKER_TYPES:
pr_writer(prompts/pr_writer.md, default sonnet) runs at finalize when the run will push — it produces the PR title and body. Overridable via--pr-writer-model/LEERIE_MODEL_PR_WRITER.dep_capture(prompts/dep_capture.md, default opus) runs at finalize (and on--recapture/ next-run backstop) — it reads worker logs, decides what the repo needs across all languages, and writessetup_packages/language_installsto.leerie/config.toml. Overridable viaLEERIE_MODEL_DEP_CAPTURE. See DESIGN §6½.
Per-worker model defaults: judgment workers (classifier, planner,
reconciler, plan_overlap_judge, provision, integrator, fit_judge, splitter)
default to Opus; the acting workers (implementer, conformer) and
satisfied_probe default to Sonnet — their job is concrete subtask
execution or lightweight per-subtask probing where throughput matters more
than broad-context judgment. To revert to the all-Sonnet pattern of earlier
versions, set LEERIE_MODEL=sonnet or pass --model sonnet. See
docs/IMPLEMENTATION.md §2 Model selection
for the full precedence table.
See docs/DESIGN.md §7 for the worker contract and
docs/IMPLEMENTATION.md §3 for the invocation
surface (flags, timeouts, schema enforcement).
For worked end-to-end examples — from invocation through clarification,
wave execution, run-branch review, and merge; and for chain orchestration
(submitting, monitoring, and cancelling a multi-run chain) — see
docs/USAGE.md.
Every Leerie document is reachable from this README. Architecture and code surface:
docs/DESIGN.md— architecture, constraints, phase flow, the evidence-gated loop, deterministic enforcementdocs/IMPLEMENTATION.md— code-surface reference (functions, caps, schemas, model / effort selection)docs/USAGE.md— worked end-to-end exampledocs/INSTALL.md— per-OS container runtime setup (Colima on macOS, containerd + nerdctl on Linux), Fly.io prerequisites
Policy and process:
CONTRIBUTING.md— development setup, task-completion checklist, PR conventions (and pointer toCLAUDE.md, the repo-local guidance for Claude Code)SECURITY.md— threat model and vulnerability reportingCODE_OF_CONDUCT.md— Contributor Covenant
Post-run analysis skills (invoked via Claude Code, not the orchestrator itself):
skills/judge-llm-batch/SKILL.md— score capturedclaude -pcalls against a 3-dimensional accuracy rubricskills/llm-self-heal/SKILL.md— autonomous prompt-patch loop for failing call types
Tests:
pip install pytest # only dev dependency
pytest tests/ # from the repo rootThe suite covers the deterministic enforcement functions, including a
coupling test that the retry-policy markers match the live check-function
strings. See docs/IMPLEMENTATION.md §10 for
the test layout. The worker invocation path is not unit-tested (a stub or
live claude binary would be needed; out of scope for the current suite).
| Path | What it is |
|---|---|
orchestrator/leerie.py |
The orchestrator — all phases, waves, caps, retries |
leerie |
Executable bash launcher — runtime preflight + nerdctl run (or Fly Machine provisioning when --runtime fly) |
Dockerfile |
Container image recipe — Debian 13 + Node + pnpm + claude CLI + baked orchestrator source. Built locally on first run, tagged leerie:<VERSION> |
fly.toml |
Fly.io Machine configuration — app name, region, vm sizing (4 cpu / 8 GB), min_machines_running=0 (no warm pool) |
prompts/classifier.md |
System prompt: classify task + surface intent questions |
prompts/planner.md |
System prompt: decompose one category into a subtask plan |
prompts/reconciler.md |
System prompt: reconcile cross-domain capability-tag drift between planner outputs |
prompts/provision.md |
System prompt: LLM-fallback install recipe synthesis when the deterministic lockfile table misses (DESIGN §6½) |
prompts/implementer.md |
System prompt: execute one subtask end to end |
prompts/conformer.md |
System prompt: post-work doc/test/lint conformance pass (advisory; DESIGN §9) |
prompts/integrator.md |
System prompt: resolve merge conflicts behaviorally |
prompts/judge.md |
System prompt: 3-dimensional accuracy rubric for the post-run judge skill |
prompts/patch_generator.md |
System prompt: minimal prompt-patch proposal for the post-run self-heal loop |
prompts/pr_writer.md |
System prompt: finalize-time PR title + body author (invoked by phase_finalize when the run will push) |
prompts/fit_judge.md |
System prompt: P1 Task-Context Fit scorer — judges whether a subtask's scope and context are co-minimized (DESIGN §5½ (P1)); calibrated to 0.70 threshold |
prompts/splitter.md |
System prompt: P1 structural splitter — labels pre-partitioned migration chunks or emits structural seams for the coupled-minority case (DESIGN §5½ (P1)) |
prompts/config_chat.md |
System prompt: interactive leerie config --chat session — reads the repo's CI config and manifests, generates .leerie/config.toml and optionally .leerie/Dockerfile |
prompts/_clarification_filter.md |
Shared include (codebase→research→ask filter) inlined by classifier.md and implementer.md via load_prompt's {{include: …}} expansion |
scripts/install.sh |
One-command curl | bash installer (preflight → runtime preflight → clone → symlink → verify) |
scripts/runtime-install.sh |
Per-OS auto-install of the container runtime (Colima on macOS; containerd + nerdctl on Debian / Fedora / Arch). Sourced by install.sh and the launcher |
scripts/container-entry.sh |
Container PID 1 entrypoint — cd /work then either exec sleep infinity (no argv: remote/Fly path, the launcher exec's the orchestrator separately via flyctl ssh console) or exec python3 /opt/leerie-image/orchestrator/leerie.py "$@" (with argv: local nerdctl path) |
scripts/setup-run.sh |
Create per-run branch + worktree (leerie/runs/<run-id>) |
scripts/new-worktree.sh |
Create per-subtask branch + worktree off the run branch |
scripts/integrate.sh |
Merge a subtask branch into the run branch |
scripts/finalize.sh |
Verify the run branch is non-empty and ready to push. The working branch is never modified locally. The push + gh pr create step lives in the host launcher (bash + jq) and runs after nerdctl run exits cleanly, using the host's own auth state — see docs/IMPLEMENTATION.md §7 Host-side finalize. Skipped when --no-push is set. |
scripts/host-finalize.sh |
Host-side push + PR creation block. Sourced by three call sites: the local-runtime post-run code path in the launcher, decide_teardown in scripts/remote/provision.sh (Fly clean-exit auto-finalize), and leerie --finalize <run-id> (recovery fast-path). Provides host_finalize <run-dir>; uses pr_title / pr_body from run.json when the pr_writer worker populated them, otherwise falls back to a deterministic body. |
scripts/cleanup.sh |
Remove worktrees for one run (default --run-id) or all runs (--all-runs). State dir always preserved as audit. --branches also deletes the matching leerie/runs/<id> run branch and leerie/subtasks/<id>/* subtask branches. --subtask-branches deletes only the subtask branches and keeps leerie/runs/<id> (the post-finalize default — the run branch is the PR head). |
scripts/remote/_log.sh |
Shared remote_log() helper — timestamped, repo-tagged stderr lines. Sourced by every other scripts/remote/*.sh file so all Fly-mode output is uniformly labeled. |
scripts/remote/build-push.sh |
Build and push a self-contained leerie image to Fly.io's registry (source baked in at /opt/leerie-image/). Default mode is Fly's remote builder (no host Docker daemon required); --local-build / LEERIE_LOCAL_BUILD=1 opts into the legacy nerdctl/docker path. See docs/IMPLEMENTATION.md §0.5 Registry publish path. |
scripts/remote/provision.sh |
Fly Machine lifecycle helper (sourced by the launcher's RUNTIME=fly branch). Provides provision_machine() (create → wait-started → register decide_teardown trap), stop_machine(), destroy_machine(), and decide_teardown() (classifies $LEERIE_REMOTE_EXIT_RC; on clean exit runs fetch_branch BEFORE destroy_machine so no work is lost; on sync failure leaves the machine RUNNING with sync_failed_at written for user-driven recovery; on pause-worthy non-zero rc stops the machine). See docs/IMPLEMENTATION.md §7 Machine lifecycle. |
scripts/remote/lib.sh |
Shared bash helpers sourced by provision.sh, resume-machine.sh, and re-seed.sh. Provides update_run_json() (atomic merge into the run sidecar) and wait_for_started() (poll Fly Machine status until ready). |
scripts/remote/resume-machine.sh |
Resume helper for paused remote runs. Reads fly_machine_id from the sidecar, runs flyctl machine start, waits for started, and clears paused_at/pause_reason. See docs/IMPLEMENTATION.md §7. |
scripts/remote/seed-auth.sh |
Worker auth + config seeding (sourced by the launcher after provision_machine() returns). Provides seed_auth(), which tar-pipes ~/.claude.json + ~/.claude/ (with .claude/local excluded — duplicates the Dockerfile-installed claude CLI) to /home/leerie/ via flyctl ssh console -C "tar -xC ...", writes git identity to /home/leerie/.gitconfig, and pre-warms claude --version once so the orchestrator's preflight call hits warm caches. See docs/IMPLEMENTATION.md §7 Worker auth + config seeding. |
scripts/remote/seed-repo.sh |
Single-channel git-aware repo seeding helper (sourced by the launcher after provision_machine() succeeds). Provides seed_repo(): wipe /work contents (preserving the inode), then tar-pipe a git-aware payload — git ls-files -z --cached --others --exclude-standard (honors .gitignore) + .git/ verbatim + the repo's local .claude/ verbatim (force-included) − .leerie/ (defensively excluded; run state lives outside the repo at $LEERIE_STATE_HOST_DIR, not in-repo) — to /work on the machine. No in-machine git clone; the host has the repo, and Fly machines deliberately receive no GitHub credentials. See docs/IMPLEMENTATION.md §7 Repo seeding. |
scripts/remote/re-seed.sh |
Mid-run re-rsync helper (Phase 4). Wakes a paused machine, runs a safety check, and re-runs seed_repo_dirty. Invoked by leerie --re-seed <run-id> and the auto-re-seed step on --resume --runtime fly. |
scripts/remote/fetch-branch.sh |
Post-run stream-back helper (sourced by decide_teardown BEFORE destroy_machine on clean exit, and by the leerie --finalize fast-path). Provides fetch_branch(): discovers the completed run-id on the machine, probes whether the run branch actually exists (skipping the bundle for cleared-but-empty terminal-state runs), streams the leerie/runs/<run-id> git bundle to the host via git fetch, tars .leerie/runs/<run-id>/ back, and strips the mechanism-flag no_push from the host-side run.json (the user's intent lives in fly-machine.json as host_no_push). See docs/IMPLEMENTATION.md §7 Run branch stream-back. |
scripts/remote/aws-credentials.sh |
Standalone AWS credential/profile/region resolution helper for the EC2 runtime. Provides resolve_aws_credentials(): resolves credentials and region host-side in the same precedence order the AWS CLI/SDKs use — explicit AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY env vars, then a named profile (--profile / AWS_PROFILE / default) via static credentials or cached SSO token, ending with an actionable error rather than a silent fallthrough (no IMDS instance-role fallback — this runs on the operator's host, not on an EC2 instance). Pure file I/O against ~/.aws/config, ~/.aws/credentials, and ~/.aws/sso/cache/*.json + bash/python3 stdlib — no aws binary or boto3 dependency, mirroring the existing detect_bedrock_mode()/bedrock_preflight() precedent in the launcher. Sourced by the launcher's RUNTIME=ec2 branch and by leerie --stop --runtime ec2, both of which call resolve_aws_credentials and export its resolved credentials/region before ec2-lib.sh's require_aws() preflight runs. |
scripts/remote/ec2-lib.sh |
Shared bash helpers for the EC2 lifecycle, parallel to scripts/remote/lib.sh's role for the Fly path. Provides require_aws(): the host-side preflight the launcher's RUNTIME=ec2 branch calls before provisioning (also called by leerie --stop --runtime ec2 before pausing), modeled on require_flyctl()'s two-stage shape (binary present? → authenticated?) — checks command -v aws (actionable install hint if missing, no auto-install) and probes aws sts get-caller-identity (with a resolved --profile), reusing bedrock_preflight()'s exact aws sso login --profile <profile> recovery-hint vocabulary on failure. Also provides resolve_ami()/resolve_instance_type()/resolve_key_name()/resolve_security_group()/resolve_subnet_id(), one required-var read per LEERIE_EC2_* var. See docs/IMPLEMENTATION.md's Files table. |
scripts/remote/ec2-seed-repo.sh |
EC2 counterpart to scripts/remote/seed-repo.sh (DESIGN.md §6 EC2 runtime lifecycle, "Seed" row). Same .gitignore-aware payload logic (bundle for committed state + porcelain-filtered dirty-delta rsync, .leerie/ excluded except the three whitelisted config files, shallow-vs-full-bundle threshold) as the Fly path — only the transport differs: ec2_tar_pipe (plain ssh) for bulk bundle/tar data, ec2_remote_exec (SSM Session Manager) for small instance-side commands. Provides ec2_seed_repo_clone(), ec2_seed_repo_dirty(), and the wrapper ec2_seed_repo(). Consumes a new LEERIE_EC2_SSH_TARGET env var (the resolved ssh-destination for the instance, populated by ec2-provision.sh). See docs/IMPLEMENTATION.md's Files table. |
scripts/remote/ec2-provision.sh |
The provision.sh counterpart for the EC2 lifecycle (DESIGN.md §6 EC2 runtime lifecycle). Exports provision_instance(), wait_for_instance_ready(), stop_instance(), terminate_instance(), decide_ec2_teardown(). Sourced by the launcher's RUNTIME=ec2 run-launch dispatch and by the leerie --stop --runtime ec2 (stop_instance()) and leerie --kill --runtime ec2 (terminate_instance(), with fetch-before-terminate ordering) paths. See docs/IMPLEMENTATION.md's Files table. |
scripts/remote/ec2-resume-instance.sh |
Resume helper for paused EC2 runs — the EC2 counterpart to scripts/remote/resume-machine.sh. Exports resume_instance(): starts a stopped instance (idempotent no-op if already running), waits on wait_for_instance_ready(), re-resolves LEERIE_EC2_SSH_TARGET from the instance's current public IP (EC2 assigns a new one on every stop/start cycle), re-arms the decide_ec2_teardown trap, and clears paused_at/pause_reason on the run sidecar. Never terminates the instance or deletes its volume on any path. See docs/IMPLEMENTATION.md's Files table. |
scripts/remote/ec2-fetch-branch.sh |
EC2 counterpart to scripts/remote/fetch-branch.sh, sourced by ec2-provision.sh's _try_fetch_state_for_ec2_teardown() hook before terminate_instance(). Exports fetch_state_ec2(): same four steps as fetch_branch() (run discovery, git-bundle stream-back, run-state tar stream-back, best-effort never-clobbering .leerie/config.toml/Dockerfile stream-back) — transport substituted to ec2_remote_exec (SSM, small commands) plus a private binary-safe ssh download helper (_ec2_fetch_ssh) for bulk data, since ec2_tar_pipe is upload-only. See docs/IMPLEMENTATION.md's Files table. |
scripts/remote/ec2-ssm.sh |
SSM Session Manager transport substitution for flyctl ssh console's launch/attach roles (DESIGN.md §6 EC2 runtime lifecycle; the stream-back role is ec2-fetch-branch.sh, already shipped). Wired into the launcher's RUNTIME=ec2 dispatch. Exports ec2_launch_detached() / ec2_attach(): both bootstrap a short python3 -/sh -s invocation via AWS-StartInteractiveCommand and pipe the real (potentially multi-KB) payload through the session's stdin, since SSM's --parameters document parameter is capped at ~4 KB. See docs/IMPLEMENTATION.md's Files table. |
commands/leerie.md |
Thin plugin skill — reachable as /leerie from Claude Code; relays the --clarify Q-and-A flow |
commands/chain.md |
Thin plugin skill — reachable as /chain from Claude Code; relays the multi-run chain verbs (submit/status/list/kill/stop/resume/finalize/attach) to leerie --chain and the ID-dispatched verbs (DESIGN §19) |
skills/judge-llm-batch/SKILL.md |
Post-run skill — scores captured claude -p calls against a 3-dimensional accuracy rubric (schema, factual grounding, hallucination-freeness) |
skills/llm-self-heal/SKILL.md |
Post-run skill — autonomous self-heal loop that proposes prompt patches against failing call types, replays under judge scoring, and reports the best-found patch |
chain/Dockerfile |
leerie-chain container image — Debian 13-slim + git + gh + flyctl + stdlib Python3. Leaner than the root Dockerfile: omits mise, claude-code, and build-essential (the chain app runs the HTTP API, not worker tasks). Entrypoint: python3 -m chain. |
chain/fly.toml |
Fly app config for the persistent leerie-chain HTTP service — min_machines_running=1, [http_service] on port 8080, [mounts] SQLite volume at /data. Provision once with fly launch --config chain/fly.toml. |
CLAUDE.md |
Repo-local guidance for Claude Code working in this codebase (the three-layer rule, mandatory requirements, code style) |
CONTRIBUTING.md |
Development setup, task-completion checklist, PR conventions |
SECURITY.md |
Threat model, supported versions, vulnerability reporting policy |
CODE_OF_CONDUCT.md |
Contributor Covenant Code of Conduct |
docs/DESIGN.md |
Full design document and rationale (theory) |
docs/IMPLEMENTATION.md |
Current code-surface spec — functions, caps, schemas (mechanism) |
docs/INSTALL.md |
Per-OS container runtime setup and the Fly.io runtime prerequisites |
docs/USAGE.md |
End-to-end walkthrough of one Leerie run + chain orchestration example |
Acting workers use --dangerously-skip-permissions. That is a real risk
surface — it is what makes the run unattended. It is bounded by two
isolation layers: (1) worktree isolation — each worker operates in its
own isolated git checkout, not your main working tree; (2) the container
the orchestrator runs in — PID-namespace + cgroups bound every worker
subprocess inside the per-run container (see
docs/DESIGN.md §6 and SECURITY.md).
These bound the blast radius; they do not eliminate it. Run on
repositories you trust and review the run branch (leerie/runs/<run-id>)
before relying on the result. Push + PR at finalize is the natural
review surface; you can also pass --no-push to keep finalize fully
local.
The run writes only to <state-root>/runs/<run-id>/ (where <state-root>
is the resolved state directory — default $HOME/.leerie/<basename>/,
overridable via LEERIE_STATE_DIR / --state-dir / leerie.toml state_dir;
never inside the repo itself) and to leerie/runs/<run-id> plus
leerie/subtasks/<run-id>/<subtask-id> branches. Phase 6 (unless
--no-push) pushes the run branch to origin and opens a PR against
your working branch — your working branch itself is never modified
locally. After a run, the run branch (leerie/runs/<run-id>) is kept
as an audit trail; per-subtask branches are auto-deleted at finalize,
but each worker's commits remain reachable from the run branch's
--no-ff merge graph (git log leerie/runs/<run-id> --graph). Remove
the run branch (and any leftover subtask branches) with
scripts/cleanup.sh --run-id <id> --branches (or --all-runs --branches
for an audit cleanup across every past run).
-
claude: command not found— Leerie shells out to the Claude Code CLI; install it from https://claude.ai/code and confirm withclaude --version. There is no fallback path. -
Exits with code 10 — not an error. Leerie needs clarification answers and you are running non-interactively. Read
<state-root>/pending-questions.json, write the answers to<state-root>/answers.json, then./leerie --resume --answers <state-root>/answers.json(where<state-root>is the resolved state directory — default$HOME/.leerie/<basename>/). The plugin skill atcommands/leerie.mdhandles this relay automatically when invoked as/leerie. -
Run interrupted (Ctrl-C, SIGTERM, SIGHUP, CI cancel, terminal close, reboot) — worktrees are torn down but state.json + branches are preserved. Resume with
./leerie --resume(auto-picks the most recent resumable run) or./leerie --resume <id>. Runleerie --listto see what's in flight. The explicit "throw this away" command isscripts/cleanup.sh --run-id <id> --branches— Ctrl-C alone is always safely resumable. -
Run hit the Claude Code subscription rate-limit — leerie detects the session-limit message from
claude -pand exits cleanly. Worktrees are torn down; state and branches are preserved. A rate-limit resets on a clock, so leerie auto-resumes: when the reset time can be parsed unambiguously it sleeps until the reset window; when it cannot (malformed time, unfamiliar timezone, or a future format change) it sleeps a fixed backoff (5 min) and re-resumes, polling until the limit clears. Ctrl-C during the wait drops to a manual--resume. Auto-resume passes only--resume <id>; CLI-only overrides (--model,--max-workers, etc.) on the original launch are not preserved across an auto-resume. Set those via env (LEERIE_*) orleerie.tomlif you want them to survive — both channels are re-resolved on every--resume. -
Run ran out of credits — distinct from a rate-limit: credits don't reset on a clock (they return on a top-up or billing cycle), so leerie does not auto-resume. It tears down worktrees, preserves state and branches, prints an
out of credits — leerie --resume <id>hint, and exits with code 75. Add credits, then re-run that--resumecommand. (An org with "extra usage" disabled at the org level is not out of credits — that's a benign standing state and never triggers this pause.) -
A subtask reports
blocked— the implementer hit something it cannot resolve and bailed before integration. Read the blocker reason in<state-root>/runs/<run-id>/state.jsonunderblocked[<subtask-id>], address the upstream cause, then resume. Seedocs/DESIGN.md§8 for the evidence-gated loop. -
Worktree or branch conflicts on a re-run —
scripts/cleanup.sh --run-id <id> --branchesremoves that run's worktrees and deletes its branches so a fresh run with the same task starts clean. For a global sweep across every past run, use--all-runs --branches. Then re-invoke as normal. -
Push or PR failed at finalize — the run completed locally. Check
leerie --listfor the run's status (push-failed/pr-failed) and read$LEERIE_STATE_HOST_DIR/runs/<run-id>/run.jsonfor the captured stderr. The error message at finalize names the exact retry command. Local commits are intact on the run branch.
Do I need an Anthropic API key?
No. Leerie runs entirely on the Claude Code CLI and your existing
subscription. The orchestrator shells out to claude -p workers; no API
key is read or sent.
Can I run multiple Leerie instances in the same repository?
Yes. Each invocation derives a unique run_id and namespaces all of its
state under $LEERIE_STATE_HOST_DIR/runs/<run-id>/ and its branches under
leerie/runs/<run-id> (run branch) and leerie/subtasks/<run-id>/<sid>
(subtask branches) — so parallel runs in the same clone never collide.
Use --list to see what's in flight and --resume <id> to
resume a specific one.
Does Leerie work outside a git repository?
No. Per-subtask isolation is provided by git worktree; the worktree
mechanism is load-bearing, not optional.
What if my project has no test runner?
That's fine — running tests is advisory only (DESIGN §9 Post-work
conformance). When _infer_build_lint_test() finds no test command,
the conformance phase reports the test axis as not-applicable and
surfaces no warning. The subtask's terminal status is determined by the
implementer's confidence gate (DESIGN §8), not by whether tests ran.
See docs/IMPLEMENTATION.md §5 for the
conformance phase's advisory contract.
Can I see what each worker did?
Yes. Every worker commits to its own leerie/subtasks/<run-id>/<subtask-id>
branch during the run; at finalize, those branches are auto-deleted, but
the integrator merges each one into the run branch with --no-ff, so
every worker's commits remain reachable from leerie/runs/<run-id> as a
named merge bubble. git log leerie/runs/<run-id> --graph is your
per-worker audit trail. When you no longer need the run branch either,
scripts/cleanup.sh --run-id <id> --branches removes it (and any
leftover subtask branches); --all-runs --branches removes all of them.
Why not use the Claude Code SDK or the in-session Agent tool?
Two platform constraints make subprocess workers the right shape. See
docs/DESIGN.md §2.
Contributions welcome. See CONTRIBUTING.md for
development setup, the task-completion checklist, and PR conventions.
Security issues: see SECURITY.md.
MIT — see LICENSE.
See GitHub Releases for the current release. The orchestrator's phase flow, wave scheduling, cross-domain dependency
resolution, and git worktree mechanics are all tested. First contact with a live
claude -p session is the remaining verification step. Limitations and planned
work are in docs/DESIGN.md.