A repo-local automation loop for AI agents built on team-harness.
Project description
loopy-loop
loopy-loop is a repo-local automation loop for AI agents.
- A FastAPI coordinator owns session-local loop state in
.loopy_loop/sessions/<session_id>/state.json. - One or more blocking workers poll the coordinator over HTTP.
- Each assignment loads workflow files from the active workflow set, runs
team_harness.TeamHarness, writes iteration artifacts under.loopy_loop/sessions/<session_id>/iterations/, and reports completion back to the coordinator.
Install
uv sync --extra dev
Or:
uv pip install .
Skills
This repo ships an Agent Skill that teaches Claude Code, Codex, and other compatible agents how to set up and run loopy-loop in a target repo. Install it with:
npx skills add https://github.com/writeitai/loopy-loop --skill loopy-loop
The skill source lives under skills/loopy-loop/.
Repo Layout
target repo/
├── loopy_loop_config.yaml
└── .loopy_loop/
├── workflow_sets/<workflow_set>/workflows/<workflow_id>/{prompt.txt,config.yaml}
└── sessions/<session_id>/
├── goal.md
├── state.json
├── child_requests/
├── children/
└── iterations/
Quick Start
loopy init
loopy init --template inner_outer_eval
loopy init --template pm_planner_dispatcher
loopy coordinator --host 127.0.0.1 --port 8080
loopy worker --coordinator http://127.0.0.1:8080
loopy status
loopy stop
loopy init is idempotent. It creates:
loopy_loop_config.yaml.loopy_loop/workflow_sets/main/workflows/goal_check/prompt.txt.loopy_loop/workflow_sets/main/workflows/goal_check/config.yaml.gitignoreentry for.loopy_loop/sessions/
Use loopy init --template inner_outer_eval to scaffold the packaged workflow
set for outer planning, inner implementation, eval review, and eval running.
Use loopy init --template pm_planner_dispatcher to scaffold the packaged PM
workflow set for planner/dispatcher orchestration that starts child sessions.
CLI Reference
loopy init [--template default|inner_outer_eval|pm_planner_dispatcher]
- Scaffolds the root config and reserved
goal_checkworkflow. --template inner_outer_evalscaffolds the packaged inner/outer/eval workflow set instead:outer,inner,eval_reviewer, andeval_runner.--template pm_planner_dispatcherscaffolds the packaged PM orchestration workflow set:planneranddispatcher.- Does not overwrite existing workflow files.
loopy coordinator --host 0.0.0.0 --port 8080 [--resume] [--workflow-set NAME] [--goal-file PATH]
- Runs the FastAPI coordinator with exactly two endpoints:
/registerand/finished. - On a fresh start, creates a new session directory with
goal.mdand session-localstate.json. - If the latest session
state.jsonis terminal, archives it next to that session state file and starts fresh. - If the latest session
state.jsonis alreadyrunning, startup fails unless--resumeis passed. --workflow-setoverridesworkflow_setfor the new session.--goal-fileoverrides the configured goal file for the new session.
loopy worker --coordinator http://127.0.0.1:8080
- Calls
/registeronce to receive the first task. - Loops calling
/finishedafter each completed task until it receives astopresponse. - Loads
loopy_loop_config.yaml,.loopy_loop/workflow_sets/<workflow_set>/workflows/<workflow_id>/config.yaml, and the matchingprompt.txtfrom disk on each task. - Uses the coordinator
config_snapshotas the execution snapshot for the session.
loopy status
- Prints current session id, iteration count, current task, and stop reason.
loopy stop
- Sets
stop_requested=truein the latest session-localstate.json.
Config Reference
Root config (loopy_loop_config.yaml at the repo root):
goal_file: "loopy_loop_goal.txt"
workflow_set: "main"
max_turns: 20
goal_check_consecutive_failures_cap: 3
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: {}
# Optional coordinator retry controls. Omit to use team-harness defaults.
# team_harness_max_retries: 8
# team_harness_retry_base_delay_s: 2.0
# team_harness_retry_max_delay_s: 60.0
team_harness_api_base: "https://openrouter.ai/api/v1"
team_harness_api_key_env: "OPENROUTER_API_KEY"
Rules:
- Session ids start with a UTC timestamp for filesystem sorting and include a
deterministic
goal_hashderived from the text loaded fromgoal_file workflow_setnames the workflow set used whenloopy coordinatoris started without--workflow-setgoal_fileis resolved relative toloopy_loop_config.yaml; inlinegoalvalues in YAML are rejectedcompletion_criteria,stop_criteria, andteam_harness_system_prompt_extensionare optional; omitted criteria default to empty lists and the prompt extension defaults to an empty stringteam_harness_modelcontrols the team-harness coordinator model; worker subprocess defaults are controlled byteam_harness_agent_modelsteam_harness_api_baseis normalized by loopy-loop: trailing slash stripped,/v1appended when missingteam_harness_api_key_envmust be set during coordinator preflight and again in the worker beforeTeamHarness(...)team_harness_agent_reasoning_effortsis optional and only affects workers whose team-harness template supports a reasoning-effort flagteam_harness_max_retries,team_harness_retry_base_delay_s, andteam_harness_retry_max_delay_sare optional coordinator retry controls for transient team-harness API/network errors
Workflow config:
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: ""
Rules:
- Workflow id is the folder name under
.loopy_loop/workflow_sets/<workflow_set>/workflows/ must_followmust resolve during coordinator preflightrun_everyis based on completed iteration count, not wall clockprioritybreaks ties among eligible workflows; higher values run firstrun_on_start=truemakes a workflow eligible before any successful workflow has runrun_after_successescan run 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 a required eval artifact. To stop the loop, the workflow must update the session-scopedcontrol.json.goal_checkis reserved and scaffolded withnot_before_iteration: 1
Workflow Sets and Child Sessions
Workflow sets are mandatory. Even a single-loop repo uses
.loopy_loop/workflow_sets/main/workflows/...; the old
.loopy_loop/workflows/... layout is not loaded.
A workflow can request one sequential child session by writing a JSON file under
the active session's child_requests/ directory:
{
"workflow_set": "pm_planner_dispatcher",
"goal": "Implement the selected planner item.",
"schema_version": 1
}
The coordinator creates the child session under the parent session's
children/ directory, copies the request goal into the child goal.md, runs
the requested workflow set, and resumes the parent session after the child
reaches a terminal state. v1 is depth-first and single-child-at-a-time.
The packaged pm_planner_dispatcher workflow set uses this contract as a
generic parent loop:
plannermaintains PM state, selects one work item, and reviews terminal child-session evidence.dispatcherwrites one child request for the selected work item or imports terminal child evidence back into PM state.
Cadence example:
# Run eval_reviewer at the beginning and then after every 10 successful inner runs.
enabled: true
priority: 100
run_on_start: true
run_after_successes:
workflow_id: inner
every: 10
HTTP Contract Summary
Endpoints:
POST /registerPOST /finished
Both endpoints return a TaskResponse with action of either "run" or "stop".
A run response carries workflow_id, session_id, iteration, and config_snapshot.
A stop response carries stop_reason.
Stale /finished calls (mismatched session_id, workflow_id, or
iteration) do not mutate state and return the current running task's response.
If there is no active task, /finished acts like /register and dispatches the
next available task. If a worker exits after writing result.json but before
/finished is acknowledged, the next /register recovers the completed result
from the iteration directory instead of marking it abandoned.
See docs/http-contract.md for the exact JSON payloads.
Session Continuity
Every TeamHarness.run() call is fresh. Continuity comes from:
- git state in the target repo
.loopy_loop/sessions/<session_id>/...artifacts- the coordinator state in
.loopy_loop/sessions/<session_id>/state.json
team-harness outputs are routed into the active loopy-loop session under
.loopy_loop/sessions/<session_id>/harness_outputs/<NNNN>_<workflow_id>/<team_harness_run_id>/.
Workflow prompts receive session-scoped paths for reusable project state and eval definitions:
.loopy_loop/sessions/<session_id>/project_state/.loopy_loop/sessions/<session_id>/eval_checks/.loopy_loop/sessions/<session_id>/goal.md.loopy_loop/sessions/<session_id>/updates_from_user.md.loopy_loop/sessions/<session_id>/child_requests/.loopy_loop/sessions/<session_id>/control.json.loopy_loop/sessions/<session_id>/project_state/finished.md.loopy_loop/sessions/<session_id>/harness_outputs/
These directories are workflow-owned. The coordinator only owns
.loopy_loop/sessions/<session_id>/state.json and iteration dispatch state.
Each session's goal.md is the source of truth for the target, constraints,
and completion intent. For top-level sessions it is copied from goal_file or
the --goal-file CLI override. Workflow state should not copy or restate that
goal.
project_state/README.md should explain state ownership: memory.md is
essential durable facts only, finished.md is outer-owned accepted completions
only, eval_results.md owns eval detail, and current_state.md carries live
status, the latest eval headline, and the next action.
Write runtime requests for the outer loop into updates_from_user.md. The outer
workflow should treat non-empty content as highest-priority planning input,
reflect it into project_state/, and then clear the file. Verified completed
work belongs in project_state/finished.md; what_we_have.md should remain the
concise current capability summary.
Eval workflows should run session checks with --output-dir pointing at
.loopy_loop/sessions/<session_id>/eval_results/. Raw eval-banana reports stay
there; project_state/eval_results.md should summarize and link the latest
reports instead of copying them. project_state/current_state.md should only
carry the latest eval headline and the next action.
For implementation work that changes repo files, the default delivery path is a
branch, PR, passing checks, and merge. Multi-repo work should create and merge
one PR per changed repo when possible. Tasks should opt out only when they are
session-state-only, eval-only, research-only, planning-only, or the repo has no
usable remote or auth. project_state/finished.md should record delivery
evidence for each changed repo: repo, branch, PR URL, merge status, merge
commit, and checks/CI status. If PR creation or merge is blocked, record the
exact blocker and remaining action in project_state/current_state.md.
Control Files
control.json is the session-scoped workflow stop switch:
{
"state": "running",
"reason": "session active",
"stop_reason": null,
"schema_version": 1
}
Workflows leave it alone while the loop should continue. To stop successfully:
{
"state": "stopped",
"reason": "evals passed",
"stop_reason": "goal_met",
"schema_version": 1
}
To stop because the loop cannot continue:
{
"state": "stopped",
"reason": "specific terminal blocker",
"stop_reason": "unresolvable_error",
"schema_version": 1
}
goal_check.json is a per-iteration eval artifact for the reserved
goal_check workflow or any workflow with emits_goal_check=true:
.loopy_loop/sessions/<session_id>/iterations/<NNNN>_<workflow_id>/goal_check.json
{"goal_met": false, "reason": "docs still missing", "schema_version": 1}
If goal_check.json is missing or invalid repeatedly, the coordinator stops
with stop_reason="goal_check_broken" after the configured failure cap. A
valid goal_check.json does not stop the loop by itself; stopping is controlled
by session control.json.
Project details
Release history Release notifications | RSS feed
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.2.0.tar.gz.
File metadata
- Download URL: loopy_loop-0.2.0.tar.gz
- Upload date:
- Size: 116.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b76d6e7d33d43f9bcaa0476a64934d56e955c1c36a07c3d2ffa85a6c99d85da7
|
|
| MD5 |
718508a1d178356f06a9d7de7c5a6dfc
|
|
| BLAKE2b-256 |
f848af9c91aecef7690a2eda2be29df666f5f6727fc6f90c6054e522ed5d001e
|
Provenance
The following attestation bundles were made for loopy_loop-0.2.0.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.2.0.tar.gz -
Subject digest:
b76d6e7d33d43f9bcaa0476a64934d56e955c1c36a07c3d2ffa85a6c99d85da7 - Sigstore transparency entry: 1667428397
- Sigstore integration time:
-
Permalink:
writeitai/loopy-loop@2a3791e8139a0314abb5ac3632ad608c651a038c -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/writeitai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2a3791e8139a0314abb5ac3632ad608c651a038c -
Trigger Event:
push
-
Statement type:
File details
Details for the file loopy_loop-0.2.0-py3-none-any.whl.
File metadata
- Download URL: loopy_loop-0.2.0-py3-none-any.whl
- Upload date:
- Size: 59.7 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 |
ce236c3d4aac097d6bce0a7ac8a9b00385cbd2cffb96868db0f80aaee299717c
|
|
| MD5 |
d8b270370596775d38cfd1b870f1b1eb
|
|
| BLAKE2b-256 |
00c86fa399d00b5ef911b7d14924c0f4a62bd1420bdd37a9a34a6214cc2e3e53
|
Provenance
The following attestation bundles were made for loopy_loop-0.2.0-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.2.0-py3-none-any.whl -
Subject digest:
ce236c3d4aac097d6bce0a7ac8a9b00385cbd2cffb96868db0f80aaee299717c - Sigstore transparency entry: 1667428484
- Sigstore integration time:
-
Permalink:
writeitai/loopy-loop@2a3791e8139a0314abb5ac3632ad608c651a038c -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/writeitai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2a3791e8139a0314abb5ac3632ad608c651a038c -
Trigger Event:
push
-
Statement type: