loopy-loop 0.3.0__tar.gz → 0.5.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 (120) hide show
  1. loopy_loop-0.5.0/CHANGELOG.md +206 -0
  2. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/Makefile +1 -1
  3. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/PKG-INFO +56 -16
  4. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/README.md +54 -15
  5. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/design/designs/improvement-proposals.md +77 -36
  6. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/docs/http-contract.md +32 -1
  7. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/docs/session-layout.md +37 -2
  8. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/pyproject.toml +2 -1
  9. loopy_loop-0.5.0/skills/loopy-loop/SKILL.md +575 -0
  10. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/loopy_loop/cli.py +221 -14
  11. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/loopy_loop/config.py +57 -0
  12. loopy_loop-0.5.0/src/loopy_loop/coordinator_app.py +1472 -0
  13. loopy_loop-0.5.0/src/loopy_loop/events.py +83 -0
  14. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/loopy_loop/harness_runner.py +75 -10
  15. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/loopy_loop/models.py +94 -0
  16. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/loopy_loop/recovery.py +2 -3
  17. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/loopy_loop/sessions.py +28 -0
  18. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/eval_reviewer/prompt.txt +4 -2
  19. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/eval_runner/prompt.txt +14 -4
  20. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/inner/prompt.txt +3 -1
  21. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/outer/prompt.txt +15 -4
  22. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/loopy_loop/templates/pm_planner_dispatcher/.loopy_loop/workflow_sets/pm_planner_dispatcher/workflows/dispatcher/prompt.txt +12 -1
  23. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/loopy_loop/templates/pm_planner_dispatcher/.loopy_loop/workflow_sets/pm_planner_dispatcher/workflows/planner/prompt.txt +8 -0
  24. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/loopy_loop/templates/pm_planner_dispatcher/loopy_loop_config.yaml +7 -4
  25. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/loopy_loop/worker.py +131 -11
  26. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/tests/test_cli.py +105 -0
  27. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/tests/test_coordinator_app.py +7 -0
  28. loopy_loop-0.5.0/src/tests/test_events_and_usage.py +622 -0
  29. loopy_loop-0.5.0/src/tests/test_failure_taxonomy.py +488 -0
  30. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/tests/test_idempotent_finished.py +3 -0
  31. loopy_loop-0.5.0/src/tests/test_session_stack_recovery.py +824 -0
  32. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/tests/test_worker.py +80 -3
  33. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/uv.lock +17 -1
  34. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/src/app/docs/cli-reference/page.mdx +1 -1
  35. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/src/app/docs/http-contract/page.mdx +12 -0
  36. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/src/app/docs/session-layout/page.mdx +9 -0
  37. loopy_loop-0.3.0/CHANGELOG.md +0 -88
  38. loopy_loop-0.3.0/skills/loopy-loop/SKILL.md +0 -379
  39. loopy_loop-0.3.0/src/loopy_loop/coordinator_app.py +0 -900
  40. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/.eval-banana/config.toml +0 -0
  41. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/.github/workflows/ci.yml +0 -0
  42. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/.github/workflows/docs-deploy.yml +0 -0
  43. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/.github/workflows/release.yml +0 -0
  44. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/.gitignore +0 -0
  45. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/.team-harness/config.toml +0 -0
  46. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/.team-harness/coordinator_system_message.md +0 -0
  47. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/.team-harness/worker_footer.md +0 -0
  48. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/.team-harness/worker_suffix.md +0 -0
  49. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/AGENTS.md +0 -0
  50. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/CLAUDE.md +0 -0
  51. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/LICENSE +0 -0
  52. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/design/analysis/codex_parallel_analysis.md +0 -0
  53. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/design/analysis/loopy-loop-state-and-roadmap.md +0 -0
  54. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/design/analysis/synthesis.md +0 -0
  55. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/design/decisions.md +0 -0
  56. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/design/designs/documentation-site.md +0 -0
  57. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/design/designs/success-semantics-and-evaluation.md +0 -0
  58. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/docs/images/logo.png +0 -0
  59. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/skills-lock.json +0 -0
  60. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/loopy_loop/__init__.py +0 -0
  61. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/loopy_loop/scheduler.py +0 -0
  62. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/loopy_loop/state_store.py +0 -0
  63. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/loopy_loop/templates/inner_outer_eval/.gitignore +0 -0
  64. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/eval_reviewer/config.yaml +0 -0
  65. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/eval_runner/config.yaml +0 -0
  66. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/inner/config.yaml +0 -0
  67. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/outer/config.yaml +0 -0
  68. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/loopy_loop/templates/inner_outer_eval/loopy_loop_config.yaml +0 -0
  69. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/loopy_loop/templates/inner_outer_eval/loopy_loop_goal.txt +0 -0
  70. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/loopy_loop/templates/pm_planner_dispatcher/.gitignore +0 -0
  71. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/loopy_loop/templates/pm_planner_dispatcher/.loopy_loop/workflow_sets/pm_planner_dispatcher/workflows/dispatcher/config.yaml +0 -0
  72. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/loopy_loop/templates/pm_planner_dispatcher/.loopy_loop/workflow_sets/pm_planner_dispatcher/workflows/planner/config.yaml +0 -0
  73. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/loopy_loop/templates/pm_planner_dispatcher/loopy_loop_goal.txt +0 -0
  74. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/loopy_loop/worker_identity.py +0 -0
  75. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/tests/__init__.py +0 -0
  76. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/tests/conftest.py +0 -0
  77. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/tests/test_api_base_normalization.py +0 -0
  78. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/tests/test_config.py +0 -0
  79. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/tests/test_examples.py +0 -0
  80. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/tests/test_fresh_run_archive.py +0 -0
  81. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/tests/test_goal_check_gate.py +0 -0
  82. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/tests/test_goal_hash_derivation.py +0 -0
  83. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/tests/test_harness_runner.py +0 -0
  84. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/tests/test_must_follow_success.py +0 -0
  85. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/tests/test_scheduler.py +0 -0
  86. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/tests/test_sessions.py +0 -0
  87. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/tests/test_state_store.py +0 -0
  88. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/src/tests/test_worker_liveness_recovery.py +0 -0
  89. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/.gitignore +0 -0
  90. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/README.md +0 -0
  91. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/mdx-components.tsx +0 -0
  92. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/next.config.ts +0 -0
  93. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/package-lock.json +0 -0
  94. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/package.json +0 -0
  95. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/postcss.config.mjs +0 -0
  96. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/public/.nojekyll +0 -0
  97. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/public/CNAME +0 -0
  98. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/src/app/docs/child-sessions/page.mdx +0 -0
  99. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/src/app/docs/concepts/page.mdx +0 -0
  100. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/src/app/docs/configuration/page.mdx +0 -0
  101. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/src/app/docs/evaluation/page.mdx +0 -0
  102. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/src/app/docs/getting-started/page.mdx +0 -0
  103. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/src/app/docs/layout.tsx +0 -0
  104. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/src/app/docs/page.mdx +0 -0
  105. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/src/app/docs/success-and-control/page.mdx +0 -0
  106. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/src/app/docs/troubleshooting/page.mdx +0 -0
  107. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/src/app/docs/workflows/page.mdx +0 -0
  108. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/src/app/globals.css +0 -0
  109. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/src/app/layout.tsx +0 -0
  110. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/src/app/page.tsx +0 -0
  111. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/src/components/docs/DocsPageNavigation.tsx +0 -0
  112. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/src/components/docs/DocsSidebar.tsx +0 -0
  113. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/src/components/docs/DocsTableOfContents.tsx +0 -0
  114. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/src/components/docs/MobileDocsSidebar.tsx +0 -0
  115. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/src/components/search/SearchCommand.tsx +0 -0
  116. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/src/components/site/SiteHeader.tsx +0 -0
  117. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/src/lib/docs/navigation.ts +0 -0
  118. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/src/lib/search/pagefind.ts +0 -0
  119. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/src/lib/utils.ts +0 -0
  120. {loopy_loop-0.3.0 → loopy_loop-0.5.0}/website/tsconfig.json +0 -0
@@ -0,0 +1,206 @@
1
+ # Changelog
2
+
3
+ ## 0.5.0
4
+
5
+ - **events.jsonl is now written (P1.1).** The coordinator appends one
6
+ versioned JSON event per significant transition — `session_started`,
7
+ `task_dispatched`, `task_finished` (with tokens/duration/failure_kind),
8
+ `iteration_abandoned`, `goal_check`, `child_started`, `child_finished`,
9
+ `session_stopped` — to each session's `events.jsonl` after the producing
10
+ state mutation commits. Delivery is best-effort by design: a crash in the
11
+ commit-to-append window drops the event while the durable truth
12
+ (`state.json` history/ledger) survives; readers key on `event_id` and
13
+ tolerate gaps and a torn final line. New `loopy events [--follow]
14
+ [--json]` tails the deepest active session's stream and follows the
15
+ active session as it changes.
16
+ - **Usage/cost ledger + `max_cost_usd` (P1.1).** The worker reads
17
+ coordinator-model token usage from team-harness's `run.json` and reports
18
+ it (plus wall-clock duration) on `/finished`; the coordinator keeps a
19
+ durable per-session ledger in `state.json` and records a finalized
20
+ child's totals on its `children.json` record. With the new optional
21
+ `model_prices` (USD per 1M tokens, coordinator-side), `loopy status`
22
+ shows estimated cost and the new optional `max_cost_usd` budget stops
23
+ the loop with `stop_reason="max_cost_usd"`. Cost explicitly covers the
24
+ harness coordinator model only — agent-CLI subprocess spend is not
25
+ measurable and is never pretended into the number.
26
+ - **`loopy status` shows the session stack and `--watch`.** While a child
27
+ session runs, `status` now walks the durable parent→child pointers and
28
+ shows the live child (previously it showed only the suspended parent);
29
+ `--watch` re-renders every 2 seconds.
30
+
31
+ - **Failure taxonomy + per-workflow failure cap (P2.3).** Failed iterations
32
+ now record a `failure_kind` — `transient` (provider said retry;
33
+ team-harness's own retries were exhausted), `deterministic` (auth/config
34
+ errors retries cannot fix), `crash` (worker died mid-iteration), or
35
+ `unknown` — on `result.json`, `/finished`, and session history. A new
36
+ coordinator-side `workflow_consecutive_failures_cap` (default 5) stops the
37
+ loop with `stop_reason="workflow_failure_cap"` when the same workflow fails
38
+ that many iterations in a row (crash-abandoned iterations included; any
39
+ success resets the workflow's counter) instead of burning the remaining
40
+ `max_turns` on a wedged workflow. The cap is not part of the wire config
41
+ snapshot, so released workers are unaffected.
42
+ - **Agent Skill rewritten for the current API (P1.3).** `skills/loopy-loop/SKILL.md`
43
+ still described the removed pre-0.2.0 surface (top-level `.loopy_loop/state.json`,
44
+ polling multi-worker model, inline `goal`, `.loopy_loop/workflows/<id>/` layout) and
45
+ would have taught agents to generate broken setups. It now documents the 0.4.0
46
+ reality: `goal_file`, workflow sets, all three templates, the single
47
+ identity-verified worker, child sessions, crash recovery, and resume semantics —
48
+ validated against clean `loopy init` runs of every template. Remaining plural
49
+ "workers" wording in the README corrected to the single-worker model.
50
+ - **eval-banana is now a hard dependency.** The recommended
51
+ `inner_outer_eval` template (and the PM template's child sessions) shell
52
+ out to the `eval-banana` CLI; previously it had to be installed
53
+ separately, so a fresh install could dispatch eval workflows against a
54
+ missing tool. It now installs with loopy-loop — no extra, no preflight.
55
+ - **Worker appends its bundled-CLI scripts directory to `PATH`.** Harness
56
+ agents inherit the worker's environment, but under `uv tool install` /
57
+ `pipx` only loopy-loop's own entry points are exposed on `PATH`, so
58
+ dependency CLIs such as `eval-banana` were invisible to agents. The
59
+ directory is derived from where eval-banana's script was actually
60
+ installed (its package record; `sysconfig` only as fallback — the default
61
+ scheme is wrong for e.g. `pip install --user` under a system
62
+ interpreter). It is appended, never prepended, and existing `PATH`
63
+ entries are preserved verbatim: the target repo's own `python`/tooling
64
+ and any eval-banana already on `PATH` keep winning.
65
+
66
+ ## 0.4.0 (breaking)
67
+
68
+ **Breaking API interaction — completions must echo the dispatched
69
+ `attempt_id`.** Every dispatched task now carries an `attempt_id` that the
70
+ worker must echo on `/finished`; a completion without the live task's attempt
71
+ is treated as stale (this is what fences superseded work). A 0.3.0 worker
72
+ therefore cannot complete tasks against a 0.4.0 coordinator — upgrade workers
73
+ and coordinator together (they normally ship in the same install).
74
+
75
+ - **Durable session-stack recovery (P0.1).** While a child session runs, the
76
+ parent's `state.json` records `active_child_session_id`; on `--resume` the
77
+ coordinator walks the pointer chain to the deepest live session instead of
78
+ silently reopening the parent and orphaning the running child. Terminal
79
+ children found at startup are finalized (children.json completed, pointer
80
+ cleared) and their parent resumed. Every interrupted-dispatch crash window
81
+ reconciles deterministically: dangling pointers are cleared, a fully
82
+ created child whose parent commit never landed is adopted, and leftover
83
+ request files never dispatch twice (children.json records the originating
84
+ `request_file`). Invalid child requests are terminally rejected
85
+ (`*.json.rejected`) instead of being re-read forever.
86
+ - **Attempt ids.** Every dispatched task carries a unique `attempt_id`
87
+ (also on the wire in `TaskResponse`, echoed in `FinishedRequest`); a late
88
+ `/finished` from a superseded attempt of the same coordinates is treated as
89
+ stale rather than recorded as the current result.
90
+ - Iteration artifacts (`result.json`, `result_text.txt`, `prompt.txt`,
91
+ `harness_run_id.txt`, `pending_finished_request.json`), `children.json`,
92
+ and `salvage.json` are all written atomically (unique temp + rename) — a
93
+ crash can never leave a truncated recovery artifact.
94
+ - Internal: the three duplicated dispatch blocks in the coordinator collapsed
95
+ into one `_advance()` step (stop checks → child dispatch → next workflow →
96
+ stamped task), so they can no longer drift apart. `_advance()` also enforces
97
+ the suspended-parent invariant: a parent with a live child can never acquire
98
+ its own task (a duplicate `/finished` retry gets the child's live task
99
+ instead), and a coordinator-level transition lock serializes cross-store
100
+ handoffs.
101
+ - Review hardening (adversarial Codex review of the above): request-file
102
+ tombstones apply only to RUNNING child records (a completed child's
103
+ filename is reusable for new work); the children.json record lands BEFORE
104
+ the child state so an interrupted dispatch is always discoverable
105
+ (`failed_dispatch` + exactly-once redispatch); startup reconciles every
106
+ running-projected record (terminal children finalize even without a
107
+ pointer); the first child task carries an attempt id; attempt checks are
108
+ strict whenever the live task has one (including `result.json` provenance —
109
+ a stale artifact can no longer complete a new attempt); semantically
110
+ unusable child requests (unknown workflow set, no eligible workflow) are
111
+ terminally rejected instead of wedging every completion; packaged prompts
112
+ instruct atomic control/goal_check publication; the crash model (process
113
+ crash, no fsync) is documented.
114
+ - **The `pm_planner_dispatcher` template is executable from a clean init
115
+ (P0.4).** `loopy init --template pm_planner_dispatcher` now also ships the
116
+ `inner_outer_eval` child workflow set its dispatcher spawns — previously a
117
+ clean init could not execute a single child session. The child set is
118
+ sourced from the `inner_outer_eval` template itself, so the two copies can
119
+ never drift apart.
120
+
121
+ ## 0.3.0 (breaking)
122
+
123
+ **Breaking API change — `/register` requires the worker's process identity.**
124
+ A register without a `worker` object is rejected with HTTP 400. Pre-0.3
125
+ workers cannot register against a 0.3 coordinator; upgrade workers and
126
+ coordinator together (they normally ship in the same install).
127
+
128
+ - **Worker liveness verification (D7).** The worker sends its process
129
+ identity (hostname + pid + a pid-reuse-proof start-time token) with
130
+ `/register` and `/finished`; the coordinator stamps it onto the dispatched
131
+ task. A `/register` while the recorded worker is *verifiably still alive*
132
+ returns HTTP 409 instead of abandoning live work — closing the
133
+ duplicate-work window. Unverifiable identities (remote hosts, or a
134
+ team-harness without process identity) keep the pre-existing
135
+ assume-abandoned recovery behavior. Because every dispatched task now has
136
+ a recorded owner, a stale `/finished` is replayed **only to that owner**
137
+ (anyone else gets HTTP 409) — a task persisted by a pre-identity version
138
+ keeps the legacy replay for that one resume.
139
+ - **Orphaned-agent recovery (P2.5 / TH-D5 consumer side).** When a worker is
140
+ confirmed dead with nothing recoverable, the coordinator applies
141
+ `recovery_policy` (new coordinator-side config, NOT part of the wire
142
+ snapshot; default `drain`, one shared `recovery_drain_timeout_s` deadline,
143
+ default 600s) to agent processes the dead worker's harness run left behind,
144
+ via team-harness's process reaper: drained agents finish and their repo
145
+ edits survive; a `salvage.json` in the interrupted iteration directory
146
+ records what was handled; the history entry is `abandoned_after_<policy>`
147
+ when anything settled. Recovery runs OUTSIDE the state lock (`loopy
148
+ status`/`stop` stay usable while draining), validates that each discovered
149
+ run record actually belongs to the iteration, is same-host-only (a worker
150
+ identity from another hostname skips reaping), and refuses to dispatch
151
+ replacement work (HTTP 409) when any orphan may still be running or when
152
+ team-harness's parent-liveness guard reports the run's owner alive.
153
+ Requires team-harness with the process reaper (> 0.2.10); older versions
154
+ skip orphan recovery gracefully.
155
+ - A stale `/finished` from a **different identified worker** now gets HTTP 409
156
+ instead of a second copy of the live task; unknown identities keep the
157
+ pre-existing stale-retry behavior. State-lock contention surfaces as a clean
158
+ HTTP 503 (and friendly CLI errors) instead of raw tracebacks.
159
+ - The bundled worker uses an unbounded read timeout on `/register` only
160
+ (recovery can legitimately block registration up to the drain deadline),
161
+ keeps the bounded timeout on `/finished`, and exits with code 3 on a 409.
162
+
163
+ ## 0.2.1
164
+
165
+ - Improve README onboarding, install, initialization, configuration, and logging docs.
166
+ - Update package and skill descriptions to avoid unclear repository-scope jargon.
167
+
168
+ ## 0.2.0 (breaking)
169
+
170
+ **Breaking API change — drop leases, polling, and worker identity.**
171
+
172
+ The three-endpoint API (`POST /workers/register`, `POST /workers/{worker_id}/next`,
173
+ `POST /workers/{worker_id}/finished`) is replaced by a two-endpoint ping-pong API:
174
+
175
+ - `POST /register` — returns `TaskResponse` directly (`action: "run"` or `"stop"`).
176
+ - `POST /finished` — returns `TaskResponse` for the next task or stop signal.
177
+
178
+ The `"wait"` action is gone. The worker loop is now a simple `while task.action == "run"`
179
+ loop with no polling.
180
+
181
+ **Removed models and fields:**
182
+ - `WorkerState`, `ActiveAssignment`, `RegisterWorkerResponse`, `NextActionResponse` removed.
183
+ - `assignment_id` removed from `FinishedRequest` and `HistoryEntry`.
184
+ - `worker_id` removed from `HistoryEntry`.
185
+ - `workers` dict and `active_assignment` removed from `LoopState`.
186
+ - Constants `DEFAULT_LEASE_SECONDS`, `DEFAULT_POLL_INTERVAL_SECONDS`,
187
+ `DEFAULT_FINISHED_RETRY_ATTEMPTS`, `DEFAULT_FINISHED_RETRY_BACKOFF_SECONDS`,
188
+ `WAIT_ACTION` removed.
189
+
190
+ **Added:**
191
+ - `CurrentTask` model replaces `ActiveAssignment` (no `worker_id`, no lease).
192
+ - `TaskResponse` model returned by both `/register` and `/finished`.
193
+ - `LoopState.current_task: CurrentTask | None` replaces `active_assignment`.
194
+
195
+ **Crash recovery:** If a worker crashes mid-run, the next `/register` call detects the
196
+ orphaned `current_task`, records it in history as `error="abandoned"`, and dispatches
197
+ fresh work.
198
+
199
+ **Migration note:** Existing `state.json` files containing `active_assignment` or `workers`
200
+ fields will have those fields silently dropped on first read (Pydantic ignores unknown
201
+ fields). Any in-flight run at upgrade time will lose its `active_assignment` record without
202
+ a history entry. Drain all workers before deploying this version.
203
+
204
+ ## 0.1.0
205
+
206
+ - Initial release.
@@ -17,7 +17,7 @@ claudecontinue:
17
17
  ## Codex
18
18
  codex:
19
19
  # do /fast
20
- codex --yolo --model gpt-5.5
20
+ codex --yolo --model gpt-5.6-sol
21
21
 
22
22
  install_globally:
23
23
  uv tool install --editable "$(CURDIR)"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: loopy-loop
3
- Version: 0.3.0
3
+ Version: 0.5.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
@@ -23,6 +23,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
23
  Classifier: Typing :: Typed
24
24
  Requires-Python: >=3.12
25
25
  Requires-Dist: click>=8.1
26
+ Requires-Dist: eval-banana>=0.3.1
26
27
  Requires-Dist: fastapi>=0.115.0
27
28
  Requires-Dist: filelock>=3.16
28
29
  Requires-Dist: httpx>=0.28
@@ -58,12 +59,13 @@ and PRs.
58
59
 
59
60
  Under the hood, loopy-loop runs a small FastAPI coordinator and a single
60
61
  worker. The coordinator owns the loop state and chooses the next workflow. The
61
- workers run assignments through
62
+ worker runs assignments through
62
63
  [`team-harness`](https://github.com/writeitai/team-harness), which can delegate
63
64
  to agent CLIs such as Codex, Claude Code, and Gemini. The packaged
64
65
  `inner_outer_eval` template also uses
65
66
  [`eval-banana`](https://github.com/writeitai/eval-banana) conventions for
66
- session-scoped evaluation checks.
67
+ session-scoped evaluation checks; eval-banana installs automatically as a
68
+ loopy-loop dependency.
67
69
 
68
70
  ## Install
69
71
 
@@ -118,7 +120,8 @@ This is the recommended starting template. It creates:
118
120
  - a `.gitignore` entry for `.loopy_loop/sessions/`
119
121
 
120
122
  `loopy init` is idempotent. It creates missing files and leaves existing files
121
- alone.
123
+ alone — except `.gitignore`, which is updated in place to ensure the sessions
124
+ ignore rule.
122
125
 
123
126
  ## Write the Goal
124
127
 
@@ -244,6 +247,7 @@ goal_file: loopy_loop_goal.txt
244
247
  workflow_set: inner_outer_eval
245
248
  max_turns: 160
246
249
  goal_check_consecutive_failures_cap: 3
250
+ # workflow_consecutive_failures_cap: 5
247
251
  team_harness_provider: "codex"
248
252
  team_harness_model: "gpt-5.5"
249
253
  team_harness_agents:
@@ -278,7 +282,21 @@ Important rules:
278
282
  processes a dead worker left running: drain lets them finish (one shared
279
283
  bounded deadline) before the iteration is re-run; reap kills them
280
284
  immediately. These are coordinator-side settings and are not part of the
281
- config snapshot sent to workers.
285
+ config snapshot sent to the worker.
286
+ - `workflow_consecutive_failures_cap` (default 5) is a per-workflow circuit
287
+ breaker: that many consecutive failed iterations of the same workflow stop
288
+ the loop with `stop_reason="workflow_failure_cap"` instead of retrying a
289
+ wedged workflow until `max_turns`. Any success of the workflow resets its
290
+ counter. Coordinator-side only; not part of the config snapshot sent to
291
+ the worker.
292
+ - `model_prices` (optional, coordinator-side only) sets USD prices per 1M
293
+ tokens for the harness coordinator model (`prompt_usd_per_1m`,
294
+ `completion_usd_per_1m`); with prices set, `loopy status` derives an
295
+ estimated cost from the token ledger. `max_cost_usd` (optional, requires
296
+ `model_prices`) stops the loop with `stop_reason="max_cost_usd"` once the
297
+ session tree's estimated cost reaches the budget. Cost covers the harness
298
+ COORDINATOR model only — agent-CLI subprocesses (codex, claude, gemini)
299
+ bill through their own accounts and are not measurable here.
282
300
 
283
301
  Workflow config lives beside each workflow prompt:
284
302
 
@@ -332,8 +350,12 @@ Important session files:
332
350
 
333
351
  - `goal.md`: the exact goal text copied into the session.
334
352
  - `session.json`: session metadata.
335
- - `state.json`: coordinator-owned dispatch state.
336
- - `events.jsonl`: reserved append-only diagnostics log.
353
+ - `state.json`: coordinator-owned dispatch state, including the session's
354
+ token/duration usage ledger.
355
+ - `events.jsonl`: append-only event stream — one versioned JSON line per
356
+ significant transition (`session_started`, `task_dispatched`,
357
+ `task_finished`, `iteration_abandoned`, `goal_check`, `child_started`,
358
+ `child_finished`, `session_stopped`). Tail it with `loopy events --follow`.
337
359
  - `control.json`: workflow-owned stop switch.
338
360
  - `updates_from_user.md`: human-writable inbox for changes after the session
339
361
  starts.
@@ -419,7 +441,13 @@ To stop because the loop cannot continue:
419
441
  A valid `goal_check.json` does not stop the loop by itself. It is evidence.
420
442
  Stopping is controlled by session `control.json`. If goal-check output is
421
443
  missing or invalid repeatedly, the coordinator stops with
422
- `stop_reason="goal_check_broken"` after the configured failure cap.
444
+ `stop_reason="goal_check_broken"` after the configured failure cap. Similarly,
445
+ consecutive failed iterations of any single workflow stop the loop with
446
+ `stop_reason="workflow_failure_cap"` after `workflow_consecutive_failures_cap`.
447
+ Failed iterations record a `failure_kind` in history — `transient` (provider
448
+ said retry; team-harness's own retries were exhausted), `deterministic`
449
+ (auth/config errors retries cannot fix), `crash` (worker died mid-iteration),
450
+ or `unknown` — so a stopped run is legible without reading harness logs.
423
451
 
424
452
  ## Workflow Sets and Child Sessions
425
453
 
@@ -431,8 +459,11 @@ Workflow sets are mandatory. Even a single-loop repo uses:
431
459
 
432
460
  The older `.loopy_loop/workflows/...` layout is not loaded.
433
461
 
434
- A workflow can request one sequential child session by writing a JSON file under
435
- the active session's `child_requests/` directory:
462
+ A top-level session's workflow can request one sequential child session by
463
+ writing a uniquely named `*.json` file under the active session's
464
+ `child_requests/` directory (only `*.json` filenames are scanned; publish via
465
+ a non-`.json` temp name plus rename — invalid requests are renamed to
466
+ `*.json.rejected` and skipped):
436
467
 
437
468
  ```json
438
469
  {
@@ -484,7 +515,8 @@ loopy init [--template default|inner_outer_eval|pm_planner_dispatcher]
484
515
  Scaffolds loopy-loop files. The default template creates only the reserved
485
516
  `goal_check` workflow. `inner_outer_eval` creates the recommended outer/inner/eval
486
517
  workflow set. `pm_planner_dispatcher` creates planner/dispatcher workflows for
487
- child-session orchestration.
518
+ child-session orchestration — and also ships the `inner_outer_eval` child set
519
+ its dispatcher spawns, so a clean init is executable end to end.
488
520
 
489
521
  ```bash
490
522
  loopy coordinator --host 0.0.0.0 --port 8080 [--resume] [--workflow-set NAME] [--goal-file PATH]
@@ -500,16 +532,24 @@ loopy worker --coordinator http://127.0.0.1:8080
500
532
  Runs a blocking worker until the coordinator returns `action: "stop"`.
501
533
 
502
534
  ```bash
503
- loopy status
535
+ loopy status # session stack, usage totals, estimated cost
536
+ loopy status --watch # re-render every 2 seconds
537
+ loopy events # the active session's event stream
538
+ loopy events --follow # tail it live (--json for raw lines)
504
539
  loopy stop
505
540
  ```
506
541
 
507
- `status` prints the latest session state. `stop` sets `stop_requested=true` in
508
- the latest session-local state.
542
+ `status` prints the latest session state the whole session stack while a
543
+ child runs (the live child is shown under its suspended parent), each
544
+ session's subtree token usage, and (with `model_prices` configured) estimated
545
+ cost. `stop` still flags the latest **top-level** session only: a running
546
+ child does not see the flag, and the stop takes effect once the child reaches
547
+ a terminal state and the parent resumes.
509
548
 
510
549
  ## Related Projects
511
550
 
512
551
  - [`team-harness`](https://github.com/writeitai/team-harness): the model and
513
- agent-CLI orchestration layer used by loopy-loop workers.
552
+ agent-CLI orchestration layer used by the loopy-loop worker.
514
553
  - [`eval-banana`](https://github.com/writeitai/eval-banana): a lightweight YAML
515
- evaluation framework used by the packaged eval workflows.
554
+ evaluation framework used by the packaged eval workflows. Installed
555
+ automatically as a loopy-loop dependency.
@@ -18,12 +18,13 @@ and PRs.
18
18
 
19
19
  Under the hood, loopy-loop runs a small FastAPI coordinator and a single
20
20
  worker. The coordinator owns the loop state and chooses the next workflow. The
21
- workers run assignments through
21
+ worker runs assignments through
22
22
  [`team-harness`](https://github.com/writeitai/team-harness), which can delegate
23
23
  to agent CLIs such as Codex, Claude Code, and Gemini. The packaged
24
24
  `inner_outer_eval` template also uses
25
25
  [`eval-banana`](https://github.com/writeitai/eval-banana) conventions for
26
- session-scoped evaluation checks.
26
+ session-scoped evaluation checks; eval-banana installs automatically as a
27
+ loopy-loop dependency.
27
28
 
28
29
  ## Install
29
30
 
@@ -78,7 +79,8 @@ This is the recommended starting template. It creates:
78
79
  - a `.gitignore` entry for `.loopy_loop/sessions/`
79
80
 
80
81
  `loopy init` is idempotent. It creates missing files and leaves existing files
81
- alone.
82
+ alone — except `.gitignore`, which is updated in place to ensure the sessions
83
+ ignore rule.
82
84
 
83
85
  ## Write the Goal
84
86
 
@@ -204,6 +206,7 @@ goal_file: loopy_loop_goal.txt
204
206
  workflow_set: inner_outer_eval
205
207
  max_turns: 160
206
208
  goal_check_consecutive_failures_cap: 3
209
+ # workflow_consecutive_failures_cap: 5
207
210
  team_harness_provider: "codex"
208
211
  team_harness_model: "gpt-5.5"
209
212
  team_harness_agents:
@@ -238,7 +241,21 @@ Important rules:
238
241
  processes a dead worker left running: drain lets them finish (one shared
239
242
  bounded deadline) before the iteration is re-run; reap kills them
240
243
  immediately. These are coordinator-side settings and are not part of the
241
- config snapshot sent to workers.
244
+ config snapshot sent to the worker.
245
+ - `workflow_consecutive_failures_cap` (default 5) is a per-workflow circuit
246
+ breaker: that many consecutive failed iterations of the same workflow stop
247
+ the loop with `stop_reason="workflow_failure_cap"` instead of retrying a
248
+ wedged workflow until `max_turns`. Any success of the workflow resets its
249
+ counter. Coordinator-side only; not part of the config snapshot sent to
250
+ the worker.
251
+ - `model_prices` (optional, coordinator-side only) sets USD prices per 1M
252
+ tokens for the harness coordinator model (`prompt_usd_per_1m`,
253
+ `completion_usd_per_1m`); with prices set, `loopy status` derives an
254
+ estimated cost from the token ledger. `max_cost_usd` (optional, requires
255
+ `model_prices`) stops the loop with `stop_reason="max_cost_usd"` once the
256
+ session tree's estimated cost reaches the budget. Cost covers the harness
257
+ COORDINATOR model only — agent-CLI subprocesses (codex, claude, gemini)
258
+ bill through their own accounts and are not measurable here.
242
259
 
243
260
  Workflow config lives beside each workflow prompt:
244
261
 
@@ -292,8 +309,12 @@ Important session files:
292
309
 
293
310
  - `goal.md`: the exact goal text copied into the session.
294
311
  - `session.json`: session metadata.
295
- - `state.json`: coordinator-owned dispatch state.
296
- - `events.jsonl`: reserved append-only diagnostics log.
312
+ - `state.json`: coordinator-owned dispatch state, including the session's
313
+ token/duration usage ledger.
314
+ - `events.jsonl`: append-only event stream — one versioned JSON line per
315
+ significant transition (`session_started`, `task_dispatched`,
316
+ `task_finished`, `iteration_abandoned`, `goal_check`, `child_started`,
317
+ `child_finished`, `session_stopped`). Tail it with `loopy events --follow`.
297
318
  - `control.json`: workflow-owned stop switch.
298
319
  - `updates_from_user.md`: human-writable inbox for changes after the session
299
320
  starts.
@@ -379,7 +400,13 @@ To stop because the loop cannot continue:
379
400
  A valid `goal_check.json` does not stop the loop by itself. It is evidence.
380
401
  Stopping is controlled by session `control.json`. If goal-check output is
381
402
  missing or invalid repeatedly, the coordinator stops with
382
- `stop_reason="goal_check_broken"` after the configured failure cap.
403
+ `stop_reason="goal_check_broken"` after the configured failure cap. Similarly,
404
+ consecutive failed iterations of any single workflow stop the loop with
405
+ `stop_reason="workflow_failure_cap"` after `workflow_consecutive_failures_cap`.
406
+ Failed iterations record a `failure_kind` in history — `transient` (provider
407
+ said retry; team-harness's own retries were exhausted), `deterministic`
408
+ (auth/config errors retries cannot fix), `crash` (worker died mid-iteration),
409
+ or `unknown` — so a stopped run is legible without reading harness logs.
383
410
 
384
411
  ## Workflow Sets and Child Sessions
385
412
 
@@ -391,8 +418,11 @@ Workflow sets are mandatory. Even a single-loop repo uses:
391
418
 
392
419
  The older `.loopy_loop/workflows/...` layout is not loaded.
393
420
 
394
- A workflow can request one sequential child session by writing a JSON file under
395
- the active session's `child_requests/` directory:
421
+ A top-level session's workflow can request one sequential child session by
422
+ writing a uniquely named `*.json` file under the active session's
423
+ `child_requests/` directory (only `*.json` filenames are scanned; publish via
424
+ a non-`.json` temp name plus rename — invalid requests are renamed to
425
+ `*.json.rejected` and skipped):
396
426
 
397
427
  ```json
398
428
  {
@@ -444,7 +474,8 @@ loopy init [--template default|inner_outer_eval|pm_planner_dispatcher]
444
474
  Scaffolds loopy-loop files. The default template creates only the reserved
445
475
  `goal_check` workflow. `inner_outer_eval` creates the recommended outer/inner/eval
446
476
  workflow set. `pm_planner_dispatcher` creates planner/dispatcher workflows for
447
- child-session orchestration.
477
+ child-session orchestration — and also ships the `inner_outer_eval` child set
478
+ its dispatcher spawns, so a clean init is executable end to end.
448
479
 
449
480
  ```bash
450
481
  loopy coordinator --host 0.0.0.0 --port 8080 [--resume] [--workflow-set NAME] [--goal-file PATH]
@@ -460,16 +491,24 @@ loopy worker --coordinator http://127.0.0.1:8080
460
491
  Runs a blocking worker until the coordinator returns `action: "stop"`.
461
492
 
462
493
  ```bash
463
- loopy status
494
+ loopy status # session stack, usage totals, estimated cost
495
+ loopy status --watch # re-render every 2 seconds
496
+ loopy events # the active session's event stream
497
+ loopy events --follow # tail it live (--json for raw lines)
464
498
  loopy stop
465
499
  ```
466
500
 
467
- `status` prints the latest session state. `stop` sets `stop_requested=true` in
468
- the latest session-local state.
501
+ `status` prints the latest session state the whole session stack while a
502
+ child runs (the live child is shown under its suspended parent), each
503
+ session's subtree token usage, and (with `model_prices` configured) estimated
504
+ cost. `stop` still flags the latest **top-level** session only: a running
505
+ child does not see the flag, and the stop takes effect once the child reaches
506
+ a terminal state and the parent resumes.
469
507
 
470
508
  ## Related Projects
471
509
 
472
510
  - [`team-harness`](https://github.com/writeitai/team-harness): the model and
473
- agent-CLI orchestration layer used by loopy-loop workers.
511
+ agent-CLI orchestration layer used by the loopy-loop worker.
474
512
  - [`eval-banana`](https://github.com/writeitai/eval-banana): a lightweight YAML
475
- evaluation framework used by the packaged eval workflows.
513
+ evaluation framework used by the packaged eval workflows. Installed
514
+ automatically as a loopy-loop dependency.