loopy-loop 0.2.1__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- loopy_loop-0.4.0/.github/workflows/docs-deploy.yml +66 -0
- loopy_loop-0.4.0/AGENTS.md +80 -0
- loopy_loop-0.4.0/CHANGELOG.md +143 -0
- loopy_loop-0.4.0/CLAUDE.md +17 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/Makefile +2 -2
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/PKG-INFO +16 -5
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/README.md +14 -3
- loopy_loop-0.4.0/design/analysis/codex_parallel_analysis.md +665 -0
- loopy_loop-0.4.0/design/analysis/loopy-loop-state-and-roadmap.md +445 -0
- loopy_loop-0.4.0/design/analysis/synthesis.md +274 -0
- loopy_loop-0.4.0/design/decisions.md +205 -0
- loopy_loop-0.4.0/design/designs/documentation-site.md +302 -0
- loopy_loop-0.4.0/design/designs/improvement-proposals.md +385 -0
- loopy_loop-0.4.0/design/designs/success-semantics-and-evaluation.md +189 -0
- loopy_loop-0.4.0/docs/http-contract.md +178 -0
- loopy_loop-0.4.0/docs/images/logo.png +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/docs/session-layout.md +38 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/pyproject.toml +2 -2
- loopy_loop-0.4.0/skills-lock.json +16 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/cli.py +37 -2
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/config.py +18 -0
- loopy_loop-0.4.0/src/loopy_loop/coordinator_app.py +1181 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/harness_runner.py +13 -9
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/models.py +45 -0
- loopy_loop-0.4.0/src/loopy_loop/recovery.py +261 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/sessions.py +28 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/eval_reviewer/prompt.txt +4 -2
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/eval_runner/prompt.txt +11 -1
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/inner/prompt.txt +3 -1
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/outer/prompt.txt +15 -4
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/templates/pm_planner_dispatcher/.loopy_loop/workflow_sets/pm_planner_dispatcher/workflows/planner/prompt.txt +8 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/templates/pm_planner_dispatcher/loopy_loop_config.yaml +7 -4
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/worker.py +48 -7
- loopy_loop-0.4.0/src/loopy_loop/worker_identity.py +102 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/tests/test_cli.py +105 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/tests/test_coordinator_app.py +32 -21
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/tests/test_idempotent_finished.py +10 -3
- loopy_loop-0.4.0/src/tests/test_session_stack_recovery.py +824 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/tests/test_worker.py +8 -2
- loopy_loop-0.4.0/src/tests/test_worker_liveness_recovery.py +728 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/uv.lock +5 -5
- loopy_loop-0.4.0/website/.gitignore +25 -0
- loopy_loop-0.4.0/website/README.md +71 -0
- loopy_loop-0.4.0/website/mdx-components.tsx +36 -0
- loopy_loop-0.4.0/website/next.config.ts +26 -0
- loopy_loop-0.4.0/website/package-lock.json +4779 -0
- loopy_loop-0.4.0/website/package.json +41 -0
- loopy_loop-0.4.0/website/postcss.config.mjs +8 -0
- loopy_loop-0.4.0/website/public/.nojekyll +0 -0
- loopy_loop-0.4.0/website/public/CNAME +1 -0
- loopy_loop-0.4.0/website/src/app/docs/child-sessions/page.mdx +135 -0
- loopy_loop-0.4.0/website/src/app/docs/cli-reference/page.mdx +104 -0
- loopy_loop-0.4.0/website/src/app/docs/concepts/page.mdx +63 -0
- loopy_loop-0.4.0/website/src/app/docs/configuration/page.mdx +118 -0
- loopy_loop-0.4.0/website/src/app/docs/evaluation/page.mdx +110 -0
- loopy_loop-0.4.0/website/src/app/docs/getting-started/page.mdx +122 -0
- loopy_loop-0.4.0/website/src/app/docs/http-contract/page.mdx +227 -0
- loopy_loop-0.4.0/website/src/app/docs/layout.tsx +46 -0
- loopy_loop-0.4.0/website/src/app/docs/page.mdx +37 -0
- loopy_loop-0.4.0/website/src/app/docs/session-layout/page.mdx +211 -0
- loopy_loop-0.4.0/website/src/app/docs/success-and-control/page.mdx +172 -0
- loopy_loop-0.4.0/website/src/app/docs/troubleshooting/page.mdx +186 -0
- loopy_loop-0.4.0/website/src/app/docs/workflows/page.mdx +126 -0
- loopy_loop-0.4.0/website/src/app/globals.css +166 -0
- loopy_loop-0.4.0/website/src/app/layout.tsx +46 -0
- loopy_loop-0.4.0/website/src/app/page.tsx +49 -0
- loopy_loop-0.4.0/website/src/components/docs/DocsPageNavigation.tsx +42 -0
- loopy_loop-0.4.0/website/src/components/docs/DocsSidebar.tsx +62 -0
- loopy_loop-0.4.0/website/src/components/docs/DocsTableOfContents.tsx +81 -0
- loopy_loop-0.4.0/website/src/components/docs/MobileDocsSidebar.tsx +54 -0
- loopy_loop-0.4.0/website/src/components/search/SearchCommand.tsx +181 -0
- loopy_loop-0.4.0/website/src/components/site/SiteHeader.tsx +38 -0
- loopy_loop-0.4.0/website/src/lib/docs/navigation.ts +64 -0
- loopy_loop-0.4.0/website/src/lib/search/pagefind.ts +86 -0
- loopy_loop-0.4.0/website/src/lib/utils.ts +7 -0
- loopy_loop-0.4.0/website/tsconfig.json +42 -0
- loopy_loop-0.2.1/CHANGELOG.md +0 -46
- loopy_loop-0.2.1/docs/http-contract.md +0 -106
- loopy_loop-0.2.1/src/loopy_loop/coordinator_app.py +0 -706
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/.eval-banana/config.toml +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/.github/workflows/ci.yml +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/.github/workflows/release.yml +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/.gitignore +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/.team-harness/config.toml +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/.team-harness/coordinator_system_message.md +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/.team-harness/worker_footer.md +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/.team-harness/worker_suffix.md +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/LICENSE +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/skills/loopy-loop/SKILL.md +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/__init__.py +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/scheduler.py +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/state_store.py +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/templates/inner_outer_eval/.gitignore +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/eval_reviewer/config.yaml +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/eval_runner/config.yaml +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/inner/config.yaml +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/outer/config.yaml +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/templates/inner_outer_eval/loopy_loop_config.yaml +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/templates/inner_outer_eval/loopy_loop_goal.txt +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/templates/pm_planner_dispatcher/.gitignore +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/templates/pm_planner_dispatcher/.loopy_loop/workflow_sets/pm_planner_dispatcher/workflows/dispatcher/config.yaml +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/templates/pm_planner_dispatcher/.loopy_loop/workflow_sets/pm_planner_dispatcher/workflows/dispatcher/prompt.txt +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/templates/pm_planner_dispatcher/.loopy_loop/workflow_sets/pm_planner_dispatcher/workflows/planner/config.yaml +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/templates/pm_planner_dispatcher/loopy_loop_goal.txt +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/tests/__init__.py +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/tests/conftest.py +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/tests/test_api_base_normalization.py +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/tests/test_config.py +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/tests/test_examples.py +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/tests/test_fresh_run_archive.py +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/tests/test_goal_check_gate.py +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/tests/test_goal_hash_derivation.py +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/tests/test_harness_runner.py +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/tests/test_must_follow_success.py +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/tests/test_scheduler.py +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/tests/test_sessions.py +0 -0
- {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/tests/test_state_store.py +0 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
name: Docs Site
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["main"]
|
|
6
|
+
paths:
|
|
7
|
+
- "website/**"
|
|
8
|
+
- ".github/workflows/docs-deploy.yml"
|
|
9
|
+
pull_request:
|
|
10
|
+
branches: ["main"]
|
|
11
|
+
paths:
|
|
12
|
+
- "website/**"
|
|
13
|
+
- ".github/workflows/docs-deploy.yml"
|
|
14
|
+
workflow_dispatch:
|
|
15
|
+
|
|
16
|
+
# Allow GITHUB_TOKEN to deploy to GitHub Pages.
|
|
17
|
+
permissions:
|
|
18
|
+
contents: read
|
|
19
|
+
pages: write
|
|
20
|
+
id-token: write
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
build:
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
defaults:
|
|
26
|
+
run:
|
|
27
|
+
working-directory: website
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v4
|
|
30
|
+
|
|
31
|
+
- uses: actions/setup-node@v4
|
|
32
|
+
with:
|
|
33
|
+
node-version: "22"
|
|
34
|
+
cache: npm
|
|
35
|
+
cache-dependency-path: website/package-lock.json
|
|
36
|
+
|
|
37
|
+
- name: Install dependencies
|
|
38
|
+
run: npm ci
|
|
39
|
+
|
|
40
|
+
# `next build` type-checks the project and generates .next/types, so a
|
|
41
|
+
# separate pre-build `tsc` step is intentionally omitted (it would run
|
|
42
|
+
# before those generated types exist).
|
|
43
|
+
- name: Build static site (Next.js export + Pagefind index)
|
|
44
|
+
run: npm run build
|
|
45
|
+
|
|
46
|
+
- name: Upload Pages artifact
|
|
47
|
+
uses: actions/upload-pages-artifact@v3
|
|
48
|
+
with:
|
|
49
|
+
path: website/out
|
|
50
|
+
|
|
51
|
+
deploy:
|
|
52
|
+
# Only publish from main; PRs run the build job above as a check.
|
|
53
|
+
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
|
|
54
|
+
needs: build
|
|
55
|
+
runs-on: ubuntu-latest
|
|
56
|
+
# Serialize production deployments only, so PR build-checks stay concurrent.
|
|
57
|
+
concurrency:
|
|
58
|
+
group: "pages-deploy"
|
|
59
|
+
cancel-in-progress: false
|
|
60
|
+
environment:
|
|
61
|
+
name: github-pages
|
|
62
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
63
|
+
steps:
|
|
64
|
+
- name: Deploy to GitHub Pages
|
|
65
|
+
id: deployment
|
|
66
|
+
uses: actions/deploy-pages@v4
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# AGENTS.md — working agreement for this repo
|
|
2
|
+
|
|
3
|
+
`loopy-loop` runs long-running AI agent workflows inside a repository. A FastAPI
|
|
4
|
+
**coordinator** owns durable loop state in files and picks the next workflow; a single
|
|
5
|
+
**worker** runs each assignment through `team-harness`. Continuity lives in files and git,
|
|
6
|
+
not in a chat transcript. Read `README.md` for the user-facing model, and
|
|
7
|
+
`design/decisions.md` for *why the system is the way it is*.
|
|
8
|
+
|
|
9
|
+
This file is the working agreement for any agent (or human) changing this repo. Three
|
|
10
|
+
things are non-negotiable.
|
|
11
|
+
|
|
12
|
+
## Rule 1 — Respect the deliberate decisions; don't "fix" them
|
|
13
|
+
|
|
14
|
+
`design/decisions.md` is the canonical Architecture Decision Log (D1, D2, …). **Several
|
|
15
|
+
decisions there look like defects if you only skim the code, and are recorded precisely so
|
|
16
|
+
they don't get "fixed" by accident.** Before changing behavior in these areas, read the
|
|
17
|
+
relevant decision (and its companion design doc):
|
|
18
|
+
|
|
19
|
+
- **D3 — iteration success ≠ "work is good."** `IterationResult.success` means only that
|
|
20
|
+
the harness ran without erroring. Do not make it consult worker exit codes / infer
|
|
21
|
+
semantic success. The eval layer (`control.json` + `goal_check.json`) decides quality.
|
|
22
|
+
- **D4 — evaluation is LLM-as-judge; agents don't author deterministic checks.** Do not
|
|
23
|
+
"add deterministic checks" to the stock `inner_outer_eval` eval workflows. (A target
|
|
24
|
+
repo that owns its own test suite is a different case — see D4.)
|
|
25
|
+
- **D2 — single worker is deliberate.** Do not add parallel loopy workers as a scaling
|
|
26
|
+
feature.
|
|
27
|
+
- **D5 — full autonomy with a last-resort escape hatch.** See Rule 2.
|
|
28
|
+
|
|
29
|
+
If you believe a decision is genuinely wrong, propose amending `design/decisions.md`
|
|
30
|
+
(state what changes and why) — do not silently contradict it in code.
|
|
31
|
+
|
|
32
|
+
## Rule 2 — Autonomy is the goal; the human escape hatch is a LAST resort (D5)
|
|
33
|
+
|
|
34
|
+
This system is meant to run **fully autonomously, with no human in the loop**. Human
|
|
35
|
+
involvement is a last resort, never a normal step.
|
|
36
|
+
|
|
37
|
+
- **The one sanctioned escape hatch already exists.** When a workflow hits a *genuinely
|
|
38
|
+
terminal* blocker — a decision only a human can make, a missing credential, a
|
|
39
|
+
billable/destructive action it isn't permitted to take — it writes the session
|
|
40
|
+
`control.json` with:
|
|
41
|
+
```json
|
|
42
|
+
{"state": "stopped", "reason": "<specific terminal blocker>", "stop_reason": "unresolvable_error", "schema_version": 1}
|
|
43
|
+
```
|
|
44
|
+
This stops the loop as terminal, with a recorded reason. That is the entire
|
|
45
|
+
human-in-the-loop mechanism, and it is enough.
|
|
46
|
+
- **Exhaust autonomous options first.** Re-scope, retry with a better child goal, route
|
|
47
|
+
around the blocker. Reach for `unresolvable_error` only when a blocker is genuinely
|
|
48
|
+
unavoidable without a human. It should fire rarely.
|
|
49
|
+
- **Make the give-up legible.** The recorded blocker is the only thing a human sees when a
|
|
50
|
+
run stops — state exactly what was missing and what was tried.
|
|
51
|
+
- **Do NOT build or assume a preferred human gate.** No `paused` / `waiting_for_human`
|
|
52
|
+
state, no `gate_request.json`, no external-action approval flow. That was considered and
|
|
53
|
+
rejected (D5; `design/designs/improvement-proposals.md` P0.2). Do not pause a run to ask
|
|
54
|
+
a question when you could either solve it autonomously or stop cleanly with
|
|
55
|
+
`unresolvable_error`.
|
|
56
|
+
|
|
57
|
+
## Rule 3 — Design and decision docs must be understandable cold, by future agents AND humans
|
|
58
|
+
|
|
59
|
+
A design or decision doc is read by someone who was **not** in the conversation that
|
|
60
|
+
produced it — a future agent with no memory of this session, or a human who is not a
|
|
61
|
+
specialist. Write for them.
|
|
62
|
+
|
|
63
|
+
- **Explain, don't just name.** Naming a mechanism ("the ping-pong protocol", "cadence
|
|
64
|
+
scheduling", "the double loop") is not explaining it. State, in plain language, *what it
|
|
65
|
+
is, what problem it solves, and why we chose it*, with a concrete example where it helps.
|
|
66
|
+
- **The reasoning lives in the doc, not in your head.** Don't rely on a future reader
|
|
67
|
+
re-deriving the rationale. A decision-log entry may state the conclusion tersely; the
|
|
68
|
+
companion design section must be self-contained.
|
|
69
|
+
- **Anchor claims in the code.** Reference the file/function (e.g. `coordinator_app.py`,
|
|
70
|
+
`harness_runner.py`) so a reader can verify — but lead with the plain-English meaning.
|
|
71
|
+
- **Keep `design/` honest about status.** `design/designs/` is binding; `design/analysis/`
|
|
72
|
+
is working notes (may be superseded); `improvement-proposals.md` is *proposed, not
|
|
73
|
+
decided*. Don't cite a proposal as if it were a decision.
|
|
74
|
+
|
|
75
|
+
When in doubt on any rule, favor the version a stranger could read cold and fully
|
|
76
|
+
understand.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
Note: `CLAUDE.md` points here. Keep this file as the single source; don't fork the two.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.4.0 (breaking)
|
|
4
|
+
|
|
5
|
+
**Breaking API interaction — completions must echo the dispatched
|
|
6
|
+
`attempt_id`.** Every dispatched task now carries an `attempt_id` that the
|
|
7
|
+
worker must echo on `/finished`; a completion without the live task's attempt
|
|
8
|
+
is treated as stale (this is what fences superseded work). A 0.3.0 worker
|
|
9
|
+
therefore cannot complete tasks against a 0.4.0 coordinator — upgrade workers
|
|
10
|
+
and coordinator together (they normally ship in the same install).
|
|
11
|
+
|
|
12
|
+
- **Durable session-stack recovery (P0.1).** While a child session runs, the
|
|
13
|
+
parent's `state.json` records `active_child_session_id`; on `--resume` the
|
|
14
|
+
coordinator walks the pointer chain to the deepest live session instead of
|
|
15
|
+
silently reopening the parent and orphaning the running child. Terminal
|
|
16
|
+
children found at startup are finalized (children.json completed, pointer
|
|
17
|
+
cleared) and their parent resumed. Every interrupted-dispatch crash window
|
|
18
|
+
reconciles deterministically: dangling pointers are cleared, a fully
|
|
19
|
+
created child whose parent commit never landed is adopted, and leftover
|
|
20
|
+
request files never dispatch twice (children.json records the originating
|
|
21
|
+
`request_file`). Invalid child requests are terminally rejected
|
|
22
|
+
(`*.json.rejected`) instead of being re-read forever.
|
|
23
|
+
- **Attempt ids.** Every dispatched task carries a unique `attempt_id`
|
|
24
|
+
(also on the wire in `TaskResponse`, echoed in `FinishedRequest`); a late
|
|
25
|
+
`/finished` from a superseded attempt of the same coordinates is treated as
|
|
26
|
+
stale rather than recorded as the current result.
|
|
27
|
+
- Iteration artifacts (`result.json`, `result_text.txt`, `prompt.txt`,
|
|
28
|
+
`harness_run_id.txt`, `pending_finished_request.json`), `children.json`,
|
|
29
|
+
and `salvage.json` are all written atomically (unique temp + rename) — a
|
|
30
|
+
crash can never leave a truncated recovery artifact.
|
|
31
|
+
- Internal: the three duplicated dispatch blocks in the coordinator collapsed
|
|
32
|
+
into one `_advance()` step (stop checks → child dispatch → next workflow →
|
|
33
|
+
stamped task), so they can no longer drift apart. `_advance()` also enforces
|
|
34
|
+
the suspended-parent invariant: a parent with a live child can never acquire
|
|
35
|
+
its own task (a duplicate `/finished` retry gets the child's live task
|
|
36
|
+
instead), and a coordinator-level transition lock serializes cross-store
|
|
37
|
+
handoffs.
|
|
38
|
+
- Review hardening (adversarial Codex review of the above): request-file
|
|
39
|
+
tombstones apply only to RUNNING child records (a completed child's
|
|
40
|
+
filename is reusable for new work); the children.json record lands BEFORE
|
|
41
|
+
the child state so an interrupted dispatch is always discoverable
|
|
42
|
+
(`failed_dispatch` + exactly-once redispatch); startup reconciles every
|
|
43
|
+
running-projected record (terminal children finalize even without a
|
|
44
|
+
pointer); the first child task carries an attempt id; attempt checks are
|
|
45
|
+
strict whenever the live task has one (including `result.json` provenance —
|
|
46
|
+
a stale artifact can no longer complete a new attempt); semantically
|
|
47
|
+
unusable child requests (unknown workflow set, no eligible workflow) are
|
|
48
|
+
terminally rejected instead of wedging every completion; packaged prompts
|
|
49
|
+
instruct atomic control/goal_check publication; the crash model (process
|
|
50
|
+
crash, no fsync) is documented.
|
|
51
|
+
- **The `pm_planner_dispatcher` template is executable from a clean init
|
|
52
|
+
(P0.4).** `loopy init --template pm_planner_dispatcher` now also ships the
|
|
53
|
+
`inner_outer_eval` child workflow set its dispatcher spawns — previously a
|
|
54
|
+
clean init could not execute a single child session. The child set is
|
|
55
|
+
sourced from the `inner_outer_eval` template itself, so the two copies can
|
|
56
|
+
never drift apart.
|
|
57
|
+
|
|
58
|
+
## 0.3.0 (breaking)
|
|
59
|
+
|
|
60
|
+
**Breaking API change — `/register` requires the worker's process identity.**
|
|
61
|
+
A register without a `worker` object is rejected with HTTP 400. Pre-0.3
|
|
62
|
+
workers cannot register against a 0.3 coordinator; upgrade workers and
|
|
63
|
+
coordinator together (they normally ship in the same install).
|
|
64
|
+
|
|
65
|
+
- **Worker liveness verification (D7).** The worker sends its process
|
|
66
|
+
identity (hostname + pid + a pid-reuse-proof start-time token) with
|
|
67
|
+
`/register` and `/finished`; the coordinator stamps it onto the dispatched
|
|
68
|
+
task. A `/register` while the recorded worker is *verifiably still alive*
|
|
69
|
+
returns HTTP 409 instead of abandoning live work — closing the
|
|
70
|
+
duplicate-work window. Unverifiable identities (remote hosts, or a
|
|
71
|
+
team-harness without process identity) keep the pre-existing
|
|
72
|
+
assume-abandoned recovery behavior. Because every dispatched task now has
|
|
73
|
+
a recorded owner, a stale `/finished` is replayed **only to that owner**
|
|
74
|
+
(anyone else gets HTTP 409) — a task persisted by a pre-identity version
|
|
75
|
+
keeps the legacy replay for that one resume.
|
|
76
|
+
- **Orphaned-agent recovery (P2.5 / TH-D5 consumer side).** When a worker is
|
|
77
|
+
confirmed dead with nothing recoverable, the coordinator applies
|
|
78
|
+
`recovery_policy` (new coordinator-side config, NOT part of the wire
|
|
79
|
+
snapshot; default `drain`, one shared `recovery_drain_timeout_s` deadline,
|
|
80
|
+
default 600s) to agent processes the dead worker's harness run left behind,
|
|
81
|
+
via team-harness's process reaper: drained agents finish and their repo
|
|
82
|
+
edits survive; a `salvage.json` in the interrupted iteration directory
|
|
83
|
+
records what was handled; the history entry is `abandoned_after_<policy>`
|
|
84
|
+
when anything settled. Recovery runs OUTSIDE the state lock (`loopy
|
|
85
|
+
status`/`stop` stay usable while draining), validates that each discovered
|
|
86
|
+
run record actually belongs to the iteration, is same-host-only (a worker
|
|
87
|
+
identity from another hostname skips reaping), and refuses to dispatch
|
|
88
|
+
replacement work (HTTP 409) when any orphan may still be running or when
|
|
89
|
+
team-harness's parent-liveness guard reports the run's owner alive.
|
|
90
|
+
Requires team-harness with the process reaper (> 0.2.10); older versions
|
|
91
|
+
skip orphan recovery gracefully.
|
|
92
|
+
- A stale `/finished` from a **different identified worker** now gets HTTP 409
|
|
93
|
+
instead of a second copy of the live task; unknown identities keep the
|
|
94
|
+
pre-existing stale-retry behavior. State-lock contention surfaces as a clean
|
|
95
|
+
HTTP 503 (and friendly CLI errors) instead of raw tracebacks.
|
|
96
|
+
- The bundled worker uses an unbounded read timeout on `/register` only
|
|
97
|
+
(recovery can legitimately block registration up to the drain deadline),
|
|
98
|
+
keeps the bounded timeout on `/finished`, and exits with code 3 on a 409.
|
|
99
|
+
|
|
100
|
+
## 0.2.1
|
|
101
|
+
|
|
102
|
+
- Improve README onboarding, install, initialization, configuration, and logging docs.
|
|
103
|
+
- Update package and skill descriptions to avoid unclear repository-scope jargon.
|
|
104
|
+
|
|
105
|
+
## 0.2.0 (breaking)
|
|
106
|
+
|
|
107
|
+
**Breaking API change — drop leases, polling, and worker identity.**
|
|
108
|
+
|
|
109
|
+
The three-endpoint API (`POST /workers/register`, `POST /workers/{worker_id}/next`,
|
|
110
|
+
`POST /workers/{worker_id}/finished`) is replaced by a two-endpoint ping-pong API:
|
|
111
|
+
|
|
112
|
+
- `POST /register` — returns `TaskResponse` directly (`action: "run"` or `"stop"`).
|
|
113
|
+
- `POST /finished` — returns `TaskResponse` for the next task or stop signal.
|
|
114
|
+
|
|
115
|
+
The `"wait"` action is gone. The worker loop is now a simple `while task.action == "run"`
|
|
116
|
+
loop with no polling.
|
|
117
|
+
|
|
118
|
+
**Removed models and fields:**
|
|
119
|
+
- `WorkerState`, `ActiveAssignment`, `RegisterWorkerResponse`, `NextActionResponse` removed.
|
|
120
|
+
- `assignment_id` removed from `FinishedRequest` and `HistoryEntry`.
|
|
121
|
+
- `worker_id` removed from `HistoryEntry`.
|
|
122
|
+
- `workers` dict and `active_assignment` removed from `LoopState`.
|
|
123
|
+
- Constants `DEFAULT_LEASE_SECONDS`, `DEFAULT_POLL_INTERVAL_SECONDS`,
|
|
124
|
+
`DEFAULT_FINISHED_RETRY_ATTEMPTS`, `DEFAULT_FINISHED_RETRY_BACKOFF_SECONDS`,
|
|
125
|
+
`WAIT_ACTION` removed.
|
|
126
|
+
|
|
127
|
+
**Added:**
|
|
128
|
+
- `CurrentTask` model replaces `ActiveAssignment` (no `worker_id`, no lease).
|
|
129
|
+
- `TaskResponse` model returned by both `/register` and `/finished`.
|
|
130
|
+
- `LoopState.current_task: CurrentTask | None` replaces `active_assignment`.
|
|
131
|
+
|
|
132
|
+
**Crash recovery:** If a worker crashes mid-run, the next `/register` call detects the
|
|
133
|
+
orphaned `current_task`, records it in history as `error="abandoned"`, and dispatches
|
|
134
|
+
fresh work.
|
|
135
|
+
|
|
136
|
+
**Migration note:** Existing `state.json` files containing `active_assignment` or `workers`
|
|
137
|
+
fields will have those fields silently dropped on first read (Pydantic ignores unknown
|
|
138
|
+
fields). Any in-flight run at upgrade time will lose its `active_assignment` record without
|
|
139
|
+
a history entry. Drain all workers before deploying this version.
|
|
140
|
+
|
|
141
|
+
## 0.1.0
|
|
142
|
+
|
|
143
|
+
- Initial release.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
The working agreement for this repo lives in **[`AGENTS.md`](./AGENTS.md)** — read it first.
|
|
4
|
+
It is the single source; this file only points to it so the two never diverge.
|
|
5
|
+
|
|
6
|
+
Quick orientation for what most often trips up a change here:
|
|
7
|
+
|
|
8
|
+
- **`design/decisions.md`** is the canonical decision log. Several decisions look like
|
|
9
|
+
defects if you only skim the code and are recorded so they don't get "fixed" by accident
|
|
10
|
+
(notably **D3** iteration-success semantics and **D4** LLM-as-judge evaluation).
|
|
11
|
+
- **Autonomy is the goal; the human escape hatch is a last resort (D5).** The system runs
|
|
12
|
+
unattended. When a workflow hits a genuinely terminal blocker it stops by writing
|
|
13
|
+
`control.json` with `stop_reason: "unresolvable_error"` and a specific recorded reason —
|
|
14
|
+
that is the entire human-in-the-loop mechanism. Do **not** build or assume a preferred
|
|
15
|
+
`paused` / `waiting_for_human` gate; exhaust autonomous options first, then stop cleanly.
|
|
16
|
+
|
|
17
|
+
See `AGENTS.md` Rules 1–3 for the full agreement.
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
## Start Claude Code with MCP configuration
|
|
8
8
|
claude:
|
|
9
9
|
@test -f .mcp.json || (echo "Error: .mcp.json not found. Please create it first." && exit 1)
|
|
10
|
-
|
|
10
|
+
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 CLAUDE_CODE_ENABLE_AUTO_MODE=1 claude --dangerously-skip-permissions --mcp-config .mcp.json --remote-control
|
|
11
11
|
|
|
12
12
|
## Continue previous Claude Code session
|
|
13
13
|
claudecontinue:
|
|
14
14
|
@test -f .mcp.json || (echo "Error: .mcp.json not found. Please create it first." && exit 1)
|
|
15
|
-
|
|
15
|
+
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 CLAUDE_CODE_ENABLE_AUTO_MODE=1 claude --dangerously-skip-permissions --mcp-config .mcp.json --continue --remote-control
|
|
16
16
|
|
|
17
17
|
## Codex
|
|
18
18
|
codex:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: loopy-loop
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Run long-running AI agent workflows inside your repository.
|
|
5
5
|
Project-URL: Homepage, https://github.com/writeitai/loopy-loop
|
|
6
6
|
Project-URL: Repository, https://github.com/writeitai/loopy-loop
|
|
@@ -29,7 +29,7 @@ Requires-Dist: httpx>=0.28
|
|
|
29
29
|
Requires-Dist: pydantic>=2.11
|
|
30
30
|
Requires-Dist: pyyaml>=6.0.2
|
|
31
31
|
Requires-Dist: rich>=14.0
|
|
32
|
-
Requires-Dist: team-harness>=0.
|
|
32
|
+
Requires-Dist: team-harness>=0.3.0
|
|
33
33
|
Requires-Dist: uvicorn[standard]>=0.32.0
|
|
34
34
|
Provides-Extra: dev
|
|
35
35
|
Requires-Dist: pyright>=1.1; extra == 'dev'
|
|
@@ -40,6 +40,10 @@ Description-Content-Type: text/markdown
|
|
|
40
40
|
|
|
41
41
|
# loopy-loop
|
|
42
42
|
|
|
43
|
+
<p align="center">
|
|
44
|
+
<img src="docs/images/logo.png" alt="loopy-loop logo" width="400">
|
|
45
|
+
</p>
|
|
46
|
+
|
|
43
47
|
`loopy-loop` runs long-running AI agent workflows inside your repository.
|
|
44
48
|
It turns a goal file in your repository into an inspectable sequence of agent
|
|
45
49
|
iterations: plan, implement, evaluate, record evidence, and continue until the
|
|
@@ -52,8 +56,8 @@ logs. You can pause, resume, audit what happened, adjust the goal, inspect every
|
|
|
52
56
|
prompt/result pair, and keep the actual project changes in normal git branches
|
|
53
57
|
and PRs.
|
|
54
58
|
|
|
55
|
-
Under the hood, loopy-loop runs a small FastAPI coordinator and
|
|
56
|
-
|
|
59
|
+
Under the hood, loopy-loop runs a small FastAPI coordinator and a single
|
|
60
|
+
worker. The coordinator owns the loop state and chooses the next workflow. The
|
|
57
61
|
workers run assignments through
|
|
58
62
|
[`team-harness`](https://github.com/writeitai/team-harness), which can delegate
|
|
59
63
|
to agent CLIs such as Codex, Claude Code, and Gemini. The packaged
|
|
@@ -269,6 +273,12 @@ Important rules:
|
|
|
269
273
|
- `team_harness_max_retries`, `team_harness_retry_base_delay_s`, and
|
|
270
274
|
`team_harness_retry_max_delay_s` are optional retry controls for transient
|
|
271
275
|
team-harness API/network errors.
|
|
276
|
+
- `recovery_policy` (`drain` by default, or `reap`) and
|
|
277
|
+
`recovery_drain_timeout_s` control what crash recovery does with agent
|
|
278
|
+
processes a dead worker left running: drain lets them finish (one shared
|
|
279
|
+
bounded deadline) before the iteration is re-run; reap kills them
|
|
280
|
+
immediately. These are coordinator-side settings and are not part of the
|
|
281
|
+
config snapshot sent to workers.
|
|
272
282
|
|
|
273
283
|
Workflow config lives beside each workflow prompt:
|
|
274
284
|
|
|
@@ -474,7 +484,8 @@ loopy init [--template default|inner_outer_eval|pm_planner_dispatcher]
|
|
|
474
484
|
Scaffolds loopy-loop files. The default template creates only the reserved
|
|
475
485
|
`goal_check` workflow. `inner_outer_eval` creates the recommended outer/inner/eval
|
|
476
486
|
workflow set. `pm_planner_dispatcher` creates planner/dispatcher workflows for
|
|
477
|
-
child-session orchestration
|
|
487
|
+
child-session orchestration — and also ships the `inner_outer_eval` child set
|
|
488
|
+
its dispatcher spawns, so a clean init is executable end to end.
|
|
478
489
|
|
|
479
490
|
```bash
|
|
480
491
|
loopy coordinator --host 0.0.0.0 --port 8080 [--resume] [--workflow-set NAME] [--goal-file PATH]
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# loopy-loop
|
|
2
2
|
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="docs/images/logo.png" alt="loopy-loop logo" width="400">
|
|
5
|
+
</p>
|
|
6
|
+
|
|
3
7
|
`loopy-loop` runs long-running AI agent workflows inside your repository.
|
|
4
8
|
It turns a goal file in your repository into an inspectable sequence of agent
|
|
5
9
|
iterations: plan, implement, evaluate, record evidence, and continue until the
|
|
@@ -12,8 +16,8 @@ logs. You can pause, resume, audit what happened, adjust the goal, inspect every
|
|
|
12
16
|
prompt/result pair, and keep the actual project changes in normal git branches
|
|
13
17
|
and PRs.
|
|
14
18
|
|
|
15
|
-
Under the hood, loopy-loop runs a small FastAPI coordinator and
|
|
16
|
-
|
|
19
|
+
Under the hood, loopy-loop runs a small FastAPI coordinator and a single
|
|
20
|
+
worker. The coordinator owns the loop state and chooses the next workflow. The
|
|
17
21
|
workers run assignments through
|
|
18
22
|
[`team-harness`](https://github.com/writeitai/team-harness), which can delegate
|
|
19
23
|
to agent CLIs such as Codex, Claude Code, and Gemini. The packaged
|
|
@@ -229,6 +233,12 @@ Important rules:
|
|
|
229
233
|
- `team_harness_max_retries`, `team_harness_retry_base_delay_s`, and
|
|
230
234
|
`team_harness_retry_max_delay_s` are optional retry controls for transient
|
|
231
235
|
team-harness API/network errors.
|
|
236
|
+
- `recovery_policy` (`drain` by default, or `reap`) and
|
|
237
|
+
`recovery_drain_timeout_s` control what crash recovery does with agent
|
|
238
|
+
processes a dead worker left running: drain lets them finish (one shared
|
|
239
|
+
bounded deadline) before the iteration is re-run; reap kills them
|
|
240
|
+
immediately. These are coordinator-side settings and are not part of the
|
|
241
|
+
config snapshot sent to workers.
|
|
232
242
|
|
|
233
243
|
Workflow config lives beside each workflow prompt:
|
|
234
244
|
|
|
@@ -434,7 +444,8 @@ loopy init [--template default|inner_outer_eval|pm_planner_dispatcher]
|
|
|
434
444
|
Scaffolds loopy-loop files. The default template creates only the reserved
|
|
435
445
|
`goal_check` workflow. `inner_outer_eval` creates the recommended outer/inner/eval
|
|
436
446
|
workflow set. `pm_planner_dispatcher` creates planner/dispatcher workflows for
|
|
437
|
-
child-session orchestration
|
|
447
|
+
child-session orchestration — and also ships the `inner_outer_eval` child set
|
|
448
|
+
its dispatcher spawns, so a clean init is executable end to end.
|
|
438
449
|
|
|
439
450
|
```bash
|
|
440
451
|
loopy coordinator --host 0.0.0.0 --port 8080 [--resume] [--workflow-set NAME] [--goal-file PATH]
|