loopy-loop
loopy-loop runs long-running AI agent workflows inside your repository.
It turns a goal file in your repository into an inspectable sequence of agent
iterations: plan, implement, evaluate, record evidence, and continue until the
goal is met or the loop hits a terminal blocker.
The value is control and durability. Instead of asking one agent to solve a large task in one fragile chat, loopy-loop gives each durable goal layer a persistent session directory, immutable attempt assignments, explicit stop conditions, and structured evidence. You can stop and resume the service, append instructions while it runs, audit what happened, inspect attempt traces, and keep the actual project changes in normal git branches and PRs.
Under the hood, loopy-loop runs a small FastAPI coordinator and a single
worker. The coordinator owns the loop state and chooses the next workflow. The
worker runs assignments through
team-harness, which can delegate
to agent CLIs such as Codex, Claude Code, and Gemini. The packaged
inner_outer_eval template also uses
eval-banana conventions for
session-scoped evaluation checks; eval-banana installs automatically as a
loopy-loop dependency.
Install
Install the CLI from the official PyPI package.
With uv, install it as a command-line tool:
uv tool install loopy-loop
Or with pip:
pip install loopy-loop
For development inside this repository:
uv sync --extra dev
Version 0.7's recursive contract spans three owned projects. It requires
team-harness>=0.5.0 for caller-owned run records, pre-call coordinator input,
spawn assignment envelopes, and canonical stdout/stderr capture; it requires
eval-banana>=0.3.5 for hermetic --no-project-config evaluation, explicit
harness-agent validation, and the public canonical check-definition digest
used to verify eval receipts. Version 0.3.5 also retains the exact judge input
and gives every per-check result, stream, and deterministic evidence directory
a common collision-safe stem inside the caller-owned eval trace. Install all
three companion changes together.
For coordinated development across the repositories, install the corresponding
team-harness and eval-banana checkouts as editable dependencies:
uv pip install -e /path/to/team-harness -e /path/to/eval-banana
An older dependency is not a reduced-fidelity v2 mode: a fresh v2 session fails registration clearly if the worker cannot advertise the required capabilities.
Install the Agent Skill
This repo also ships an Agent Skill that teaches Claude Code, Codex, and compatible agents how to set up and run loopy-loop in another target repo.
npx skills add https://github.com/writeitai/loopy-loop --skill loopy-loop
The skill source lives under skills/loopy-loop/.
Initialize a Target Repo
Run this from the repository you want agents to work on:
loopy init --template inner_outer_eval
This is the recommended starting template. It creates:
loopy_loop_config.yamlloopy_loop_goal.txt.loopy_loop/workflow_sets/inner_outer_eval/contract.yaml.loopy_loop/workflow_sets/inner_outer_eval/workflows/outer/.loopy_loop/workflow_sets/inner_outer_eval/workflows/inner/.loopy_loop/workflow_sets/inner_outer_eval/workflows/eval_reviewer/.loopy_loop/workflow_sets/inner_outer_eval/workflows/eval_runner/- additive
.gitignoreentries for session state, traces, the trace-finalization outbox, repository identity, and root state/lock/archive files
loopy init is idempotent. It creates missing files and leaves existing files
alone — except .gitignore, which is updated additively with all runtime ignore
rules.
Write the Goal
The loop goal lives in loopy_loop_goal.txt. Replace the scaffolded example
with the real target, including constraints and observable completion criteria.
Example:
Implement passwordless email login.
Completion criteria:
- Users can request a one-time login link from the sign-in page.
- The link expires after 15 minutes and cannot be reused.
- Existing password login keeps working.
- Tests cover token expiry, token reuse, and successful login.
- README documents required environment variables.
Keep the goal specific enough that a reviewer or eval workflow can decide
whether the loop is done. For one-off overrides, start the coordinator with
--goal-file PATH; the file is copied into the session as goal.md.
Run the Loop
Start the coordinator in one terminal:
loopy coordinator --host 127.0.0.1 --port 8080
Start a worker in another terminal:
loopy worker --coordinator http://127.0.0.1:8080
Useful control commands:
loopy status
loopy update Prioritize the failing integration test
loopy stop
loopy traces list
If the coordinator stops while a session is still running, restart it with:
loopy coordinator --host 127.0.0.1 --port 8080 --resume
The default templates use team_harness_provider: "codex", so the coordinator
uses local Codex authentication. If you switch to an OpenAI-compatible provider,
export the environment variable named in team_harness_api_key_env, usually
OPENROUTER_API_KEY, in both the coordinator and worker shells.
How It Works
At a high level:
loopy initwrites a root config, a goal file, and workflow files into the target repo.loopy coordinatorloadsloopy_loop_config.yaml, freezes the goal and workflow-set contract, creates a v2 root session under.loopy_loop/sessions/, and exposes/registerand/finished.- A v2 worker advertises its protocol/capabilities and repository identity. The coordinator assigns work only to a matching checkout, freezes the exact workflow config/prompt/contract and assignment, creates the active attempt trace, and returns one identity-bound attempt with its assignment hash.
- Before calling a model, the worker reopens and verifies that same trace,
records the exact task response, verifies the frozen
assignment.json, and writes the rendered prompt and git-before evidence. The assignment gives the harness coordinator absolute paths for its own session layer while durable receipts continue to use portable logical references. team-harnessruns the coordinator model. It may dynamically spawn Codex, Claude Code, Gemini, or other configured agents; each direct spawn receives an automatic assignment envelope identifying its parent attempt, delegated task, relevant state paths, and output directory.- The worker records the normalized result and compact evidence with the
session and posts a completion bound to the exact worker, repository,
attempt, and assignment hash. The coordinator records the exact observed
completion response (or an explicit unavailable status after interruption)
and then seals detailed observable execution under
.loopy_loop/traces/. - The coordinator checks structural protocol evidence, session-local eval and control artifacts, child requests, and stop/budget conditions. Semantic quality remains the responsibility of the workflow/eval agents (D3/D4).
The inner_outer_eval template is organized around four workflows:
outer: reviews implementation evidence, accepts or returns work, maintains the accepted ledger, and publishes eval-readiness context without closing the session.inner: dynamically plans/delegates and implements one focused unit in the target repo; its harness coordinator integrates all spawned-agent work.eval_reviewer: creates or refreshes outcome-oriented, session-scoped eval-banana checks.eval_runner: runs those checks, publishes the canonical eval receipt and matchinggoal_check.json, and alone may request successful terminal control for this layer.
The loop does not hide state inside a chat transcript. Continuity comes from
git plus compact files in .loopy_loop/sessions/<session_id>/; detailed
execution records live separately in .loopy_loop/traces/.
Repo Layout
After initialization, the target repo has this shape:
target repo/
├── loopy_loop_config.yaml
├── loopy_loop_goal.txt
└── .loopy_loop/
├── repository.json # ignored checkout identity
├── workflow_sets/
│ └── <workflow_set>/
│ ├── contract.yaml
│ └── workflows/<workflow_id>/
│ ├── config.yaml
│ └── prompt.txt
├── sessions/
│ └── <session_id>/
│ ├── goal.md
│ ├── goal_contract.json
│ ├── session.json
│ ├── workflow_contract.json
│ ├── state.json
│ ├── control.json
│ ├── inputs/{user_updates.jsonl,accepted_request.json,artifacts/}
│ ├── project_state/
│ ├── eval_checks/
│ ├── eval_readiness/
│ ├── eval_receipts/
│ ├── child_requests/{pending,accepted,rejected}/
│ ├── child_outcomes/
│ ├── parent_acceptance/
│ ├── git_receipts/
│ ├── delivery_receipts/
│ ├── trace_seals/
│ ├── iterations/
│ └── children/<child_session_id>/...
├── traces/<root>/sessions/<session>/attempts/<attempt>/
└── trace_finalization_outbox/
Workflow definitions are part of the repo and should usually be committed.
Session directories, traces, the trace-finalization outbox, and
repository.json are runtime output and are ignored by default. Session
state/evidence is required for recovery; trace retention is independent from
that compact truth.
Configuration
Root config lives at loopy_loop_config.yaml:
goal_file: loopy_loop_goal.txt
workflow_set: inner_outer_eval
max_turns: 160
goal_check_consecutive_failures_cap: 3
# workflow_consecutive_failures_cap: 5
team_harness_provider: "codex"
team_harness_model: "gpt-5.5"
team_harness_agents:
- "codex"
- "claude"
- "gemini"
team_harness_agent_models:
codex: "gpt-5.5"
claude: "claude-opus-4-8"
gemini: "gemini-3.5-flash"
team_harness_agent_reasoning_efforts:
codex: "high"
team_harness_api_base: "https://openrouter.ai/api/v1"
team_harness_api_key_env: "OPENROUTER_API_KEY"
Important rules:
-
workflow_setselects the default workflow set for new sessions. -
goal_fileis resolved relative toloopy_loop_config.yaml. -
Inline
goalvalues in YAML are rejected; the goal should live in a file. -
max_turnsis the maximum number of completed workflow iterations. -
team_harness_modelcontrols the team-harness coordinator model. -
team_harness_agent_modelscontrols default models for worker subprocesses. -
model_tiers(optional) declares named worker tiers — tier name → agent →{model, effort}— as the single source of truth for this repo's model ids. Loopy renders the table into the harness system prompt so coordinators can passspawn_agent(model=…, effort=…)to select one bundle for a task (guidance, not enforcement — see D8/D9 indesign/decisions.md). The per-spawneffortargument was introduced in team-harness 0.4.0. Loopy 0.7 requires team-harness 0.5.0 for the wider caller/trace contract, so current installs always have it. Withdefault_tierset, the named tier derivesteam_harness_agent_modelsandteam_harness_agent_reasoning_efforts(the tier must cover every configured agent); setting those mappings explicitly alongsidedefault_tieris a config error.model_tiers: strong: codex: {model: "gpt-5.6-sol", effort: "xhigh"} claude: {model: "claude-fable-5", effort: "max"} economy: codex: {model: "gpt-5.6-terra", effort: "low"} claude: {model: "claude-haiku-4-5"} default_tier: "economy"
-
team_harness_api_baseis normalized by loopy-loop: trailing slash stripped,/v1appended when missing. -
team_harness_max_retries,team_harness_retry_base_delay_s, andteam_harness_retry_max_delay_sare optional retry controls for transient team-harness API/network errors. -
recovery_policy(drainby default, orreap) andrecovery_drain_timeout_scontrol what crash recovery does with agent processes left by an interrupted worker task: drain lets them finish within one shared bounded deadline; reap kills them immediately. The interrupted task is recorded as abandoned, consumes a turn, and then normal scheduling continues only if no stop condition fires. These are coordinator-side settings and are not part of the config snapshot sent to the worker. -
workflow_consecutive_failures_cap(default 5) is a per-workflow circuit breaker: that many consecutive failed iterations of the same workflow stop the loop withstop_reason="workflow_failure_cap"instead of retrying a wedged workflow untilmax_turns. Any success of the workflow resets its counter. Coordinator-side only; not part of the config snapshot sent to the worker. -
model_prices(optional, coordinator-side only) sets USD prices per 1M tokens for the harness coordinator model (prompt_usd_per_1m,completion_usd_per_1m); with prices set,loopy statusderives an estimated cost from the token ledger.max_cost_usd(optional, requiresmodel_prices) stops the loop withstop_reason="max_cost_usd"once the session tree's estimated cost reaches the budget. Cost covers the harness COORDINATOR model only — agent-CLI subprocesses (codex, claude, gemini) bill through their own accounts and are not measurable here.
Workflow config lives beside each workflow prompt:
enabled: true
priority: 0
run_every: 1
must_follow: null
not_before_iteration: 0
run_on_start: false
run_after_successes: null
emits_goal_check: false
description: ""
Workflow rules:
- The workflow id is the folder name under
.loopy_loop/workflow_sets/<workflow_set>/workflows/. prioritybreaks ties among eligible workflows; higher values run first.run_everyis based on completed iteration count, not wall clock.run_on_start=truemakes a workflow eligible before any successful workflow has run.must_followandrun_after_successes.workflow_idmust reference existing workflow ids.run_after_successescan schedule a workflow after every N successful runs of another workflow:
run_after_successes:
workflow_id: inner
every: 10
emits_goal_check=truelets a non-goal_checkworkflow writegoal_check.jsonas an eval artifact. Stopping still requires updating sessioncontrol.json.
Each workflow set may declare contract.yaml. It names the layer kind, every
workflow role's responsibility, accountable state paths, eval author/runner/
goal-control roles, task-acceptance owner, terminal-blocker reporting roles,
and whether the set uses the recursive child interface. This is accountability
metadata and prompt context, not a filesystem ACL or semantic scheduler gate
(D8). All built-in templates declare session_protocol_version: 2; an explicit
contract that omits the field also selects v2. An older
custom set without a contract receives a conservative derived protocol-v1 role
contract and remains executable; add and validate an explicit v2 contract
before expecting evidence-bound terminal control and child requests.
Output and Logging
Each coordinator run creates a root under .loopy_loop/sessions/. Recursive
workflow sets may create child sessions beneath it, but one worker still
advances only the deepest active session. Session files hold compact durable
truth: scoped goals, progress and decisions, task/recovery state, eval and
control receipts, child handoffs, and git/delivery evidence.
Each attempt freezes its workflow sources and assignment.json. The
assignment identifies the exact repository/session/workflow/attempt and gives
the harness coordinator absolute paths to its own state and outputs. Durable
receipts use logical session:/, parent:/, root:/, and trace: references
so a stopped checkout can move. Team-harness gives every direct spawn its own
absolute assignment/output paths and dynamic delegated task; the original
coordinator remains accountable for integrating the result.
Detailed observable execution goes to the separately gitignored
.loopy_loop/traces/<root>/sessions/<session>/attempts/<attempt>/ tree. It
contains the attempt manifest, protocol exchange, canonical team-harness run,
direct-agent streams, raw eval output, and verbose git/service records. These
are raw local records and may contain private data. A sealed manifest means
the local inventory is integrity-checked and its channel completeness is
known; it does not mean the attempt succeeded semantically.
Use loopy status and loopy events for compact progress, and loopy traces list plus loopy traces inspect for attempt detail. The complete artifact and
writer/reader reference is docs/session-layout.md.
Legacy v1 files remain readable on resume but are not the fresh-session write
contract.
Control and Completion
Each session evaluates its own scoped goal. A delivery child may prove its task
while its parent still needs integration or release work. In the packaged
inner_outer_eval set, outer accepts task evidence, eval_reviewer authors
LLM-as-judge checks, and only eval_runner may publish successful terminal
control after a matching same-session eval receipt. Readiness informs prompts;
it does not become a semantic scheduler gate.
The one D5 escape hatch is an identity-bound v2 unresolvable_error. It is for
a genuinely terminal blocker after autonomous alternatives are exhausted:
{
"schema_version": 2,
"control_id": "control-blocker-id",
"state": "stopped",
"reason": "specific terminal blocker",
"stop_reason": "unresolvable_error",
"producer": {
"session_id": "session-id",
"workflow_id": "inner",
"attempt_id": "attempt-id"
},
"attempted_routes": ["retry", "re-scope", "alternate local route"],
"evidence_refs": ["session:/protocol_failures/blocker.json"],
"created_at": "2026-07-15T12:00:00Z"
}
Both successful and blocker control must identify the exact current session/workflow/attempt. A delegate reports its conclusion to the harness coordinator; it cannot publish a durable decision for another layer or a later attempt. Invalid v2 control is archived with repair diagnostics instead of being treated as semantic failure. Repeated broken protocol or workflow execution is bounded by the configured failure caps.
The exact eval receipt, goal-check projection, successful control, blocker, and rejection rules are in docs/http-contract.md.
Workflow Sets and Child Sessions
Workflow sets are mandatory. Even a single-loop repo uses:
.loopy_loop/workflow_sets/main/workflows/...
The older .loopy_loop/workflows/... layout is not loaded.
A session workflow may request one sequential child by atomically publishing a
unique *.json file under the assignment's absolute
child_requests/pending/ path. The same edge works recursively, so one-loop,
planner/dispatcher, and deeper trees use one state machine. Only the deepest
session runs an assignment; every ancestor is suspended on one child.
The v2 request carries parent provenance, a child-scoped goal contract, and
hashed input references. The coordinator archives the accepted request and
copies each verified input into the child's immutable inputs/ area. Child
attempts receive those logical references, hashes, and absolute local paths;
later parent edits cannot change accepted work.
The stock PM dispatcher first freezes the selected work item and its planning
evidence under project_state/dispatch_inputs/<request_id>.json, then hashes
that immutable snapshot into the request. Only after the request is atomically
published does it update the mutable work_items.md ledger. Hashing the ledger
itself would invalidate the request when that required status update occurs.
When the child stops, the engine writes a factual outcome. The parent then writes a separate acceptance, rework, or reroute decision after reviewing the evidence. Terminal descendants unwind iteratively, so the same edge supports three or more active depths without a depth-specific scheduler. Invalid requests are archived with reasons and can be repaired autonomously.
The packaged pm_planner_dispatcher workflow set uses this contract for PM
orchestration:
plannermaintains PM state, selects one work item, reviews terminal child evidence, and owns parent-acceptance/eval-readiness receipts.dispatcherfreezes the selected item into an immutable request input, publishes the v2 child assignment, and tracks factual lifecycle evidence without deciding acceptance.eval_reviewerandeval_runnerevaluate the PM layer's own broader goal; only that layer'seval_runnermay request successful terminal control.
HTTP Contract
The coordinator exposes exactly two endpoints:
POST /registerPOST /finished
Both return a TaskResponse with action equal to "run" or "stop".
Fresh v2 registration binds worker protocol/capabilities to the absolute checkout and stable repository ID. A missing capability returns HTTP 426 without advancing state. A run response identifies the frozen session/workflow/attempt assignment; completion must echo its worker, repository, attempt, and assignment hash. Stale or mismatched completion cannot mutate current work. Durable local result and pending-completion records allow the next registration to recover an interrupted acknowledgement exactly once.
The authoritative payload models are RegisterRequest, TaskResponse, and
FinishedRequest in
src/loopy_loop/models.py. The recursive ownership
and compatibility rules are in docs/http-contract.md.
CLI Reference
loopy init [--template default|inner_outer_eval|pm_planner_dispatcher]
Scaffolds loopy-loop files. The default template creates only the reserved
goal_check workflow. inner_outer_eval creates the recommended outer/inner/eval
workflow set. pm_planner_dispatcher creates planner/dispatcher workflows for
child-session orchestration — and also ships the inner_outer_eval child set
its dispatcher spawns, so a clean init is executable end to end.
loopy coordinator --host 0.0.0.0 --port 8080 [--resume] [--workflow-set NAME] [--goal-file PATH]
Runs the coordinator. --workflow-set and --goal-file override the root
config for the new session. --resume reuses a non-terminal latest session.
loopy worker --coordinator http://127.0.0.1:8080
Runs a blocking worker until the coordinator returns action: "stop".
loopy status # session stack, usage totals, estimated cost
loopy status --watch # re-render every 2 seconds
loopy events # the active session's event stream
loopy events --follow # tail it live (--json for raw lines)
loopy update TEXT... # append to the deepest active layer
loopy update --session SESSION_ID TEXT...
loopy stop # tree-wide stop at the next safe boundary
loopy traces list
loopy traces inspect MANIFEST_OR_ID
status prints the latest session state — the whole session stack while a
child runs (the live child is shown under its suspended parent), each
session's subtree token usage, and (with model_prices configured) estimated
cost. update appends the input record exactly as supplied; without --session it is
routed to the deepest active layer and later assignments append delivery and
acknowledgement records rather than editing history. stop projects root stop
intent through the whole active path and takes effect at the next register or
finish boundary; it does not invent a mid-harness interruption mechanism.
Trace commands accept a manifest ID, a trace root, or a manifest path confined
to this repository's .loopy_loop/traces/. inspect prints the manifest plus
its currently observed integrity. Trace finalization is crash-safe: startup
retries only transitions proved by durable attempt history, records an
unobserved HTTP response as unavailable, and never lets trace-storage failure
change semantic acceptance. See
docs/session-layout.md for
the exact outbox and seal ordering.
Related Projects
team-harness: the model and agent-CLI orchestration layer used by the loopy-loop worker.eval-banana: a lightweight YAML evaluation framework used by the packaged eval workflows. Installed automatically as a loopy-loop dependency.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file loopy_loop-0.7.1.tar.gz.
File metadata
- Download URL: loopy_loop-0.7.1.tar.gz
- Upload date:
- Size: 8.1 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
040799f7551785b16f5cf3a3e62385fed9c716e69c8a400123dc8cfd9d8b0102
|
|
| MD5 |
4589952a42bf66f1fee65840c2dafa0e
|
|
| BLAKE2b-256 |
9982aca75177b5da9c42a0c244b552861eb40871478e9bf5292a084b39845061
|
Provenance
The following attestation bundles were made for loopy_loop-0.7.1.tar.gz:
Publisher:
release.yml on writeitai/loopy-loop
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
loopy_loop-0.7.1.tar.gz -
Subject digest:
040799f7551785b16f5cf3a3e62385fed9c716e69c8a400123dc8cfd9d8b0102 - Sigstore transparency entry: 2188214426
- Sigstore integration time:
-
Permalink:
writeitai/loopy-loop@bba312b95a57251390430d4814fbf31178924b95 -
Branch / Tag:
refs/tags/v0.7.1 - Owner: https://github.com/writeitai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@bba312b95a57251390430d4814fbf31178924b95 -
Trigger Event:
push
-
Statement type:
File details
Details for the file loopy_loop-0.7.1-py3-none-any.whl.
File metadata
- Download URL: loopy_loop-0.7.1-py3-none-any.whl
- Upload date:
- Size: 172.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
97e8a21d9ae6f0339ce3f796704d0b9ed9f9e1b992f91f33f15861de00f5da7a
|
|
| MD5 |
f3d7c99ee49703c2bfc901989ccfc042
|
|
| BLAKE2b-256 |
85b09caccd0cf3b205f09e3f2ba123a2263d989e39a58d91e381c3d0a58c098e
|
Provenance
The following attestation bundles were made for loopy_loop-0.7.1-py3-none-any.whl:
Publisher:
release.yml on writeitai/loopy-loop
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
loopy_loop-0.7.1-py3-none-any.whl -
Subject digest:
97e8a21d9ae6f0339ce3f796704d0b9ed9f9e1b992f91f33f15861de00f5da7a - Sigstore transparency entry: 2188214439
- Sigstore integration time:
-
Permalink:
writeitai/loopy-loop@bba312b95a57251390430d4814fbf31178924b95 -
Branch / Tag:
refs/tags/v0.7.1 - Owner: https://github.com/writeitai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@bba312b95a57251390430d4814fbf31178924b95 -
Trigger Event:
push
-
Statement type: