clearbox 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. clearbox-0.1.0/.gitignore +7 -0
  2. clearbox-0.1.0/LICENSE +21 -0
  3. clearbox-0.1.0/PKG-INFO +580 -0
  4. clearbox-0.1.0/README.md +549 -0
  5. clearbox-0.1.0/clearbox/__init__.py +1 -0
  6. clearbox-0.1.0/clearbox/__main__.py +84 -0
  7. clearbox-0.1.0/clearbox/agents.py +12 -0
  8. clearbox-0.1.0/clearbox/app.py +736 -0
  9. clearbox-0.1.0/clearbox/bootstrap.py +154 -0
  10. clearbox-0.1.0/clearbox/engine.py +1409 -0
  11. clearbox-0.1.0/clearbox/harness/__init__.py +31 -0
  12. clearbox-0.1.0/clearbox/harness/base.py +122 -0
  13. clearbox-0.1.0/clearbox/harness/claude_sdk.py +160 -0
  14. clearbox-0.1.0/clearbox/harness/mock.py +40 -0
  15. clearbox-0.1.0/clearbox/harness/openai_check.py +117 -0
  16. clearbox-0.1.0/clearbox/harness/openai_compat.py +672 -0
  17. clearbox-0.1.0/clearbox/harness/probe.py +89 -0
  18. clearbox-0.1.0/clearbox/harness/registry.py +220 -0
  19. clearbox-0.1.0/clearbox/harness/tools.py +503 -0
  20. clearbox-0.1.0/clearbox/mcp_bridge.py +185 -0
  21. clearbox-0.1.0/clearbox/mcp_tools.py +939 -0
  22. clearbox-0.1.0/clearbox/static/app.js +1649 -0
  23. clearbox-0.1.0/clearbox/static/index.html +121 -0
  24. clearbox-0.1.0/clearbox/static/style.css +454 -0
  25. clearbox-0.1.0/clearbox/store.py +865 -0
  26. clearbox-0.1.0/examples/demo_plan.py +66 -0
  27. clearbox-0.1.0/examples/file_conflict_plan.py +89 -0
  28. clearbox-0.1.0/examples/mixed_plan.py +164 -0
  29. clearbox-0.1.0/pyproject.toml +50 -0
  30. clearbox-0.1.0/tests/fake_openai.py +187 -0
  31. clearbox-0.1.0/tests/test_agents_api.py +135 -0
  32. clearbox-0.1.0/tests/test_file_conflicts.py +323 -0
  33. clearbox-0.1.0/tests/test_flow.py +217 -0
  34. clearbox-0.1.0/tests/test_mixed_run.py +98 -0
  35. clearbox-0.1.0/tests/test_openai_harness.py +139 -0
  36. clearbox-0.1.0/tests/test_rework.py +92 -0
@@ -0,0 +1,7 @@
1
+ __pycache__/
2
+ .venv/
3
+ *.db
4
+ *.db-*
5
+ workspace/
6
+ dist/
7
+ *.egg-info/
clearbox-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kai Habermann
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,580 @@
1
+ Metadata-Version: 2.5
2
+ Name: clearbox
3
+ Version: 0.1.0
4
+ Summary: Observable, reviewable agent work: DAG plans over MCP, executed node by node and watched live
5
+ Project-URL: Homepage, https://github.com/KaiHabermann/Clearbox
6
+ Project-URL: Repository, https://github.com/KaiHabermann/Clearbox
7
+ Project-URL: Issues, https://github.com/KaiHabermann/Clearbox/issues
8
+ Author: Kai Habermann
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: agents,claude,code-review,dag,llm,mcp,openai,orchestration
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Environment :: Web Environment
15
+ Classifier: Framework :: FastAPI
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Software Development :: Build Tools
23
+ Classifier: Topic :: Software Development :: Quality Assurance
24
+ Requires-Python: >=3.10
25
+ Requires-Dist: claude-agent-sdk>=0.2.100
26
+ Requires-Dist: fastapi>=0.115
27
+ Requires-Dist: mcp<3,>=2.3
28
+ Requires-Dist: openai>=1.40
29
+ Requires-Dist: uvicorn>=0.30
30
+ Description-Content-Type: text/markdown
31
+
32
+ # Clearbox
33
+
34
+ **Observable, reviewable agent work.** Big changes, broken into a graph you can watch and audit.
35
+
36
+ A small service that lets a model **post a plan as a DAG of work items over MCP**, executes the
37
+ plan node by node (roots first, then their dependents, in parallel where possible) with agent
38
+ sessions attached to the nodes, and shows everything live in the browser: which nodes are being
39
+ worked on right now, and — on click — the agent's session in that node as it happens.
40
+
41
+ ```
42
+ model ──MCP──► create_plan / add_node / add_edge / start_plan
43
+ │
44
+ ┌───────▼────────┐ SQLite (plans, nodes, edges, agent profiles, transcripts)
45
+ │ Clearbox │──► built-in agent per ready node, chosen by its agent profile:
46
+ │ scheduler │ · Claude Agent SDK
47
+ │ │ · OpenAI-compatible endpoints (OpenAI, vLLM, Ollama, …)
48
+ └───────┬────────┘◄── or external agents: claim_next_node / complete_node
49
+ │ SSE
50
+ browser UI (DAG view, running markers, live transcripts)
51
+ ```
52
+
53
+ ## Install
54
+
55
+ Needs Python ≥ 3.10.
56
+
57
+ ```powershell
58
+ uv tool install clearbox # or: pipx install clearbox, or: pip install clearbox
59
+ clearbox --mock # fake agents: try the UI without spending tokens
60
+ ```
61
+
62
+ ## Quick start
63
+
64
+ From a clone, with [uv](https://docs.astral.sh/uv/):
65
+
66
+ ```powershell
67
+ cd Clearbox
68
+ uv run clearbox --mock # fake agents: try the UI without spending tokens
69
+ uv run python examples/demo_plan.py --start
70
+ ```
71
+
72
+ Open <http://127.0.0.1:8765/>.
73
+
74
+ For real agents drop `--mock`. Built-in agents run through the Claude Agent SDK, which ships its
75
+ own Claude Code CLI and uses your existing Claude login (or `ANTHROPIC_API_KEY`).
76
+
77
+ ```powershell
78
+ uv run clearbox # real Claude sessions, 3 in parallel
79
+ uv run clearbox --max-concurrency 5 --permission-mode acceptEdits
80
+ ```
81
+
82
+ Nodes can also run on any OpenAI-compatible endpoint instead — a local Ollama or vLLM model, or
83
+ OpenAI itself — and one plan can mix them per node, see
84
+ [Agents and harnesses](#agents-and-harnesses):
85
+
86
+ ```powershell
87
+ uv run clearbox --openai-base-url http://localhost:11434/v1 --openai-model qwen2.5-coder:14b
88
+ ```
89
+
90
+ Without uv: `pip install -e .` then `clearbox ...` (or `python -m clearbox ...`).
91
+
92
+ ### Connect a model
93
+
94
+ The MCP endpoint is `http://127.0.0.1:8765/mcp` (streamable HTTP).
95
+
96
+ ```powershell
97
+ claude mcp add --transport http clearbox http://127.0.0.1:8765/mcp
98
+ ```
99
+
100
+ Then ask, e.g. *"Use clearbox to make a plan for … and start it."*
101
+
102
+ ## MCP tools
103
+
104
+ | Tool | What it does |
105
+ | --- | --- |
106
+ | `create_plan(title, description="", parent_plan_id=None, parent_node_id=None)` | New plan → integer id (1, 2, 3 …). With a parent it becomes the **subplan** of that node. |
107
+ | `add_node(plan_id, title, description="", agent=None, files=None, results_from=None)` | New node → `[plan_id, node_id]`. Node ids count up per plan. `agent` (profile name or id) pins the node to an agent profile; omitted = inherit. `files` declares the paths it will create or modify, see [File scoping](#file-scoping-avoiding-clobbered-edits). `results_from` lists earlier nodes whose result text it receives, see [Inherited results](#inherited-results-edges-order-results_from-passes). |
108
+ | `add_edge(plan_id, from_node_id, to_node_id)` | `from` must finish before `to` starts. Cycles are rejected. Passes no result; that is `results_from`. |
109
+ | `remove_edge`, `remove_node` | Corrections while building. |
110
+ | `update_node(plan_id, node_id, title=None, description=None, files=None, results_from=None)` | Change only what you pass; `files` / `results_from` omitted keeps the current value, `[]` clears it. |
111
+ | `set_node_files(plan_id, node_id, files)` | Replace a node's declared files on their own, without touching title/description; `[]` or `null` clears them. Only before the node has run. |
112
+ | `set_node_results_from(plan_id, node_id, node_ids)` | Replace the earlier nodes whose result text the node receives; `[]` or `null` clears them. Each must be a node it already depends on through edges. Only before the node has run. |
113
+ | `get_plan(plan_id)`, `list_plans()` | Structure, status, results, progress. `get_plan` also shows `default_agent` and, per node, the resolved `agent` and its `agent_override`, plus `files` and `conflicts` (see [File scoping](#file-scoping-avoiding-clobbered-edits)) and `results_from`. |
114
+ | `list_agents()` | Agent profiles (secrets redacted to `has_api_key`; `default: true` marks the server default). |
115
+ | `create_agent(name, harness, model=None, base_url=None, api_key_env=None, params=None, tools=None, permission_mode=None, system_append=None, max_parallel=None, description="")` | New agent profile. Deliberately takes no literal `api_key`: secrets should not pass through a model's context. |
116
+ | `update_agent(agent, …same fields…)` | Change only the fields passed; `""` clears a text field, `max_parallel=0` removes the limit, `params`/`tools` replace the whole object. |
117
+ | `delete_agent(agent)` | Delete a profile; nodes and plans that used it inherit again. |
118
+ | `set_node_agent(plan_id, node_id, agent)` | Per-node override; `null` clears it. Applies the next time the node starts. |
119
+ | `set_plan_agent(plan_id, agent)` | Default for the plan and its subplans; `null` clears it. |
120
+ | `test_agent(agent)` | Liveness check: for `openai` lists the endpoint's models (falls back to a tiny completion) → `{ok, detail, reachable, model_listed, models_sample, credential}`. |
121
+ | `start_plan(plan_id, executor="builtin" \| "external")`, `stop_plan(plan_id)` | Run / pause a top-level plan. |
122
+ | `claim_next_node(agent_name, plan_id=None)` | External agents: take the next ready node, with the same prompt a built-in agent would get. |
123
+ | `report_progress(plan_id, node_id, message, kind="text")` | Append to the node's live transcript. |
124
+ | `complete_node(plan_id, node_id, result)`, `fail_node(plan_id, node_id, error)` | Finish a claimed node. |
125
+ | `set_plan_review_agent(plan_id, agent)` | Name (or `null` to clear) the agent profile that automatically reviews this plan's steps and, once it finishes, the plan as a whole; see [Reviewing](#reviewing). |
126
+ | `review_node(plan_id, node_id, agent=None)`, `review_plan(plan_id, agent=None)` | Review a done node's diff, or a finished plan's steps as a whole, right now; `agent` is a one-off override, used for this run only. |
127
+ | `get_reviews(plan_id)` | Every review recorded for this plan: the plan-level one plus each node's verdict and findings. |
128
+ | `rework_node(plan_id, node_id, instructions=None)` | Re-run one finished (`done` or `failed`) step with its review findings, or your `instructions`, in its prompt; dependants are left alone. It is reviewed again by itself afterwards, see [Rework](#rework-closing-the-review-loop). |
129
+
130
+ ## How execution works
131
+
132
+ * Node states: `pending → ready → running → done | failed`.
133
+ * A node becomes **ready** when all its prerequisites are done. Root nodes are ready immediately.
134
+ * **builtin** executor: the service starts one agent session per ready node (up to
135
+ `--max-concurrency` in total, and up to each agent profile's `max_parallel`), on the harness the
136
+ node's agent profile names (see [Agents and harnesses](#agents-and-harnesses)). The prompt contains the overall goal, the chain of parent nodes for
137
+ subplans, a one-line list of the other nodes of the plan, the node's own task, its file scope, and
138
+ **only the earlier results it asked for** (see [Inherited results](#inherited-results-edges-order-results_from-passes)).
139
+ The agent's final message becomes the node's result; it reaches a later node only if that node
140
+ requests it.
141
+ * **external** executor: ready nodes wait until something calls `claim_next_node`.
142
+ * **Subplans**: a node with a subplan skips the agent; when it is reached its subplan runs, and the
143
+ node is done once the whole subplan is done (its result summarises the subplan's final nodes).
144
+ Built-in agents also get the MCP tools, so an agent can decide mid-run that its node is too big
145
+ and *expand* it into a subplan (`create_plan(parent_plan_id=…, parent_node_id=…)`); the node then
146
+ finishes after that subplan (disable with `--no-agent-mcp`). `openai` agents get the same tools
147
+ through a built-in MCP client, prefixed `vc_` (`vc_create_plan`, …).
148
+ * All agents of one plan tree share a working directory: `<workdir>/plan-<root id>`
149
+ (default `./workspace/plan-N`).
150
+ * A failed node blocks its dependents. Use **Retry** in the UI. **Stop** cancels running built-in
151
+ agents and re-queues their nodes; **Reset** clears all results and transcripts.
152
+ * Everything is stored in `clearbox.db`. Built-in sessions that were running when the service
153
+ stopped are re-queued on the next start.
154
+
155
+ ### File scoping: avoiding clobbered edits
156
+
157
+ Independent nodes run in parallel, and nothing stops two of them from editing the same file at the
158
+ same time — in practice they overwrite each other's work. `files` on a node lists the paths it will
159
+ **create or modify**; reading a file never needs declaring, since reads cannot clobber anything.
160
+ Declare nothing and a node behaves exactly as before — no conflicts, no waiting; this is also how
161
+ every plan made before this feature keeps working unchanged.
162
+
163
+ Declaring `files` buys three things: conflicting nodes are serialized (below), the agent is told its
164
+ scope, and the node gets a **complete diff** for review. Before a node starts, Clearbox snapshots
165
+ every declared path and compares it again when the node finishes, so a change shows up however it
166
+ was made, from the shell as well as from the file tools. A node that declares nothing only gets the
167
+ writes made through its file tools (see [Reviewing](#reviewing)).
168
+
169
+ Two nodes that could otherwise run at the same time and declare a shared file never actually run
170
+ together: they run one after the other, **lowest node number first**. This spans the whole plan
171
+ tree — a node in a subplan can conflict with one in the root plan or a sibling subplan — because all
172
+ agents of one plan tree share a single working directory (`<workdir>/plan-<root id>`, see above).
173
+ Nodes already ordered by a dependency edge are never held back for this: if `A` must finish before
174
+ `B` starts, they cannot overlap, so sharing a file is fine and costs nothing extra. A node takes all
175
+ of its declared files at once, never one at a time, so two nodes that each declare the other's file
176
+ cannot deadlock — the lower-numbered one always gets to start.
177
+
178
+ Paths are relative to the plan tree's working directory, exactly like the ones a node's agent reads
179
+ and writes; they may name a file that does not exist yet (the node is about to create it) or a
180
+ directory, which claims everything under it (e.g. `files=["clearbox/"]` for a node that
181
+ restructures a whole package). Matching is case-insensitive and treats `\` and `/` alike on every
182
+ platform, not just Windows — `App.js` and `app.js` are treated as the same file even on a
183
+ case-sensitive filesystem. That occasionally serializes two genuinely different files; the
184
+ alternative is the opposite mistake, two agents writing what they each think is a different file and
185
+ one edit getting silently lost, which is worse.
186
+
187
+ Example: node 3 declares `files=["src/parser.py"]`, node 5 (no dependency between them) declares
188
+ `files=["src/"]`. Both become ready at the same time. Node 3 starts; node 5 is held back with
189
+ `blocked_by` reason `"running"` until node 3 finishes, then starts. Had node 5 instead been ready
190
+ first purely by luck of scheduling, it would still wait — the rule is node number, not arrival order.
191
+
192
+ REST exposes the same thing as `PUT /api/plans/{plan_id}/nodes/{node_id}/files` (body `{"files":
193
+ [...]}`). Every node view, from REST and from MCP's `get_plan` alike, carries `files` (its own
194
+ declared paths) and `conflicts` (the other nodes in the tree that share one and could run at the
195
+ same time — these are the ones that get serialized); REST's node view adds `blocked_by` while a node
196
+ is waiting: which node holds the file, and whether that node is `"running"` or merely has an earlier
197
+ `"turn"`.
198
+
199
+ In the UI, nodes that share a file are drawn one step lower than the last within their layer and
200
+ joined by a dashed connector, so the stagger is visible at a glance even though it is not a
201
+ dependency edge; a node waiting on one says so in its drawer ("waiting for 3 "Parse config" to
202
+ release `src/parser.py`"), and the drawer's **Files** row lets you view and edit a node's declared
203
+ files directly (disabled once it has started).
204
+
205
+ ### Inherited results: edges order, `results_from` passes
206
+
207
+ An edge means "runs after" and nothing more. A node starts with **no earlier results** in its prompt
208
+ unless it asks for them with `results_from=[…]`, so every description must be self-contained: what to
209
+ change, where, and any decision an earlier node made that this one has to follow. This is deliberate.
210
+ Predecessor summaries run to hundreds of words each; a node handed three of them spends its budget
211
+ reading them, and then re-verifying them in the code, instead of doing its own task. Most nodes need
212
+ nothing beyond their description.
213
+
214
+ What a node's prompt contains, in order: the overall goal (plan title and description, plus the parent
215
+ chain for subplans), the other nodes of the plan as one-liners (so nodes don't overlap), the requested
216
+ results, if any, then its own task and its file scope. The requested results are introduced as finished
217
+ work to build on, not to redo.
218
+
219
+ `results_from` only *passes*; it never orders. A node may only request nodes of its own plan that it
220
+ already depends on, directly or through other nodes — anything else might not have a result yet when
221
+ it runs, and is rejected with a message saying which edge is missing. `add_node` accepts the list
222
+ before the edges exist (you add those next), so the check runs when the edges are known:
223
+ `set_node_results_from`, `update_node` and `start_plan` reject it. Like `files`, it can only change
224
+ while the node is `pending` or `ready`.
225
+
226
+ The useful pattern: implementation nodes request nothing and start with a short prompt; a final docs or
227
+ summary node depends on all of them and requests every result, writing from them instead of
228
+ rediscovering the work:
229
+
230
+ ```
231
+ store = add_node(p, "Store: add the column", …, files=["clearbox/store.py"])
232
+ engine = add_node(p, "Engine: use it", …, files=["clearbox/engine.py"])
233
+ docs = add_node(p, "Docs", "Document the change in README.md …", files=["README.md"],
234
+ results_from=[store[1], engine[1]])
235
+ add_edge(p, store[1], engine[1]); add_edge(p, engine[1], docs[1]) # docs depends on store via engine
236
+ ```
237
+
238
+ REST: `PUT /api/plans/{plan_id}/nodes/{node_id}/results-from` (body `{"node_ids": [...]}`), and
239
+ `GET /api/plans/{plan_id}/nodes/{node_id}/context` → `{"context": "…"}`, the exact prompt the node gets,
240
+ built from the plan as it is now (for a node that already ran: what it got, unless the plan changed
241
+ since). In the UI, the node drawer's **Inherits results from** row shows and edits the list (checkboxes
242
+ offer only the node's dependencies; editable while pending or ready), the **Depends on** row is marked
243
+ "ordering only", and the collapsed **Prompt** block shows that same context.
244
+
245
+ ## Reviewing
246
+
247
+ Reviewing is a second, separate layer on top of execution: it never edits anything, never fails a
248
+ node and never blocks a dependent — it only records a verdict (`ok` | `concerns` | `error`) and
249
+ findings for a human to act on. Reviewing is **off** for a plan until it names a review agent with
250
+ `set_plan_review_agent` (MCP) / `PUT /api/plans/{id}/review-agent`; a plan that never sets one
251
+ behaves exactly as it did before reviewing existed, and nobody pays for review tokens by accident.
252
+
253
+ There are two levels, deliberately kept apart so each stays inside a context it can actually judge:
254
+
255
+ * **Step review** (`review_node` / `POST /api/plans/{id}/nodes/{node_id}/review`): once a node is
256
+ `done`, a reviewer looks at *that node's* captured diff against *that node's* task description —
257
+ one diff, one task, nothing else. It runs in the background after the node has already finished,
258
+ so it never delays whatever depends on it. The diff has two sources: a snapshot of the node's
259
+ declared `files` taken before it starts, compared again when it finishes (directories are walked,
260
+ so added and deleted files show up too), plus every write the node makes through the file tools
261
+ (`write_file`, `edit_file`, the Claude harness's own edit tools). Within the declared scope the
262
+ diff is therefore complete, including changes made through `bash`, `sed -i` or a heredoc; outside
263
+ it, and for a node that declares nothing, it is best-effort and covers file-tool writes only. The
264
+ reviewer is told which case applies, so it can treat a missing change in the declared scope as
265
+ not made and stays cautious elsewhere. Agents are still asked to edit through the file tools: those
266
+ edits show up in the transcript as reviewable steps, and they are the only ones captured outside
267
+ the declared scope.
268
+ * **Plan review** (`review_plan` / `POST /api/plans/{id}/review`): once every node in the tree is
269
+ done, one reviewer looks at the plan as a whole — every node's title, description, result and
270
+ step-review verdict — and asks what was missed, contradicted or left out across steps. This is
271
+ explicitly **not** a code review: it never reads diffs or source, only reads what the step
272
+ reviews and results already say.
273
+
274
+ Both levels run automatically once `set_plan_review_agent` names an agent (the plan review fires
275
+ once, right after the last node completes), and both can also be triggered on demand with an
276
+ optional one-off `agent=` override, whether or not the plan has one set — so a plan finished before
277
+ reviewing existed can still be reviewed after the fact. `get_reviews(plan_id)` /
278
+ `GET /api/plans/{id}` read every verdict and finding back. The intended loop: read the plan-level
279
+ review's oversights, then turn each one into a follow-up node (or plan) rather than editing by
280
+ hand — reviews describe gaps, they don't fill them.
281
+
282
+ ### Rework: closing the review loop
283
+
284
+ For a single step, the loop closes in place: **step runs → step review → rework with the findings →
285
+ automatic re-review**. `rework_node(plan_id, node_id, instructions=None)` (MCP) /
286
+ `POST /api/plans/{id}/nodes/{node_id}/rework` (body `{"instructions": "…"}`, optional) re-runs that
287
+ one node's agent. Its prompt is the node's normal context plus a final **REWORK** section: the
288
+ previous attempt is already in the working tree, so do not redo the step from scratch, address
289
+ exactly what is listed and change only what that needs. What is listed is your `instructions`
290
+ or, if you leave them out, the review's verdict, summary and findings. "Re-run this step and fix X"
291
+ works without any review too. With neither instructions nor a review the call is refused; use
292
+ retry or a new node instead.
293
+
294
+ * **Always explicit.** Nothing in Clearbox re-runs a node because a review disliked it. Reviews stay
295
+ advisory, and a rework is always something a person or a planning model asked for.
296
+ * **One node only.** Only a `done` or `failed` node can be reworked. A running one has to be stopped
297
+ first, and a node that wraps a subplan is refused: rework a step inside it instead. Nodes that depend on it are
298
+ **not** reset or re-run, even though they may have built on the old output. That limit is
299
+ deliberate: cascading resets would discard finished work that may be fine, and deciding what
300
+ needs redoing is a judgement for whoever asked for the rework. The node view lists its
301
+ `started_dependants` (direct dependants that already ran or are running), and the UI warns when
302
+ some are done, so you know what you are choosing. If they need redoing, rework them too or add
303
+ follow-up nodes.
304
+ * **Re-review by itself.** A rework is just another run of the node: its transcript, token counters,
305
+ diff and file locks reset as on any start, and it finishes through the normal path. So if the plan
306
+ has a review agent and the run captured a diff, a new step review is queued with no extra trigger.
307
+ Read it with `get_reviews` and repeat if needed. Without a review agent, trigger one with
308
+ `review_node`.
309
+
310
+ The brief stays on the node as `rework_brief` after the run (so a retry of a failed rework still
311
+ carries it); the next rework replaces it, and **Reset** clears it.
312
+
313
+ ## Agents and harnesses
314
+
315
+ A **harness** is the runtime that executes a node; an **agent profile** is a named, persisted
316
+ configuration of one (harness + model + connection + limits). Nodes and plans refer to a profile by
317
+ name or id and never hold connection details themselves, so one plan can run its mechanical nodes
318
+ on a cheap or local model and its hard nodes on a strong one. Every harness emits the same
319
+ transcript events, so the UI, the database and the live stream look identical whichever ran.
320
+
321
+ | Harness | What runs the node | Tools |
322
+ | --- | --- | --- |
323
+ | `claude-sdk` | The Claude Agent SDK (its bundled Claude Code CLI, your Claude login or `ANTHROPIC_API_KEY`). | Its own (Bash, Read, Edit, …), gated by `permission_mode`. |
324
+ | `openai` | Any endpoint speaking the OpenAI chat-completions API, driven by Clearbox's own agent loop (ask → run the tool calls → feed results back → repeat until a reply without tool calls). | Clearbox's built-in toolset (below) + the MCP tools. |
325
+ | `mock` | A fake agent that prints a plausible transcript. No cost. | Pretend ones. |
326
+
327
+ On every start the server makes sure these profiles exist (created only when absent, never
328
+ overwritten): `claude`, `mock`, and — only if `--openai-base-url`, `OPENAI_BASE_URL` or
329
+ `OPENAI_API_KEY` is set — `openai`. The `claude` profile stores no model / permission mode / max
330
+ turns, so `--model`, `--permission-mode` and `--max-turns` keep applying to it; a value set on the
331
+ profile overrides the flag. Manage profiles in the UI (**Agents** in the sidebar, with a **Test
332
+ connection** button), over MCP (`create_agent` …) or over REST (`/api/agents`).
333
+
334
+ ```powershell
335
+ uv run clearbox --openai-base-url http://localhost:11434/v1 --openai-model qwen2.5-coder:14b
336
+ $env:OPENAI_API_KEY = "sk-…"; uv run clearbox --agent-profile openai # every node on gpt-4o-mini
337
+ ```
338
+
339
+ ### Profile fields
340
+
341
+ | Field | Meaning | Default |
342
+ | --- | --- | --- |
343
+ | `name` | Unique, not purely numeric; what nodes, plans, MCP tools and the UI refer to. | required |
344
+ | `harness` | `claude-sdk`, `openai` or `mock`. | required |
345
+ | `model` | Model name as the endpoint knows it (`gpt-4o-mini`, `qwen2.5-coder:14b`, `vendor/model` on OpenRouter, `claude-opus-5-5`). | `claude-sdk`: `--model`, else the SDK default. `openai`: **required**. |
346
+ | `base_url` | `openai` only: endpoint root, the part before `/chat/completions` — usually ends in `/v1`. | `https://api.openai.com/v1` |
347
+ | `api_key_env` | `openai` only: **name** of the server environment variable holding the key, read when a node starts. | none (`OPENAI_API_KEY` for the default OpenAI url) |
348
+ | `api_key` | Literal key, settable from the UI / REST only. Write-only: responses only say `has_api_key`. | none |
349
+ | `params` | JSON object. `openai`: `temperature`, `top_p`, `max_tokens`, `max_completion_tokens`, `reasoning_effort`, `seed`, `parallel_tool_calls`, `max_turns` (50), `stream` (true; false for endpoints with broken streaming), `timeout` (600 s), `max_retries` (2), `extra_body` (passed through verbatim), `input_price_per_mtok` / `output_price_per_mtok` (USD, enables the cost shown in the transcript). `claude-sdk`: `max_turns`. `mock`: `speed`. | `{}` |
350
+ | `tools` | JSON object, `openai` only: `{"preset": "all" \| "readonly" \| "none", "allow": […], "deny": […], "mcp": true \| false}`. `allow` adds to the preset, `deny` removes (deny wins); `mcp` overrides `--no-agent-mcp`'s on/off for this profile (it cannot turn the tools on when the server has `--no-agent-mcp`). | preset `all`, MCP as the server |
351
+ | `permission_mode` | `claude-sdk` only, same values as `--permission-mode`. | `--permission-mode` |
352
+ | `system_append` | Extra system-prompt text for every node on this profile. | none |
353
+ | `max_parallel` | Max nodes running on this profile at once, across all plans (on top of `--max-concurrency`). Useful for a local GPU. | no limit |
354
+ | `description` | Free text for humans. | `""` |
355
+
356
+ ### Which agent runs a node
357
+
358
+ First hit wins: **the node's own profile → its plan's default → each ancestor plan's default, up to
359
+ the root plan → the server default (`--agent-profile`)**. With no profile anywhere the node runs on
360
+ the CLI-configured harness (`claude`, or `mock` with `--mock`) — exactly what a plan without any
361
+ agent configuration did before profiles existed. The choice is made each time a node starts, so
362
+ editing a profile or an assignment affects the next node to start, not running ones.
363
+
364
+ In the UI the node drawer shows the resolved agent and where it came from (*set on this node*,
365
+ *plan default*, *inherited from plan N*, *server default*) and can override it; the topbar sets the
366
+ plan's default for the plan and its subplans. Graph nodes get a coloured chip only when their agent
367
+ differs from the plan's default.
368
+
369
+ ### Secrets
370
+
371
+ Put api keys in environment variables of the Clearbox process and reference them by name with
372
+ `api_key_env`; the value is read when a node starts and lives only in the HTTP client. A literal
373
+ key can be typed into the Agents panel (or sent as `api_key` to `POST`/`PATCH /api/agents`) for
374
+ convenience: it is stored in the database but **never returned** by REST, the stream, MCP or the
375
+ logs — they only show `has_api_key`. `""` clears it; leaving the field out keeps it. MCP's
376
+ `create_agent`/`update_agent` do not accept a key at all. Error texts from endpoints are scrubbed of
377
+ the key before they reach a transcript. Endpoints on localhost or a private network get a
378
+ placeholder key when none is configured, since they rarely check it.
379
+
380
+ ### Tools of the `openai` harness
381
+
382
+ > **Warning:** these tools run arbitrary code in the plan's working directory, with the rights of
383
+ > the Clearbox process. The file tools are jailed to that directory (`..` and symlinks are
384
+ > resolved before the check), but `bash` is **not** jailed — a shell can always `cd` out. Use preset
385
+ > `readonly` (or `deny: ["bash"]`) for models you would not hand a shell, and a sandbox for
386
+ > anything you do not trust.
387
+
388
+ | Tool | What it does | `readonly` |
389
+ | --- | --- | --- |
390
+ | `read_file(path, offset=0, limit=2000)` | Read a text file with line numbers. | ✓ |
391
+ | `write_file(path, content)` | Create or overwrite a file (creates parent directories). | |
392
+ | `edit_file(path, old_string, new_string, replace_all=false)` | Exact string replacement; `old_string` must be unique unless `replace_all`. | |
393
+ | `list_dir(path, depth)` | Directory listing, optionally recursive. | ✓ |
394
+ | `glob(pattern)` | Find files, e.g. `**/*.py`, newest first. | ✓ |
395
+ | `grep(pattern, path, glob, max_results=100)` | Regex search over file contents → `path:line: text`. | ✓ |
396
+ | `bash(command, timeout_ms=120000)` | Run a shell command (`cmd.exe` on Windows, `/bin/sh` elsewhere), max 600 000 ms; kills the process tree on timeout. | |
397
+ | `vc_*` | The clearbox MCP tools (`vc_create_plan`, `vc_add_node`, …) so the agent can expand its node into a subplan; off with `tools.mcp=false` or `--no-agent-mcp`. | |
398
+
399
+ Walks skip `.git`, `node_modules`, `__pycache__` and virtualenvs; results are truncated like the
400
+ Claude harness's.
401
+
402
+ ### Which endpoint works
403
+
404
+ | Server | `base_url` | Notes |
405
+ | --- | --- | --- |
406
+ | OpenAI | `https://api.openai.com/v1` (leave empty) | `api_key_env=OPENAI_API_KEY` |
407
+ | OpenRouter | `https://openrouter.ai/api/v1` | model is the `vendor/model` slug; `api_key_env=OPENROUTER_API_KEY` |
408
+ | vLLM | `http://<host>:8000/v1` | start it with `--enable-auto-tool-choice --tool-call-parser …` or tool calls are rejected |
409
+ | LM Studio | `http://localhost:1234/v1` | load a model with tool-use support first |
410
+ | Ollama | `http://localhost:11434/v1` | model is the tag from `ollama list`, e.g. `qwen2.5-coder:14b` |
411
+
412
+ Common pitfalls (the transcript error says which one hit):
413
+
414
+ * **`base_url` must include `/v1`** (the path before `/chat/completions`); without it the endpoint
415
+ answers 404.
416
+ * **The model must support tool / function calling.** One that doesn't either gets a 400 or simply
417
+ answers in prose and never touches a file. For pure-text nodes use preset `none`.
418
+ * **Small local models often loop** — re-reading the same file, repeating a call. Cap them with
419
+ `params.max_turns` (e.g. 15–25), keep their nodes mechanical, and set `max_parallel: 1` if they
420
+ share one GPU.
421
+ * Unsure? **Test connection** in the Agents panel (or `test_agent`) checks reachability, the key and
422
+ whether the model name is listed, without spending tokens.
423
+
424
+ ### Walkthrough: a mixed-harness plan
425
+
426
+ `examples/mixed_plan.py` builds a plan that reviews a repository (default: this one) without
427
+ changing it. Two cheap-model nodes do the mechanical work (inventory the files, inventory the
428
+ tests) in parallel, a strong-model node assesses the architecture from their results, and a
429
+ strong-model node reviews that assessment:
430
+
431
+ ```powershell
432
+ uv run clearbox --openai-base-url http://localhost:11434/v1 --openai-model qwen2.5-coder:14b
433
+ uv run python examples/mixed_plan.py --start
434
+ ```
435
+
436
+ It creates the agents it needs if missing and prints them: `cheap` (an `openai` copy of the first
437
+ configured `openai` profile with preset `readonly`, `max_turns` 20, `max_parallel` 1) and `strong`
438
+ (`claude-sdk` in permission mode `default`, i.e. read tools only), then runs `test_agent` on
439
+ `cheap`. It assigns them the three ways: `set_plan_agent` makes `cheap` the plan default (the
440
+ inventory nodes inherit it), `add_node(..., agent="strong")` pins the assessment, and
441
+ `set_node_agent` moves the review node. `--cheap NAME` / `--strong NAME` use profiles you already
442
+ have; `--repo PATH` reviews another directory. With no `openai` profile configured (e.g. against
443
+ `uv run clearbox --mock`) or with `--mock` it creates `mock-cheap` / `mock-strong` instead, so
444
+ the whole flow can be watched for free.
445
+
446
+ ## UI
447
+
448
+ * Left: plans with progress bars. Top: breadcrumb (for subplans), status, Start / Stop / Reset.
449
+ * "Working now" lists every node in the plan tree that has an agent on it right now — click to jump.
450
+ * Graph: top-down layered DAG. Running nodes glow and spin, edges into them animate;
451
+ subplan nodes are drawn as a stack with a progress bar (double-click to open). Nodes within a
452
+ layer that share a declared file are nudged down a step each and joined by a dashed connector —
453
+ the file stagger, see [File scoping](#file-scoping-avoiding-clobbered-edits).
454
+ * Click a node: task, prerequisites, result/error, the **agent** that runs it (with where that
455
+ came from, and a select to override it), **Inherits results from** and the **Prompt** block (the
456
+ exact prompt it gets, see [Inherited results](#inherited-results-edges-order-results_from-passes)), the **Files** row (its declared paths, which other nodes
457
+ share one, and — while waiting — which node to wait for) and the **live agent session**
458
+ (messages, thinking, tool calls with their results, final result with turns / time / cost).
459
+ * Nodes whose agent differs from the plan's default carry a coloured chip naming it; a dot marks
460
+ an override set on the node itself. The topbar sets the plan's default agent.
461
+ * **Agents** in the sidebar manages the profiles: add one, pick its harness, model, endpoint and
462
+ key, choose its toolset, cap its parallelism, and **Test connection** before trusting it.
463
+ * A reviewed node carries a small verdict marker (✓ / ! / ?) in its top-right corner; its full-view
464
+ **Review** tab shows the verdict, summary and findings and a button to (re-)review it on demand.
465
+ A review chip next to the plan status does the same for the plan-level review, see
466
+ [Reviewing](#reviewing).
467
+ * **⟲ Rework this step** (in the Review tab and the drawer of a finished step) opens a box
468
+ pre-filled with the review's findings. Edit it or clear it to fall back on the findings, then
469
+ **Re-run with these instructions**. A line warns when later steps that depend on it are already
470
+ done and will not be re-run, see [Rework](#rework-closing-the-review-loop).
471
+ * URLs are deep-linkable: `/#/plan/2/node/3`, `/#/agents`, `/#/agents/4`.
472
+
473
+ ## Developing Clearbox with Clearbox
474
+
475
+ Run a *frozen* copy as the service so agents can edit the source without breaking the running
476
+ instance, and keep the database outside the repo:
477
+
478
+ ```powershell
479
+ uv tool install --force . # snapshot of the current code -> `clearbox` on PATH
480
+ clearbox --db $HOME\.clearbox\vc.db --workdir $HOME\.clearbox\workspace
481
+ ```
482
+
483
+ The repo contains a `.mcp.json`, so Claude Code started in this folder offers the `clearbox`
484
+ server automatically. Create plans with `working_directory` set to the repo path so built-in agents
485
+ work on the code. To pick up the agents' changes, stop the service and run
486
+ `uv tool install --force .` again.
487
+
488
+ Agent profiles live in that database, so they survive the reinstall — configure an endpoint once
489
+ and every later plan can use it. Two things worth knowing when a plan builds Clearbox itself:
490
+
491
+ * Built-in `claude-sdk` agents use the login of the **service** process, not of the Claude Code
492
+ session that planned the work. After `claude /login`, or when a session limit hits agents but not
493
+ your own terminal, restart the service so it picks the credentials up.
494
+ * Nodes of one plan can run on different accounts or vendors by giving them different profiles —
495
+ useful for spreading a long plan over a rate limit, or for keeping mechanical nodes off a
496
+ metered model.
497
+
498
+ ## Options
499
+
500
+ | Flag | Env | Default |
501
+ | --- | --- | --- |
502
+ | `--host`, `--port` | `CLEARBOX_HOST`, `CLEARBOX_PORT` | `127.0.0.1`, `8765` |
503
+ | `--db` | `CLEARBOX_DB` | `clearbox.db` |
504
+ | `--workdir` | `CLEARBOX_WORKDIR` | `workspace` |
505
+ | `--max-concurrency` | `CLEARBOX_MAX_CONCURRENCY` | `3` |
506
+ | `--permission-mode` | `CLEARBOX_PERMISSION_MODE` | `acceptEdits` |
507
+ | `--model` | `CLEARBOX_MODEL` | SDK default |
508
+ | `--max-turns` | | unlimited |
509
+ | `--mock`, `--mock-speed` | `CLEARBOX_AGENT=mock` | off |
510
+ | `--no-agent-mcp` | | agents get the MCP tools |
511
+ | `--agent-profile NAME` | `CLEARBOX_AGENT_PROFILE` | `mock` with `--mock`, else `claude` |
512
+ | `--openai-base-url URL` | `OPENAI_BASE_URL` | `https://api.openai.com/v1` |
513
+ | `--openai-model NAME` | `CLEARBOX_OPENAI_MODEL` | `gpt-4o-mini` for the OpenAI endpoint, else none |
514
+ | `--openai-api-key-env NAME` | | `OPENAI_API_KEY` |
515
+
516
+ `--model`, `--permission-mode` and `--max-turns` configure the `claude` profile's empty fields (and
517
+ any `claude-sdk` profile that leaves them empty). The three `--openai-*` flags only **bootstrap**
518
+ the `openai` profile the first time: once it exists they are ignored with a warning, because the
519
+ profile is the source of truth — edit it in the Agents panel or with `update_agent`. An
520
+ `--agent-profile` naming a profile that does not exist stops the server with a message listing the
521
+ ones that do.
522
+
523
+ `acceptEdits` lets agents edit files in their workspace but blocks other tools that would need
524
+ approval; `bypassPermissions` lets them run anything (only use it in a sandbox). For `openai`
525
+ agents the equivalent control is the profile's `tools` preset.
526
+
527
+ ## Layout of the code
528
+
529
+ ```
530
+ clearbox/
531
+ store.py SQLite schema + DAG operations (ids, cycle check) + agent profiles
532
+ engine.py scheduler, node lifecycle, subplans, external-agent protocol
533
+ bootstrap.py built-in profiles, server default, startup banner and validation
534
+ mcp_bridge.py MCP client that hands the clearbox tools to non-Claude harnesses (vc_*)
535
+ mcp_tools.py MCP tool definitions
536
+ app.py FastAPI: REST, SSE live stream, MCP mounted at /mcp
537
+ agents.py backwards-compatible aliases; the implementations moved to harness/
538
+ harness/
539
+ base.py the event contract every harness implements (AgentJob, Harness, HarnessError)
540
+ claude_sdk.py Claude Agent SDK harness
541
+ openai_compat.py OpenAI-compatible harness: the agent loop (stream, tool calls, usage)
542
+ mock.py fake harness
543
+ tools.py sandboxed toolset for harnesses that bring none (read_file … bash)
544
+ registry.py picks the harness for a node; per-profile caches and limits
545
+ openai_check.py /models check behind test_agent
546
+ probe.py tiny completion round trip when there is no /models listing
547
+ static/ UI (plain HTML/CSS/JS, no build step)
548
+ examples/demo_plan.py builds a demo plan through MCP
549
+ examples/mixed_plan.py builds a plan that mixes a cheap and a strong agent
550
+ tests/test_flow.py end-to-end test (run against a server started with --mock)
551
+ tests/test_agents_api.py REST agent lifecycle, incl. the secret-handling rules
552
+ tests/test_mixed_run.py per-node agents and max_parallel in a real run
553
+ tests/test_openai_harness.py the agent loop against tests/fake_openai.py (no network, no DB)
554
+ ```
555
+
556
+ ## Releasing
557
+
558
+ The git tag is the trigger and the single source of truth. Bump the version, tag it, push:
559
+
560
+ ```powershell
561
+ uv version --bump patch # or minor / major; edits pyproject.toml
562
+ git commit -am "release v0.1.1"
563
+ git tag v0.1.1
564
+ git push origin main --tags
565
+ ```
566
+
567
+ `.github/workflows/release.yml` then builds the sdist and wheel, refuses to continue if the tag
568
+ disagrees with `pyproject.toml`, checks the wheel really contains `clearbox/static/`, publishes to
569
+ PyPI, and only then creates the GitHub release with the artifacts attached. PyPI goes first
570
+ because an upload there is permanent, while a GitHub release can be deleted and recreated.
571
+
572
+ Publishing uses **PyPI Trusted Publishing**, so there is no API token in the repository. Configure
573
+ it once on PyPI under *Publishing* with: owner `KaiHabermann`, repository `Clearbox`, workflow
574
+ `release.yml`, environment `pypi`. Before the first release the project does not exist on PyPI
575
+ yet — add it as a *pending publisher* on your account instead, with the same four values.
576
+
577
+ `.github/workflows/ci.yml` runs the same build plus the mock-agent test suite on every push and
578
+ pull request, and installs the built wheel into a clean environment to catch a packaging mistake
579
+ that an editable install would hide. Action versions are pinned exactly and kept current by
580
+ Dependabot.