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.
- clearbox-0.1.0/.gitignore +7 -0
- clearbox-0.1.0/LICENSE +21 -0
- clearbox-0.1.0/PKG-INFO +580 -0
- clearbox-0.1.0/README.md +549 -0
- clearbox-0.1.0/clearbox/__init__.py +1 -0
- clearbox-0.1.0/clearbox/__main__.py +84 -0
- clearbox-0.1.0/clearbox/agents.py +12 -0
- clearbox-0.1.0/clearbox/app.py +736 -0
- clearbox-0.1.0/clearbox/bootstrap.py +154 -0
- clearbox-0.1.0/clearbox/engine.py +1409 -0
- clearbox-0.1.0/clearbox/harness/__init__.py +31 -0
- clearbox-0.1.0/clearbox/harness/base.py +122 -0
- clearbox-0.1.0/clearbox/harness/claude_sdk.py +160 -0
- clearbox-0.1.0/clearbox/harness/mock.py +40 -0
- clearbox-0.1.0/clearbox/harness/openai_check.py +117 -0
- clearbox-0.1.0/clearbox/harness/openai_compat.py +672 -0
- clearbox-0.1.0/clearbox/harness/probe.py +89 -0
- clearbox-0.1.0/clearbox/harness/registry.py +220 -0
- clearbox-0.1.0/clearbox/harness/tools.py +503 -0
- clearbox-0.1.0/clearbox/mcp_bridge.py +185 -0
- clearbox-0.1.0/clearbox/mcp_tools.py +939 -0
- clearbox-0.1.0/clearbox/static/app.js +1649 -0
- clearbox-0.1.0/clearbox/static/index.html +121 -0
- clearbox-0.1.0/clearbox/static/style.css +454 -0
- clearbox-0.1.0/clearbox/store.py +865 -0
- clearbox-0.1.0/examples/demo_plan.py +66 -0
- clearbox-0.1.0/examples/file_conflict_plan.py +89 -0
- clearbox-0.1.0/examples/mixed_plan.py +164 -0
- clearbox-0.1.0/pyproject.toml +50 -0
- clearbox-0.1.0/tests/fake_openai.py +187 -0
- clearbox-0.1.0/tests/test_agents_api.py +135 -0
- clearbox-0.1.0/tests/test_file_conflicts.py +323 -0
- clearbox-0.1.0/tests/test_flow.py +217 -0
- clearbox-0.1.0/tests/test_mixed_run.py +98 -0
- clearbox-0.1.0/tests/test_openai_harness.py +139 -0
- clearbox-0.1.0/tests/test_rework.py +92 -0
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.
|
clearbox-0.1.0/PKG-INFO
ADDED
|
@@ -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.
|