clearbox 0.2.2__tar.gz → 0.3.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 (53) hide show
  1. {clearbox-0.2.2 → clearbox-0.3.0}/PKG-INFO +125 -61
  2. {clearbox-0.2.2 → clearbox-0.3.0}/README.md +124 -60
  3. {clearbox-0.2.2 → clearbox-0.3.0}/clearbox/__main__.py +10 -1
  4. {clearbox-0.2.2 → clearbox-0.3.0}/clearbox/app.py +138 -14
  5. clearbox-0.3.0/clearbox/diffs.py +140 -0
  6. {clearbox-0.2.2 → clearbox-0.3.0}/clearbox/engine.py +101 -475
  7. {clearbox-0.2.2 → clearbox-0.3.0}/clearbox/harness/base.py +3 -0
  8. {clearbox-0.2.2 → clearbox-0.3.0}/clearbox/harness/claude_sdk.py +2 -4
  9. {clearbox-0.2.2 → clearbox-0.3.0}/clearbox/harness/openai_compat.py +8 -9
  10. {clearbox-0.2.2 → clearbox-0.3.0}/clearbox/harness/probe.py +2 -3
  11. {clearbox-0.2.2 → clearbox-0.3.0}/clearbox/harness/registry.py +7 -9
  12. {clearbox-0.2.2 → clearbox-0.3.0}/clearbox/mcp_bridge.py +19 -48
  13. clearbox-0.3.0/clearbox/mcp_tools.py +868 -0
  14. clearbox-0.3.0/clearbox/prompts.py +191 -0
  15. clearbox-0.3.0/clearbox/reviews.py +240 -0
  16. clearbox-0.3.0/clearbox/static/agents.js +298 -0
  17. clearbox-0.3.0/clearbox/static/app.js +449 -0
  18. clearbox-0.3.0/clearbox/static/core.js +166 -0
  19. clearbox-0.3.0/clearbox/static/drawer.js +561 -0
  20. clearbox-0.3.0/clearbox/static/graph.js +345 -0
  21. {clearbox-0.2.2 → clearbox-0.3.0}/clearbox/static/index.html +2 -1
  22. {clearbox-0.2.2 → clearbox-0.3.0}/clearbox/store.py +33 -7
  23. clearbox-0.3.0/examples/demo_plan.py +85 -0
  24. {clearbox-0.2.2 → clearbox-0.3.0}/examples/file_conflict_plan.py +15 -18
  25. {clearbox-0.2.2 → clearbox-0.3.0}/examples/mixed_plan.py +21 -8
  26. {clearbox-0.2.2 → clearbox-0.3.0}/pyproject.toml +11 -1
  27. clearbox-0.3.0/tests/test_agent_scope.py +204 -0
  28. {clearbox-0.2.2 → clearbox-0.3.0}/tests/test_agents_api.py +3 -3
  29. {clearbox-0.2.2 → clearbox-0.3.0}/tests/test_file_conflicts.py +2 -2
  30. {clearbox-0.2.2 → clearbox-0.3.0}/tests/test_flow.py +109 -26
  31. clearbox-0.3.0/tests/test_host_check.py +85 -0
  32. {clearbox-0.2.2 → clearbox-0.3.0}/tests/test_mixed_run.py +3 -3
  33. clearbox-0.3.0/tests/test_review_format.py +59 -0
  34. clearbox-0.3.0/tests/test_rework.py +177 -0
  35. clearbox-0.3.0/tests/unit/conftest.py +114 -0
  36. clearbox-0.3.0/tests/unit/test_scheduler.py +148 -0
  37. clearbox-0.3.0/tests/unit/test_store.py +41 -0
  38. clearbox-0.2.2/clearbox/agents.py +0 -12
  39. clearbox-0.2.2/clearbox/mcp_tools.py +0 -976
  40. clearbox-0.2.2/clearbox/static/app.js +0 -1730
  41. clearbox-0.2.2/examples/demo_plan.py +0 -71
  42. clearbox-0.2.2/tests/test_rework.py +0 -97
  43. {clearbox-0.2.2 → clearbox-0.3.0}/.gitignore +0 -0
  44. {clearbox-0.2.2 → clearbox-0.3.0}/LICENSE +0 -0
  45. {clearbox-0.2.2 → clearbox-0.3.0}/clearbox/__init__.py +0 -0
  46. {clearbox-0.2.2 → clearbox-0.3.0}/clearbox/bootstrap.py +0 -0
  47. {clearbox-0.2.2 → clearbox-0.3.0}/clearbox/harness/__init__.py +0 -0
  48. {clearbox-0.2.2 → clearbox-0.3.0}/clearbox/harness/mock.py +0 -0
  49. {clearbox-0.2.2 → clearbox-0.3.0}/clearbox/harness/openai_check.py +0 -0
  50. {clearbox-0.2.2 → clearbox-0.3.0}/clearbox/harness/tools.py +0 -0
  51. {clearbox-0.2.2 → clearbox-0.3.0}/clearbox/static/style.css +0 -0
  52. {clearbox-0.2.2 → clearbox-0.3.0}/tests/fake_openai.py +0 -0
  53. {clearbox-0.2.2 → clearbox-0.3.0}/tests/test_openai_harness.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: clearbox
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Summary: Observable, reviewable agent work: DAG plans over MCP, executed node by node and watched live
5
5
  Project-URL: Homepage, https://github.com/KaiHabermann/Clearbox
6
6
  Project-URL: Repository, https://github.com/KaiHabermann/Clearbox
@@ -39,7 +39,7 @@ sessions attached to the nodes, and shows everything live in the browser: which
39
39
  worked on right now, and — on click — the agent's session in that node as it happens.
40
40
 
41
41
  ```
42
- model ──MCP──► create_plan / add_node / add_edge / start_plan
42
+ model ──MCP──► submit_plan (plan + nodes + edges) / start_plan
43
43
  │
44
44
  ┌───────▼────────┐ SQLite (plans, nodes, edges, agent profiles, transcripts)
45
45
  │ Clearbox │──► built-in agent per ready node, chosen by its agent profile:
@@ -69,7 +69,8 @@ uv run clearbox --mock # fake agents: try the UI without spending token
69
69
  uv run python examples/demo_plan.py --start
70
70
  ```
71
71
 
72
- Open <http://127.0.0.1:8765/>.
72
+ Open <http://127.0.0.1:8765/>. The port is `--port` / `CLEARBOX_PORT` (default `8765`); the
73
+ examples, the tests and this repo's `.mcp.json` follow `CLEARBOX_PORT` too.
73
74
 
74
75
  For real agents drop `--mock`. Built-in agents run through the Claude Agent SDK, which ships its
75
76
  own Claude Code CLI and uses your existing Claude login (or `ANTHROPIC_API_KEY`).
@@ -94,7 +95,8 @@ Without uv: `pip install -e .` then `clearbox ...` (or `python -m clearbox ...`)
94
95
 
95
96
  ### Connect a model
96
97
 
97
- The MCP endpoint is `http://127.0.0.1:8765/mcp` (streamable HTTP).
98
+ The MCP endpoint is `http://127.0.0.1:8765/mcp` (streamable HTTP; use your port if you passed
99
+ `--port`).
98
100
 
99
101
  ```powershell
100
102
  claude mcp add --transport http clearbox http://127.0.0.1:8765/mcp
@@ -106,22 +108,16 @@ Then ask, e.g. *"Use clearbox to make a plan for … and start it."*
106
108
 
107
109
  | Tool | What it does |
108
110
  | --- | --- |
109
- | `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. |
110
- | `add_node(plan_id, title, description="", agent=None, files=None, results_from=None, kind=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). `kind="user"` makes it the user's own job, see [User-job nodes](#user-job-nodes-a-step-a-person-does); default `"agent"`. |
111
+ | `submit_plan(title, working_directory=None, description="", test_instructions=None, nodes=[…], edges=[…], parent_plan_id=None, parent_node_id=None, max_parallel=None)` | **The way to build a plan**: the plan, all its nodes and all its edges in one call → `{"plan_id": …, "nodes": {key: node_id}}`. `nodes` are `{key, title, description, files, results_from, kind, agent}` objects, where `key` is your own label; `edges` are `[from_key, to_key]` pairs and `results_from` names keys too. All or nothing: every problem (duplicate or unknown keys, cycles, a `results_from` the node does not depend on, a path outside the working directory, an unknown agent or kind, the working-directory and subplan rules) is reported in one error, and then nothing has been created. Plan fields as for `create_plan`; with a parent it becomes the **subplan** of that node. |
112
+ | `create_plan(title, description="", parent_plan_id=None, parent_node_id=None, working_directory=None, max_parallel=None, test_instructions=None)` | New empty plan → integer id (1, 2, 3 …). With a parent it becomes the **subplan** of that node. A top-level plan must name its `working_directory`; `test_instructions` is free text saying how the project's tests are run, see [Test levels](#test-levels-how-much-a-worker-agent-tests). Subplans inherit both and may not set them. |
113
+ | `update_plan(plan_id, max_parallel=None, test_instructions=None)` | Change a top-level plan's settings; only what you pass changes. `max_parallel=0` removes the limit, `test_instructions=""` removes the instructions. Same as `PATCH /api/plans/{plan_id}`. |
114
+ | `add_node(plan_id, title, description="", agent=None, files=None, results_from=None, kind=None)` | Extend or correct a plan one node at a time → `[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 node ids whose result text it receives, see [Inherited results](#inherited-results-edges-order-results_from-passes). `kind="user"` makes it the user's own job, see [User-job nodes](#user-job-nodes-a-step-a-person-does); default `"agent"`. |
111
115
  | `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`. |
112
- | `remove_edge`, `remove_node` | Corrections while building. |
113
- | `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. |
114
- | `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. |
115
- | `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. |
116
- | `set_node_kind(plan_id, node_id, kind)` | Make a node the user's job (`"user"`) or an agent's (`"agent"`), see [User-job nodes](#user-job-nodes-a-step-a-person-does). Only before the node has run. |
117
- | `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)), `results_from`, `kind` and `waiting_for_user` (a user node that is ready and waiting to be marked done). |
118
- | `list_agents()` | Agent profiles (secrets redacted to `has_api_key`; `default: true` marks the server default), including each one's `capability` and `test_level`. |
119
- | `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="", capability=None, test_level=None)` | New agent profile. `capability` (`read` \| `run` \| `edit` \| `full`) and `test_level` (`none` \| `light` \| `standard` \| `thorough`) are explained under [Capability](#capability-what-an-agent-may-do) and [Test levels](#test-levels-how-much-a-worker-agent-tests); `permission_mode` and `tools` are raw escape hatches. Deliberately takes no literal `api_key`: secrets should not pass through a model's context. |
120
- | `update_agent(agent, …same fields…)` | Change only the fields passed; `""` clears a text field (for `capability` / `test_level`: back to the default), `max_parallel=0` removes the limit, `params`/`tools` replace the whole object. |
121
- | `delete_agent(agent)` | Delete a profile; nodes and plans that used it inherit again. |
122
- | `set_node_agent(plan_id, node_id, agent)` | Per-node override; `null` clears it. Applies the next time the node starts. |
116
+ | `remove_edge`, `remove_node` | Corrections. |
117
+ | `update_node(plan_id, node_id, title=None, description=None, files=None, results_from=None, kind=None, agent=None)` | Change only what you pass, all or nothing. `files` / `results_from` omitted keeps the current value, `[]` clears it; they and `kind` change only before the node has run. `agent=""` clears the node's own agent so it inherits again; a new agent applies the next time the node starts. |
118
+ | `get_plan(plan_id)`, `list_plans()` | Structure, status, results, progress. `get_plan` also shows `working_directory`, `test_instructions` (the top-level plan's, for a subplan too), `default_agent` and, per node, the resolved `agent` and its `agent_override`, plus `files` and `conflicts` (see [File scoping](#file-scoping-avoiding-clobbered-edits)), `results_from`, `kind` and `waiting_for_user` (a user node that is ready and waiting to be marked done). |
119
+ | `list_agents()` | Agent profiles (secrets redacted to `has_api_key`; `default: true` marks the server default), including each one's `capability` and `test_level`. Creating, editing, deleting and testing profiles is setup work, done in the web UI's Agents panel or over REST (`/api/agents`), not over MCP. |
123
120
  | `set_plan_agent(plan_id, agent)` | Default for the plan and its subplans; `null` clears it. |
124
- | `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}`. |
125
121
  | `start_plan(plan_id, executor="builtin" \| "external")`, `stop_plan(plan_id)` | Run / pause a top-level plan. |
126
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. |
127
123
  | `report_progress(plan_id, node_id, message, kind="text")` | Append to the node's live transcript. |
@@ -147,10 +143,11 @@ Then ask, e.g. *"Use clearbox to make a plan for … and start it."*
147
143
  node done, see [User-job nodes](#user-job-nodes-a-step-a-person-does).
148
144
  * **Subplans**: a node with a subplan skips the agent; when it is reached its subplan runs, and the
149
145
  node is done once the whole subplan is done (its result summarises the subplan's final nodes).
150
- Built-in agents also get the MCP tools, so an agent can decide mid-run that its node is too big
151
- and *expand* it into a subplan (`create_plan(parent_plan_id=…, parent_node_id=…)`); the node then
146
+ Built-in agents also get MCP tools, so an agent can decide mid-run that its node is too big
147
+ and *expand* it into a subplan (`submit_plan(parent_plan_id=…, parent_node_id=…, nodes=…)`); the node then
152
148
  finishes after that subplan (disable with `--no-agent-mcp`). `openai` agents get the same tools
153
- through a built-in MCP client, prefixed `vc_` (`vc_create_plan`, …).
149
+ through a built-in MCP client, prefixed `vc_` (`vc_submit_plan`, …). These are a restricted set
150
+ on their own endpoint, see [Worker agents' MCP tools](#worker-agents-mcp-tools).
154
151
  * All agents of one plan tree share a working directory: `<workdir>/plan-<root id>`
155
152
  (default `./workspace/plan-N`).
156
153
  * A failed node blocks its dependents. Use **Retry** in the UI. **Stop** cancels running built-in
@@ -161,7 +158,7 @@ Then ask, e.g. *"Use clearbox to make a plan for … and start it."*
161
158
  ### User-job nodes: a step a person does
162
159
 
163
160
  Some steps of a plan are not an agent's to do: push a branch, open a merge request, deploy, approve
164
- something. Mark such a node with `kind="user"` (`add_node(..., kind="user")`, `set_node_kind`, or the
161
+ something. Mark such a node with `kind="user"` (on a `submit_plan` node, `add_node`, `update_node`, or the
165
162
  **Done by** select in the node drawer; changeable while the node is pending or ready). Nothing else
166
163
  about the node changes: it has edges, it can declare `files` and request `results_from`, and it
167
164
  blocks its dependents like any other node.
@@ -251,9 +248,10 @@ work to build on, not to redo.
251
248
 
252
249
  `results_from` only *passes*; it never orders. A node may only request nodes of its own plan that it
253
250
  already depends on, directly or through other nodes — anything else might not have a result yet when
254
- it runs, and is rejected with a message saying which edge is missing. `add_node` accepts the list
255
- before the edges exist (you add those next), so the check runs when the edges are known:
256
- `set_node_results_from`, `update_node` and `start_plan` reject it. Like `files`, it can only change
251
+ it runs, and is rejected with a message saying which edge is missing. `submit_plan` checks it
252
+ against the edges of the same call; `add_node` accepts the list before the edges exist (you add
253
+ those next), so then the check runs when the edges are known: `update_node` and `start_plan`
254
+ reject it. Like `files`, it can only change
257
255
  while the node is `pending` or `ready`.
258
256
 
259
257
  The useful pattern: implementation nodes request nothing and start with a short prompt; a final docs or
@@ -261,11 +259,12 @@ summary node depends on all of them and requests every result, writing from them
261
259
  rediscovering the work:
262
260
 
263
261
  ```
264
- store = add_node(p, "Store: add the column", …, files=["clearbox/store.py"])
265
- engine = add_node(p, "Engine: use it", …, files=["clearbox/engine.py"])
266
- docs = add_node(p, "Docs", "Document the change in README.md …", files=["README.md"],
267
- results_from=[store[1], engine[1]])
268
- add_edge(p, store[1], engine[1]); add_edge(p, engine[1], docs[1]) # docs depends on store via engine
262
+ submit_plan(title, working_directory, nodes=[
263
+ {"key": "store", "title": "Store: add the column", …, "files": ["clearbox/store.py"]},
264
+ {"key": "engine", "title": "Engine: use it", …, "files": ["clearbox/engine.py"]},
265
+ {"key": "docs", "title": "Docs", "description": "Document the change in README.md …",
266
+ "files": ["README.md"], "results_from": ["store", "engine"]},
267
+ ], edges=[["store", "engine"], ["engine", "docs"]]) # docs depends on store via engine
269
268
  ```
270
269
 
271
270
  REST: `PUT /api/plans/{plan_id}/nodes/{node_id}/results-from` (body `{"node_ids": [...]}`), and
@@ -297,20 +296,31 @@ There are two levels, deliberately kept apart so each stays inside a context it
297
296
  reviewer is told which case applies, so it can treat a missing change in the declared scope as
298
297
  not made and stays cautious elsewhere. Agents are still asked to edit through the file tools: those
299
298
  edits show up in the transcript as reviewable steps, and they are the only ones captured outside
300
- the declared scope. The reviewer is asked to run the test suite before its verdict, so the
301
- automatic reviews get capability `run` (read, search, shell; no edits) whatever the profile
302
- allows for normal runs, see [Capability](#capability-what-an-agent-may-do). On-demand reviews
303
- triggered with `review_node` / `review_plan` currently still run with read-only tools.
299
+ the declared scope. The reviewer is asked to run the test suite before its verdict (with the
300
+ plan's `test_instructions`, if it has them), so reviews get capability `run` (read, search,
301
+ shell; no edits) whatever the profile allows for normal runs, see
302
+ [Capability](#capability-what-an-agent-may-do).
304
303
  * **Plan review** (`review_plan` / `POST /api/plans/{id}/review`): once every node in the tree is
305
- done, one reviewer looks at the plan as a whole — every node's title, description, result and
306
- step-review verdict — and asks what was missed, contradicted or left out across steps. This is
304
+ done, one reviewer looks at the plan as a whole — every node's title, status, description, result
305
+ and step review — and asks what was missed, contradicted or left out across steps. This is
307
306
  explicitly **not** a code review: it never reads diffs or source, only reads what the step
308
307
  reviews and results already say.
309
308
 
310
309
  Both levels run automatically once `set_plan_review_agent` names an agent (the plan review fires
311
310
  once, right after the last node completes), and both can also be triggered on demand with an
312
311
  optional one-off `agent=` override, whether or not the plan has one set — so a plan finished before
313
- reviewing existed can still be reviewed after the fact. `get_reviews(plan_id)` /
312
+ reviewing existed can still be reviewed after the fact. An automatic and an on-demand review are
313
+ the same session: same prompt, same tools, same transcript (marked as a review), same record; an
314
+ on-demand one replaces a queued or running review of the same node or plan. Every reviewer ends
315
+ its reply with
316
+
317
+ VERDICT: ok | concerns | error
318
+ SUMMARY: <one or two sentences>
319
+ FINDINGS:
320
+ <bullet list, or "none">
321
+
322
+ Anything before the last `VERDICT:` line is dropped; a reply with no `VERDICT:` line at all is
323
+ recorded as `error` with the whole reply as its summary. `get_reviews(plan_id)` /
314
324
  `GET /api/plans/{id}` read every verdict and finding back. The intended loop: read the plan-level
315
325
  review's oversights, then turn each one into a follow-up node (or plan) rather than editing by
316
326
  hand — reviews describe gaps, they don't fill them.
@@ -365,7 +375,8 @@ overwritten): `claude`, `mock`, and — only if `--openai-base-url`, `OPENAI_BAS
365
375
  `OPENAI_API_KEY` is set — `openai`. The `claude` profile stores no model / permission mode / max
366
376
  turns, so `--model`, `--permission-mode` and `--max-turns` keep applying to it; a value set on the
367
377
  profile overrides the flag. Manage profiles in the UI (**Agents** in the sidebar, with a **Test
368
- connection** button), over MCP (`create_agent` …) or over REST (`/api/agents`).
378
+ connection** button) or over REST (`/api/agents`). MCP only lists them (`list_agents`), so a
379
+ planning model can pick one; setting them up is not in its context.
369
380
 
370
381
  ```powershell
371
382
  uv run clearbox --openai-base-url http://localhost:11434/v1 --openai-model qwen2.5-coder:14b
@@ -391,6 +402,31 @@ $env:OPENAI_API_KEY = "sk-…"; uv run clearbox --agent-profile openai # every
391
402
  | `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 |
392
403
  | `description` | Free text for humans. | `""` |
393
404
 
405
+ ### Worker agents' MCP tools
406
+
407
+ A built-in worker never sees the `/mcp` endpoint your own session uses. When the engine starts a
408
+ built-in node it mints a random token for that run and hands the agent
409
+ `http://127.0.0.1:<port>/mcp-agent/<token>` instead; the token names the node and is revoked the
410
+ moment the run ends (finished, failed or stopped), after which the endpoint answers 404, as it
411
+ does for a token it never minted. That endpoint serves only what expanding a node needs:
412
+ `submit_plan`, `create_plan`, `add_node`, `add_edge`, `remove_edge`, `remove_node`, `update_node`
413
+ and `get_plan`, plus read-only `list_agents` and `report_progress` for the agent's own node. Every
414
+ plan argument must be inside the node's own subtree: `submit_plan` / `create_plan` attach a subplan
415
+ to the agent's own node or to a node of a subplan below it, and the other tools work only on those
416
+ subplans. There is no `start_plan` / `stop_plan`, no agent-profile admin, no review, rework or
417
+ working-directory tool, no `claim_next_node` / `complete_node` / `fail_node`, and no way to pick an
418
+ agent: `submit_plan`, `add_node` and `update_node` refuse an `agent` argument with an error.
419
+ Instead a subplan a worker creates gets **the creating node's resolved agent profile as its plan
420
+ default**, so expanding a node never runs the sub-work on a more capable agent than the node itself
421
+ had (see [Which agent runs a node](#which-agent-runs-a-node)). Reviewers get no clearbox tools at
422
+ all; external agents claiming over `/mcp` are trusted like you. The token never appears in a
423
+ transcript; only a `--mock` server hands it out, on a debug route the tests use.
424
+
425
+ This closes the tool layer, not the machine: an agent with capability `run` or `full` has a shell
426
+ and can reach `/mcp` on localhost directly. The scoping protects `read` and `edit` agents, and
427
+ stops a prompt injection from steering an agent through its own clearbox tools; it is no sandbox
428
+ for a shell-capable agent.
429
+
394
430
  ### Capability: what an agent may do
395
431
 
396
432
  A profile's `capability` is one setting that means the same thing on every harness; the registry
@@ -433,11 +469,15 @@ Every worker prompt ends with a testing paragraph chosen by the profile's `test_
433
469
  | `thorough` | run the full suite, and add tests for behaviour you added |
434
470
 
435
471
  **`standard` is the default**, for new profiles and for every existing one (`null`), and for
436
- external agents claiming through `claim_next_node`. `standard` and `thorough` also tell the agent
437
- where the run commands live (each test file's docstring) and never to use port 8765 or the project's
438
- own database. The level is for worker agents only: reviewers keep their own prompt and **always run
439
- the suite** before giving a verdict, whatever the profile's level — which is why they need
440
- capability `run`.
472
+ external agents claiming through `claim_next_node`. `standard` and `thorough` also say how the
473
+ project's tests are run: the plan's `test_instructions` verbatim when it has them (set them on
474
+ `submit_plan`, `create_plan`, `update_plan` or the 🧪 pill in the UI's topbar), otherwise an
475
+ instruction to work it out from the repository itself (README, CLAUDE.md / AGENTS.md, CI config,
476
+ the test files) rather than invent a harness. Either way the agent is told that any server it starts for testing
477
+ gets a free port, never the port of the Clearbox server running the plan. The level is for worker
478
+ agents only: reviewers keep their own prompt and **always run the suite** before giving a verdict,
479
+ whatever the profile's level — which is why they need capability `run`. They get the same
480
+ how-to-test text.
441
481
 
442
482
  ### Which agent runs a node
443
483
 
@@ -447,6 +487,10 @@ the CLI-configured harness (`claude`, or `mock` with `--mock`) — exactly what
447
487
  agent configuration did before profiles existed. The choice is made each time a node starts, so
448
488
  editing a profile or an assignment affects the next node to start, not running ones.
449
489
 
490
+ A subplan that a **worker agent** creates by expanding its node starts with that node's resolved
491
+ profile as its plan default, so its nodes run on the same agent as the node that made them unless
492
+ you change it (see [Worker agents' MCP tools](#worker-agents-mcp-tools)).
493
+
450
494
  In the UI the node drawer shows the resolved agent and where it came from (*set on this node*,
451
495
  *plan default*, *inherited from plan N*, *server default*) and can override it; the topbar sets the
452
496
  plan's default for the plan and its subplans. Graph nodes get a coloured chip only when their agent
@@ -458,8 +502,8 @@ Put api keys in environment variables of the Clearbox process and reference them
458
502
  `api_key_env`; the value is read when a node starts and lives only in the HTTP client. A literal
459
503
  key can be typed into the Agents panel (or sent as `api_key` to `POST`/`PATCH /api/agents`) for
460
504
  convenience: it is stored in the database but **never returned** by REST, the stream, MCP or the
461
- logs — they only show `has_api_key`. `""` clears it; leaving the field out keeps it. MCP's
462
- `create_agent`/`update_agent` do not accept a key at all. Error texts from endpoints are scrubbed of
505
+ logs — they only show `has_api_key`. `""` clears it; leaving the field out keeps it. MCP has no
506
+ tool that creates or edits a profile, so a key never passes through a model's context. Error texts from endpoints are scrubbed of
463
507
  the key before they reach a transcript. Endpoints on localhost or a private network get a
464
508
  placeholder key when none is configured, since they rarely check it.
465
509
 
@@ -480,7 +524,7 @@ placeholder key when none is configured, since they rarely check it.
480
524
  | `glob(pattern)` | Find files, e.g. `**/*.py`, newest first. | ✓ |
481
525
  | `grep(pattern, path, glob, max_results=100)` | Regex search over file contents → `path:line: text`. | ✓ |
482
526
  | `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. | |
483
- | `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`. | |
527
+ | `vc_*` | The worker's scoped clearbox MCP tools (`vc_submit_plan`, `vc_add_node`, …, see [Worker agents' MCP tools](#worker-agents-mcp-tools)) so the agent can expand its node into a subplan; off with `tools.mcp=false` or `--no-agent-mcp`. | |
484
528
 
485
529
  Walks skip `.git`, `node_modules`, `__pycache__` and virtualenvs; results are truncated like the
486
530
  Claude harness's.
@@ -504,7 +548,7 @@ Common pitfalls (the transcript error says which one hit):
504
548
  * **Small local models often loop** — re-reading the same file, repeating a call. Cap them with
505
549
  `params.max_turns` (e.g. 15–25), keep their nodes mechanical, and set `max_parallel: 1` if they
506
550
  share one GPU.
507
- * Unsure? **Test connection** in the Agents panel (or `test_agent`) checks reachability, the key and
551
+ * Unsure? **Test connection** in the Agents panel (or `POST /api/agents/{id}/test`) checks reachability, the key and
508
552
  whether the model name is listed, without spending tokens.
509
553
 
510
554
  ### Walkthrough: a mixed-harness plan
@@ -523,10 +567,11 @@ It creates the agents it needs if missing and prints them: `cheap` (an `openai`
523
567
  configured `openai` profile with preset `readonly`, `max_turns` 20, `max_parallel` 1) and `strong`
524
568
  (`claude-sdk` with raw permission mode `default` — note that with no `capability` set this is
525
569
  `full`, every tool pre-approved; set `capability="read"` on it for a genuinely read-only
526
- reviewer), then runs `test_agent` on
527
- `cheap`. It assigns them the three ways: `set_plan_agent` makes `cheap` the plan default (the
528
- inventory nodes inherit it), `add_node(..., agent="strong")` pins the assessment, and
529
- `set_node_agent` moves the review node. `--cheap NAME` / `--strong NAME` use profiles you already
570
+ reviewer) over REST, then tests
571
+ `cheap` (`POST /api/agents/{id}/test`). It builds the plan node by node, the incremental way, and
572
+ assigns the agents the three ways: `set_plan_agent` makes `cheap` the plan default (the inventory
573
+ nodes inherit it), `add_node(..., agent="strong")` pins the assessment, and
574
+ `update_node(..., agent="strong")` moves the review node. `--cheap NAME` / `--strong NAME` use profiles you already
530
575
  have; `--repo PATH` reviews another directory. With no `openai` profile configured (e.g. against
531
576
  `uv run clearbox --mock`) or with `--mock` it creates `mock-cheap` / `mock-strong` instead, so
532
577
  the whole flow can be watched for free.
@@ -555,6 +600,8 @@ the whole flow can be watched for free.
555
600
  key, choose **What may this agent do** (the [capability](#capability-what-an-agent-may-do)) and
556
601
  its **Test level**, cap its parallelism, and **Test connection** before trusting it. The raw
557
602
  **Permission mode** and **Tools preset** selects sit under *Advanced* as escape hatches.
603
+ * The topbar's 📁 pill shows a top-level plan's working directory (click to change it before the
604
+ plan starts); the 🧪 pill next to it holds its `test_instructions` (click to edit, any time).
558
605
  * A reviewed node carries a small verdict marker (✓ / ! / ?) in its top-right corner; its full-view
559
606
  **Review** tab shows the verdict, summary and findings and a button to (re-)review it on demand.
560
607
  A review chip next to the plan status does the same for the plan-level review, see
@@ -595,6 +642,7 @@ and every later plan can use it. Two things worth knowing when a plan builds Cle
595
642
  | Flag | Env | Default |
596
643
  | --- | --- | --- |
597
644
  | `--host`, `--port` | `CLEARBOX_HOST`, `CLEARBOX_PORT` | `127.0.0.1`, `8765` |
645
+ | `--allowed-host NAME[:PORT]` (repeatable) | `CLEARBOX_ALLOWED_HOSTS` (comma-separated) | loopback and `--host` only |
598
646
  | `--db` | `CLEARBOX_DB` | `<data dir>/clearbox.db` |
599
647
  | `--workdir` | `CLEARBOX_WORKDIR` | `<data dir>/workspace` |
600
648
  | `--max-concurrency` | `CLEARBOX_MAX_CONCURRENCY` | `3` |
@@ -611,7 +659,7 @@ and every later plan can use it. Two things worth knowing when a plan builds Cle
611
659
  `--model`, `--permission-mode` and `--max-turns` configure the `claude` profile's empty fields (and
612
660
  any `claude-sdk` profile that leaves them empty). The three `--openai-*` flags only **bootstrap**
613
661
  the `openai` profile the first time: once it exists they are ignored with a warning, because the
614
- profile is the source of truth — edit it in the Agents panel or with `update_agent`. An
662
+ profile is the source of truth — edit it in the Agents panel or with `PATCH /api/agents/{id}`. An
615
663
  `--agent-profile` naming a profile that does not exist stops the server with a message listing the
616
664
  ones that do.
617
665
 
@@ -624,17 +672,29 @@ prompts for Bash, and an SDK session has nobody to answer, so agents could not r
624
672
  is what capability replaced. `bypassPermissions` lifts every check (only in a sandbox). For
625
673
  `openai` agents the raw equivalent is the profile's `tools` preset.
626
674
 
675
+ **Security.** The API has no authentication: whoever can reach the port can run agents with
676
+ shell access on this machine. It listens on `127.0.0.1` by default, and every request must be
677
+ addressed to one of the server's own names (`127.0.0.1`, `localhost`, `[::1]`, the `--host` it
678
+ binds unless that is a wildcard, and each `--allowed-host`) or it gets a `421`; a request whose
679
+ `Origin` is not one of those gets a `403`. That keeps other web pages in your browser away from
680
+ it, including through DNS rebinding. Serving it under a name or on the network (`--host 0.0.0.0`,
681
+ which prints a warning) needs that name in `--allowed-host`, e.g. `--allowed-host mybox.lan`; a
682
+ name without a port is allowed with the server's port and without one (behind a reverse proxy).
683
+ Only do that on a network you trust.
684
+
627
685
  ## Layout of the code
628
686
 
629
687
  ```
630
688
  clearbox/
631
689
  store.py SQLite schema + DAG operations (ids, cycle check) + agent profiles
632
690
  engine.py scheduler, node lifecycle, subplans, external-agent protocol
691
+ prompts.py what a worker agent is told: system prompt, test levels, the per-node context
692
+ diffs.py before-snapshots of a node's files and the diffs taken from them when it finishes
693
+ reviews.py step and plan reviews: reviewer prompts, reply parser, the review session
633
694
  bootstrap.py built-in profiles, server default, startup banner and validation
634
695
  mcp_bridge.py MCP client that hands the clearbox tools to non-Claude harnesses (vc_*)
635
- mcp_tools.py MCP tool definitions
636
- app.py FastAPI: REST, SSE live stream, MCP mounted at /mcp
637
- agents.py backwards-compatible aliases; the implementations moved to harness/
696
+ mcp_tools.py MCP tool definitions, and the scoped subset worker agents get
697
+ app.py FastAPI: REST, SSE live stream, MCP mounted at /mcp and the workers' /mcp-agent/<token>
638
698
  harness/
639
699
  base.py the event contract every harness implements (AgentJob, Harness, HarnessError)
640
700
  claude_sdk.py Claude Agent SDK harness
@@ -642,14 +702,18 @@ clearbox/
642
702
  mock.py fake harness
643
703
  tools.py sandboxed toolset for harnesses that bring none (read_file … bash)
644
704
  registry.py picks the harness for a node; per-profile caches and limits
645
- openai_check.py /models check behind test_agent
705
+ openai_check.py /models check behind POST /api/agents/{id}/test
646
706
  probe.py tiny completion round trip when there is no /models listing
647
- static/ UI (plain HTML/CSS/JS, no build step)
707
+ static/ UI (plain HTML/CSS and ES modules, no build step): app.js (entry: routing, plan
708
+ view, live feed), core.js (state, helpers), graph.js, drawer.js, agents.js
648
709
  examples/demo_plan.py builds a demo plan through MCP
649
710
  examples/mixed_plan.py builds a plan that mixes a cheap and a strong agent
711
+ tests/unit/ in-process unit tests of the scheduler and store: `uv run pytest`
650
712
  tests/test_flow.py end-to-end test (run against a server started with --mock)
651
713
  tests/test_agents_api.py REST agent lifecycle, incl. the secret-handling rules
652
714
  tests/test_mixed_run.py per-node agents and max_parallel in a real run
715
+ tests/test_agent_scope.py the workers' scoped MCP endpoint: subtree rule, inherited agent, token lifetime
716
+ tests/test_host_check.py Host and Origin checks: foreign names and pages refused, own names and the stream pass
653
717
  tests/test_openai_harness.py the agent loop against tests/fake_openai.py (no network, no DB)
654
718
  ```
655
719
 
@@ -693,7 +757,7 @@ it once on PyPI under *Publishing* with: owner `KaiHabermann`, repository `Clear
693
757
  `release.yml`, environment `pypi`. Before the first release the project does not exist on PyPI
694
758
  yet — add it as a *pending publisher* on your account instead, with the same four values.
695
759
 
696
- `.github/workflows/ci.yml` runs the same build plus the mock-agent test suite on every push and
697
- pull request, and installs the built wheel into a clean environment to catch a packaging mistake
698
- that an editable install would hide. Action versions are pinned exactly and kept current by
699
- Dependabot.
760
+ `.github/workflows/ci.yml` runs the same build plus the unit tests (`uv run pytest`) and the
761
+ mock-agent test suite on every push and pull request, and installs the built wheel into a clean
762
+ environment to catch a packaging mistake that an editable install would hide. Action versions
763
+ are pinned exactly and kept current by Dependabot.