Skip to main content

loopy-loop

loopy-loop logo

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.2 for hermetic --no-project-config evaluation and explicit harness-agent validation. Install all three companion changes together. While developing before those releases are published, install the corresponding team-harness and eval-banana checkouts into this environment 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.yaml
  • loopy_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 .gitignore entries 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:

  1. loopy init writes a root config, a goal file, and workflow files into the target repo.
  2. loopy coordinator loads loopy_loop_config.yaml, freezes the goal and workflow-set contract, creates a v2 root session under .loopy_loop/sessions/, and exposes /register and /finished.
  3. 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.
  4. 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.
  5. team-harness runs 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.
  6. 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/.
  7. 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 matching goal_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_set selects the default workflow set for new sessions.

  • goal_file is resolved relative to loopy_loop_config.yaml.

  • Inline goal values in YAML are rejected; the goal should live in a file.

  • max_turns is the maximum number of completed workflow iterations.

  • team_harness_model controls the team-harness coordinator model.

  • team_harness_agent_models controls 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 pass spawn_agent(model=…, effort=…) to select one bundle for a task (guidance, not enforcement — see D8/D9 in design/decisions.md). The per-spawn effort argument 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. With default_tier set, the named tier derives team_harness_agent_models and team_harness_agent_reasoning_efforts (the tier must cover every configured agent); setting those mappings explicitly alongside default_tier is 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_base is normalized by loopy-loop: trailing slash stripped, /v1 appended when missing.

  • team_harness_max_retries, team_harness_retry_base_delay_s, and team_harness_retry_max_delay_s are optional retry controls for transient team-harness API/network errors.

  • recovery_policy (drain by default, or reap) and recovery_drain_timeout_s control 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 with stop_reason="workflow_failure_cap" instead of retrying a wedged workflow until max_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 status derives an estimated cost from the token ledger. max_cost_usd (optional, requires model_prices) stops the loop with stop_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/.
  • priority breaks ties among eligible workflows; higher values run first.
  • run_every is based on completed iteration count, not wall clock.
  • run_on_start=true makes a workflow eligible before any successful workflow has run.
  • must_follow and run_after_successes.workflow_id must reference existing workflow ids.
  • run_after_successes can schedule a workflow after every N successful runs of another workflow:
run_after_successes:
  workflow_id: inner
  every: 10
  • emits_goal_check=true lets a non-goal_check workflow write goal_check.json as an eval artifact. Stopping still requires updating session control.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.

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:

  • planner maintains PM state, selects one work item, reviews terminal child evidence, and owns parent-acceptance/eval-readiness receipts.
  • dispatcher publishes the selected v2 child assignment and tracks factual lifecycle evidence without deciding acceptance.
  • eval_reviewer and eval_runner evaluate the PM layer's own broader goal; only that layer's eval_runner may request successful terminal control.

HTTP Contract

The coordinator exposes exactly two endpoints:

  • POST /register
  • POST /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

loopy_loop-0.7.0.tar.gz (8.1 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

loopy_loop-0.7.0-py3-none-any.whl (171.3 kB view details)

Uploaded Python 3

File details

Details for the file loopy_loop-0.7.0.tar.gz.

File metadata

  • Download URL: loopy_loop-0.7.0.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

Hashes for loopy_loop-0.7.0.tar.gz
Algorithm Hash digest
SHA256 920b46310a56cd87fa641e04795ac900212420de48d9ce7f35c8e08adb7d5054
MD5 85e811ae9b85b41dea498d200d4e6df3
BLAKE2b-256 913b40ec1ca627b333e9408ac1ce0870339f3d9b7ad1f65521032a9db60ac1d7

See more details on using hashes here.

Provenance

The following attestation bundles were made for loopy_loop-0.7.0.tar.gz:

Publisher: release.yml on writeitai/loopy-loop

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file loopy_loop-0.7.0-py3-none-any.whl.

File metadata

  • Download URL: loopy_loop-0.7.0-py3-none-any.whl
  • Upload date:
  • Size: 171.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for loopy_loop-0.7.0-py3-none-any.whl
Algorithm Hash digest
SHA256 75f9c5ed5d0d20949ad051283a7a2b319712602f7846daa8f9f497c17e2f253e
MD5 5dbd9c028c88df53191afeaa1903bc33
BLAKE2b-256 920332c0e2c14141847d62045dcc3b1d6ad45f827af8b3900cf4bab8a976d8a6

See more details on using hashes here.

Provenance

The following attestation bundles were made for loopy_loop-0.7.0-py3-none-any.whl:

Publisher: release.yml on writeitai/loopy-loop

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.12.0

2 files

0.11.0

2 files

0.10.1

2 files

0.10.0

2 files

0.9.0

2 files

0.8.0

2 files

0.7.2

2 files

0.7.1

2 files

This release

0.7.0 This release

2 files

0.6.0

2 files

0.5.0

2 files

0.4.0

2 files

0.3.0

2 files

0.2.1

2 files

0.2.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page