loopy-loop 0.4.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 (118) hide show
  1. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/CHANGELOG.md +63 -0
  2. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/Makefile +1 -1
  3. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/PKG-INFO +54 -15
  4. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/README.md +52 -14
  5. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/design/designs/improvement-proposals.md +77 -36
  6. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/docs/http-contract.md +17 -0
  7. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/docs/session-layout.md +17 -2
  8. {loopy_loop-0.4.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.4.0 → loopy_loop-0.5.0}/src/loopy_loop/cli.py +197 -14
  11. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/config.py +57 -0
  12. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/coordinator_app.py +302 -11
  13. loopy_loop-0.5.0/src/loopy_loop/events.py +83 -0
  14. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/harness_runner.py +62 -1
  15. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/models.py +73 -0
  16. {loopy_loop-0.4.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 +3 -3
  17. {loopy_loop-0.4.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
  18. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/worker.py +125 -11
  19. loopy_loop-0.5.0/src/tests/test_events_and_usage.py +622 -0
  20. loopy_loop-0.5.0/src/tests/test_failure_taxonomy.py +488 -0
  21. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_worker.py +80 -3
  22. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/uv.lock +17 -1
  23. loopy_loop-0.4.0/skills/loopy-loop/SKILL.md +0 -379
  24. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/.eval-banana/config.toml +0 -0
  25. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/.github/workflows/ci.yml +0 -0
  26. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/.github/workflows/docs-deploy.yml +0 -0
  27. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/.github/workflows/release.yml +0 -0
  28. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/.gitignore +0 -0
  29. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/.team-harness/config.toml +0 -0
  30. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/.team-harness/coordinator_system_message.md +0 -0
  31. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/.team-harness/worker_footer.md +0 -0
  32. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/.team-harness/worker_suffix.md +0 -0
  33. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/AGENTS.md +0 -0
  34. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/CLAUDE.md +0 -0
  35. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/LICENSE +0 -0
  36. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/design/analysis/codex_parallel_analysis.md +0 -0
  37. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/design/analysis/loopy-loop-state-and-roadmap.md +0 -0
  38. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/design/analysis/synthesis.md +0 -0
  39. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/design/decisions.md +0 -0
  40. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/design/designs/documentation-site.md +0 -0
  41. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/design/designs/success-semantics-and-evaluation.md +0 -0
  42. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/docs/images/logo.png +0 -0
  43. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/skills-lock.json +0 -0
  44. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/__init__.py +0 -0
  45. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/recovery.py +0 -0
  46. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/scheduler.py +0 -0
  47. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/sessions.py +0 -0
  48. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/state_store.py +0 -0
  49. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/templates/inner_outer_eval/.gitignore +0 -0
  50. {loopy_loop-0.4.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
  51. {loopy_loop-0.4.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 +0 -0
  52. {loopy_loop-0.4.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
  53. {loopy_loop-0.4.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
  54. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/inner/prompt.txt +0 -0
  55. {loopy_loop-0.4.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
  56. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/outer/prompt.txt +0 -0
  57. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/templates/inner_outer_eval/loopy_loop_config.yaml +0 -0
  58. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/templates/inner_outer_eval/loopy_loop_goal.txt +0 -0
  59. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/templates/pm_planner_dispatcher/.gitignore +0 -0
  60. {loopy_loop-0.4.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
  61. {loopy_loop-0.4.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
  62. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/templates/pm_planner_dispatcher/.loopy_loop/workflow_sets/pm_planner_dispatcher/workflows/planner/prompt.txt +0 -0
  63. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/templates/pm_planner_dispatcher/loopy_loop_config.yaml +0 -0
  64. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/templates/pm_planner_dispatcher/loopy_loop_goal.txt +0 -0
  65. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/worker_identity.py +0 -0
  66. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/__init__.py +0 -0
  67. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/conftest.py +0 -0
  68. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_api_base_normalization.py +0 -0
  69. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_cli.py +0 -0
  70. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_config.py +0 -0
  71. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_coordinator_app.py +0 -0
  72. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_examples.py +0 -0
  73. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_fresh_run_archive.py +0 -0
  74. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_goal_check_gate.py +0 -0
  75. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_goal_hash_derivation.py +0 -0
  76. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_harness_runner.py +0 -0
  77. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_idempotent_finished.py +0 -0
  78. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_must_follow_success.py +0 -0
  79. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_scheduler.py +0 -0
  80. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_session_stack_recovery.py +0 -0
  81. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_sessions.py +0 -0
  82. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_state_store.py +0 -0
  83. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_worker_liveness_recovery.py +0 -0
  84. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/.gitignore +0 -0
  85. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/README.md +0 -0
  86. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/mdx-components.tsx +0 -0
  87. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/next.config.ts +0 -0
  88. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/package-lock.json +0 -0
  89. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/package.json +0 -0
  90. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/postcss.config.mjs +0 -0
  91. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/public/.nojekyll +0 -0
  92. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/public/CNAME +0 -0
  93. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/app/docs/child-sessions/page.mdx +0 -0
  94. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/app/docs/cli-reference/page.mdx +0 -0
  95. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/app/docs/concepts/page.mdx +0 -0
  96. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/app/docs/configuration/page.mdx +0 -0
  97. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/app/docs/evaluation/page.mdx +0 -0
  98. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/app/docs/getting-started/page.mdx +0 -0
  99. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/app/docs/http-contract/page.mdx +0 -0
  100. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/app/docs/layout.tsx +0 -0
  101. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/app/docs/page.mdx +0 -0
  102. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/app/docs/session-layout/page.mdx +0 -0
  103. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/app/docs/success-and-control/page.mdx +0 -0
  104. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/app/docs/troubleshooting/page.mdx +0 -0
  105. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/app/docs/workflows/page.mdx +0 -0
  106. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/app/globals.css +0 -0
  107. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/app/layout.tsx +0 -0
  108. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/app/page.tsx +0 -0
  109. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/components/docs/DocsPageNavigation.tsx +0 -0
  110. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/components/docs/DocsSidebar.tsx +0 -0
  111. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/components/docs/DocsTableOfContents.tsx +0 -0
  112. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/components/docs/MobileDocsSidebar.tsx +0 -0
  113. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/components/search/SearchCommand.tsx +0 -0
  114. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/components/site/SiteHeader.tsx +0 -0
  115. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/lib/docs/navigation.ts +0 -0
  116. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/lib/search/pagefind.ts +0 -0
  117. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/lib/utils.ts +0 -0
  118. {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/tsconfig.json +0 -0
@@ -1,5 +1,68 @@
1
1
  # Changelog
2
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
+
3
66
  ## 0.4.0 (breaking)
4
67
 
5
68
  **Breaking API interaction — completions must echo the dispatched
@@ -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.4.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
  {
@@ -501,16 +532,24 @@ loopy worker --coordinator http://127.0.0.1:8080
501
532
  Runs a blocking worker until the coordinator returns `action: "stop"`.
502
533
 
503
534
  ```bash
504
- 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)
505
539
  loopy stop
506
540
  ```
507
541
 
508
- `status` prints the latest session state. `stop` sets `stop_requested=true` in
509
- 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.
510
548
 
511
549
  ## Related Projects
512
550
 
513
551
  - [`team-harness`](https://github.com/writeitai/team-harness): the model and
514
- agent-CLI orchestration layer used by loopy-loop workers.
552
+ agent-CLI orchestration layer used by the loopy-loop worker.
515
553
  - [`eval-banana`](https://github.com/writeitai/eval-banana): a lightweight YAML
516
- 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
  {
@@ -461,16 +491,24 @@ loopy worker --coordinator http://127.0.0.1:8080
461
491
  Runs a blocking worker until the coordinator returns `action: "stop"`.
462
492
 
463
493
  ```bash
464
- 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)
465
498
  loopy stop
466
499
  ```
467
500
 
468
- `status` prints the latest session state. `stop` sets `stop_requested=true` in
469
- 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.
470
507
 
471
508
  ## Related Projects
472
509
 
473
510
  - [`team-harness`](https://github.com/writeitai/team-harness): the model and
474
- agent-CLI orchestration layer used by loopy-loop workers.
511
+ agent-CLI orchestration layer used by the loopy-loop worker.
475
512
  - [`eval-banana`](https://github.com/writeitai/eval-banana): a lightweight YAML
476
- 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.
@@ -1,12 +1,12 @@
1
1
  # Design: Improvement Proposals
2
2
 
3
- **Status:** Proposed (not yet accepted)
3
+ **Status:** Living — per-proposal statuses below (last status pass: 2026-07-13)
4
4
  **Date:** 2026-07-12
5
5
  **Relationship to other docs:** Companion to
6
6
  [`success-semantics-and-evaluation.md`](./success-semantics-and-evaluation.md)
7
7
  (which records decisions that are *already made* and should not be reverted). This
8
- doc is the opposite: forward-looking changes we are *considering*. It is derived
9
- from the July 2026 review under [`../analysis/`](../analysis/), curated and
8
+ doc is a tracked ledger of changes we are considering or have since shipped. It is
9
+ derived from the July 2026 review under [`../analysis/`](../analysis/), curated and
10
10
  re-prioritized.
11
11
 
12
12
  **Framing decision that drives priority:** we intend to drive a large, multi-phase
@@ -16,7 +16,10 @@ not after a single-loop pilot. That decision moves the parent/child machinery fr
16
16
  flagship use case. The ordering below reflects that.
17
17
 
18
18
  Each proposal has: **what**, **why**, **sketch**, **effort** (S≈1–2 days, M≈1 week,
19
- L≈multi-week), and **status**. Nothing here is committed until we accept it.
19
+ L≈multi-week), and **status**. The **Status** line is authoritative read it first.
20
+ For proposals marked implemented, the What/Why text is preserved as the *original
21
+ problem statement*: it describes the pre-implementation state that motivated the
22
+ work, not the current behavior.
20
23
 
21
24
  ---
22
25
 
@@ -86,8 +89,9 @@ hard OS ceiling (see P2.5). This is the right split, because the two crash cases
86
89
  running. That is a process-cleanup problem, handled separately in P2.5 — do not fold it
87
90
  into P0.1.
88
91
 
89
- **Effort.** M–L. **Status.** Proposed **highest priority** given the double-loop
90
- decision.
92
+ **Effort.** M–L. **Status.** **Implemented in 0.4.0** `active_child_session_id`
93
+ pointer, startup session-stack walk with adoption/finalization, attempt ids,
94
+ request-file tombstones, atomic artifact writes.
91
95
 
92
96
  ### P0.2 — Keep the last-resort human escape hatch; do NOT build a preferred human-gate
93
97
 
@@ -126,9 +130,9 @@ resume-after-human flow, no external-action approval gate.
126
130
  `unresolvable_error` reason with a good message is likely sufficient. Only split it
127
131
  if operational reports actually need to tell the two apart.
128
132
 
129
- **Effort.** S (prompt guidance only) — or none. **Status.** Reframed: **do not build
130
- the pause/resume gate.** The autonomy escape hatch already exists; only optional
131
- prompt/report polish remains.
133
+ **Effort.** S (prompt guidance only) — or none. **Status.** **Resolved by decision
134
+ (D5), no code** — do not build the pause/resume gate. The autonomy escape hatch
135
+ already exists; only optional prompt/report polish remains.
132
136
 
133
137
  ### P0.3 — Per-child budgets *(withdrawn)*
134
138
 
@@ -160,7 +164,9 @@ the PM template, or make workflow-set dependencies declarative and installable
160
164
  (`requires_workflow_sets: [...]`) with preflight validation and a clean-install
161
165
  template smoke test in CI.
162
166
 
163
- **Effort.** S–M. **Status.** Proposed.
167
+ **Effort.** S–M. **Status.** **Implemented in 0.4.0** — the PM template ships its
168
+ child workflow set via packaged-template extra sources, with an end-to-end
169
+ clean-init smoke test.
164
170
 
165
171
  ---
166
172
 
@@ -188,7 +194,17 @@ usage adapter with explicit `known`/`unknown` fields (don't pretend worker token
188
194
  costs are always measurable). Budgets enforceable at session/child/workflow/wall-clock
189
195
  levels, checked before dispatch and after each result.
190
196
 
191
- **Effort.** M–L. **Status.** Proposed.
197
+ **Effort.** M–L. **Status.** **Implemented (unreleased)** — versioned events
198
+ appended after each committing mutation (best-effort delivery by design —
199
+ state.json stays the durable truth; torn-tail-tolerant reader); worker-read token usage from team-harness `run.json` with explicit
200
+ unknown-usage accounting; durable per-session ledger + children.json roll-up;
201
+ `model_prices`-derived cost with the `max_cost_usd` stop condition
202
+ (coordinator-side, requires prices — preflight-enforced); `loopy status`
203
+ (session stack + usage + cost, `--watch`) and `loopy events [--follow]
204
+ [--json]`. Scope notes: budget enforcement is per session subtree (a running
205
+ child enforces against its own spend until finalized — bounded by the
206
+ two-level depth limit); only session-level budgets shipped (per-workflow and
207
+ wall-clock budgets were not needed yet).
192
208
 
193
209
  ### P1.2 — Deterministic backstop under the judge (per target, for high-stakes work)
194
210
 
@@ -212,7 +228,10 @@ outcome"; add the deterministic suite as the objective floor so a mis-judged
212
228
  > that overrides the stock policy.
213
229
 
214
230
  **Effort.** S–M (mostly the child workflow set + runner-side report parsing).
215
- **Status.** Proposedneeded before high-stakes acceptance is trustworthy.
231
+ **Status.** **Deferred (2026-07-13)** out of scope for the pre-target milestone.
232
+ It only pays off against a target repo that owns a trustworthy suite, so build it
233
+ as part of that target's dedicated child workflow set when the double loop is
234
+ pointed at one, not generically now. The boundary above still binds when it lands.
216
235
 
217
236
  ### P1.3 — Fix the Agent Skill and stale "multi-worker" claims
218
237
 
@@ -239,15 +258,16 @@ coordinator is explicitly safe under.
239
258
  **What.** Replace hardcoded model IDs (repeated across `config.py`, `cli.py`, packaged
240
259
  YAML, README, `.team-harness/config.toml`, `.eval-banana/config.toml`, and sibling
241
260
  repos) with named profiles (`balanced`, `high_assurance`, `cheap`) resolved once in a
242
- project-local file. Pin `team-harness>=0.2.10,<0.3`. Make eval-banana a declared
243
- extra (`loopy-loop[eval]`) or fail preflight with a clear message.
261
+ project-local file. Consider bounding the `team-harness` dependency once its contract
262
+ churns independently of loopy releases. ~~Make eval-banana a declared extra
263
+ (`loopy-loop[eval]`) or fail preflight with a clear message~~ — superseded, see Status.
244
264
 
245
265
  **Why.** Model IDs churn (git history is full of "bump gpt-5.4 → 5.5"); duplicating a
246
266
  model string across N files and sibling repos guarantees drift and a launch-day
247
267
  footgun (install today, hit a decommissioned model). team-harness has already shipped
248
268
  breaking pre-1.0 changes, so an unbounded `>=` dependency is a standing risk.
249
- eval-banana is required by the recommended template but is not a hard dependency, so a
250
- template can require a tool that isn't installed.
269
+ eval-banana was required by the recommended template without being a dependency, so a
270
+ template could require a tool that wasn't installed.
251
271
 
252
272
  **Sketch.** One resolved profile → exact provider/model/effort, snapshotted into the
253
273
  session. `loopy doctor` validates the profile against the actually-installed CLIs.
@@ -255,7 +275,13 @@ Consider a small compatibility matrix (loopy-loop × team-harness × eval-banana
255
275
  in CI: "treat the trio as one release train" until the contracts have survived real
256
276
  use.
257
277
 
258
- **Effort.** M. **Status.** Proposed.
278
+ **Effort.** M. **Status.** **Partially resolved (2026-07-13)** — the eval-banana
279
+ piece landed the simple way: it is now a plain hard dependency (chosen over the
280
+ optional-extra design; one less install mode to document), and the worker appends
281
+ the scripts directory that actually holds the installed `eval-banana` script
282
+ (derived from the package's install record, `sysconfig` as fallback) to `PATH` so
283
+ the CLI resolves for spawned agents across supported install modes (`uv tool
284
+ install` and pipx expose only the primary package's entry points). Named profiles and trio pinning remain **Proposed**.
259
285
 
260
286
  ### P2.2 — `_advance()` refactor, folded into the P0.1 state-machine work
261
287
 
@@ -270,7 +296,8 @@ P0.1, not as a cosmetic pass first — encode the new session-stack invariants a
270
296
  table-driven transition tests at the same time, or the refactor just centralizes the
271
297
  current ambiguity.
272
298
 
273
- **Effort.** S (as part of P0.1). **Status.** Proposed.
299
+ **Effort.** S (as part of P0.1). **Status.** **Implemented in 0.4.0** — folded into
300
+ P0.1 as `_advance()`.
274
301
 
275
302
  ### P2.3 — Richer failure taxonomy + per-workflow failure cap
276
303
 
@@ -285,7 +312,15 @@ becomes a failed iteration the scheduler can retry until `max_turns` — spendin
285
312
  without distinguishing a transient 429 from a deterministic test failure. A wedged
286
313
  `inner` shouldn't be able to burn the whole turn budget.
287
314
 
288
- **Effort.** S–M. **Status.** Proposed.
315
+ **Effort.** S–M. **Status.** **Implemented (unreleased)** — `failure_kind`
316
+ (`transient`/`deterministic`/`crash`/`unknown`, derived from team-harness's
317
+ structured `retryable` signal) recorded on results, `/finished`, and history;
318
+ per-workflow consecutive-failure counter in `LoopState` with coordinator-side
319
+ `workflow_consecutive_failures_cap` (default 5) stopping the loop with
320
+ `stop_reason="workflow_failure_cap"`. Retry/backoff at the loopy level was
321
+ deliberately NOT added: team-harness already retries transient API errors with
322
+ backoff (`team_harness_max_retries` etc.); a second retry layer would multiply
323
+ delays without new information.
289
324
 
290
325
  ### P2.4 — Operator experience: `doctor`, `validate`, session-aware `status`/`stop`
291
326
 
@@ -345,8 +380,10 @@ the ownership split. Summary of the split:
345
380
  team-harness provides the liveness check + policy ops + timeout (TH-D5); the logical-session
346
381
  resume path is a separate, larger option and is **not** proposed here.
347
382
 
348
- **Effort.** M (team-harness, tracked there) + S (loopy surfacing). **Status.** Proposed —
349
- separate from P0.1, which recovers state only. Pairs with the P0.1 worker-liveness bullet.
383
+ **Effort.** M (team-harness, tracked there) + S (loopy surfacing). **Status.**
384
+ **Implemented** team-harness 0.3.0 (TH-D5: process-group identity + `th reap`)
385
+ plus loopy-loop 0.3.0 (worker identity on `/register`, bounded-drain recovery of
386
+ orphaned agents, `salvage.json`).
350
387
 
351
388
  ---
352
389
 
@@ -368,18 +405,22 @@ separate from P0.1, which recovers state only. Pairs with the P0.1 worker-livene
368
405
 
369
406
  ## Suggested sequencing
370
407
 
371
- 1. **P0.1 + P2.2 together**the session-stack/state-machine hardening and the
372
- `_advance()` refactor are one coherent piece of work. (P0.2 needs no code — the
373
- autonomy escape hatch already exists; at most it's prompt polish.)
374
- 2. **P0.4**a PM template runnable from a clean init; the double loop is now
375
- executable end to end.
376
- 3. **P1.1** — events/cost ledger + `status --watch`, so double-loop runs are legible.
377
- 4. **P1.2 + P1.3** — deterministic backstop for high-stakes targets; fix the skill.
378
- 5. **P2.1, P2.3, P2.4, P2.5** profiles/pins, failure taxonomy, operator UX, and
379
- orphan-process cleanup (rolling).
380
-
381
- After (1)–(3), the double loop is ready to drive a large, multi-phase target as a
382
- sequence of narrow, deterministically-backstopped work packages that runs
383
- unattended — falling back to the `unresolvable_error` escape hatch only when a
384
- blocker is genuinely unresolvable without a human. That target-execution design is
385
- the subject of a separate doc, not this one.
408
+ 1. ~~**P0.1 + P2.2 together**~~**done (0.4.0)**: session-stack/state-machine
409
+ hardening plus the `_advance()` refactor. (P0.2 needed no code — the autonomy
410
+ escape hatch already exists.)
411
+ 2. ~~**P0.4**~~**done (0.4.0)**: PM template runnable from a clean init; the
412
+ double loop is executable end to end. (P2.5 also **done**: team-harness 0.3.0 +
413
+ loopy-loop 0.3.0.)
414
+ 3. **P1.3** — fix the Agent Skill and stale claims (now also stale against
415
+ 0.3.0/0.4.0: worker identity, attempt ids, workflow sets).
416
+ 4. **P2.3** — failure taxonomy + per-workflow failure cap.
417
+ 5. **P1.1** — events/cost ledger + `status --watch`, so double-loop runs are legible.
418
+ 6. **P2.1 remainder, P2.4** profiles/pins and operator UX (rolling). **P1.2** is
419
+ deferred into the flagship target's own workflow-set design.
420
+
421
+ After (3)–(5), the double loop is ready to drive a large, multi-phase target as a
422
+ sequence of narrow work packages that runs unattended — falling back to the
423
+ `unresolvable_error` escape hatch only when a blocker is genuinely unresolvable
424
+ without a human. Deterministic backstops (P1.2) arrive with that target's own
425
+ workflow-set design. That target-execution design is the subject of a separate
426
+ doc, not this one.
@@ -136,6 +136,9 @@ Request:
136
136
  "success": true,
137
137
  "text": "done",
138
138
  "error": null,
139
+ "failure_kind": null,
140
+ "usage": {"prompt_tokens": 5210, "completion_tokens": 902, "turns": 3},
141
+ "duration_s": 187.4,
139
142
  "attempt_id": "a1b2c3d4e5f6",
140
143
  "worker": {
141
144
  "hostname": "buildbox",
@@ -147,6 +150,20 @@ Request:
147
150
 
148
151
  `worker` is optional (same semantics as `/register`): the calling worker will
149
152
  run the next dispatched task, so its identity is stamped onto that task.
153
+ `failure_kind` is optional and classifies a failed iteration for the history
154
+ record: `transient` (provider said retry — the harness's own retries were
155
+ already exhausted), `deterministic` (auth/config error retries cannot fix), or
156
+ `unknown`. The coordinator itself records `crash` for iterations abandoned by
157
+ a dead worker. Consecutive failures of the same workflow are counted
158
+ (coordinator-side `workflow_consecutive_failures_cap`, default 5; any success
159
+ of that workflow resets its counter) and stop the loop terminally with
160
+ `stop_reason="workflow_failure_cap"` at the cap.
161
+ `usage` (optional) is the coordinator-model token usage the worker read from
162
+ team-harness's `run.json`; absence means usage is UNKNOWN for the iteration,
163
+ not zero (agent-CLI subprocess usage is never measurable). `duration_s`
164
+ (optional) is the worker-measured harness wall-clock. Both feed the
165
+ session's durable ledger; with configured `model_prices` the ledger drives
166
+ the `max_cost_usd` stop condition (`stop_reason="max_cost_usd"`).
150
167
 
151
168
  Response: same shape as `/register` response (`action` is either `"run"` or `"stop"`).
152
169
 
@@ -47,8 +47,19 @@ One session directory is created per fresh coordinator run and reused for all it
47
47
 
48
48
  `events.jsonl`
49
49
 
50
- - Reserved append-only event log for diagnostics
51
- - Created at session start in v1
50
+ - Append-only event stream: one JSON object per line, one file per session
51
+ - Envelope: `event_id` (unique per emission), `schema_version` (1), `ts`
52
+ (UTC), `session_id`, `type`, `payload`
53
+ - Types in v1: `session_started`, `task_dispatched`, `task_finished`
54
+ (success/error/failure_kind/tokens/duration), `iteration_abandoned`,
55
+ `goal_check`, `child_started`, `child_finished`, `session_stopped`
56
+ - Delivery is best-effort, not guaranteed: events append AFTER the
57
+ producing state mutation commits, so a crash or append failure in that
58
+ window loses the event (never the state), and a crash-replayed
59
+ finalization can duplicate one — consumers key on `event_id`, tolerate
60
+ gaps and a truncated final line, and never build correctness on this
61
+ stream (`state.json` history/ledger is the durable truth)
62
+ - Tail with `loopy events --follow` (`--json` for raw lines)
52
63
 
53
64
  `control.json`
54
65
 
@@ -175,6 +186,10 @@ eval-banana run \
175
186
  - The parent's `state.json` additionally records `active_child_session_id`
176
187
  while a child runs — the durable session-stack pointer a restarted
177
188
  coordinator follows to resume the child instead of orphaning it
189
+ - At finalization the record also captures the child's usage totals
190
+ (`usage`: tokens, known/unknown iteration counts, duration), which is how
191
+ a parent's tree-wide usage and `max_cost_usd` budget include finished
192
+ children without double-storing
178
193
 
179
194
  Atomicity and crash model: the coordinator- and worker-owned artifacts above
180
195
  are written atomically (same-directory temp file + rename), so a **process
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "loopy-loop"
7
- version = "0.4.0"
7
+ version = "0.5.0"
8
8
  description = "Run long-running AI agent workflows inside your repository."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -37,6 +37,7 @@ classifiers = [
37
37
  ]
38
38
  dependencies = [
39
39
  "click>=8.1",
40
+ "eval-banana>=0.3.1",
40
41
  "fastapi>=0.115.0",
41
42
  "filelock>=3.16",
42
43
  "httpx>=0.28",