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.
Files changed (117) hide show
  1. loopy_loop-0.4.0/.github/workflows/docs-deploy.yml +66 -0
  2. loopy_loop-0.4.0/AGENTS.md +80 -0
  3. loopy_loop-0.4.0/CHANGELOG.md +143 -0
  4. loopy_loop-0.4.0/CLAUDE.md +17 -0
  5. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/Makefile +2 -2
  6. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/PKG-INFO +16 -5
  7. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/README.md +14 -3
  8. loopy_loop-0.4.0/design/analysis/codex_parallel_analysis.md +665 -0
  9. loopy_loop-0.4.0/design/analysis/loopy-loop-state-and-roadmap.md +445 -0
  10. loopy_loop-0.4.0/design/analysis/synthesis.md +274 -0
  11. loopy_loop-0.4.0/design/decisions.md +205 -0
  12. loopy_loop-0.4.0/design/designs/documentation-site.md +302 -0
  13. loopy_loop-0.4.0/design/designs/improvement-proposals.md +385 -0
  14. loopy_loop-0.4.0/design/designs/success-semantics-and-evaluation.md +189 -0
  15. loopy_loop-0.4.0/docs/http-contract.md +178 -0
  16. loopy_loop-0.4.0/docs/images/logo.png +0 -0
  17. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/docs/session-layout.md +38 -0
  18. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/pyproject.toml +2 -2
  19. loopy_loop-0.4.0/skills-lock.json +16 -0
  20. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/cli.py +37 -2
  21. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/config.py +18 -0
  22. loopy_loop-0.4.0/src/loopy_loop/coordinator_app.py +1181 -0
  23. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/harness_runner.py +13 -9
  24. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/models.py +45 -0
  25. loopy_loop-0.4.0/src/loopy_loop/recovery.py +261 -0
  26. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/sessions.py +28 -0
  27. {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
  28. {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
  29. {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
  30. {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
  31. {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
  32. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/templates/pm_planner_dispatcher/loopy_loop_config.yaml +7 -4
  33. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/worker.py +48 -7
  34. loopy_loop-0.4.0/src/loopy_loop/worker_identity.py +102 -0
  35. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/tests/test_cli.py +105 -0
  36. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/tests/test_coordinator_app.py +32 -21
  37. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/tests/test_idempotent_finished.py +10 -3
  38. loopy_loop-0.4.0/src/tests/test_session_stack_recovery.py +824 -0
  39. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/tests/test_worker.py +8 -2
  40. loopy_loop-0.4.0/src/tests/test_worker_liveness_recovery.py +728 -0
  41. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/uv.lock +5 -5
  42. loopy_loop-0.4.0/website/.gitignore +25 -0
  43. loopy_loop-0.4.0/website/README.md +71 -0
  44. loopy_loop-0.4.0/website/mdx-components.tsx +36 -0
  45. loopy_loop-0.4.0/website/next.config.ts +26 -0
  46. loopy_loop-0.4.0/website/package-lock.json +4779 -0
  47. loopy_loop-0.4.0/website/package.json +41 -0
  48. loopy_loop-0.4.0/website/postcss.config.mjs +8 -0
  49. loopy_loop-0.4.0/website/public/.nojekyll +0 -0
  50. loopy_loop-0.4.0/website/public/CNAME +1 -0
  51. loopy_loop-0.4.0/website/src/app/docs/child-sessions/page.mdx +135 -0
  52. loopy_loop-0.4.0/website/src/app/docs/cli-reference/page.mdx +104 -0
  53. loopy_loop-0.4.0/website/src/app/docs/concepts/page.mdx +63 -0
  54. loopy_loop-0.4.0/website/src/app/docs/configuration/page.mdx +118 -0
  55. loopy_loop-0.4.0/website/src/app/docs/evaluation/page.mdx +110 -0
  56. loopy_loop-0.4.0/website/src/app/docs/getting-started/page.mdx +122 -0
  57. loopy_loop-0.4.0/website/src/app/docs/http-contract/page.mdx +227 -0
  58. loopy_loop-0.4.0/website/src/app/docs/layout.tsx +46 -0
  59. loopy_loop-0.4.0/website/src/app/docs/page.mdx +37 -0
  60. loopy_loop-0.4.0/website/src/app/docs/session-layout/page.mdx +211 -0
  61. loopy_loop-0.4.0/website/src/app/docs/success-and-control/page.mdx +172 -0
  62. loopy_loop-0.4.0/website/src/app/docs/troubleshooting/page.mdx +186 -0
  63. loopy_loop-0.4.0/website/src/app/docs/workflows/page.mdx +126 -0
  64. loopy_loop-0.4.0/website/src/app/globals.css +166 -0
  65. loopy_loop-0.4.0/website/src/app/layout.tsx +46 -0
  66. loopy_loop-0.4.0/website/src/app/page.tsx +49 -0
  67. loopy_loop-0.4.0/website/src/components/docs/DocsPageNavigation.tsx +42 -0
  68. loopy_loop-0.4.0/website/src/components/docs/DocsSidebar.tsx +62 -0
  69. loopy_loop-0.4.0/website/src/components/docs/DocsTableOfContents.tsx +81 -0
  70. loopy_loop-0.4.0/website/src/components/docs/MobileDocsSidebar.tsx +54 -0
  71. loopy_loop-0.4.0/website/src/components/search/SearchCommand.tsx +181 -0
  72. loopy_loop-0.4.0/website/src/components/site/SiteHeader.tsx +38 -0
  73. loopy_loop-0.4.0/website/src/lib/docs/navigation.ts +64 -0
  74. loopy_loop-0.4.0/website/src/lib/search/pagefind.ts +86 -0
  75. loopy_loop-0.4.0/website/src/lib/utils.ts +7 -0
  76. loopy_loop-0.4.0/website/tsconfig.json +42 -0
  77. loopy_loop-0.2.1/CHANGELOG.md +0 -46
  78. loopy_loop-0.2.1/docs/http-contract.md +0 -106
  79. loopy_loop-0.2.1/src/loopy_loop/coordinator_app.py +0 -706
  80. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/.eval-banana/config.toml +0 -0
  81. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/.github/workflows/ci.yml +0 -0
  82. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/.github/workflows/release.yml +0 -0
  83. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/.gitignore +0 -0
  84. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/.team-harness/config.toml +0 -0
  85. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/.team-harness/coordinator_system_message.md +0 -0
  86. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/.team-harness/worker_footer.md +0 -0
  87. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/.team-harness/worker_suffix.md +0 -0
  88. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/LICENSE +0 -0
  89. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/skills/loopy-loop/SKILL.md +0 -0
  90. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/__init__.py +0 -0
  91. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/scheduler.py +0 -0
  92. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/state_store.py +0 -0
  93. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/templates/inner_outer_eval/.gitignore +0 -0
  94. {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
  95. {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
  96. {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
  97. {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
  98. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/templates/inner_outer_eval/loopy_loop_config.yaml +0 -0
  99. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/templates/inner_outer_eval/loopy_loop_goal.txt +0 -0
  100. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/templates/pm_planner_dispatcher/.gitignore +0 -0
  101. {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
  102. {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
  103. {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
  104. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/loopy_loop/templates/pm_planner_dispatcher/loopy_loop_goal.txt +0 -0
  105. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/tests/__init__.py +0 -0
  106. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/tests/conftest.py +0 -0
  107. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/tests/test_api_base_normalization.py +0 -0
  108. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/tests/test_config.py +0 -0
  109. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/tests/test_examples.py +0 -0
  110. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/tests/test_fresh_run_archive.py +0 -0
  111. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/tests/test_goal_check_gate.py +0 -0
  112. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/tests/test_goal_hash_derivation.py +0 -0
  113. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/tests/test_harness_runner.py +0 -0
  114. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/tests/test_must_follow_success.py +0 -0
  115. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/tests/test_scheduler.py +0 -0
  116. {loopy_loop-0.2.1 → loopy_loop-0.4.0}/src/tests/test_sessions.py +0 -0
  117. {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
- PATH="$(HOME)/.bun/bin:$(PATH)" CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 TELEGRAM_STATE_DIR=$(PWD)/.claude/channels/telegram claude --dangerously-skip-permissions --mcp-config .mcp.json --remote-control
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
- PATH="$(HOME)/.bun/bin:$(PATH)" CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 TELEGRAM_STATE_DIR=$(PWD)/.claude/channels/telegram claude --dangerously-skip-permissions --mcp-config .mcp.json --continue --remote-control
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.2.1
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.2.10
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 one or more
56
- workers. The coordinator owns the loop state and chooses the next workflow. The
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 one or more
16
- workers. The coordinator owns the loop state and chooses the next workflow. The
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]