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.
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/CHANGELOG.md +5 -0
- loopy_loop-0.2.1/PKG-INFO +505 -0
- loopy_loop-0.2.1/README.md +465 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/pyproject.toml +2 -2
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/skills/loopy-loop/SKILL.md +2 -2
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/uv.lock +1 -1
- loopy_loop-0.2.0/PKG-INFO +0 -408
- loopy_loop-0.2.0/README.md +0 -368
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/.eval-banana/config.toml +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/.github/workflows/ci.yml +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/.github/workflows/release.yml +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/.gitignore +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/.team-harness/config.toml +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/.team-harness/coordinator_system_message.md +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/.team-harness/worker_footer.md +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/.team-harness/worker_suffix.md +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/LICENSE +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/Makefile +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/docs/http-contract.md +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/docs/session-layout.md +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/__init__.py +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/cli.py +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/config.py +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/coordinator_app.py +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/harness_runner.py +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/models.py +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/scheduler.py +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/sessions.py +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/state_store.py +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/templates/inner_outer_eval/.gitignore +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/templates/inner_outer_eval/loopy_loop_config.yaml +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/templates/inner_outer_eval/loopy_loop_goal.txt +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/templates/pm_planner_dispatcher/.gitignore +0 -0
- {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
- {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
- {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
- {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
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/templates/pm_planner_dispatcher/loopy_loop_config.yaml +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/templates/pm_planner_dispatcher/loopy_loop_goal.txt +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/loopy_loop/worker.py +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/tests/__init__.py +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/tests/conftest.py +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/tests/test_api_base_normalization.py +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/tests/test_cli.py +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/tests/test_config.py +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/tests/test_coordinator_app.py +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/tests/test_examples.py +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/tests/test_fresh_run_archive.py +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/tests/test_goal_check_gate.py +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/tests/test_goal_hash_derivation.py +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/tests/test_harness_runner.py +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/tests/test_idempotent_finished.py +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/tests/test_must_follow_success.py +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/tests/test_scheduler.py +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/tests/test_sessions.py +0 -0
- {loopy_loop-0.2.0 → loopy_loop-0.2.1}/src/tests/test_state_store.py +0 -0
- {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.
|