harness-dispatch 0.4.0 → 0.5.0
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.
- package/README.md +228 -86
- package/config.default.yaml +416 -397
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +2 -2
- package/dist/auth.js.map +1 -1
- package/dist/billing.d.ts +35 -4
- package/dist/billing.d.ts.map +1 -1
- package/dist/billing.js +48 -17
- package/dist/billing.js.map +1 -1
- package/dist/bin.d.ts.map +1 -1
- package/dist/bin.js +253 -23
- package/dist/bin.js.map +1 -1
- package/dist/breaker-store.d.ts +60 -9
- package/dist/breaker-store.d.ts.map +1 -1
- package/dist/breaker-store.js +246 -50
- package/dist/breaker-store.js.map +1 -1
- package/dist/circuit-breaker.d.ts +35 -0
- package/dist/circuit-breaker.d.ts.map +1 -1
- package/dist/circuit-breaker.js +68 -11
- package/dist/circuit-breaker.js.map +1 -1
- package/dist/config/coercions.d.ts +31 -0
- package/dist/config/coercions.d.ts.map +1 -0
- package/dist/config/coercions.js +169 -0
- package/dist/config/coercions.js.map +1 -0
- package/dist/config/env-interpolation.d.ts +36 -0
- package/dist/config/env-interpolation.d.ts.map +1 -0
- package/dist/config/env-interpolation.js +73 -0
- package/dist/config/env-interpolation.js.map +1 -0
- package/dist/config/protocol.d.ts +51 -0
- package/dist/config/protocol.d.ts.map +1 -0
- package/dist/config/protocol.js +300 -0
- package/dist/config/protocol.js.map +1 -0
- package/dist/config/validation.d.ts +72 -0
- package/dist/config/validation.d.ts.map +1 -0
- package/dist/config/validation.js +189 -0
- package/dist/config/validation.js.map +1 -0
- package/dist/config.d.ts +19 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +229 -511
- package/dist/config.js.map +1 -1
- package/dist/dispatch-log.d.ts.map +1 -1
- package/dist/dispatch-log.js +2 -3
- package/dist/dispatch-log.js.map +1 -1
- package/dist/dispatchers/generic-cli.d.ts +15 -1
- package/dist/dispatchers/generic-cli.d.ts.map +1 -1
- package/dist/dispatchers/generic-cli.js +65 -5
- package/dist/dispatchers/generic-cli.js.map +1 -1
- package/dist/dispatchers/openai-compatible.d.ts.map +1 -1
- package/dist/dispatchers/openai-compatible.js +52 -2
- package/dist/dispatchers/openai-compatible.js.map +1 -1
- package/dist/dispatchers/shared/kill-tree.d.ts +17 -4
- package/dist/dispatchers/shared/kill-tree.d.ts.map +1 -1
- package/dist/dispatchers/shared/kill-tree.js +28 -4
- package/dist/dispatchers/shared/kill-tree.js.map +1 -1
- package/dist/dispatchers/shared/rate-limit-headers.d.ts +17 -0
- package/dist/dispatchers/shared/rate-limit-headers.d.ts.map +1 -1
- package/dist/dispatchers/shared/rate-limit-headers.js +27 -0
- package/dist/dispatchers/shared/rate-limit-headers.js.map +1 -1
- package/dist/dispatchers/shared/stream-subprocess.d.ts.map +1 -1
- package/dist/dispatchers/shared/stream-subprocess.js +5 -0
- package/dist/dispatchers/shared/stream-subprocess.js.map +1 -1
- package/dist/dispatchers/shared/subprocess.d.ts.map +1 -1
- package/dist/dispatchers/shared/subprocess.js +3 -0
- package/dist/dispatchers/shared/subprocess.js.map +1 -1
- package/dist/dispatchers/shared/which-available.d.ts.map +1 -1
- package/dist/dispatchers/shared/which-available.js +16 -2
- package/dist/dispatchers/shared/which-available.js.map +1 -1
- package/dist/file-lock.d.ts +17 -0
- package/dist/file-lock.d.ts.map +1 -0
- package/dist/file-lock.js +143 -0
- package/dist/file-lock.js.map +1 -0
- package/dist/http/server.d.ts +11 -0
- package/dist/http/server.d.ts.map +1 -1
- package/dist/http/server.js +331 -162
- package/dist/http/server.js.map +1 -1
- package/dist/job-runner.js +23 -5
- package/dist/job-runner.js.map +1 -1
- package/dist/jobs.d.ts +91 -1
- package/dist/jobs.d.ts.map +1 -1
- package/dist/jobs.js +647 -36
- package/dist/jobs.js.map +1 -1
- package/dist/leaderboard.d.ts +24 -1
- package/dist/leaderboard.d.ts.map +1 -1
- package/dist/leaderboard.js +94 -2
- package/dist/leaderboard.js.map +1 -1
- package/dist/mcp/config-hot-reload.d.ts.map +1 -1
- package/dist/mcp/config-hot-reload.js +1 -1
- package/dist/mcp/config-hot-reload.js.map +1 -1
- package/dist/mcp/dispatcher-factory.d.ts +16 -1
- package/dist/mcp/dispatcher-factory.d.ts.map +1 -1
- package/dist/mcp/dispatcher-factory.js +41 -3
- package/dist/mcp/dispatcher-factory.js.map +1 -1
- package/dist/mcp/tools.d.ts +23 -4
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +163 -16
- package/dist/mcp/tools.js.map +1 -1
- package/dist/observability/index.d.ts +16 -3
- package/dist/observability/index.d.ts.map +1 -1
- package/dist/observability/index.js +23 -7
- package/dist/observability/index.js.map +1 -1
- package/dist/observability/spans.d.ts.map +1 -1
- package/dist/observability/spans.js +4 -1
- package/dist/observability/spans.js.map +1 -1
- package/dist/quota.d.ts +88 -22
- package/dist/quota.d.ts.map +1 -1
- package/dist/quota.js +163 -58
- package/dist/quota.js.map +1 -1
- package/dist/route-policy.d.ts.map +1 -1
- package/dist/route-policy.js +13 -3
- package/dist/route-policy.js.map +1 -1
- package/dist/router.d.ts +25 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +94 -40
- package/dist/router.js.map +1 -1
- package/dist/safety.d.ts.map +1 -1
- package/dist/safety.js +11 -2
- package/dist/safety.js.map +1 -1
- package/dist/state-dir.d.ts +17 -0
- package/dist/state-dir.d.ts.map +1 -0
- package/dist/state-dir.js +21 -0
- package/dist/state-dir.js.map +1 -0
- package/dist/status.d.ts +36 -0
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +61 -4
- package/dist/status.js.map +1 -1
- package/dist/types.d.ts +50 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/working-dir.d.ts +13 -0
- package/dist/working-dir.d.ts.map +1 -1
- package/dist/working-dir.js +27 -0
- package/dist/working-dir.js.map +1 -1
- package/dist/workspace-lock.d.ts +40 -0
- package/dist/workspace-lock.d.ts.map +1 -0
- package/dist/workspace-lock.js +258 -0
- package/dist/workspace-lock.js.map +1 -0
- package/dist/workspaces.d.ts +15 -0
- package/dist/workspaces.d.ts.map +1 -1
- package/dist/workspaces.js +104 -12
- package/dist/workspaces.js.map +1 -1
- package/package.json +98 -96
package/README.md
CHANGED
|
@@ -1,97 +1,190 @@
|
|
|
1
1
|
# harness-dispatch
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
3
|
+
**Route whole coding tasks — not API requests — to the agent CLIs you already pay for.**
|
|
4
|
+
|
|
5
|
+
Each harness keeps its own scaffolding, test loop, and codebase index. There's no
|
|
6
|
+
proxy in between and nothing is re-implemented: Claude Code stays Claude Code. One
|
|
7
|
+
orchestrating agent picks the right one per task and spends your flat-rate
|
|
8
|
+
subscription quota before anything metered.
|
|
9
|
+
|
|
10
|
+
It's a local MCP server, so the harnesses on your machine (Claude Code, Codex,
|
|
11
|
+
Cursor Agent, Antigravity CLI, plus any local or remote OpenAI-compatible endpoint)
|
|
12
|
+
become tools any AI can call. Exposing them as real tools beats asking an agent to
|
|
13
|
+
shell out to another CLI: models are trained on tool calling, so they actually use
|
|
14
|
+
the tools they're given. (No Gemini CLI: Google discontinued that CLI's backend in
|
|
15
|
+
mid-2026, and Antigravity CLI replaced it.)
|
|
16
|
+
|
|
17
|
+
## What it looks like
|
|
18
|
+
|
|
19
|
+
Your agent calls one tool:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"prompt": "Port the retry logic in src/net/ to the new backoff helper, then run the tests.",
|
|
24
|
+
"workingDir": "/path/to/project",
|
|
25
|
+
"hints": { "taskType": "execute" }
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Quick tasks come straight back:
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"mode": "single",
|
|
34
|
+
"completed": true,
|
|
35
|
+
"success": true,
|
|
36
|
+
"route": "codex_cli",
|
|
37
|
+
"model": "gpt-5.6-terra",
|
|
38
|
+
"output": "Ported 4 call sites to withBackoff(); 118 tests pass.",
|
|
39
|
+
"durationMs": 47210,
|
|
40
|
+
"routing": { "tier": 1, "taskType": "execute", "reason": "tier 1 best (3 available)" }
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Slow ones hand back a `jobId` after 25 seconds instead, and keep running:
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{ "mode": "single", "completed": false, "jobId": "job-1786977316001-b49d1232" }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Carry on working, then call `job_status` with that id for a live output tail or the
|
|
51
|
+
finished result. The run lives in a detached process, so **nothing is lost to a client
|
|
52
|
+
timeout — or to the server itself restarting mid-run.**
|
|
43
53
|
|
|
44
54
|
## Install
|
|
45
55
|
|
|
56
|
+
Needs Node.js `>=22.22.2` (so current LTS works) and at least one harness or endpoint.
|
|
57
|
+
|
|
46
58
|
```bash
|
|
47
59
|
npm install -g harness-dispatch
|
|
48
60
|
harness-dispatch configure --yes
|
|
49
61
|
harness-dispatch doctor --live
|
|
50
62
|
```
|
|
51
63
|
|
|
52
|
-
`configure --yes` detects installed harnesses and writes `config.yaml
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
you see a real completion before wiring the server into your agent. The live
|
|
59
|
-
probe respects billing policy — it never touches paid or unknown-billing routes
|
|
60
|
-
unless you pass `--allow-paid`.
|
|
61
|
-
|
|
62
|
-
Your Claude Code / Codex / Cursor subscriptions run by default, no opt-in needed
|
|
63
|
-
— `configure`'s output tells you if anything's blocked and why. See
|
|
64
|
-
[Adding a harness](#adding-a-harness) below for the config and the paid-usage note.
|
|
65
|
-
|
|
66
|
-
> This project was renamed from `harness-router` to `harness-dispatch` — the npm
|
|
67
|
-
> package, CLI command, env var prefix (`HARNESS_DISPATCH_*`), and MCP resource URIs
|
|
68
|
-
> (`harness-dispatch://status`) all changed together. If you have an older install,
|
|
69
|
-
> `npm uninstall -g harness-router && npm install -g harness-dispatch` and update any
|
|
70
|
-
> `mcpServers`/`claude_desktop_config.json` entry to invoke `harness-dispatch`, not the
|
|
71
|
-
> old command. Two older packages predate this rename and are no longer maintained:
|
|
72
|
-
> `harness-router` (`0.3.2` on the registry) and the separately-published
|
|
73
|
-
> `harness-router-mcp` (`0.2.0`) — both lack `usage`, `/v1/models`, `/v1/usage`,
|
|
74
|
-
> Antigravity support, and every fix described in this README. Note that `npx -y
|
|
75
|
-
> harness-dispatch` (used by the plugin's fallback launch path, see
|
|
76
|
-
> [plugin/README.md](./plugin/README.md)) resolves to whatever is currently on the npm
|
|
77
|
-
> registry, which can lag behind a local clone's `dist/`. Run `npm ls -g harness-dispatch`
|
|
78
|
-
> to check which version is actually installed.
|
|
79
|
-
|
|
80
|
-
You can also run without a global install:
|
|
64
|
+
`configure --yes` detects installed harnesses and writes `config.yaml`; without
|
|
65
|
+
`--yes` it previews and writes nothing. `doctor` then checks the whole chain:
|
|
66
|
+
binary, config load, harness detection, auth and billing classification, route
|
|
67
|
+
readiness. `--live` goes further and routes one tiny real prompt, so you see a
|
|
68
|
+
completion before wiring anything into your agent. The live probe never touches paid or
|
|
69
|
+
unknown-billing routes unless you pass `--allow-paid`.
|
|
81
70
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
71
|
+
Your Claude Code / Codex / Cursor subscriptions run by default with no opt-in;
|
|
72
|
+
`configure` tells you if anything is blocked and why.
|
|
73
|
+
|
|
74
|
+
No global install needed either: `npx harness-dispatch configure`.
|
|
85
75
|
|
|
86
76
|
### Plugin install (Claude Code / Claude Desktop / Codex)
|
|
87
77
|
|
|
88
78
|
The `plugin/` directory packages the MCP server plus a delegation skill and
|
|
89
79
|
`/route` + `/jobs` commands for one-step installs — see
|
|
90
|
-
[plugin/README.md](
|
|
80
|
+
[plugin/README.md](https://github.com/fstubner/harness-dispatch/blob/main/plugin/README.md)
|
|
81
|
+
(absolute link on purpose: `plugin/` is not shipped in the npm tarball, so a
|
|
82
|
+
relative link is dead on npmjs.com). Claude Code:
|
|
91
83
|
`/plugin marketplace add <repo path or URL>` then
|
|
92
84
|
`/plugin install harness-dispatch@harness-dispatch`. Codex:
|
|
93
85
|
`node plugin/scripts/install-codex.mjs`.
|
|
94
86
|
|
|
87
|
+
## What it does on your machine
|
|
88
|
+
|
|
89
|
+
Stated plainly, up front, rather than left to be inferred:
|
|
90
|
+
|
|
91
|
+
- It **spawns the CLIs above as subprocesses** with your prompts.
|
|
92
|
+
- Those CLIs **read and write files** under the `workingDir` you pass (that's the
|
|
93
|
+
point of the tool) and **run shell commands**, depending on the workspace and
|
|
94
|
+
safety policy in effect.
|
|
95
|
+
- At most **4 agent CLIs run at once**; extra dispatches queue and start as slots
|
|
96
|
+
free. Tune with `max_concurrent_runs`.
|
|
97
|
+
- `serve` additionally binds a local HTTP port: loopback only by default,
|
|
98
|
+
bearer-token gated. Read [HTTP Surface](#http-surface) before pointing `--host`
|
|
99
|
+
anywhere else.
|
|
100
|
+
|
|
101
|
+
None of this is unusual for a coding-agent tool. It's here in one place so you can
|
|
102
|
+
decide before installing rather than after.
|
|
103
|
+
|
|
104
|
+
## Billing, and what it can't promise
|
|
105
|
+
|
|
106
|
+
**A configured harness runs automatically**, with nothing to switch on. Routes that
|
|
107
|
+
have no billing backstop at all, meaning a raw metered API key or billing it can't
|
|
108
|
+
classify, stay blocked until you set `allow_paid_usage: true` on them.
|
|
109
|
+
|
|
110
|
+
The honest limit: if a harness's account already has paid or overage billing switched
|
|
111
|
+
on at the **provider** (Cursor on-demand, Claude usage credits, Codex flexible
|
|
112
|
+
credits), harness-dispatch will spend that too. It cannot see or change provider-side
|
|
113
|
+
billing state. What it does is refuse routes where *no* provider-side ceiling exists
|
|
114
|
+
at all.
|
|
115
|
+
|
|
116
|
+
Run `status` (or `status --json`) for any route's billing classification; the `note:`
|
|
117
|
+
lines spell out the reasoning per route.
|
|
118
|
+
|
|
119
|
+
<details>
|
|
120
|
+
<summary>Renamed from <code>harness-router</code> — upgrade notes</summary>
|
|
121
|
+
|
|
122
|
+
The npm package, CLI command, env var prefix (`HARNESS_DISPATCH_*`), and MCP resource
|
|
123
|
+
URIs (`harness-dispatch://status`) all changed together. From an older install:
|
|
124
|
+
`npm uninstall -g harness-router && npm install -g harness-dispatch`, then update any
|
|
125
|
+
`mcpServers` / `claude_desktop_config.json` entry to invoke `harness-dispatch`.
|
|
126
|
+
|
|
127
|
+
Two packages predate the rename and are unmaintained: `harness-router` (`0.3.2`) and
|
|
128
|
+
the separately-published `harness-router-mcp` (`0.2.0`). Both lack `usage`,
|
|
129
|
+
`/v1/models`, `/v1/usage`, Antigravity support, and every fix described here.
|
|
130
|
+
|
|
131
|
+
`npx -y harness-dispatch`, the plugin's fallback launch path, resolves to whatever
|
|
132
|
+
is currently on the npm registry, which can lag a local clone's `dist/`. Check with
|
|
133
|
+
`npm ls -g harness-dispatch`.
|
|
134
|
+
|
|
135
|
+
</details>
|
|
136
|
+
|
|
137
|
+
## Safety profiles, and Cursor on Windows
|
|
138
|
+
|
|
139
|
+
A caller asks for one of three profiles, and each is a limit rather than a
|
|
140
|
+
capability:
|
|
141
|
+
|
|
142
|
+
| Profile | Means |
|
|
143
|
+
|---|---|
|
|
144
|
+
| `read_only` | Look, don't touch |
|
|
145
|
+
| `workspace_edit` | Edit files in the workspace, no arbitrary shell |
|
|
146
|
+
| `full_auto` | Edit files and run shell |
|
|
147
|
+
|
|
148
|
+
A route declares the floor it actually runs at (`effective_safety`), and is
|
|
149
|
+
skipped when that floor exceeds what was asked for. A route is never quietly
|
|
150
|
+
given more access than the caller requested.
|
|
151
|
+
|
|
152
|
+
`cursor_cli` is the interesting case, because its capability differs by mode:
|
|
153
|
+
|
|
154
|
+
- `read_only` uses `--mode plan`, which is genuinely read-only (verified: asked
|
|
155
|
+
to create one file and overwrite another, it did neither).
|
|
156
|
+
- `full_auto` uses print mode, which edits and runs shell.
|
|
157
|
+
- `workspace_edit` is **skipped on Windows**. Cursor's print mode grants write
|
|
158
|
+
and shell together, and `--sandbox enabled` — the flag that would constrain
|
|
159
|
+
shell while allowing edits — is macOS/Linux only. There is no edit-without-shell
|
|
160
|
+
mode to route to, so claiming that level would mean handing shell access to a
|
|
161
|
+
caller who explicitly asked not to have it.
|
|
162
|
+
|
|
163
|
+
Cursor still edits code on Windows. Ask for `full_auto`.
|
|
164
|
+
|
|
165
|
+
### Overriding it
|
|
166
|
+
|
|
167
|
+
If you accept that Cursor's editing mode carries shell access and you want it
|
|
168
|
+
to serve `workspace_edit` anyway, declare the floor yourself in `config.yaml` —
|
|
169
|
+
your value replaces the shipped default:
|
|
170
|
+
|
|
171
|
+
```yaml
|
|
172
|
+
clis:
|
|
173
|
+
- name: cursor_cli
|
|
174
|
+
harness: cursor
|
|
175
|
+
command: cursor-agent
|
|
176
|
+
effective_safety:
|
|
177
|
+
read_only: read_only
|
|
178
|
+
workspace_edit: workspace_edit # you are accepting shell access here
|
|
179
|
+
full_auto: full_auto
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
That is a deliberate local decision, not a bug workaround: the shipped default
|
|
183
|
+
is conservative because the tool cannot verify what a given `cursor-agent`
|
|
184
|
+
build will do. On macOS and Linux the better route is `--sandbox enabled`,
|
|
185
|
+
which constrains shell for real — untested here, so it is not shipped on by
|
|
186
|
+
default.
|
|
187
|
+
|
|
95
188
|
## Adding a harness
|
|
96
189
|
|
|
97
190
|
`config.yaml` is entirely optional. There is no separate hidden defaults format —
|
|
@@ -264,8 +357,9 @@ harness-dispatch auth rotate # rotate HTTP bearer token
|
|
|
264
357
|
```
|
|
265
358
|
|
|
266
359
|
Hidden compatibility aliases currently map old alpha commands to the new surface:
|
|
267
|
-
`dashboard` and `list-services` map to `status`,
|
|
268
|
-
|
|
360
|
+
`dashboard` and `list-services` map to `status`, `route <prompt>` runs a one-off
|
|
361
|
+
routed dispatch, and `mcp --http <port>` maps to `serve`.
|
|
362
|
+
They are not part of the public v0.5.0 vocabulary.
|
|
269
363
|
|
|
270
364
|
## MCP Surface
|
|
271
365
|
|
|
@@ -379,11 +473,18 @@ curl http://127.0.0.1:3333/v1/chat/completions \
|
|
|
379
473
|
The REST surface is OpenAI-compatible enough for local clients that can speak
|
|
380
474
|
`/v1/chat/completions`. The `model` field is treated as a routing/model hint.
|
|
381
475
|
|
|
476
|
+
Non-streaming completions are backed by the same persisted job pipeline as the
|
|
477
|
+
MCP `dispatch` tool: the reply carries `harness_dispatch.jobId`, and the same id
|
|
478
|
+
is sent early as an `x-harness-dispatch-job-id` response header. If your client
|
|
479
|
+
times out mid-run (curl defaults, CI step limits), the run still finishes and
|
|
480
|
+
the result persists — recover it with the `job_status` MCP tool or by reading
|
|
481
|
+
`~/.harness-dispatch/jobs/<jobId>/output/`.
|
|
482
|
+
|
|
382
483
|
## Endpoint Modes
|
|
383
484
|
|
|
384
|
-
|
|
485
|
+
harness-dispatch supports two local/custom endpoint patterns:
|
|
385
486
|
|
|
386
|
-
- `direct_openai_compatible`:
|
|
487
|
+
- `direct_openai_compatible`: harness-dispatch calls an OpenAI-compatible
|
|
387
488
|
`/v1/chat/completions` endpoint directly. This is the right mode for Ollama,
|
|
388
489
|
LM Studio, vLLM, LiteLLM, and private local HTTP model servers.
|
|
389
490
|
- `harness_native_endpoint`: a downstream CLI keeps its agent scaffold but is
|
|
@@ -435,7 +536,7 @@ services:
|
|
|
435
536
|
2. Verify configured routes without spending quota where possible.
|
|
436
537
|
3. Classify auth and billing so paid or unknown-paid routes are not selected by accident.
|
|
437
538
|
4. Choose routed harnesses, model priority, and safety profile.
|
|
438
|
-
5. Write
|
|
539
|
+
5. Write config YAML.
|
|
439
540
|
6. Connect selected MCP agents or print snippets.
|
|
440
541
|
|
|
441
542
|
The current command is conservative: it prints detected routes by default and writes
|
|
@@ -454,8 +555,10 @@ shape. Each route includes:
|
|
|
454
555
|
- tier and model metadata
|
|
455
556
|
- quota score and local call count
|
|
456
557
|
- circuit breaker state — a tripped route's remaining cooldown is persisted to disk
|
|
457
|
-
(`~/.harness-dispatch/breaker_state
|
|
458
|
-
excludes that route instead of retrying an exhausted
|
|
558
|
+
(one file per route under `~/.harness-dispatch/breaker_state/`), so a server
|
|
559
|
+
restart mid-cooldown still excludes that route instead of retrying an exhausted
|
|
560
|
+
one with a clean slate; a pre-0.5 single-blob `breaker_state.json` is migrated
|
|
561
|
+
automatically on first read
|
|
459
562
|
- skip reason when a route is disabled, unavailable, paid-blocked, unknown-billing,
|
|
460
563
|
safety-incompatible, or circuit-broken
|
|
461
564
|
- token limits when known
|
|
@@ -470,7 +573,9 @@ Workspace policy:
|
|
|
470
573
|
|
|
471
574
|
- `shared`: run directly in the caller's `workingDir`.
|
|
472
575
|
- `shared_locked`: run directly in `workingDir`, but serialize write-capable
|
|
473
|
-
dispatches for the same directory
|
|
576
|
+
dispatches for the same directory across ALL processes — concurrent dispatches
|
|
577
|
+
from separate server instances and detached job runners queue on a heartbeated
|
|
578
|
+
cross-process lock rather than editing the directory at the same time.
|
|
474
579
|
- `copy`: copy the project into `.harness-dispatch/workspaces/...`, run the agent
|
|
475
580
|
there, and return the isolated workspace path plus changed-file metadata.
|
|
476
581
|
- `git_worktree`: create a detached git worktree for the route and return the
|
|
@@ -520,12 +625,49 @@ size-capped via single-file rotation, and never sent anywhere. Job artifacts
|
|
|
520
625
|
`~/.harness-dispatch/jobs/<jobId>/` and are pruned after 7 days of inactivity by
|
|
521
626
|
default — set `retention: { jobs_days: N }` in `config.yaml` (or
|
|
522
627
|
`HARNESS_DISPATCH_JOB_MAX_AGE_MS` for a millisecond override) to change that
|
|
523
|
-
window.
|
|
628
|
+
window. `0` means keep forever, and a running or queued job with a live
|
|
629
|
+
heartbeat is never pruned regardless of the window.
|
|
630
|
+
|
|
631
|
+
At most **4 agent CLIs run at once**, machine-wide. Dispatches past that limit
|
|
632
|
+
wait in `queued` and start as slots free — you still get a `jobId` back
|
|
633
|
+
immediately and nothing is rejected or lost, only delayed. The bound exists
|
|
634
|
+
because agent CLIs are heavyweight processes, not fan-outable HTTP calls: a
|
|
635
|
+
measured burst of 13 concurrent runs exhausted memory and failed half of them.
|
|
636
|
+
Change it with `max_concurrent_runs: N` in `config.yaml`; `0` removes the bound.
|
|
637
|
+
|
|
638
|
+
Prompts and outputs flow only to the harnesses/endpoints you configured. **The
|
|
639
|
+
router makes no other network call by default.**
|
|
640
|
+
|
|
641
|
+
Routes rank on the `tier` and `weight` you set. Optionally, public Arena ELO
|
|
642
|
+
benchmark data can inform ranking and derive tiers automatically:
|
|
643
|
+
|
|
644
|
+
```yaml
|
|
645
|
+
leaderboard:
|
|
646
|
+
enabled: true # default false
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
Turning it on adds one GET to `api.wulong.dev` per process, refreshed daily.
|
|
650
|
+
It sends nothing about you or your prompts. It is off by default because a
|
|
651
|
+
benchmark maintained elsewhere should not quietly reorder the subscriptions
|
|
652
|
+
you are paying for, and because a routing tool should not need the network to
|
|
653
|
+
decide which of your local CLIs to run.
|
|
654
|
+
|
|
655
|
+
## Chaining delegated work
|
|
656
|
+
|
|
657
|
+
Pass the jobIds of earlier dispatches as `contextJobs` and their prompts and
|
|
658
|
+
outputs are rendered into the new prompt directly:
|
|
659
|
+
|
|
660
|
+
```json
|
|
661
|
+
{ "prompt": "Now write the migration.", "contextJobs": ["job-1786977300001-0f0aaaaa"] }
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
Without it, chaining means reading the first job's output into your own context
|
|
665
|
+
and re-summarising it into the second prompt — which spends the context that
|
|
666
|
+
delegating was meant to save, and loses detail in the retelling.
|
|
524
667
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
for route scoring; it sends nothing about you or your prompts.
|
|
668
|
+
Injected context is capped (24k characters total, 8k per job, 16 jobs) so it
|
|
669
|
+
cannot crowd out the task itself, and a referenced job that is missing or still
|
|
670
|
+
running is reported in the preamble rather than silently dropped.
|
|
529
671
|
|
|
530
672
|
## Development
|
|
531
673
|
|