Skip to main content

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
  • .gitignore entry 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_check workflow.
  • --template inner_outer_eval scaffolds the packaged inner/outer/eval workflow set instead: outer, inner, eval_reviewer, and eval_runner.
  • --template pm_planner_dispatcher scaffolds the packaged PM orchestration workflow set: planner and dispatcher.
  • 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: /register and /finished.
  • On a fresh start, creates a new session directory with goal.md and session-local state.json.
  • If the latest session state.json is terminal, archives it next to that session state file and starts fresh.
  • If the latest session state.json is already running, startup fails unless --resume is passed.
  • --workflow-set overrides workflow_set for the new session.
  • --goal-file overrides the configured goal file for the new session.

loopy worker --coordinator http://127.0.0.1:8080

  • Calls /register once to receive the first task.
  • Loops calling /finished after each completed task until it receives a stop response.
  • Loads loopy_loop_config.yaml, .loopy_loop/workflow_sets/<workflow_set>/workflows/<workflow_id>/config.yaml, and the matching prompt.txt from disk on each task.
  • Uses the coordinator config_snapshot as the execution snapshot for the session.

loopy status

  • Prints current session id, iteration count, current task, and stop reason.

loopy stop

  • Sets stop_requested=true in the latest session-local state.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_hash derived from the text loaded from goal_file
  • workflow_set names the workflow set used when loopy coordinator is started without --workflow-set
  • goal_file is resolved relative to loopy_loop_config.yaml; inline goal values in YAML are rejected
  • completion_criteria, stop_criteria, and team_harness_system_prompt_extension are optional; omitted criteria default to empty lists and the prompt extension defaults to an empty string
  • team_harness_model controls the team-harness coordinator model; worker subprocess defaults are controlled by team_harness_agent_models
  • team_harness_api_base is normalized by loopy-loop: trailing slash stripped, /v1 appended when missing
  • team_harness_api_key_env must be set during coordinator preflight and again in the worker before TeamHarness(...)
  • team_harness_agent_reasoning_efforts is optional and only affects workers whose team-harness template supports a reasoning-effort flag
  • team_harness_max_retries, team_harness_retry_base_delay_s, and team_harness_retry_max_delay_s are 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_follow must resolve during coordinator preflight
  • run_every is based on completed iteration count, not wall clock
  • priority breaks ties among eligible workflows; higher values run first
  • run_on_start=true makes a workflow eligible before any successful workflow has run
  • run_after_successes can run 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 a required eval artifact. To stop the loop, the workflow must update the session-scoped control.json.
  • goal_check is reserved and scaffolded with not_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:

  • planner maintains PM state, selects one work item, and reviews terminal child-session evidence.
  • dispatcher writes 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 /register
  • POST /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


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.2.0.tar.gz (116.1 kB 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.2.0-py3-none-any.whl (59.7 kB view details)

Uploaded Python 3

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

Hashes for loopy_loop-0.2.0.tar.gz
Algorithm Hash digest
SHA256 b76d6e7d33d43f9bcaa0476a64934d56e955c1c36a07c3d2ffa85a6c99d85da7
MD5 718508a1d178356f06a9d7de7c5a6dfc
BLAKE2b-256 f848af9c91aecef7690a2eda2be29df666f5f6727fc6f90c6054e522ed5d001e

See more details on using hashes here.

Provenance

The following attestation bundles were made for loopy_loop-0.2.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.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

Hashes for loopy_loop-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ce236c3d4aac097d6bce0a7ac8a9b00385cbd2cffb96868db0f80aaee299717c
MD5 d8b270370596775d38cfd1b870f1b1eb
BLAKE2b-256 00c86fa399d00b5ef911b7d14924c0f4a62bd1420bdd37a9a34a6214cc2e3e53

See more details on using hashes here.

Provenance

The following attestation bundles were made for loopy_loop-0.2.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.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page