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.
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/CHANGELOG.md +63 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/Makefile +1 -1
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/PKG-INFO +54 -15
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/README.md +52 -14
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/design/designs/improvement-proposals.md +77 -36
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/docs/http-contract.md +17 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/docs/session-layout.md +17 -2
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/pyproject.toml +2 -1
- loopy_loop-0.5.0/skills/loopy-loop/SKILL.md +575 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/cli.py +197 -14
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/config.py +57 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/coordinator_app.py +302 -11
- loopy_loop-0.5.0/src/loopy_loop/events.py +83 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/harness_runner.py +62 -1
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/models.py +73 -0
- {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
- {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
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/worker.py +125 -11
- loopy_loop-0.5.0/src/tests/test_events_and_usage.py +622 -0
- loopy_loop-0.5.0/src/tests/test_failure_taxonomy.py +488 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_worker.py +80 -3
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/uv.lock +17 -1
- loopy_loop-0.4.0/skills/loopy-loop/SKILL.md +0 -379
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/.eval-banana/config.toml +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/.github/workflows/ci.yml +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/.github/workflows/docs-deploy.yml +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/.github/workflows/release.yml +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/.gitignore +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/.team-harness/config.toml +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/.team-harness/coordinator_system_message.md +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/.team-harness/worker_footer.md +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/.team-harness/worker_suffix.md +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/AGENTS.md +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/CLAUDE.md +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/LICENSE +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/design/analysis/codex_parallel_analysis.md +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/design/analysis/loopy-loop-state-and-roadmap.md +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/design/analysis/synthesis.md +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/design/decisions.md +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/design/designs/documentation-site.md +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/design/designs/success-semantics-and-evaluation.md +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/docs/images/logo.png +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/skills-lock.json +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/__init__.py +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/recovery.py +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/scheduler.py +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/sessions.py +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/state_store.py +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/templates/inner_outer_eval/.gitignore +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/templates/inner_outer_eval/loopy_loop_config.yaml +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/templates/inner_outer_eval/loopy_loop_goal.txt +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/templates/pm_planner_dispatcher/.gitignore +0 -0
- {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
- {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
- {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
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/templates/pm_planner_dispatcher/loopy_loop_config.yaml +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/templates/pm_planner_dispatcher/loopy_loop_goal.txt +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/loopy_loop/worker_identity.py +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/__init__.py +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/conftest.py +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_api_base_normalization.py +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_cli.py +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_config.py +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_coordinator_app.py +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_examples.py +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_fresh_run_archive.py +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_goal_check_gate.py +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_goal_hash_derivation.py +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_harness_runner.py +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_idempotent_finished.py +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_must_follow_success.py +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_scheduler.py +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_session_stack_recovery.py +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_sessions.py +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_state_store.py +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/src/tests/test_worker_liveness_recovery.py +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/.gitignore +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/README.md +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/mdx-components.tsx +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/next.config.ts +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/package-lock.json +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/package.json +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/postcss.config.mjs +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/public/.nojekyll +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/public/CNAME +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/app/docs/child-sessions/page.mdx +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/app/docs/cli-reference/page.mdx +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/app/docs/concepts/page.mdx +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/app/docs/configuration/page.mdx +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/app/docs/evaluation/page.mdx +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/app/docs/getting-started/page.mdx +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/app/docs/http-contract/page.mdx +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/app/docs/layout.tsx +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/app/docs/page.mdx +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/app/docs/session-layout/page.mdx +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/app/docs/success-and-control/page.mdx +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/app/docs/troubleshooting/page.mdx +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/app/docs/workflows/page.mdx +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/app/globals.css +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/app/layout.tsx +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/app/page.tsx +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/components/docs/DocsPageNavigation.tsx +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/components/docs/DocsSidebar.tsx +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/components/docs/DocsTableOfContents.tsx +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/components/docs/MobileDocsSidebar.tsx +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/components/search/SearchCommand.tsx +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/components/site/SiteHeader.tsx +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/lib/docs/navigation.ts +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/lib/search/pagefind.ts +0 -0
- {loopy_loop-0.4.0 → loopy_loop-0.5.0}/website/src/lib/utils.ts +0 -0
- {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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: loopy-loop
|
|
3
|
-
Version: 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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
435
|
-
the active session's
|
|
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
|
|
509
|
-
the
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
395
|
-
the active session's
|
|
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
|
|
469
|
-
the
|
|
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
|
|
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:**
|
|
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
|
|
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**.
|
|
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.**
|
|
90
|
-
|
|
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.**
|
|
130
|
-
the pause/resume gate
|
|
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.**
|
|
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.**
|
|
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.**
|
|
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.
|
|
243
|
-
|
|
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
|
|
250
|
-
template
|
|
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.**
|
|
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.**
|
|
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.**
|
|
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.**
|
|
349
|
-
|
|
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.
|
|
372
|
-
`_advance()` refactor
|
|
373
|
-
|
|
374
|
-
2.
|
|
375
|
-
executable end to end.
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
-
-
|
|
51
|
-
-
|
|
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.
|
|
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",
|