loopy-loop 0.3.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/CHANGELOG.md +55 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/PKG-INFO +3 -2
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/README.md +2 -1
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/docs/http-contract.md +15 -1
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/docs/session-layout.md +20 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/pyproject.toml +1 -1
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/loopy_loop/cli.py +24 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/loopy_loop/coordinator_app.py +412 -131
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/loopy_loop/harness_runner.py +13 -9
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/loopy_loop/models.py +21 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/loopy_loop/recovery.py +2 -3
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/loopy_loop/sessions.py +28 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/eval_reviewer/prompt.txt +4 -2
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/eval_runner/prompt.txt +11 -1
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/inner/prompt.txt +3 -1
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/outer/prompt.txt +15 -4
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/loopy_loop/templates/pm_planner_dispatcher/.loopy_loop/workflow_sets/pm_planner_dispatcher/workflows/planner/prompt.txt +8 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/loopy_loop/templates/pm_planner_dispatcher/loopy_loop_config.yaml +7 -4
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/loopy_loop/worker.py +8 -2
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/tests/test_cli.py +105 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/tests/test_coordinator_app.py +7 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/tests/test_idempotent_finished.py +3 -0
- loopy_loop-0.4.0/src/tests/test_session_stack_recovery.py +824 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/src/app/docs/cli-reference/page.mdx +1 -1
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/src/app/docs/http-contract/page.mdx +12 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/src/app/docs/session-layout/page.mdx +9 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/.eval-banana/config.toml +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/.github/workflows/ci.yml +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/.github/workflows/docs-deploy.yml +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/.github/workflows/release.yml +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/.gitignore +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/.team-harness/config.toml +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/.team-harness/coordinator_system_message.md +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/.team-harness/worker_footer.md +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/.team-harness/worker_suffix.md +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/AGENTS.md +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/CLAUDE.md +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/LICENSE +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/Makefile +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/design/analysis/codex_parallel_analysis.md +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/design/analysis/loopy-loop-state-and-roadmap.md +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/design/analysis/synthesis.md +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/design/decisions.md +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/design/designs/documentation-site.md +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/design/designs/improvement-proposals.md +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/design/designs/success-semantics-and-evaluation.md +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/docs/images/logo.png +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/skills/loopy-loop/SKILL.md +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/skills-lock.json +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/loopy_loop/__init__.py +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/loopy_loop/config.py +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/loopy_loop/scheduler.py +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/loopy_loop/state_store.py +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/loopy_loop/templates/inner_outer_eval/.gitignore +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/eval_reviewer/config.yaml +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/eval_runner/config.yaml +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/inner/config.yaml +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/outer/config.yaml +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/loopy_loop/templates/inner_outer_eval/loopy_loop_config.yaml +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/loopy_loop/templates/inner_outer_eval/loopy_loop_goal.txt +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/loopy_loop/templates/pm_planner_dispatcher/.gitignore +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/loopy_loop/templates/pm_planner_dispatcher/.loopy_loop/workflow_sets/pm_planner_dispatcher/workflows/dispatcher/config.yaml +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/loopy_loop/templates/pm_planner_dispatcher/.loopy_loop/workflow_sets/pm_planner_dispatcher/workflows/dispatcher/prompt.txt +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/loopy_loop/templates/pm_planner_dispatcher/.loopy_loop/workflow_sets/pm_planner_dispatcher/workflows/planner/config.yaml +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/loopy_loop/templates/pm_planner_dispatcher/loopy_loop_goal.txt +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/loopy_loop/worker_identity.py +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/tests/__init__.py +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/tests/conftest.py +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/tests/test_api_base_normalization.py +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/tests/test_config.py +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/tests/test_examples.py +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/tests/test_fresh_run_archive.py +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/tests/test_goal_check_gate.py +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/tests/test_goal_hash_derivation.py +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/tests/test_harness_runner.py +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/tests/test_must_follow_success.py +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/tests/test_scheduler.py +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/tests/test_sessions.py +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/tests/test_state_store.py +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/tests/test_worker.py +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/src/tests/test_worker_liveness_recovery.py +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/uv.lock +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/.gitignore +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/README.md +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/mdx-components.tsx +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/next.config.ts +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/package-lock.json +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/package.json +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/postcss.config.mjs +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/public/.nojekyll +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/public/CNAME +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/src/app/docs/child-sessions/page.mdx +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/src/app/docs/concepts/page.mdx +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/src/app/docs/configuration/page.mdx +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/src/app/docs/evaluation/page.mdx +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/src/app/docs/getting-started/page.mdx +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/src/app/docs/layout.tsx +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/src/app/docs/page.mdx +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/src/app/docs/success-and-control/page.mdx +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/src/app/docs/troubleshooting/page.mdx +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/src/app/docs/workflows/page.mdx +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/src/app/globals.css +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/src/app/layout.tsx +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/src/app/page.tsx +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/src/components/docs/DocsPageNavigation.tsx +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/src/components/docs/DocsSidebar.tsx +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/src/components/docs/DocsTableOfContents.tsx +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/src/components/docs/MobileDocsSidebar.tsx +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/src/components/search/SearchCommand.tsx +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/src/components/site/SiteHeader.tsx +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/src/lib/docs/navigation.ts +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/src/lib/search/pagefind.ts +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/src/lib/utils.ts +0 -0
- {loopy_loop-0.3.0 → loopy_loop-0.4.0}/website/tsconfig.json +0 -0
|
@@ -1,5 +1,60 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0 (breaking)
|
|
4
|
+
|
|
5
|
+
**Breaking API interaction — completions must echo the dispatched
|
|
6
|
+
`attempt_id`.** Every dispatched task now carries an `attempt_id` that the
|
|
7
|
+
worker must echo on `/finished`; a completion without the live task's attempt
|
|
8
|
+
is treated as stale (this is what fences superseded work). A 0.3.0 worker
|
|
9
|
+
therefore cannot complete tasks against a 0.4.0 coordinator — upgrade workers
|
|
10
|
+
and coordinator together (they normally ship in the same install).
|
|
11
|
+
|
|
12
|
+
- **Durable session-stack recovery (P0.1).** While a child session runs, the
|
|
13
|
+
parent's `state.json` records `active_child_session_id`; on `--resume` the
|
|
14
|
+
coordinator walks the pointer chain to the deepest live session instead of
|
|
15
|
+
silently reopening the parent and orphaning the running child. Terminal
|
|
16
|
+
children found at startup are finalized (children.json completed, pointer
|
|
17
|
+
cleared) and their parent resumed. Every interrupted-dispatch crash window
|
|
18
|
+
reconciles deterministically: dangling pointers are cleared, a fully
|
|
19
|
+
created child whose parent commit never landed is adopted, and leftover
|
|
20
|
+
request files never dispatch twice (children.json records the originating
|
|
21
|
+
`request_file`). Invalid child requests are terminally rejected
|
|
22
|
+
(`*.json.rejected`) instead of being re-read forever.
|
|
23
|
+
- **Attempt ids.** Every dispatched task carries a unique `attempt_id`
|
|
24
|
+
(also on the wire in `TaskResponse`, echoed in `FinishedRequest`); a late
|
|
25
|
+
`/finished` from a superseded attempt of the same coordinates is treated as
|
|
26
|
+
stale rather than recorded as the current result.
|
|
27
|
+
- Iteration artifacts (`result.json`, `result_text.txt`, `prompt.txt`,
|
|
28
|
+
`harness_run_id.txt`, `pending_finished_request.json`), `children.json`,
|
|
29
|
+
and `salvage.json` are all written atomically (unique temp + rename) — a
|
|
30
|
+
crash can never leave a truncated recovery artifact.
|
|
31
|
+
- Internal: the three duplicated dispatch blocks in the coordinator collapsed
|
|
32
|
+
into one `_advance()` step (stop checks → child dispatch → next workflow →
|
|
33
|
+
stamped task), so they can no longer drift apart. `_advance()` also enforces
|
|
34
|
+
the suspended-parent invariant: a parent with a live child can never acquire
|
|
35
|
+
its own task (a duplicate `/finished` retry gets the child's live task
|
|
36
|
+
instead), and a coordinator-level transition lock serializes cross-store
|
|
37
|
+
handoffs.
|
|
38
|
+
- Review hardening (adversarial Codex review of the above): request-file
|
|
39
|
+
tombstones apply only to RUNNING child records (a completed child's
|
|
40
|
+
filename is reusable for new work); the children.json record lands BEFORE
|
|
41
|
+
the child state so an interrupted dispatch is always discoverable
|
|
42
|
+
(`failed_dispatch` + exactly-once redispatch); startup reconciles every
|
|
43
|
+
running-projected record (terminal children finalize even without a
|
|
44
|
+
pointer); the first child task carries an attempt id; attempt checks are
|
|
45
|
+
strict whenever the live task has one (including `result.json` provenance —
|
|
46
|
+
a stale artifact can no longer complete a new attempt); semantically
|
|
47
|
+
unusable child requests (unknown workflow set, no eligible workflow) are
|
|
48
|
+
terminally rejected instead of wedging every completion; packaged prompts
|
|
49
|
+
instruct atomic control/goal_check publication; the crash model (process
|
|
50
|
+
crash, no fsync) is documented.
|
|
51
|
+
- **The `pm_planner_dispatcher` template is executable from a clean init
|
|
52
|
+
(P0.4).** `loopy init --template pm_planner_dispatcher` now also ships the
|
|
53
|
+
`inner_outer_eval` child workflow set its dispatcher spawns — previously a
|
|
54
|
+
clean init could not execute a single child session. The child set is
|
|
55
|
+
sourced from the `inner_outer_eval` template itself, so the two copies can
|
|
56
|
+
never drift apart.
|
|
57
|
+
|
|
3
58
|
## 0.3.0 (breaking)
|
|
4
59
|
|
|
5
60
|
**Breaking API change — `/register` requires the worker's process identity.**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: loopy-loop
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Run long-running AI agent workflows inside your repository.
|
|
5
5
|
Project-URL: Homepage, https://github.com/writeitai/loopy-loop
|
|
6
6
|
Project-URL: Repository, https://github.com/writeitai/loopy-loop
|
|
@@ -484,7 +484,8 @@ loopy init [--template default|inner_outer_eval|pm_planner_dispatcher]
|
|
|
484
484
|
Scaffolds loopy-loop files. The default template creates only the reserved
|
|
485
485
|
`goal_check` workflow. `inner_outer_eval` creates the recommended outer/inner/eval
|
|
486
486
|
workflow set. `pm_planner_dispatcher` creates planner/dispatcher workflows for
|
|
487
|
-
child-session orchestration
|
|
487
|
+
child-session orchestration — and also ships the `inner_outer_eval` child set
|
|
488
|
+
its dispatcher spawns, so a clean init is executable end to end.
|
|
488
489
|
|
|
489
490
|
```bash
|
|
490
491
|
loopy coordinator --host 0.0.0.0 --port 8080 [--resume] [--workflow-set NAME] [--goal-file PATH]
|
|
@@ -444,7 +444,8 @@ loopy init [--template default|inner_outer_eval|pm_planner_dispatcher]
|
|
|
444
444
|
Scaffolds loopy-loop files. The default template creates only the reserved
|
|
445
445
|
`goal_check` workflow. `inner_outer_eval` creates the recommended outer/inner/eval
|
|
446
446
|
workflow set. `pm_planner_dispatcher` creates planner/dispatcher workflows for
|
|
447
|
-
child-session orchestration
|
|
447
|
+
child-session orchestration — and also ships the `inner_outer_eval` child set
|
|
448
|
+
its dispatcher spawns, so a clean init is executable end to end.
|
|
448
449
|
|
|
449
450
|
```bash
|
|
450
451
|
loopy coordinator --host 0.0.0.0 --port 8080 [--resume] [--workflow-set NAME] [--goal-file PATH]
|
|
@@ -33,6 +33,7 @@ Run response:
|
|
|
33
33
|
"workflow_id": "planner",
|
|
34
34
|
"session_id": "20260419_143022_71393ee22450_ab12cd34",
|
|
35
35
|
"iteration": 3,
|
|
36
|
+
"attempt_id": "a1b2c3d4e5f6",
|
|
36
37
|
"config_snapshot": {
|
|
37
38
|
"goal": "Ship a minimal working landing page",
|
|
38
39
|
"goal_hash": "71393ee22450",
|
|
@@ -115,6 +116,13 @@ Rules:
|
|
|
115
116
|
escape hatch is to kill that process and register again — the 409 message
|
|
116
117
|
names its pid.
|
|
117
118
|
- If the loop is in a terminal state, `/register` immediately returns `action=stop`.
|
|
119
|
+
- On `--resume`, the coordinator walks the durable parent→child session
|
|
120
|
+
pointers to the deepest live session: a running child continues where it
|
|
121
|
+
was (previously a restart silently reopened the parent and orphaned the
|
|
122
|
+
child), a child found terminal is finalized and its parent resumed, and
|
|
123
|
+
each interrupted-dispatch crash window reconciles deterministically
|
|
124
|
+
(dangling pointers cleared, fully-created-but-unpointed children adopted,
|
|
125
|
+
leftover request files never dispatched twice).
|
|
118
126
|
|
|
119
127
|
## POST /finished
|
|
120
128
|
|
|
@@ -128,6 +136,7 @@ Request:
|
|
|
128
136
|
"success": true,
|
|
129
137
|
"text": "done",
|
|
130
138
|
"error": null,
|
|
139
|
+
"attempt_id": "a1b2c3d4e5f6",
|
|
131
140
|
"worker": {
|
|
132
141
|
"hostname": "buildbox",
|
|
133
142
|
"pid": 4242,
|
|
@@ -143,7 +152,12 @@ Response: same shape as `/register` response (`action` is either `"run"` or `"st
|
|
|
143
152
|
|
|
144
153
|
Rules:
|
|
145
154
|
|
|
146
|
-
-
|
|
155
|
+
- Every dispatched task carries a unique `attempt_id`; the worker echoes it on
|
|
156
|
+
`/finished`. A call whose attempt id differs from the live task's — even
|
|
157
|
+
with matching session/workflow/iteration — is stale: it belongs to a
|
|
158
|
+
superseded attempt whose work was already recovered or abandoned.
|
|
159
|
+
- If `session_id` + `workflow_id` + `iteration` (+ `attempt_id`, when both
|
|
160
|
+
sides carry one) does not match `current_task`,
|
|
147
161
|
the call is treated as stale: state is not mutated and `current_task` is not
|
|
148
162
|
changed. The current task's run response is returned only when the caller's
|
|
149
163
|
identity matches the task's recorded owner (or either identity is unknown —
|
|
@@ -165,6 +165,26 @@ eval-banana run \
|
|
|
165
165
|
- If the file is missing but `result.json` exists for the active task, the
|
|
166
166
|
coordinator can reconstruct the finished request from `result.json`
|
|
167
167
|
|
|
168
|
+
`children.json` (parent sessions)
|
|
169
|
+
|
|
170
|
+
- Index of child sessions dispatched by this session; each record carries the
|
|
171
|
+
child `session_id`, `workflow_set`, `status`, timestamps, `stop_reason`, and
|
|
172
|
+
the originating `request_file` name (which makes the dispatch scan
|
|
173
|
+
idempotent: a request whose filename already appears here is never
|
|
174
|
+
dispatched twice, even if a crash left the file behind)
|
|
175
|
+
- The parent's `state.json` additionally records `active_child_session_id`
|
|
176
|
+
while a child runs — the durable session-stack pointer a restarted
|
|
177
|
+
coordinator follows to resume the child instead of orphaning it
|
|
178
|
+
|
|
179
|
+
Atomicity and crash model: the coordinator- and worker-owned artifacts above
|
|
180
|
+
are written atomically (same-directory temp file + rename), so a **process
|
|
181
|
+
crash** never leaves them truncated — readers see either the previous or the
|
|
182
|
+
new complete file. This does not extend to power loss / kernel crashes (no
|
|
183
|
+
fsync), and it cannot be enforced for **workflow-written** signals
|
|
184
|
+
(`control.json`, `goal_check.json`, child requests): the packaged prompts
|
|
185
|
+
instruct agents to publish those via temp-file + rename, but a torn write by a
|
|
186
|
+
non-compliant agent is read as invalid output.
|
|
187
|
+
|
|
168
188
|
`salvage.json`
|
|
169
189
|
|
|
170
190
|
- Written into the interrupted iteration's directory during crash recovery,
|
|
@@ -46,6 +46,20 @@ PACKAGED_TEMPLATE_FILES_BY_NAME = {
|
|
|
46
46
|
".loopy_loop/workflow_sets/pm_planner_dispatcher/workflows/dispatcher/prompt.txt",
|
|
47
47
|
],
|
|
48
48
|
}
|
|
49
|
+
# Files a template ships FROM ANOTHER template's directory. The
|
|
50
|
+
# pm_planner_dispatcher dispatcher spawns child sessions running the
|
|
51
|
+
# inner_outer_eval workflow set, so a clean `loopy init` must ship that set
|
|
52
|
+
# too — sourced from the inner_outer_eval template itself so the two copies
|
|
53
|
+
# can never drift apart.
|
|
54
|
+
PACKAGED_TEMPLATE_EXTRA_SOURCES: dict[str, list[tuple[str, str]]] = {
|
|
55
|
+
PM_PLANNER_DISPATCHER_TEMPLATE_NAME: [
|
|
56
|
+
(INNER_OUTER_EVAL_TEMPLATE_NAME, relative_path)
|
|
57
|
+
for relative_path in PACKAGED_TEMPLATE_FILES_BY_NAME[
|
|
58
|
+
INNER_OUTER_EVAL_TEMPLATE_NAME
|
|
59
|
+
]
|
|
60
|
+
if relative_path.startswith(".loopy_loop/workflow_sets/")
|
|
61
|
+
]
|
|
62
|
+
}
|
|
49
63
|
PACKAGED_TEMPLATE_NAMES = list(PACKAGED_TEMPLATE_FILES_BY_NAME)
|
|
50
64
|
GITIGNORE_LINES = [".loopy_loop/sessions/"]
|
|
51
65
|
ROOT_CONFIG_TEMPLATE = f"""goal_file: "{DEFAULT_GOAL_FILENAME}"
|
|
@@ -176,6 +190,16 @@ def _init_packaged_template(*, repo_root: Path, template_name: str) -> list[str]
|
|
|
176
190
|
repo_root=repo_root,
|
|
177
191
|
)
|
|
178
192
|
)
|
|
193
|
+
for source_template, relative_path in PACKAGED_TEMPLATE_EXTRA_SOURCES.get(
|
|
194
|
+
template_name, []
|
|
195
|
+
):
|
|
196
|
+
created.extend(
|
|
197
|
+
_copy_template_file_if_missing(
|
|
198
|
+
source_root=files("loopy_loop").joinpath("templates", source_template),
|
|
199
|
+
relative_path=relative_path,
|
|
200
|
+
repo_root=repo_root,
|
|
201
|
+
)
|
|
202
|
+
)
|
|
179
203
|
return created
|
|
180
204
|
|
|
181
205
|
|