loopy-loop 0.2.0__tar.gz → 0.2.1__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 (65) hide show
  1. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/CHANGELOG.md +5 -0
  2. loopy_loop-0.2.1/PKG-INFO +505 -0
  3. loopy_loop-0.2.1/README.md +465 -0
  4. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/pyproject.toml +2 -2
  5. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/skills/loopy-loop/SKILL.md +2 -2
  6. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/uv.lock +1 -1
  7. loopy_loop-0.2.0/PKG-INFO +0 -408
  8. loopy_loop-0.2.0/README.md +0 -368
  9. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/.eval-banana/config.toml +0 -0
  10. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/.github/workflows/ci.yml +0 -0
  11. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/.github/workflows/release.yml +0 -0
  12. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/.gitignore +0 -0
  13. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/.team-harness/config.toml +0 -0
  14. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/.team-harness/coordinator_system_message.md +0 -0
  15. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/.team-harness/worker_footer.md +0 -0
  16. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/.team-harness/worker_suffix.md +0 -0
  17. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/LICENSE +0 -0
  18. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/Makefile +0 -0
  19. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/docs/http-contract.md +0 -0
  20. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/docs/session-layout.md +0 -0
  21. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/__init__.py +0 -0
  22. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/cli.py +0 -0
  23. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/config.py +0 -0
  24. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/coordinator_app.py +0 -0
  25. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/harness_runner.py +0 -0
  26. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/models.py +0 -0
  27. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/scheduler.py +0 -0
  28. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/sessions.py +0 -0
  29. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/state_store.py +0 -0
  30. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/templates/inner_outer_eval/.gitignore +0 -0
  31. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/eval_reviewer/config.yaml +0 -0
  32. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/eval_reviewer/prompt.txt +0 -0
  33. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/eval_runner/config.yaml +0 -0
  34. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/eval_runner/prompt.txt +0 -0
  35. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/inner/config.yaml +0 -0
  36. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/inner/prompt.txt +0 -0
  37. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/outer/config.yaml +0 -0
  38. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/outer/prompt.txt +0 -0
  39. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/templates/inner_outer_eval/loopy_loop_config.yaml +0 -0
  40. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/templates/inner_outer_eval/loopy_loop_goal.txt +0 -0
  41. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/templates/pm_planner_dispatcher/.gitignore +0 -0
  42. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/templates/pm_planner_dispatcher/.loopy_loop/workflow_sets/pm_planner_dispatcher/workflows/dispatcher/config.yaml +0 -0
  43. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/templates/pm_planner_dispatcher/.loopy_loop/workflow_sets/pm_planner_dispatcher/workflows/dispatcher/prompt.txt +0 -0
  44. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/templates/pm_planner_dispatcher/.loopy_loop/workflow_sets/pm_planner_dispatcher/workflows/planner/config.yaml +0 -0
  45. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/templates/pm_planner_dispatcher/.loopy_loop/workflow_sets/pm_planner_dispatcher/workflows/planner/prompt.txt +0 -0
  46. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/templates/pm_planner_dispatcher/loopy_loop_config.yaml +0 -0
  47. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/templates/pm_planner_dispatcher/loopy_loop_goal.txt +0 -0
  48. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/worker.py +0 -0
  49. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/tests/__init__.py +0 -0
  50. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/tests/conftest.py +0 -0
  51. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/tests/test_api_base_normalization.py +0 -0
  52. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/tests/test_cli.py +0 -0
  53. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/tests/test_config.py +0 -0
  54. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/tests/test_coordinator_app.py +0 -0
  55. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/tests/test_examples.py +0 -0
  56. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/tests/test_fresh_run_archive.py +0 -0
  57. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/tests/test_goal_check_gate.py +0 -0
  58. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/tests/test_goal_hash_derivation.py +0 -0
  59. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/tests/test_harness_runner.py +0 -0
  60. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/tests/test_idempotent_finished.py +0 -0
  61. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/tests/test_must_follow_success.py +0 -0
  62. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/tests/test_scheduler.py +0 -0
  63. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/tests/test_sessions.py +0 -0
  64. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/tests/test_state_store.py +0 -0
  65. {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/tests/test_worker.py +0 -0
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.1
4
+
5
+ - Improve README onboarding, install, initialization, configuration, and logging docs.
6
+ - Update package and skill descriptions to avoid unclear repository-scope jargon.
7
+
3
8
  ## 0.2.0 (breaking)
4
9
 
5
10
  **Breaking API change — drop leases, polling, and worker identity.**
@@ -0,0 +1,505 @@
1
+ Metadata-Version: 2.4
2
+ Name: loopy-loop
3
+ Version: 0.2.1
4
+ Summary: Run long-running AI agent workflows inside your repository.
5
+ Project-URL: Homepage, https://github.com/writeitai/loopy-loop
6
+ Project-URL: Repository, https://github.com/writeitai/loopy-loop
7
+ Project-URL: Issues, https://github.com/writeitai/loopy-loop/issues
8
+ Project-URL: Changelog, https://github.com/writeitai/loopy-loop/releases
9
+ Author-email: "WriteIt.ai s.r.o." <info@writeit.ai>
10
+ License-Expression: Apache-2.0
11
+ License-File: LICENSE
12
+ Keywords: agents,ai,automation,fastapi,loop,team-harness,workflow
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
+ Classifier: Topic :: Software Development
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.12
25
+ Requires-Dist: click>=8.1
26
+ Requires-Dist: fastapi>=0.115.0
27
+ Requires-Dist: filelock>=3.16
28
+ Requires-Dist: httpx>=0.28
29
+ Requires-Dist: pydantic>=2.11
30
+ Requires-Dist: pyyaml>=6.0.2
31
+ Requires-Dist: rich>=14.0
32
+ Requires-Dist: team-harness>=0.2.10
33
+ Requires-Dist: uvicorn[standard]>=0.32.0
34
+ Provides-Extra: dev
35
+ Requires-Dist: pyright>=1.1; extra == 'dev'
36
+ Requires-Dist: pytest-asyncio>=0.25; extra == 'dev'
37
+ Requires-Dist: pytest>=8.3; extra == 'dev'
38
+ Requires-Dist: ruff>=0.4; extra == 'dev'
39
+ Description-Content-Type: text/markdown
40
+
41
+ # loopy-loop
42
+
43
+ `loopy-loop` runs long-running AI agent workflows inside your repository.
44
+ It turns a goal file in your repository into an inspectable sequence of agent
45
+ iterations: plan, implement, evaluate, record evidence, and continue until the
46
+ goal is met or the loop hits a terminal blocker.
47
+
48
+ The value is control and durability. Instead of asking one agent to solve a
49
+ large task in one fragile chat, loopy-loop gives the work a persistent session
50
+ directory, repeatable workflow prompts, explicit stop conditions, and structured
51
+ logs. You can pause, resume, audit what happened, adjust the goal, inspect every
52
+ prompt/result pair, and keep the actual project changes in normal git branches
53
+ and PRs.
54
+
55
+ Under the hood, loopy-loop runs a small FastAPI coordinator and one or more
56
+ workers. The coordinator owns the loop state and chooses the next workflow. The
57
+ workers run assignments through
58
+ [`team-harness`](https://github.com/writeitai/team-harness), which can delegate
59
+ to agent CLIs such as Codex, Claude Code, and Gemini. The packaged
60
+ `inner_outer_eval` template also uses
61
+ [`eval-banana`](https://github.com/writeitai/eval-banana) conventions for
62
+ session-scoped evaluation checks.
63
+
64
+ ## Install
65
+
66
+ Install the CLI from the official [PyPI package](https://pypi.org/project/loopy-loop/).
67
+
68
+ With `uv`, install it as a command-line tool:
69
+
70
+ ```bash
71
+ uv tool install loopy-loop
72
+ ```
73
+
74
+ Or with `pip`:
75
+
76
+ ```bash
77
+ pip install loopy-loop
78
+ ```
79
+
80
+ For development inside this repository:
81
+
82
+ ```bash
83
+ uv sync --extra dev
84
+ ```
85
+
86
+ ## Install the Agent Skill
87
+
88
+ This repo also ships an [Agent Skill](https://support.claude.com/en/articles/12512176-what-are-skills)
89
+ that teaches Claude Code, Codex, and compatible agents how to set up and run
90
+ loopy-loop in another target repo.
91
+
92
+ ```bash
93
+ npx skills add https://github.com/writeitai/loopy-loop --skill loopy-loop
94
+ ```
95
+
96
+ The skill source lives under [`skills/loopy-loop/`](./skills/loopy-loop/SKILL.md).
97
+
98
+ ## Initialize a Target Repo
99
+
100
+ Run this from the repository you want agents to work on:
101
+
102
+ ```bash
103
+ loopy init --template inner_outer_eval
104
+ ```
105
+
106
+ This is the recommended starting template. It creates:
107
+
108
+ - `loopy_loop_config.yaml`
109
+ - `loopy_loop_goal.txt`
110
+ - `.loopy_loop/workflow_sets/inner_outer_eval/workflows/outer/`
111
+ - `.loopy_loop/workflow_sets/inner_outer_eval/workflows/inner/`
112
+ - `.loopy_loop/workflow_sets/inner_outer_eval/workflows/eval_reviewer/`
113
+ - `.loopy_loop/workflow_sets/inner_outer_eval/workflows/eval_runner/`
114
+ - a `.gitignore` entry for `.loopy_loop/sessions/`
115
+
116
+ `loopy init` is idempotent. It creates missing files and leaves existing files
117
+ alone.
118
+
119
+ ## Write the Goal
120
+
121
+ The loop goal lives in `loopy_loop_goal.txt`. Replace the scaffolded example
122
+ with the real target, including constraints and observable completion criteria.
123
+
124
+ Example:
125
+
126
+ ```text
127
+ Implement passwordless email login.
128
+
129
+ Completion criteria:
130
+ - Users can request a one-time login link from the sign-in page.
131
+ - The link expires after 15 minutes and cannot be reused.
132
+ - Existing password login keeps working.
133
+ - Tests cover token expiry, token reuse, and successful login.
134
+ - README documents required environment variables.
135
+ ```
136
+
137
+ Keep the goal specific enough that a reviewer or eval workflow can decide
138
+ whether the loop is done. For one-off overrides, start the coordinator with
139
+ `--goal-file PATH`; the file is copied into the session as `goal.md`.
140
+
141
+ ## Run the Loop
142
+
143
+ Start the coordinator in one terminal:
144
+
145
+ ```bash
146
+ loopy coordinator --host 127.0.0.1 --port 8080
147
+ ```
148
+
149
+ Start a worker in another terminal:
150
+
151
+ ```bash
152
+ loopy worker --coordinator http://127.0.0.1:8080
153
+ ```
154
+
155
+ Useful control commands:
156
+
157
+ ```bash
158
+ loopy status
159
+ loopy stop
160
+ ```
161
+
162
+ If the coordinator stops while a session is still running, restart it with:
163
+
164
+ ```bash
165
+ loopy coordinator --host 127.0.0.1 --port 8080 --resume
166
+ ```
167
+
168
+ The default templates use `team_harness_provider: "codex"`, so the coordinator
169
+ uses local Codex authentication. If you switch to an OpenAI-compatible provider,
170
+ export the environment variable named in `team_harness_api_key_env`, usually
171
+ `OPENROUTER_API_KEY`, in both the coordinator and worker shells.
172
+
173
+ ## How It Works
174
+
175
+ At a high level:
176
+
177
+ 1. `loopy init` writes a root config, a goal file, and workflow files into the
178
+ target repo.
179
+ 2. `loopy coordinator` loads `loopy_loop_config.yaml`, resolves the goal text,
180
+ creates a session under `.loopy_loop/sessions/`, and exposes two HTTP
181
+ endpoints: `/register` and `/finished`.
182
+ 3. A worker calls `/register`, receives the next workflow assignment, renders
183
+ the workflow prompt with session paths, and runs it through `team-harness`.
184
+ 4. `team-harness` runs a coordinator model and can spawn external worker CLIs
185
+ such as Codex, Claude Code, or Gemini.
186
+ 5. The worker writes the rendered prompt, normalized result, result text,
187
+ harness run id, and harness output path into the iteration directory.
188
+ 6. The coordinator records the result, checks session control/eval artifacts,
189
+ and either dispatches the next workflow or stops.
190
+
191
+ The `inner_outer_eval` template is organized around four workflows:
192
+
193
+ - `outer`: plans, tracks durable project state, reviews evidence, and decides
194
+ what should happen next.
195
+ - `inner`: implements the selected work in the target repo.
196
+ - `eval_reviewer`: creates or refreshes session-scoped eval-banana checks.
197
+ - `eval_runner`: runs the eval checks and writes `goal_check.json`.
198
+
199
+ The loop does not hide state inside a chat transcript. Continuity comes from
200
+ git state plus files in `.loopy_loop/sessions/<session_id>/`.
201
+
202
+ ## Repo Layout
203
+
204
+ After initialization, the target repo has this shape:
205
+
206
+ ```text
207
+ target repo/
208
+ ├── loopy_loop_config.yaml
209
+ ├── loopy_loop_goal.txt
210
+ └── .loopy_loop/
211
+ ├── workflow_sets/
212
+ │ └── <workflow_set>/workflows/<workflow_id>/
213
+ │ ├── config.yaml
214
+ │ └── prompt.txt
215
+ └── sessions/
216
+ └── <session_id>/
217
+ ├── goal.md
218
+ ├── session.json
219
+ ├── state.json
220
+ ├── control.json
221
+ ├── updates_from_user.md
222
+ ├── project_state/
223
+ ├── eval_checks/
224
+ ├── eval_results/
225
+ ├── harness_outputs/
226
+ ├── child_requests/
227
+ ├── children/
228
+ └── iterations/
229
+ ```
230
+
231
+ Workflow definitions are part of the repo and should usually be committed.
232
+ Session directories are runtime output and are ignored by default.
233
+
234
+ ## Configuration
235
+
236
+ Root config lives at `loopy_loop_config.yaml`:
237
+
238
+ ```yaml
239
+ goal_file: loopy_loop_goal.txt
240
+ workflow_set: inner_outer_eval
241
+ max_turns: 160
242
+ goal_check_consecutive_failures_cap: 3
243
+ team_harness_provider: "codex"
244
+ team_harness_model: "gpt-5.5"
245
+ team_harness_agents:
246
+ - "codex"
247
+ - "claude"
248
+ - "gemini"
249
+ team_harness_agent_models:
250
+ codex: "gpt-5.5"
251
+ claude: "claude-opus-4-8"
252
+ gemini: "gemini-3.5-flash"
253
+ team_harness_agent_reasoning_efforts:
254
+ codex: "high"
255
+ team_harness_api_base: "https://openrouter.ai/api/v1"
256
+ team_harness_api_key_env: "OPENROUTER_API_KEY"
257
+ ```
258
+
259
+ Important rules:
260
+
261
+ - `workflow_set` selects the default workflow set for new sessions.
262
+ - `goal_file` is resolved relative to `loopy_loop_config.yaml`.
263
+ - Inline `goal` values in YAML are rejected; the goal should live in a file.
264
+ - `max_turns` is the maximum number of completed workflow iterations.
265
+ - `team_harness_model` controls the team-harness coordinator model.
266
+ - `team_harness_agent_models` controls default models for worker subprocesses.
267
+ - `team_harness_api_base` is normalized by loopy-loop: trailing slash stripped,
268
+ `/v1` appended when missing.
269
+ - `team_harness_max_retries`, `team_harness_retry_base_delay_s`, and
270
+ `team_harness_retry_max_delay_s` are optional retry controls for transient
271
+ team-harness API/network errors.
272
+
273
+ Workflow config lives beside each workflow prompt:
274
+
275
+ ```yaml
276
+ enabled: true
277
+ priority: 0
278
+ run_every: 1
279
+ must_follow: null
280
+ not_before_iteration: 0
281
+ run_on_start: false
282
+ run_after_successes: null
283
+ emits_goal_check: false
284
+ description: ""
285
+ ```
286
+
287
+ Workflow rules:
288
+
289
+ - The workflow id is the folder name under
290
+ `.loopy_loop/workflow_sets/<workflow_set>/workflows/`.
291
+ - `priority` breaks ties among eligible workflows; higher values run first.
292
+ - `run_every` is based on completed iteration count, not wall clock.
293
+ - `run_on_start=true` makes a workflow eligible before any successful workflow
294
+ has run.
295
+ - `must_follow` and `run_after_successes.workflow_id` must reference existing
296
+ workflow ids.
297
+ - `run_after_successes` can schedule a workflow after every N successful runs
298
+ of another workflow:
299
+
300
+ ```yaml
301
+ run_after_successes:
302
+ workflow_id: inner
303
+ every: 10
304
+ ```
305
+
306
+ - `emits_goal_check=true` lets a non-`goal_check` workflow write
307
+ `goal_check.json` as an eval artifact. Stopping still requires updating
308
+ session `control.json`.
309
+
310
+ ## Output and Logging
311
+
312
+ Each fresh coordinator run creates one session directory:
313
+
314
+ ```text
315
+ .loopy_loop/sessions/<session_id>/
316
+ ```
317
+
318
+ Session ids start with a UTC timestamp and include a deterministic goal hash, so
319
+ session directories sort chronologically and similar goals are easy to compare.
320
+
321
+ Important session files:
322
+
323
+ - `goal.md`: the exact goal text copied into the session.
324
+ - `session.json`: session metadata.
325
+ - `state.json`: coordinator-owned dispatch state.
326
+ - `events.jsonl`: reserved append-only diagnostics log.
327
+ - `control.json`: workflow-owned stop switch.
328
+ - `updates_from_user.md`: human-writable inbox for changes after the session
329
+ starts.
330
+ - `project_state/`: workflow-owned durable markdown state.
331
+ - `eval_checks/`: session-scoped eval-banana checks.
332
+ - `eval_results/`: raw eval-banana reports.
333
+ - `harness_outputs/`: team-harness coordinator and worker artifacts.
334
+ - `iterations/`: one directory per loopy-loop assignment.
335
+
336
+ Each iteration directory contains:
337
+
338
+ ```text
339
+ .loopy_loop/sessions/<session_id>/iterations/<NNNN>_<workflow_id>/
340
+ ├── prompt.txt
341
+ ├── result.json
342
+ ├── result_text.txt
343
+ ├── harness_run_id.txt
344
+ ├── pending_finished_request.json
345
+ └── goal_check.json # only for eval-emitting workflows
346
+ ```
347
+
348
+ `prompt.txt` is the rendered prompt sent to `TeamHarness.run(...)`.
349
+ `result.json` is loopy-loop's normalized result. `result_text.txt` is the
350
+ plain-text final response. `harness_run_id.txt` links the iteration to the
351
+ corresponding team-harness output under `harness_outputs/`.
352
+
353
+ Team-harness outputs are routed here:
354
+
355
+ ```text
356
+ .loopy_loop/sessions/<session_id>/harness_outputs/<NNNN>_<workflow_id>/<team_harness_run_id>/
357
+ ```
358
+
359
+ Eval-banana outputs should be routed here:
360
+
361
+ ```text
362
+ .loopy_loop/sessions/<session_id>/eval_results/<eval_banana_run_id>/
363
+ ```
364
+
365
+ See [docs/session-layout.md](docs/session-layout.md) for the full session file
366
+ contract.
367
+
368
+ ## Control and Completion
369
+
370
+ `control.json` is the session-scoped stop switch. It starts as:
371
+
372
+ ```json
373
+ {
374
+ "state": "running",
375
+ "reason": "session active",
376
+ "stop_reason": null,
377
+ "schema_version": 1
378
+ }
379
+ ```
380
+
381
+ To stop successfully, a workflow writes:
382
+
383
+ ```json
384
+ {
385
+ "state": "stopped",
386
+ "reason": "evals passed",
387
+ "stop_reason": "goal_met",
388
+ "schema_version": 1
389
+ }
390
+ ```
391
+
392
+ To stop because the loop cannot continue:
393
+
394
+ ```json
395
+ {
396
+ "state": "stopped",
397
+ "reason": "specific terminal blocker",
398
+ "stop_reason": "unresolvable_error",
399
+ "schema_version": 1
400
+ }
401
+ ```
402
+
403
+ `goal_check.json` is a per-iteration eval artifact:
404
+
405
+ ```json
406
+ {"goal_met": false, "reason": "docs still missing", "schema_version": 1}
407
+ ```
408
+
409
+ A valid `goal_check.json` does not stop the loop by itself. It is evidence.
410
+ Stopping is controlled by session `control.json`. If goal-check output is
411
+ missing or invalid repeatedly, the coordinator stops with
412
+ `stop_reason="goal_check_broken"` after the configured failure cap.
413
+
414
+ ## Workflow Sets and Child Sessions
415
+
416
+ Workflow sets are mandatory. Even a single-loop repo uses:
417
+
418
+ ```text
419
+ .loopy_loop/workflow_sets/main/workflows/...
420
+ ```
421
+
422
+ The older `.loopy_loop/workflows/...` layout is not loaded.
423
+
424
+ A workflow can request one sequential child session by writing a JSON file under
425
+ the active session's `child_requests/` directory:
426
+
427
+ ```json
428
+ {
429
+ "workflow_set": "pm_planner_dispatcher",
430
+ "goal": "Implement the selected planner item.",
431
+ "schema_version": 1
432
+ }
433
+ ```
434
+
435
+ The coordinator creates the child session under the parent session's
436
+ `children/` directory, copies the request goal into the child `goal.md`, runs
437
+ the requested workflow set, and resumes the parent after the child reaches a
438
+ terminal state. v1 is depth-first and single-child-at-a-time.
439
+
440
+ The packaged `pm_planner_dispatcher` workflow set uses this contract for PM
441
+ orchestration:
442
+
443
+ - `planner` maintains PM state, selects one work item, and reviews terminal
444
+ child-session evidence.
445
+ - `dispatcher` writes one child request for the selected work item or imports
446
+ terminal child evidence back into PM state.
447
+
448
+ ## HTTP Contract
449
+
450
+ The coordinator exposes exactly two endpoints:
451
+
452
+ - `POST /register`
453
+ - `POST /finished`
454
+
455
+ Both return a `TaskResponse` with `action` equal to `"run"` or `"stop"`.
456
+
457
+ A `run` response carries `workflow_set`, `workflow_id`, `session_id`,
458
+ `iteration`, and a config snapshot. A `stop` response carries `stop_reason`.
459
+
460
+ If `/finished` receives a stale response for a task that is no longer current,
461
+ the coordinator does not mutate state. If a worker exits after writing
462
+ `result.json` but before `/finished` is acknowledged, the next `/register`
463
+ recovers the completed result from the iteration directory instead of marking
464
+ the task abandoned.
465
+
466
+ See [docs/http-contract.md](docs/http-contract.md) for exact JSON payloads.
467
+
468
+ ## CLI Reference
469
+
470
+ ```bash
471
+ loopy init [--template default|inner_outer_eval|pm_planner_dispatcher]
472
+ ```
473
+
474
+ Scaffolds loopy-loop files. The default template creates only the reserved
475
+ `goal_check` workflow. `inner_outer_eval` creates the recommended outer/inner/eval
476
+ workflow set. `pm_planner_dispatcher` creates planner/dispatcher workflows for
477
+ child-session orchestration.
478
+
479
+ ```bash
480
+ loopy coordinator --host 0.0.0.0 --port 8080 [--resume] [--workflow-set NAME] [--goal-file PATH]
481
+ ```
482
+
483
+ Runs the coordinator. `--workflow-set` and `--goal-file` override the root
484
+ config for the new session. `--resume` reuses a non-terminal latest session.
485
+
486
+ ```bash
487
+ loopy worker --coordinator http://127.0.0.1:8080
488
+ ```
489
+
490
+ Runs a blocking worker until the coordinator returns `action: "stop"`.
491
+
492
+ ```bash
493
+ loopy status
494
+ loopy stop
495
+ ```
496
+
497
+ `status` prints the latest session state. `stop` sets `stop_requested=true` in
498
+ the latest session-local state.
499
+
500
+ ## Related Projects
501
+
502
+ - [`team-harness`](https://github.com/writeitai/team-harness): the model and
503
+ agent-CLI orchestration layer used by loopy-loop workers.
504
+ - [`eval-banana`](https://github.com/writeitai/eval-banana): a lightweight YAML
505
+ evaluation framework used by the packaged eval workflows.