A loop harness for Claude, Codex, Copilot, or any CLI agent. A loop runner hands each agent a prompt. Agents can edit. Gates decide what lands. You set the plan. Each agent iteration must update specs and commit through quality guardrails.
gh repo create <your-github-username>/<your-new-app-name> --template rxdt/loopgate_harness --private --clone && cd <your-new-app-name> && uv run harness install && source .venv/bin/activate && git add . && git commit --amend --no-editharness gateand to loopharness codex
Requirements: pip, uv, or poetry; Python 3.10 or newer; Linux or macOS. Windows support is experimental.
- Features
- Default Tools
- Details
- Start a project
- A LβPgate Loop
- Commands
- Directory Layout
- Expanding your harness
- FAQ
- Coordination
- Before infinity loops
Each run starts fresh, has clear limits, saves its logs, protects key files, and must pass checks you choose.
- Quality-first: Fight the AI slop with standards and style π―
- Gate-enforced work: Agent changes land only if they pass the quality gates you set β
- LβP now. What boilerplate? Start loopiing with quality checks from day one π
- Built-in stack: lint, format checks, type-checks, dependency audit, property tests, mutation tests, 100% coverage, complexity analysis, and Semgrep βββ, git hooks, are the BASICS
- Hooks ready to go: pre-commit, pre-push, and commit-message hooks are already hooked up β]
- Worker-agnostic: Claude, Codex, Copilot, Agy, or any prompt-reading CLI
- Repo-as-memory workflow: specs/status/prompt are durable but code is king, leaving you free π
- No-rot: Fresh-context agent iterations to reduce context rot π§
- Simple: One-command setup gets you going π
- Installable project template:
harness installgets a repo ready! π - Existing-repo setup:
harness initadds LoopGate without requiring the template - Progressive: Preflight vs full gate split α―β€
- Forbidden-file containment: Don't touch that!-configurable. Set forbidden files for agents β
- No-waste: Timeouts and time-limits for all looping agents β°
- Diff size guardrails: Agent's staged Lines Of Code at error 500 and prompt slop refactor π€
- No empty work: Agents blocked from empty commits β¬
- Agent containment prioritized: Stop the madness
- Industrial Security Enabled with Semgrep π
- Interactive-agent containment too!: Run
harness configure-agentsso all Claude/Codex sessions are beholden to the repo checks π₯
Edit at will
- ruff lints and formats Python code, fast
- pylint catches code errors and style problems
- pydoclint checks docstrings match function signatures
- pyright enforces types before code ever runs
- pytest runs the project's test suite (runs across multiple CPUs)
- hypothesis generates test inputs to expose edge cases. Tests the code. Real Example
- mutmut mutates your code to find weak tests. Tests the tests. Easy to use script at check_mutmut.py.
- complexipy flags functions that are too complex
- semgrep scans code for security flaws
- pip-audit scans Python environments for package vulnerabilities. Switch out with
["uv", "audit"]for faster, less-mature uv audit here: audit - preferences.py A custom AST-parser to optionally expand. It catches e.g. a style preference that tools don't.
- Forbidden paths set in [tool.harness]
- Update
[tool.harness.gate]or[tool.harness.gate]in pyproject to change what is checked before a commit or push. - Failing checks block by default. If a fresh drop-in fails across the board, set
behavior = "warn"to report without blocking, then flip back to"fail".
Note
A pre-commit or gate phase self-heals by un-staging forbidden files.
β‘ harness preflight (pre-commit)
are the fast checks to run often. Lint + check format for everyone, plus containment for the agents.
β
harness gate (pre-push) = (pre-commit) checks +
adds type-checks, security audit, dependency audit, AST-scan, complexity analysis, full test coverage, prompt to run mutmut
Only humans can bypass triggered gates and commit, always. Only humans can use flag --no-verify.
Note that semgrep --config auto needs network for semgrep registry rules.
pip-audit and uv audit also need a network connection to scan the repo.
plan.md is where you define what you want the end product to be. You must be VERY clear on what the finished product should and should not contain. Do not let agents guess. Tell agents exactly what the finished "product" should be.
PROMPT tells each agent to pick a spec.md and build. specs/ say what to build. The agent decides what next. You keep plan.md current, and specs get rewritten from it. The agent is told in docs/PROMPT.md to update the specs. Each iteration the agent updates its spec and PROJECT_STATUS.
write something into the plan
-
gh repo create <your-github-username>/<your-new-app-name> --template rxdt/loopgate_harness --private --cloneor 'Use This Template' -
Source your environment (if applicable)
-
From the root, install dependencies and run
harness installto remove template-only files and set up the git hooks. For example:uv sync && source .venv/bin/activate && harness install. -
git commit(theinstallcommand updates the repo) -
Write your grand vision in docs/plan.md
-
Optionally add the first spec in
docs/specs/(or leave it to the agents to draft the first specs based on yourplan.md) -
Product code goes in
src/. List your source code directories inpyproject.toml [tool.coverage.run] line 234 -
Run some loops!
harness run <agent=claude|codex|agy|copilot> [iterations] [minutes] -
Not what you wanted? Refine
docs/plan.md/docs/PROMPT.mdand re-run -
Configurations for Ruff linting, type-checking Pyright, Complexipy, Pytest coverage, etcetera are set in
pyproject.toml. -
Your coding quirks go in
preferences/preferences.py. Delete functions that don't serve you. Add your own.
Important
Default configuration is in pyproject.toml. Update tool settings, add agent commands, change checks, or leave it as is.
uv sync
source .venv/bin/activate
harness install
git add . && git commit
harness gate
harness run <agent>
poetry install
poetry run harness install
git add . && git commit
poetry run harness gate
poetry run harness run <agent>
python -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txt -e .
harness install
git add . && git commit
harness gate
harness run <agent>Tip
If you don't like ANYTHING in this framework, update it. Or even better, contribute.
The repo is the only memory. Each iteration is a fresh-context agent, driven by our loop runner, Ralph.
docs/specs/say WHAT to build- constant
docs/PROMPT.mdtells the agent: readdocs/specs/, reviewsrc/, build the most important unfinished thing - agent builds
- agent commits
- every git commit passes the fast preflight (lint, format, plus loop containment for the agent)
- every git push runs the full gate: lint, types, semgrep, tests, 100% coverage
- the loop stops at
max_iterations, a nonzero worker exit, or a timeout - Unspecified iterations/minutes β default to 2 iterations Γ 20 minutes each
- Each run streams agent 'thought' output live and is saved in a local scratchpad log
- The harness is worker-agnostic. Any agent CLI that reads a prompt from stdin and can edit/commit works.
Tool commands are defined in [tool.harness] in pyproject.toml.
harness install # install dependencies, remove template-only files, and set up git hooks
harness init # add LoopGate to an existing repository (configurations copied from your config files)
harness configure-agents # configures Claude and Codex with containment rules and environment variables
harness preflight # fast checks: preferences, ruff lint + format (plus loop containment)
harness gate # full pass: preferences, ruff, format, pyright, pylint, complexipy, semgrep, pip-audit, pytest @ 100% cov, hypothesis
harness info # show configured agents, checks, and protected paths
harness status # shows run log link, the newest json / latest run of N loops, 1 iteration
RALPH_LOOP=1 harness gate # explicitly run as if you are the agent in the loop
harness run <agent> [max_iterations] [max_minutes] [verbose] # claude/codex/agy/copilot, defaults: 2 20 True
# AGENT CALLS, examples
harness run claude 10 20
harness run codex 2 20
harness run agy 3 10
harness run copilot 2 20
> which harness # the actual executable
~/your-repo/.venv/bin/harness
> uv run harness # will find harness if executable existsRun uv run mutmut run && uv run mutmut export-cicd-stats, then use check_mutmut.py to write mutation-score.json for the Shields badge.
harness run launches an autonomous LLM worker with the configured permissions, e.g.
--permission-mode acceptEdits or --sandbox danger-full-access.
harness configure-agents will set rules for Codex and Claude. It sets RALPH_LOOP=1 in ~/.claude/settings.json and ~/.codex/config.toml (and saves backup files), plus rules that stop agents from changing those settings. So interactive IDE AND terminal sessions face the same gates as loop workers. You can read the exact rules at config.py:23 (Codex) and config.py:174 (Claude).
The gate bounds what any commit may touch, but the worker itself is not sandboxed to this repo unless you set that config. Consider the balance: without access it cannot do much. With machine access it can wreak havoc. Under a permissive mode it can run arbitrary shell. You are authorizing real changes. Choose the worker and permission mode deliberately.
harness/ the gate, loop runner, CLI (π€ forbidden directory)
tests/ the harness's own tests
gate.py run the full local gate + honor preferences.py
cli.py command-line entry point
js-scaffold javascript example to build upon
preferences/ user-defined preferences not covered by tools (π€ forbidden directory)
mutation/ get your mutation score and learn how to run mutmut (π€ forbidden directory)
check_mutant.py
tests/
preferences/ (π€ tests/preferences is forbidden directory)
.githooks/ pre-commit / pre-push gate hooks (π€ forbidden directory)
pyproject.toml project + tooling config (π€ forbidden file)
docs/ PROMPT, specs/, your plan (agent and human maintained)
scratchpad/ scratch dir agents can use for temp files (For the π€ to play)
src/ your product/source code (add to coverage source)
Every run is saved as a log file in scratchpad/runs/. harness status shows how many logs you have and the path to the newest one. Open that file to read what the agent thought and did. (Metrics and audited logs coming soon.)
pyproject.toml is the single source of harness configuration. Humans own it and preferences/; both are agent-protected.
If an agent edits a forbidden file, the file will be unstaged (not allowed to commit). A forbidden pattern by an agent (e.g. # noqa or nosemgrep will also prevent their commit and force them to fix it.)
- Edit rules at pyproject.toml for ruff, pylint, pydoclint, pyright, pytest, hypothesis, complexipy, mutmut
- Add forbidden files, directories, or patterns in
[tool.harness]at pyproject.toml - Add Hypothesis tests in any test directory, examples at test_properties.py.
- Run mutmut by hand with
uv run mutmut run, thenuv run mutmut browse. A surviving mutant is source code in need of a better test. Easy to use script at check_mutmut.py. - semgrep has no repo config here. It uses registry configs / Semgrep's built-in defaults which ignore tests. Feel free to add a configuration file for it or any tool.
- Update
[tool.harness.gate.checks]in pyproject.toml to change the full gate. - Run the same gate in CI by adding a step to your workflow (e.g.
.github/workflows/ci.yml):
- run: uv sync && uv run harness gate- Add or remove coding preferences preferences.py that only agents must respect. Current "preferences":
function_argument_assignment_has_star # agents use non-specific `def fun(*)`
named_with_underscore_and_not_in_class_or_dunder # agents love over-using underscore names `def _fun()`
hidden_signature_star_args # Complain when a function uses *args or **kwargs (it hides function signatures)
dynamic_star_call # Calls to def fun(*items) breaks when you can't tell how many arguments f is getting
pointless_class # ensure classes are added for good reasons (carry state, values, methods)
lazy_assert # enforce real assertions, stronger tests
objects_injected_into_runtime_memory # finds calls that manipulate global state (dangerous, tricky)
lambda_found # abolish lambdas, make agents keep their code simple
lazy_any_type_hints # abolish type `Any` used to bypass strict type-checking
chaotic_continue_statements # abolish unecessary nested continue statements, clean code
complex_comprehension # no needlessly dense list/set/dict comprehensions, prefer linear code- Who is Ralph?
Ralph is our name for LoopGate's loop runner β the small program that starts your coding agent, hands it the prompt, and starts a fresh agent when the last one finishes. The name comes from the "Ralph Wiggum" technique: run an agent in a simple loop, over and over, until the work is done. Anything starting with RALPH_ (like RALPH_LOOP=1) is just a setting Ralph gives the agent that says "you are inside the loop, follow the loop rules."
harness run <agent>exits immediately / can't find the worker?
LoopGate does not install or log in agent CLIs. Install and authenticate the worker you selected (claude, codex, copilot, or agy), confirm it is on your PATH (e.g. which codex), then retry. If which finds the binary but the run still fails, finish that tool's login/auth flow and retry harness run.
- What is the difference between a gate and a sandbox?
A gate is a workflow checkpoint that evaluates code and decides whether it is allowed to land in your commits. A sandbox is an isolated OS-level environment designed to prevent code from modifying your underlying machine. LoopGate uses gates to control your git history, but it does not provide a secure OS sandbox.
- What if I don't want to use those agents?
Remove or add commands to enable using different agents. Let's say you're going exclusive with Mistral and will only use Mistral 3. Set this and only this in [tool.harness.agents]
vibe = ["vibe", "--auto-approve", "--output", "streaming"]
Then run it with harness run vibe <loop-count> <max-minutes-per-loop
- What if I don't want to build an app in Python?
You donβt have to. The loop runner, Ralph, and the CLI take a prompt, launch agents pointed at markdown files. LoopGate is language-agnostic at the agent-loop level, only this template repo is configured to be Python-specific at pyproject.toml. You can add your language and your tool commands for your checks to run there.
- Javascript?
The included harness/js-scaffold is a simple JavaScript example to expand on. Go to pyproject.toml line 75. Update checks. Put js into list [tool.harness].languages. Remove py if unused.
npm run --prefix harness/js-scaffold gate
npm run --prefix harness/js-scaffold preflight
- Why not just a shell loop?
A shell loop only reruns an agent. LoopGate ensures fresh context, durable repo state, time and iteration limits, protected paths, and quality gates that stop bad changes before they land. Also, we like to keep our shell loops dumb round these parts. And Let the configs do the lifting.
Short definitions of common LoopGate terms.
- harness: the LoopGate tool that runs agents, manages loops, and checks changes.
- loop: one cycle of work where the worker reads instructions, works on a spec, makes changes, runs checks, and records progress.
- worker: the coding agent that does the work, such as Claude, Codex, or Copilot.
- gate: the checks that decide whether changes can be accepted. LoopGate runs these checks locally and in CI. aka pre-push, what happens right before a diff is pushed to origin.
- preflight: the quick checks that run before the full gate to catch common problems early. aka 'pre-commit', what happens right before files are git committed.
- prompt: the instructions in
docs/PROMPT.mdthat tell the worker what to do in each loop. - spec: a file in
docs/specs/that describes what needs to be built. - Ralph: LoopGate's loop runner. It starts the worker, gives it the prompt, and runs the workflow for each iteration.
Short definitions of common LoopGate terms.
- harness: the LoopGate tool that runs agents, manages loops, and checks changes.
- loop: one cycle of work where the worker reads instructions, works on a spec, makes changes, runs checks, and records progress.
- worker: the coding agent that does the work, such as Claude, Codex, or Copilot.
- gate: the checks that decide whether changes can be accepted. LoopGate runs these checks locally and in CI.
- preflight: the quick checks that run before the full gate to catch common problems early.
- prompt: the instructions in
docs/PROMPT.mdthat tell the worker what to do in each loop. - spec: a file in
docs/specs/that describes what needs to be built. - Ralph: LoopGate's loop runner. It starts the worker, gives it the prompt, and runs the workflow for each iteration.
- Use
git log --oneline <branch>..HEADto show what's unpushed. - There is NO worktree/branch creation by design. You can create branches/trees and run a loop in each, then merge (if you feel like managing that)
- Agent duties can be contained to a part of the repo. e.g. Codex-1-frontend uses
docs/specs/frontend.md, Claude-2-researcherdocs/specs/backend...
Recommendations for running several agents at once on one branch (no worktrees):
-
You (human): seed each spec once with this exact line near the top:
Spec claimed by agent: <unclaimed> -
The agents: paste this exact block into PROMPT.md line 3:
Other agents are working this repo. Before touching code, pick a spec whose claim line is <unclaimed>, replace it with your exact name `<your-agent-id>-<spec-you-worked>-<RALPH_ITERATION>/<MAX_ITERATIONS>`, e.g. `claude-0003-backend-3/3`, and commit that claim first. Own that spec's file and its tests. Set the line back to <unclaimed> on your last commit.RALPH_ITERATIONandMAX_ITERATIONSare numbers the loop hands each agent: which round it is on, and the total rounds allowed. -
What fails when agents do not claim specs/work: agents all pick the top-priority spec, duplicate work, and leave a half-staged git index.
-
What fails with too little time i.e. MAX_MINUTES too low: a worker dies mid-
gatebefore it can commit. Give each iteration enough minutes to finish (the gate itself takes a while). One successful iteration needs ~2-3 min of pure overhead aside from 'real' work.- A worker killed too soon leaves its spec claim STUCK: spec stays locked to its name. No other agent will take it until a human resets the line to
<unclaimed>. - preflight on git commit: ~ a few seconds
- full gate on git push: ~20-48s
- push + cleanup: ~ few seconds -
- A worker killed too soon leaves its spec claim STUCK: spec stays locked to its name. No other agent will take it until a human resets the line to
-
Do not rely on agent names for coordination: agents self-name inconsistently and can collide (e.g. two both call themselves the same thing). Names are for human blame/log-matching only; the claim line + committed code are what actually coordinate.
-
Which doc does what:
- specs = the product work
docs/PROMPT.md= how to operate headlessly- repo + green gate = the source of truth
docs/PROJECT_STATUS.mdis a human-readable record, not authoritative
-
No branch/worktree creation in this harness was intentional:
- For simplicity and maintainability of the framework.
- Because a fresh iteration can't see the unmerged work in another worktree, so agents miss context and scramble to merge while conflicts pile up.
- Change this behavior as you like.
-
This harness does not sandbox agents. It tries to harness bad code in loops via gates. Sandboxing agents will, e.g. prevent them from maintaining git, running Playwright, being seen as trustworthy by semgrep leading to cyclical failures, etc.
-
The gate is a guardrail, not a jail. Agents are crafty, like people. They will find a way to complete a task at all costs. Trust nothing and no one.
-
Mind your usage limits.
harness runworks agents to the cap set. You can easily burn through your tokens, context windows, and provider usage limits. Workers continue running as long as there is work to do. -
docs/PROMPT.mdtells the worker to push or not. -
Protect
mainand run the loop on its own branch. -
100% coverage does not mean good tests. That is quantity, not quality. Run
uv run mutmut runto find covered lines that no assertion actually checks.
Want to help? CONTRIBUTING.md.
