Agent Looper Reference
July 2, 2026 ยท View on GitHub
codex-looper, claude-looper, and gemini-looper run repeated file-backed prompts against local agent CLIs. By default, they loop one prompt file forever with a fresh agent session each loop. Sequence mode, completion detection, task-plan gates, git backups, circuit breakers, and presets are available when a project needs them.
Runtime requirement: Python 3.10 or newer. On Python 3.11+, the standard library TOML parser is used. On Python 3.10, the looper uses its built-in parser for the supported agent-looper.toml schema documented below; do not rely on full TOML features outside simple tables, strings, booleans, finite numbers, and string arrays.
First Run
From the repository where the agent should work:
codex-looper
If neither agent-looper.toml nor a prompt file exists, this writes starter agent-looper.toml and PROMPT.md files and prints next steps. Existing files are left unchanged.
With only PROMPT.md present, codex-looper runs in single mode without requiring a config file. With only legacy prompts.md present, it infers sequence mode for backward compatibility.
For guided setup:
codex-looper init --interactive --force
Interactive setup writes PROMPT.md in single mode when you enter one prompt. If you enter multiple prompts, it writes prompts.md and sets mode = "sequence".
Prompt defaults are resolved exactly this way:
- An explicit
--modewins. - Otherwise
[looper].modewins. - Otherwise a custom
--prompt-fileusessinglemode. - Otherwise the legacy default file
prompts.mdimpliessequence. - Otherwise
PROMPT.mdwithsinglemode is used.
Prompt Modes
single mode is the default. The whole prompt file is sent as one prompt each loop:
Inspect this repository and implement the highest-value safe cleanup.
End every reply with EXIT_SIGNAL: false unless the work is complete.
sequence mode preserves the original behavior. It splits the prompt file on lines containing only ---:
[looper]
mode = "sequence"
prompt_file = "prompts.md"
Summarize the repository layout. Do not modify files.
---
Identify one safe cleanup. Do not modify files.
---
If the cleanup is obvious, implement it and run a fast check.
Prompts in one sequence share the same agent session. After a full sequence completes, the default behavior is to start a fresh session for the next loop.
By default, the supervisor reloads prompt_file before each loop. Editing PROMPT.md or prompts.md while a looper is running affects the next loop without restarting the supervisor. Set [looper].reload_prompt_each_loop = false only when a run must keep the exact startup prompt text.
Common Commands
codex-looper
claude-looper
codex-looper --once --label smoke
codex-looper --mode sequence --prompt-file prompts.md --once
claude-looper --complete-on 'EXIT_SIGNAL:\s*true' --plan-file fix_plan.md --backup
codex-looper --cb-no-progress 3 --cb-output-decline 2 --backup --backup-keep 20
codex-looper --preset rai
codex-looper --once --label gpt5-high -- --model gpt-5.4 --effort high
claude-looper --once --label smoke -- --dangerously-skip-permissions
codex-looper --farm-session work --label cleanup --cwd /path/to/project
codex-looper --local --once --label local-preview
codex-looper --dry-run --once --label preview
Use --once or --max-loops N for bounded runs. Without either, the looper repeats until a stop condition occurs. The run subcommand is optional: in an initialized directory, codex-looper and codex-looper run are equivalent. Non-dry-run commands launch through the default farm unless --local is set.
--timeout is a per-prompt wall-clock limit for the local agent process. The default is 7200 seconds. Short values such as 90 seconds can stop a long Claude/Codex tool call even when the provider is still working; raise it on the command line or in [looper].timeout_seconds for heavier prompts.
CLI Options
Values are resolved in this order: built-in defaults, project config, preset config, environment layout defaults where documented, and finally CLI flags. Agent-native argv after -- is appended to built-in Codex/Claude command templates for that invocation. For Claude's default hybrid interface, those argv tokens are appended to the visible interactive claude pane command.
Numeric constraints are strict. Integer counters must be whole numbers and finite; values documented as nonnegative accept 0; values documented as positive must be greater than 0. Float seconds must be finite and nonnegative unless the row says positive.
| Option | Default | Constraint | Purpose |
|---|---|---|---|
--agent NAME | [looper].default_agent or wrapper default | nonempty string | Selects an agent config from agent-looper.toml. |
--interface json|hybrid | agent config | enum | Override the selected agent interface for this run. hybrid is currently Claude-only. |
--config PATH | agent-looper.toml | path string | Project config file path. Missing config is allowed. |
--preset NAME_OR_PATH | unset | name or path string | Layer a preset TOML file over project config. Named presets resolve from ${XDG_CONFIG_HOME:-~/.config}/codexfarm/presets/ and repo examples/presets/. |
--mode single|sequence | single, with legacy inference | enum | Select prompt loading mode. |
--prompt-file PATH | see prompt precedence below | path string | Prompt file. |
--label LABEL | Looper_<short-id> | string | Human-readable run/session/log label. It does not rename tmux windows. |
--timeout SECONDS | 7200 | positive finite float | Per-prompt subprocess timeout. |
--sleep SECONDS | 2 | nonnegative finite float | Sleep between completed loops. |
--max-loops N | 0 | nonnegative integer | Maximum loops; 0 means unlimited. |
--max-transient-retries N | 12 | nonnegative integer | Cap non-rate-limit transient retries; 0 means unlimited. |
--retry-notify-after SECONDS | 300 | nonnegative finite float | Show a tmux notification for retry waits at or above this threshold; 0 disables. |
--complete-on REGEX | unset | valid regex string | Enable completion detection and stop after completed loops whose output matches the regex. |
--completion-streak N | 1 | positive integer | Require N consecutive completed loops with the completion marker before stopping. |
--plan-file PATH | unset | path string | Markdown checklist gate; completion requires no unchecked - [ ] tasks. |
--backup | off | boolean flag | Create a git backup branch before each non-dry-run loop. |
--backup-prefix PREFIX | looper-backup | nonempty string | Branch prefix for backup branches. |
--backup-keep N | 10 | nonnegative integer | Prune older backup branches, keeping the newest N. 0 disables pruning. |
--cb-no-progress N | 0 | nonnegative integer | Stop after N completed loops with no git workspace fingerprint change. |
--cb-output-decline N | 0 | nonnegative integer | Stop after N consecutive completed loops whose captured output byte count is lower than the prior loop. |
--cb-output-match REGEX | unset | valid regex string | Stop after completed loop output matches the regex for the configured repeat count. |
--cb-output-match-repeats N | 1 | positive integer | Matching loops required before --cb-output-match stops the run. |
--once | off | boolean flag | Equivalent to --max-loops 1. |
--fresh-session-per-loop | on | boolean flag | Start a new agent session for each completed loop. |
--reuse-session | off | boolean flag | Reuse one agent session across all loops. |
--cwd PATH | current directory | process cwd path | Working directory for agent commands. |
--dry-run | off | boolean flag | Print commands without running agents or creating backup branches. |
--ignore-nonzero | off | boolean flag | Continue after nonzero agent exits. |
--stop-on-nonzero | on | boolean flag | Stop after nonzero agent exits. |
--hold-on-stop | off | boolean flag | Wait for Enter before closing after a stop. |
--tmux-layout auto|single|split | CODEX_LOOPER_LAYOUT or auto; farm launch sets split | enum | Split creates a second tmux pane that tails the active prompt log while the main pane keeps supervisor status. |
--local | off | boolean flag | Run in the current terminal instead of launching through the default farm. |
--farm-session [NAME] | default farm | optional session string | Select the farm tmux session for codex-add. Omitting NAME uses the default farm session. |
--farm-attach | off | boolean flag | Attach after --farm-session launch. |
--farm-add-bin PATH | codex-add | executable name or path | Launcher compatible with codex-add. |
-- AGENT_ARGS... | unset | argv tokens | Pass native flags to built-in Codex/Claude command templates. |
Use -- for one-off agent-native flags. For Claude, the correct spelling is --dangerously-skip-permissions; local Claude help recommends it only for isolated sandboxes. To make it permanent for a project, put the same values in [agents.claude].extra_args.
claude-looper defaults to interface = "hybrid". The hybrid interface starts a visible Claude TTY pane, pastes prompts into that pane, reads Claude's session JSONL files for session identity and turn advancement, and keeps the supervisor pane focused on loop state. Use --interface json or [agents.claude].interface = "json" to force the older noninteractive claude -p --output-format stream-json path.
Config Defaults
Starter agent-looper.toml defaults:
[looper]
default_agent = "codex"
mode = "single"
prompt_file = "PROMPT.md"
timeout_seconds = 7200
sleep_seconds = 2
fresh_session_per_loop = true
reload_prompt_each_loop = true
max_loops = 0
max_transient_retries = 12
retry_notify_after_seconds = 300
log_dir = ".agent-looper/runs"
completion_enabled = false
completion_marker = "EXIT_SIGNAL:\\s*true"
completion_streak = 1
plan_file = ""
backup_enabled = false
backup_prefix = "looper-backup"
backup_keep = 10
cb_no_progress = 0
cb_output_decline = 0
cb_output_match_pattern = ""
cb_output_match_repeats = 1
Agent defaults:
| Agent | Command behavior |
|---|---|
codex | First prompt: codex exec --json <prompt>; later prompts resume by Codex thread ID when available, otherwise resume --last. |
claude | Default hybrid: start a visible claude TTY pane once, paste each prompt, and detect turn completion from pane readiness plus Claude session JSONL advancement. With interface = "json": first prompt uses claude -p --output-format stream-json --verbose --name <session> <prompt>; later prompts use --resume <session>. |
gemini | Generic default: gemini -p <prompt> for every prompt. Override this if your Gemini CLI supports a better noninteractive/resume mode. |
Built-in Codex and Claude agents accept model and effort config sugar. These fields are appended after extra_args as --model <value> and --effort <value>:
[agents.codex]
kind = "codex"
model = "gpt-5.4"
effort = "high"
extra_args = ["--sandbox", "workspace-write"]
[agents.claude]
kind = "claude"
interface = "hybrid"
model = "claude-opus-4-8"
effort = "max"
Use interactive_command when Claude hybrid mode should launch a wrapper or profile command instead of the built-in claude executable:
[agents.claude]
kind = "claude"
interface = "hybrid"
interactive_command = ["claude-wrapper", "--profile", "loop"]
Custom first_command, resume_command, and interactive_command values fully control their argv, so include model or effort flags directly in those values when using custom commands.
Strict TOML typing is part of the contract:
| Config key family | Required type |
|---|---|
looper.default_agent, mode, prompt_file, log_dir, completion_marker, plan_file, backup_prefix, cb_output_match_pattern | string |
looper.timeout_seconds, sleep_seconds, retry_notify_after_seconds | finite integer or float |
looper.max_loops, max_transient_retries, completion_streak, backup_keep, cb_no_progress, cb_output_decline, cb_output_match_repeats | integer |
looper.fresh_session_per_loop, reload_prompt_each_loop, completion_enabled, backup_enabled | boolean |
agents.<name>.kind, interface, model, effort | string |
agents.<name>.extra_args, interactive_command, first_command, resume_command, stop_patterns | array of strings |
agents.<name>.scan_stdout_for_stop_patterns | boolean |
Wrong scalar types, non-finite numbers, invalid regexes, and empty values for documented nonempty fields fail during configuration instead of being coerced silently.
Custom agents can use command templates:
[agents.my_agent]
kind = "generic"
first_command = ["my-agent", "run", "--session", "{session}", "{prompt}"]
resume_command = ["my-agent", "run", "--resume", "{session}", "{prompt}"]
scan_stdout_for_stop_patterns = true
Available placeholders: {prompt}, {session}, {session_id}, {loop}, {prompt_index}, {label}, {run_dir}.
Completion Contract
Completion detection is opt-in because agent CLIs do not expose a structured "done" channel. Enable it by config or CLI:
codex-looper --complete-on 'EXIT_SIGNAL:\s*true'
Recommended prompt convention:
End every reply with a status line. Emit EXIT_SIGNAL: true only when the work is genuinely complete. Otherwise emit EXIT_SIGNAL: false.
If plan_file is configured, a completion marker only counts when that markdown file has no unchecked - [ ] tasks. Markers seen while unchecked tasks remain reset the completion streak and the loop continues.
Git Safety
--backup creates a branch before each non-dry-run loop:
looper-backup/20260622T120000Z-loop-0001
Use --backup-prefix to name a separate backup family, and --backup-keep to prune older branches. --backup-keep 0 disables pruning.
Backup branches point to the current committed HEAD; they do not snapshot uncommitted or untracked worktree contents. Commit or stash important dirty work before relying on backup branches.
Pruning is scoped to the exact configured namespace. For example, a prefix of looper-backup may prune looper-backup/... branches but not looper-backup-old/....
--cb-no-progress N stops after N completed loops where the git workspace fingerprint is unchanged. The fingerprint includes committed HEAD, porcelain status entries, tracked index metadata, and file contents for dirty and untracked paths. The looper ignores its own run log directory when computing this fingerprint.
--cb-output-decline N stops after N consecutive completed loops where captured stdout/stderr bytes decline versus the prior completed loop. This is a lightweight signal for loops that are producing less useful work over time. It is off by default.
Output-decline is byte-count based. It does not judge semantic quality, and it can be fooled by verbose low-value output or concise high-value output.
--cb-output-match REGEX --cb-output-match-repeats N stops after N consecutive completed loops whose captured logs match the configured regex. Use it for project-specific status lines such as repeated blocked reports while keeping the looper engine independent of any one prompt format. A non-matching loop resets the streak.
Every completed loop prints a compact metrics line:
loop metrics: loop=3 duration=1.25s output=1.5KiB
Presets
Presets are ordinary TOML files layered over project config before CLI flags. A path works directly:
codex-looper --preset ./my-loop.toml
Named presets resolve from:
${XDG_CONFIG_HOME:-~/.config}/codexfarm/presets/<name>.tomlexamples/presets/<name>.tomlin this repo
The repo includes examples/presets/rai.toml:
codex-looper --preset rai
That preset uses Claude, single mode, PROMPT.md, EXIT_SIGNAL: true completion, fix_plan.md, and git backups.
Retry And Stop Conditions
The looper retries the current prompt when it sees provider rate-limit, backoff, quota, temporary-unavailability, or overload signals. If structured provider output includes a relative retry delay or reset timestamp, the looper waits for that delay; otherwise it falls back to the configured sleep_seconds delay. Informational rate-limit telemetry such as Claude rate_limit_event records with status = "allowed" or status = "allowed_warning" is ignored.
Rate-limit retries are uncapped so long-running loops can wait for quota reset and keep going. Non-rate-limit transient retries are capped by max_transient_retries; set it to 0 to allow unlimited transient retries. While waiting, the looper keeps the tmux window state as RUN and writes the retry attempt, retry kind, next wait duration, and reason into @codex_stop_reason for status tooling. Retry waits at or above retry_notify_after_seconds also emit a tmux display message; set it to 0 to disable long-wait notifications.
The looper stops when it sees:
- local per-prompt timeout
- common timeout, deadline, or request-abort wording
- nonzero command exit, unless
--ignore-nonzerois set - repeated non-rate-limit transient retry signals after
max_transient_retries - configured completion marker and satisfied plan gate
- configured no-progress circuit breaker
- configured output-decline circuit breaker
- configured output-match circuit breaker
- configured max loop count
Logs are written under .agent-looper/runs/<timestamp>__<label>__<random>/. Run directory names use UTC time with subsecond precision plus a random suffix to avoid collisions. The .agent-looper/current-log pointer is updated atomically so the split tmux tail pane either sees the previous complete pointer or the next complete pointer.
Each run directory also contains durable machine-readable status:
state.json: latest snapshot with schema version, pid, label, agent, cwd, current loop/prompt, current session, status, stop reason, exit code, and last log path.events.jsonl: append-only lifecycle history for run start, loop start/end, prompt start/end, retry waits, and final stop.control.jsonl: optional append-only operator inbox. Queuestop_after_loop,stop_after_prompt, orinterrupt_nowcommands withcodex-looper control stop ...; the supervisor records consumed commands inevents.jsonland stops at the requested safe boundary.
Use codex-status loopers from a project root to read .agent-looper/runs/*/state.json without attaching to tmux. Active states whose supervisor pid is gone are shown as stale. Use codex-status loopers --repair-stale-loopers to mark those stale states stopped, append a run_stopped event, and record an external-termination stop reason. Set CODEX_LOOPER_STATE_ROOT=/path/to/runs to point it at an aggregated or remote-synced run directory.
Examples:
codex-looper control stop LOOPER-rai --after-loop --reason "merge checkpoint"
codex-looper control stop LOOPER-rai --after-prompt --state-root /path/to/.agent-looper/runs
codex-looper control stop --run-dir .agent-looper/runs/20260628T000000Z__LOOPER-rai__abc123 --now
Farm Integration
Normal non-dry-run looper commands call codex-add so existing farm behavior still owns session creation, board linking, pipe-pane logging, and annotator startup. Use --local to run in the current terminal. Use --farm-session NAME for a separate farm.
Farm windows enable tmux remain-on-exit by default, so a stopped looper leaves its final pane visible for inspection instead of closing the window. Set CODEX_REMAIN_ON_EXIT=0 when launching if you want the old close-on-exit behavior.
Looper labels are kept for logs and agent session names only. They do not rename tmux windows; farm window names come from CODEX_NAME when set, otherwise the working directory basename.
Farm-launched loopers default to CODEX_LOOPER_LAYOUT=split: the main pane shows the looper supervisor and a second detached pane tails the current .agent-looper/runs/.../loop-*.log file. In split mode, the live agent transcript is shown in the tail pane rather than duplicated in the supervisor pane. Use --tmux-layout single or CODEX_LOOPER_LAYOUT=single to keep the older one-pane view. If tmux split-pane creation fails, the looper keeps running and falls back to supervisor-pane streaming so the transcript is still visible.
Example:
codex-looper
codex-looper --farm-session work --label cleanup --cwd /path/to/project
codex-looper --local --once --label local-smoke
While a looper is running in the farm, inspect recent pane output without attaching:
codex-status activity
codex-status loopers
Current Limits
- It is a loop runner, not a scheduler, daemon, queue, or web UI.
- It does not bypass authentication, permissions, sandboxing, or provider limits unless you explicitly pass agent-native flags that do so.
- Provider status and stop detection are heuristic because Codex/Claude/Gemini CLIs do not expose a shared structured status protocol.
- Claude hybrid mode requires tmux because the real Claude interface lives in a managed pane. Use
--interface jsonfor local non-tmux runs or scripted noninteractive behavior. - The Gemini backend is intentionally generic until a stable noninteractive resume interface is confirmed.
- There is no per-worktree run lock. Starting multiple write-enabled loopers against the same checkout can race or overwrite work.
- Backup branches protect committed
HEAD, not dirty worktree state. - Use write-enabled agent flags only in repositories, worktrees, containers, or runners where automated edits are acceptable.