Autonomous, plan-driven coding-agent orchestrator (Ruby).
autoeng takes a markdown plan and drives it to a PR-ready branch: it implements
each task with a coder agent, reviews every commit with a (possibly different)
reviewer agent, runs your test suite as a verification gate, replans when reality
diverges from the plan, and stops cleanly when it is done or a budget trips. It is
built to be left unattended — every external command runs under a hardened
ProcessRunner with hard/idle timeouts and process-group kills, so it does not
hang.
The full architecture and rationale live in
DESIGN.md, which is the source of truth. This README is the user-facing overview.
- Ruby >= 3.2
- A POSIX platform (macOS, Linux, or FreeBSD; Windows is out of scope)
- At least one agent CLI on your
PATH:
By default the coder runs on Claude and the reviewer on Codex (cross-engine
review is a stronger signal than self-review). If only one CLI is installed,
autoeng falls back to same-engine review and prints a warning.
From a checkout:
git clone https://github.com/stass/autoeng
cd autoeng
bundle install
bundle exec bin/autoeng --helpOr build and install the gem:
gem build autoeng.gemspec
gem install ./autoeng-*.gem
autoeng --helpWrite a plan (or generate one — see Interactive plan creation).
A plan is plain markdown; tasks are ### Task <id>: headers with a free-form
prose body describing the work:
# Add a rate limiter
## Context
The public API needs per-client request throttling.
## Tasks
### Task t1: Add a token-bucket limiter
Implement `RateLimiter` with a configurable bucket size and refill rate;
unit-test refill, burst, and exhaustion.
### Task t2: Wire the limiter into the Rack middleware
Apply per-client limits keyed on the API token, returning 429 with a
`Retry-After` header when exhausted.Then run it from inside your project's git repository:
autoeng docs/plans/add-rate-limiter.mdautoeng creates a branch (derived from the plan filename), works task by task,
and leaves a reviewed, verified branch ready for a pull request. State and logs
land under ./.autoeng/; interrupt with Ctrl-C at any time and --resume
later.
A run is a deterministic, single-threaded state machine over phases:
IMPLEMENT -> COMMIT_REVIEW -> VERIFY -> REPLAN? -> BRANCH_REVIEW -> FINALIZE -> DONE
- Implement — the coder is given the next pending task in a fresh session and commits its work. The claimed completion is cross-checked against plan and git world-state (an agent saying "done" never overrides what actually happened).
- Commit review — each new commit is reviewed in order against its scoped
diff. Findings are handed back to the coder as an immediate scoped fix commit,
reviewed once more, bounded by
review.max_rounds. Framework/bookkeeping commits (carrying the configured trailer) are excluded. - Verify — a verification command (auto-detected or configured) must pass for
a task to count as done; on failure the output is fed back to the coder, or it
triggers a replan/block per
verify.on_failure. - Replan — when an agent emits a
needs_replansignal, the plan is mutated through pure, provenance-recording operations (insert/split/append/block/ revise/mark-done — never delete), withinreplan.max_replans, and committed as a bookkeeping commit. - Branch review — a whole-branch backstop review (
base...HEAD) across the active lenses. - Finalize — off by default; no history rewriting unless enabled.
Agents communicate decisions back to the supervisor through a structured
signal protocol (task_complete, task_failed, needs_replan,
review_clean, review_findings, question, plan_draft, plan_ready)
embedded in their output; everything else is treated as narration.
autoeng [PLAN_FILE] [options] # full run (picker if PLAN_FILE omitted)
Run autoeng --help for the complete flag list. Durations accept 30m, 90s,
4h forms throughout.
| Flag | Effect |
|---|---|
| (none) | Full run: implement, review, verify, replan, branch review. |
--plan "DESC" |
Interactive plan creation from a description. |
--review |
Review existing branch changes only. |
--tasks-only |
Implement (with per-commit review); skip the branch-review backstop. |
--resume |
Continue the latest run under ./.autoeng/. |
Modes are filters over the same phase list, not separate code paths.
| Flag | Effect |
|---|---|
--coder ENGINE |
Engine for the coder role (claude | codex). |
--reviewer ENGINE |
Engine for the reviewer role. |
--planner ENGINE |
Engine for the planner role. |
--model ROLE=MODEL[:EFFORT] |
Per-role model override; repeatable (e.g. --model coder=opus --model reviewer=gpt-5.5:medium). |
| Flag | Effect |
|---|---|
--no-replan |
Disable dynamic replanning. |
--no-per-commit-review |
Review only at the branch level. |
--max-iterations N |
Global iteration ceiling. |
--worktree |
Run inside a dedicated git worktree. |
--branch NAME |
Branch name (default: derived from the plan filename). |
--base-ref REF |
Base ref to branch from / diff against (default: auto-detected default branch). |
--verify CMD |
Verification command; "" disables the gate, auto detects it. |
Finalize (history rewriting) is off by default and configured via the
finalize.* config keys, not a flag.
| Flag | Effect |
|---|---|
--max-cost-usd N |
Dollar spend ceiling (off by default). |
--max-commits N |
Halt if the branch grows beyond N commits. |
--max-wall-clock DUR |
Halt the run after this long. |
--wait DUR |
Wait out rate limits up to DUR, then retry. |
Per-call reliability.hard_timeout / idle_timeout / git_timeout and the
anti-spin budgets.max_no_progress guard are always on and configured in
config.yml (see below).
| Flag | Effect |
|---|---|
--verbose |
Stream full tool inputs/outputs and reasoning live. |
--debug |
Add raw protocol and ProcessRunner diagnostics. |
--no-color |
Disable ANSI color. |
--quiet |
Suppress terminal narration. |
--config-dir DIR |
Config directory to use ($AUTOENG_CONFIG_DIR). |
| Command | Effect |
|---|---|
config init [--repo] |
Write a commented example config (home dir, or ./.autoeng/ with --repo). |
config show |
Print the effective merged config with each key's source layer annotated. |
config dump-defaults DIR |
Extract the embedded config.yml, prompts, and review lenses into DIR for customization. |
autoeng works out of the box with zero config — the defaults ship embedded in
the gem. To customize, layer your own files on top. Precedence (lowest to
highest):
embedded share/config.yml -> ~/.config/autoeng/ -> ./.autoeng/ -> CLI flags
Merging is a plain deep merge, and because YAML distinguishes "key absent" from
"key present but falsy", overriding a default with false/0/[] works without
sentinels. autoeng config show tells you which layer each effective value came
from.
Highlights of the schema (see share/config.yml or config dump-defaults for
the fully-commented version):
engines— how to invoke each agent CLI (command, args, model, effort, sandbox). Only argv assembly and event normalization are engine-specific.roles— the role→engine matrix (planner/coder/reviewer/finalizer); defaults to coder=claude, reviewer=codex.role_models— optional per-role model/effort overrides.review—per_commit,lenses,max_rounds,stalemate_patience,dedup_findings.verify—command(auto/""/explicit),on_failure(feedback/replan/block),max_attempts.replan—enabled,max_replans.reliability—hard_timeout,idle_timeout,git_timeout,script_timeout,heartbeat,retry_on_timeout,wait.budgets—max_no_progress,max_wall_clock,max_commits,max_cost_usd(0/nulldisables a guard).logging—level(info/verbose/debug), always-ontranscript,dir,color(terminal color;--no-coloroverrides).git—command,default_branch(auto-detect when null),worktree,commit_trailerfor bookkeeping commits.finalize—enabled(off),rebase,squash.plan—max_rounds(max planner conversation turns before giving up).notify— a POSIX completion/failure hook (off by default).plans_dir,max_iterations.
Prompts (share/prompts/*.erb) and review lenses (share/agents/*.md) resolve
per-file with the same precedence: a matching file in the repo wins, then the
home config dir, then the embedded default. You never need any of these in your
repo — run config dump-defaults DIR to get editable copies of the ones you want
to override.
Everything a run produces lands under ./.autoeng/ (git-ignored automatically):
logs/<run-id>/*.jsonl— the lossless transcript: every normalized event, byte-for-byte, one JSON object per line.logs/<run-id>/progress.txt— the human-readable narrative (ANSI-free; color is terminal-only).logs/<run-id>/run.json— the machine-readable run report on completion: tasks completed, commits, findings by severity, replans and reasons, verification results, tokens/cost, and duration.logs/latest— symlink to the most recent run's logs.runs/<run-id>/state.json— persistedRunState, written after every transition, plus a flock lock file marking a live run.
Because the plan file and git history are the real source of truth, --resume
reconstructs exactly where an interrupted run stopped. A Ctrl-C triggers a
graceful shutdown — the engine process group is killed and logs flushed within a
bounded window — then halts the run resumably: a halted state is written
and the worktree kept, so --resume picks up from the last commit. (To discard a
run instead, use the pause hotkey's Abort.)
autoeng --plan "Add per-client rate limiting to the public API"The planner explores the repo and proposes a plan, asking clarifying questions
(answered via a picker). You can accept, revise (free-text feedback, or by
editing the draft in-terminal and sending the edit back as your revision), or
reject. On accept the plan is written to plans_dir and you can continue
straight into a full run. With --quiet (unattended), it proceeds on logged
assumptions instead of pausing for input.
These are non-negotiable and enforced repo-wide:
- Every external command (engines, git, scripts) goes through
ProcessRunner— no backticks,system,Open3.capture*, or bareProcess.spawnelsewhere. - No
Timeout.timeoutanywhere; deadlines come only from theProcessRunnerwatchdog plus process-group signals, on a monotonic clock. - POSIX-only, no unbounded blocking reads.
bundle exec rspec # full test suite (FakeEngine; never spends real credits)
bundle exec rubocop # lintPure cores (signal/plan parsing, config merge, replan ops, finding dedup) are
unit-tested directly; the supervisor, phases, and engine adapters are driven by a
scripted FakeEngine so no test ever invokes real claude/codex.
BSD.