Skip to main content
Glama
README.md
# Athena Loops — backend-agnostic AI agent orchestration loop for Python

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

**agentloop** is a lightweight Python framework for multi-agent orchestration. It
implements the **orchestrator → worker → reviewer pattern** (the AI agent
orchestration loop) as a deterministic harness with a closed feedback loop: a goal
is decomposed into subtasks, fanned out to worker subagents, aggregated, and run
through a review gate that loops until the work meets its success criteria. One
loop drives any LLM backend — Anthropic Claude, Claude Code, Codex, opencode, or
aider — through a single `Agent` interface, and it ships as both an **MCP server**
and a plain CLI so any coding agent can call it.

The design principle: **the loop is a harness (deterministic code), not a skill.**
A prompt can *describe* "decompose, review, loop until done" but can't *guarantee*
it. So the control flow lives in code, and the model-facing judgement (how to
decompose, the review rubric) lives in swappable prompts. One harness drives any
backend through a single `Agent` interface.

> ![Peter Steinberger: "You should be designing loops that prompt your agents."](docs/peterloops.png)
>
> The tweet that started it all — [Peter Steinberger (@steipete)](https://twitter.com/steipete):
> *"You shouldn't be prompting coding agents anymore. You should be designing **loops**
> that prompt your agents."* agentloop is that idea as a reusable harness.

```
            ┌──────────── harness (this package) ────────────┐
goal ─▶ decompose ─▶ fan-out to subagents ─▶ aggregate ─▶ review gate ─▶ done?
            ▲                                                          │ no
            └──────────────── feedback: refine plan ◀──────────────────┘
```

## Quick start

```bash
python3 -m examples.run_demo        # zero-dependency MockAgent
python3 -m pytest                   # full test suite, no deps
```

```python
from agentloop import Orchestrator, Budget
from agentloop.adapters import MockAgent

orch = Orchestrator(MockAgent(), budget=Budget(max_iterations=4))
result = orch.run(
    goal="Write a briefing on the orchestrator-worker pattern.",
    success_criteria="Covers decomposition, execution, review, and the feedback loop.",
)
print(result.completed, result.iterations, result.stop_reason)
print(result.final_output)
```

## Use a real model

Claude via Anthropic:

```bash
pip install -e ".[claude]"
export ANTHROPIC_API_KEY=sk-...
python3 -m examples.run_demo --claude
```

Grok via xAI's OpenAI-compatible API:

```bash
pip install -e ".[grok]"
export XAI_API_KEY=xai-...
python3 -m examples.run_demo --grok
```

`GrokAgent` defaults to `grok-build-0.1` for coding-oriented loop work; pass
`model="grok-4.3"` or `--model grok-4.3` for general chat-style work.

Grok Build CLI as the worker:

```bash
curl -fsSL https://x.ai/cli/install.sh | bash
grok login
agentloop run --goal "Add a /health endpoint + test" --criteria "test passes" \
  --backend grok_build --cwd /path/to/repo --skip-permissions
```

Use `backend="grok_build"` for the local Grok Build CLI (`grok -p`), or
`backend="grok_api"` for direct xAI API calls.

## Plug into any coding agent

The loop is pluggable in two directions, both thin wrappers over the `Agent` seam:

**Inward — coding agents *are* the workers.** `CliAgent` runs each role
(decomposer / subagent / reviewer) through a headless coding-agent CLI, so the
workers get that agent's tools, file access, and repo context:

```python
from agentloop import Orchestrator
from agentloop.adapters import CliAgent

# Point the worker at a repo and let it actually edit files headlessly:
agent = CliAgent.claude_code(cwd="/path/to/repo", skip_permissions=True)
orch = Orchestrator(agent)                     # or .codex() / .opencode() / .aider() / .grok_build()
result = orch.run(goal="Add a /health endpoint + test", success_criteria="test passes")
```

Knobs for autonomous coding workers:
- `cwd=...` — run the worker inside a specific repo (works for every preset).
- `skip_permissions=True` — let the worker use tools without prompting
  (`--dangerously-skip-permissions` / `--dangerously-bypass-approvals-and-sandbox` /
  `--yes-always`). Needed for headless coding, but it bypasses all safety prompts —
  point it at a worktree or throwaway branch, not your main checkout.
- `timeout=...` — seconds to cap **each** worker CLI call. **Default is `None`
  (no cap)**: a real coding worker is slow and unpredictable, so a short per-call
  timeout just kills it mid-task and throws the work away. Bound the *run* instead
  with `Budget(max_seconds=...)`, which is checked between iterations.
- `verify_commands=[...]` — run real commands after each worker iteration and feed
  failures back into the next loop. Use this for deterministic checks like
  `python3 -m pytest`, `npm test`, or `npx playwright test`. Any failing verifier
  blocks completion even if the reviewer would otherwise accept the work.

For big builds, keep subgoals small (the worker has to finish one in a single
call) and give the loop room with `max_iterations`; one cold worker can't build
everything in one shot.

Auth piggybacks on the CLI's own login, so Claude.ai, ChatGPT, or Grok Build
subscription/OAuth sessions work with no provider API key.

**Isolated runs.** The safe default for `skip_permissions` is to run inside a
throwaway git worktree on its own branch — your main checkout is never touched:

```python
from agentloop import Orchestrator, worktree
from agentloop.adapters import CliAgent

with worktree("/path/to/repo") as wt:               # new branch + checkout
    agent = CliAgent.claude_code(cwd=wt.path, skip_permissions=True)
    Orchestrator(agent).run(goal="...", success_criteria="...")
    print(wt.changed_files())   # what the run touched
    wt.commit("agentloop run")  # optional: persist on the branch
```

Cleanup mirrors the harness's own worktrees: `cleanup="auto"` (default) **keeps**
the worktree iff the run changed something (so you can inspect/merge the branch)
and removes it if it left nothing; `"always"` / `"never"` force the choice.

**Partial work is never lost.** When you orchestrate against a repo, the loop
**commits the worktree after every iteration** (`agentloop: iteration N`). So if
a later iteration fails or a budget guard stops the run, each completed
iteration's work is preserved as a checkpoint commit on the branch — recoverable,
not discarded. The result's `worktree.checkpoints` lists them. Workers are also
told to **inspect the working directory and continue** from prior work rather
than restart, so a retry builds on what's already there instead of clobbering it.

The example runner isolates automatically when given a repo:

```bash
python3 -m examples.run_with_cli_agent claude /path/to/repo
```

```bash
python3 -m examples.run_with_cli_agent claude   # codex | opencode | aider | grok
```

Custom CLI? It's just a command template (`{prompt}`, `{system}`, `{combined}`;
no prompt placeholder ⇒ text is piped on stdin):

```python
CliAgent(["my-agent", "--system", "{system}", "--ask", "{prompt}"])
```

Presets are starting points — CLI flags vary by version; confirm yours and tweak
`agentloop/adapters/cli.py`. A non-zero exit or timeout becomes a FAILED task
(with retries), not a silent wrong answer.

**Outward — a coding agent *calls* the loop.** An MCP server exposes the loop as a
tool, so any MCP-aware agent (Claude Code, Cursor, Codex, opencode, Cline, Windsurf)
can invoke it. By default `backend="auto"` uses the same agent family as the
caller when detectable; pass `backend` explicitly to override it.

```bash
pip install -e ".[mcp]"                # installs the `mcp` SDK
python3 -m agentloop.mcp_server        # stdio transport
```

Tools:
- `orchestrate(goal, success_criteria, backend, cwd, max_iterations, skip_permissions, isolate, model, verify_commands, verify_timeout, playwright, detach=true)`
  — runs the loop. **Detached by default:** returns `{ status: "running", run_id, … }`
  immediately and runs to completion in the background — you monitor it (see below).
  Pass `detach=false` to instead block and get the result (or `{ status:
  "needs_input", questions[], token }`) back in a single call.
- `orchestrate_resume(token, answers, detach)` — continues a run that asked for input
- `orchestrate_status(run_id)` — light status of a detached run (phase, iteration, running)
- `orchestrate_tail(run_id, cursor, limit)` — the events a detached run produced since `cursor`
- `orchestrate_result(run_id, wait, timeout)` — the final result of a detached run
- `orchestrate_list()` — every detached run this server has started, with status
- `list_backends()` — the worker engines this server can drive
- `doctor(cwd?)` — non-invasive diagnostics for backend CLI availability, target
  directory access, and timeout interpretation

A completed result is structured:
`{ completed, iterations, stop_reason, final_output, summary, history[], worktree? }`
— `summary` is a one-line human-readable digest for the calling agent to show.
While it runs, `orchestrate` streams a `notifications/progress` update when it
starts and after every iteration (e.g. `iteration 2/4: 3/3 subgoals ok, gates
pass, goal incomplete`), so the caller sees live status instead of a bare
spinner.
When `cwd` is given it runs in an isolated worktree (see above) by default.

**Fire and monitor — the default.** `orchestrate` runs detached: it returns a
`run_id` immediately and the loop runs to completion (or error) in the background.
There is **no run timeout** — you watch it rather than race it against a clock:

```
orchestrate(goal=…, cwd="/repo")   # backend="auto", detach=true by default
  -> { status: "running", run_id, run_dir, events_path, tail_command }
orchestrate_status(run_id)        -> { phase, iteration, running, … }   # poll until running=false
orchestrate_tail(run_id, cursor)  -> { events[], cursor, running, more } # or stream each step
orchestrate_result(run_id)        -> the final result, once running=false
```

Poll `orchestrate_status` (it returns instantly) until `running` is false, then
read `orchestrate_result`. Avoid `orchestrate_result(wait=true)` with no timeout —
that re-opens a request for the whole run, the very thing detached mode avoids.
Intake runs in the background too, so even a slow or clarifying intake never
blocks the start call; if it pauses, the result's `stop_reason` is `needs_input`
(with `questions` + `token`) — answer it with `orchestrate_resume`.

The calling agent starts the run, then **interleaves polling `orchestrate_tail`
with talking to you** — so you both see the loop work in real time: each subgoal
as it's planned, every worker's start/finish and an output preview, the
verification results, and the reviewer's verdict. Pass the returned `cursor` back
to `orchestrate_tail` to get only what's new.

Every event is also appended to a durable JSONL log you can **`tail -f` from any
terminal**, independent of the agent:

```
<cwd>/.agentloop/runs/<run_id>/
  events.jsonl     # one event per line — tail -f this
  status.json      # latest phase / iteration / running
  result.json      # the final result (written once, at the end)
  workers/         # full stdout of each worker call: iter<N>_<subgoal>.out
```

Worker output previews ride inline in the event stream; each worker's *full*
output is written to `workers/iter<N>_<subgoal>.out` and referenced by
`data.output_path`. Because the default flow returns at once, it sidesteps the
MCP request-timeout problem entirely — there's no long-blocking call to time out.

Timeouts, briefly. The default detached flow has **no run timeout** and never
holds an MCP request open, so the host's `-32001 Request timed out` cannot occur
on it. That error applies only to the opt-in blocking mode (`detach=false`), where
one request spans the whole run; if you use that mode, configure the host to wait
generously (≥ `600000` ms). `orchestrate(timeout=…)` (per worker subprocess) and
`max_seconds` (cooperative loop budget) remain *optional* hard kills — both
default off; leave them unset for real coding runs and monitor to completion
instead. Have agents call `doctor()` before guessing about missing CLIs or broken
backend spawning.

**Plug into Claude Code.** Point `PYTHONPATH` at the repo so the server resolves
the package no matter where Claude Code launches it (no install needed):

```bash
claude mcp add athena-loops --scope user \
  -e PYTHONPATH=/abs/path/to/athena-loops \
  -- python3 -m agentloop.mcp_server

claude mcp list                        # -> athena-loops: ✔ Connected
```

Claude Code's CLI registration does not expose a per-server request-timeout flag;
use `doctor()` to distinguish host timeout symptoms from backend availability.

`--scope user` makes it available in every project; use `local` for just this
one, or `project` to write a shared `.mcp.json`. If you `pip install -e .`
instead, the `agentloop-mcp` console script is on PATH and the `-e PYTHONPATH=…`
is unnecessary: `claude mcp add athena-loops -- agentloop-mcp`.

**Plug into Cursor / Cline / Windsurf** (`.mcp.json` / `mcp.json`):

```json
{
  "mcpServers": {
    "athena-loops": {
      "command": "python3",
      "args": ["-m", "agentloop.mcp_server"],
      "env": { "PYTHONPATH": "/abs/path/to/athena-loops" },
      "timeout": 600000
    }
  }
}
```

Then (restart the session first) ask the host agent to "use agentloop to
orchestrate: <goal>". The default `backend="auto"` picks the matching worker for
the caller (`codex` from Codex, `opencode` from OpenCode, `claude_code` from
Claude Code when detectable). Choose a concrete `backend` (`claude_code`,
`codex`, `mock`, …) to override that.

For agents that *don't* speak MCP but can run a shell, there's a plain CLI over
the same contract:

```bash
agentloop run --goal "Add a /health endpoint + test" --criteria "test passes" \
  --cwd . --skip-permissions --json \
  --verify "python3 -m pytest" --verify "npx playwright test"
agentloop backends
```

`--json` prints the full result on stdout; `--progress` streams one NDJSON line
per iteration on stderr; `--goal -` / `--goal-file` read long prompts. `--verify`
is repeatable and runs each command after every iteration in the same repo/worktree
as the worker. Exit code is `0` if completed, `1` if a budget guard stopped it,
`2` on error — so scripts and agents can branch on the outcome.

For web/UI work, `--playwright` (MCP: `playwright=true`) is a one-switch shortcut
that turns on browser-level testing across the whole loop: it adds `npx playwright
test` as a verify gate *and* instructs the subagent to write/extend Playwright
tests and the reviewer to require passing ones before completing. Leave
`--verify-timeout` unset — browser launch is slow.

## How the user gives input (intake & clarification)

Before any planning, the loop runs an **intake** phase: it can propose success
criteria (if you didn't give any) and ask the clarifying questions it needs —
the diagram's "App Follow-up Questions". *Where* the human answers is a second
swappable seam, `Interaction`, mirroring the `Agent` seam:

| Surface | Interaction | UX |
|---|---|---|
| Python / headless (default) | `AutoInteraction` | never blocks; proceeds with best judgment |
| Interactive terminal | `ConsoleInteraction` | prompts the human via `input()` |
| MCP / scripted CLI | `SuspendInteraction` | returns `needs_input` + a resume token instead of blocking |

**Terminal wizard** — omit `--goal` and it asks; criteria and clarifying
questions are prompted inline:

```bash
agentloop run                         # Goal> … then proposes criteria + asks questions
agentloop run --goal "…" --non-interactive   # never prompt; use defaults
```

**Inside another agent (MCP)** — `orchestrate(...)` returns
`{ status: "needs_input", questions: [...], token }` when it needs answers; the
host agent collects them from the user and calls
`orchestrate_resume(token, answers)` to continue. Same flow on the CLI for tools:

```bash
agentloop run --goal "build an API" --ask        # prints questions + token, exits 3
agentloop run --resume <token> --answer "FastAPI" --answer "Postgres"
```

**Python** — pass your own:

```python
from agentloop import Orchestrator, ConsoleInteraction
Orchestrator(agent, interaction=ConsoleInteraction()).run(goal="…")  # criteria optional
```

## The seam (where to put what)

| Layer | Lives in | What it owns |
|-------|----------|--------------|
| **Harness** | `orchestrator.py`, `scheduler.py`, `types.py` | the loop, fan-out, aggregation, review gate, termination guards, failure capture |
| **Agent seam** | `agent.py` + `adapters/` | one `Agent.run(request) -> response` per backend (Mock, Claude, …) |
| **Skills** | `roles.py` | the prompts *inside* each box: decomposer, subagent, reviewer rubric |

To support a new backend, implement one method:

```python
from agentloop.agent import Agent, AgentRequest, AgentResponse

class MyAgent(Agent):
    def run(self, request: AgentRequest) -> AgentResponse:
        text = call_your_model(system=request.system, prompt=request.prompt)
        return AgentResponse(text=text)
```

The three roles (Orchestrator, Subagent, Reviewer) are the *same* `Agent`
invoked with different system prompts — not separate classes.

## Two gaps in the original diagram, handled here

- **Termination guards** — `Budget` caps iterations, wall-clock time, and total
  agent calls so the NO-branch can't spin forever.
- **Subagent failure handling** — a subagent that raises becomes a `FAILED`
  `TaskResult` (with retries), visible to the reviewer and the feedback step,
  instead of crashing the run or being silently dropped.

## Layout

```
agentloop/
  orchestrator.py   # the loop (deterministic harness) — emits live events
  scheduler.py      # parallel/sequential subagent execution + retries
  roles.py          # role prompts — the tunable "skills"
  agent.py          # Agent interface + robust JSON extraction
  types.py          # Budget, Subgoal, TaskResult, ReviewResult, LoopState, LoopEvent, ...
  interaction.py    # Interaction seam: Auto / Console / Suspend (intake & clarify)
  verifier.py       # CommandVerifier: real verify-command gate after each iteration
  runs.py           # detached background runs + the tail-able event log
  mcp_server.py     # MCP tools: orchestrate(detach), orchestrate_tail/status/result
  cli.py            # `agentloop run` / `agentloop backends` entry point
  adapters/
    mock.py         # deterministic, dependency-free (demo + tests)
    claude.py       # Anthropic SDK backend
    cli.py          # CliAgent: drive any headless coding-agent CLI as a backend
examples/run_demo.py, examples/run_with_cli_agent.py
tests/  # full suite (orchestrator, scheduler, CLI adapter, MCP server, ...)
```

## FAQ

**What is agentloop?** A backend-agnostic Python framework that implements the AI
agent orchestration loop — the orchestrator–worker–reviewer pattern with a closed
feedback loop — as deterministic harness code rather than a prompt.

**What is the orchestrator–worker–reviewer pattern?** An LLM agent decomposes a
goal into subtasks (orchestrator), parallel worker subagents execute them, and a
reviewer gates the aggregated result against success criteria, looping with
refined plans until done or a budget guard stops it.

**Which LLM backends does agentloop support?** Any model behind a single
`Agent.run()` method. Built-in adapters cover a dependency-free MockAgent,
Anthropic Claude, xAI Grok, and headless coding-agent CLIs — Claude Code, Codex,
opencode, aider, and Grok Build — via `CliAgent`.

**How do I orchestrate multiple coding agents from Claude Code, Cursor, or Cline?**
Run agentloop as an MCP server (`python3 -m agentloop.mcp_server`) and call its
`orchestrate` tool, or use the plain `agentloop run` CLI from any agent that has a
shell.

**Does agentloop need an API key?** No — when you drive it through a coding-agent
CLI it piggybacks on that CLI's own login, so subscription/OAuth sessions can work
without provider API keys. Direct API backends use their provider keys.

**How does agentloop avoid infinite agent loops?** A `Budget` caps iterations,
wall-clock time, and total agent calls, and a failing subagent becomes a `FAILED`
task result (with retries) instead of crashing or silently vanishing.

**Is this like Peter Steinberger Loops / the agent loop technique?** Yes — it's the
same family of idea popularized by Peter Steinberger's writing on running coding
agents in a loop ("Peter Steinberger Loops"). agentloop turns that pattern into a
reusable, backend-agnostic harness with an explicit review gate and budget guards,
rather than a one-off shell script.

## License

Athena Loops is released under the [MIT License](LICENSE).

Maintenance

ActivityStale
ResponsivenessWithin a week