cargo-hauler 0.4.9 → 0.6.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/CHANGELOG.md +38 -0
- package/README.md +142 -33
- package/artifact/agent-bundle.hooks.json +1 -1
- package/artifact/agent-bundle.manifest.json +1 -1
- package/artifact/claude/.claude-plugin/marketplace.json +1 -1
- package/artifact/claude/.claude-plugin/plugin.json +1 -1
- package/artifact/claude/INSTALL.md +1 -1
- package/artifact/claude/bin/cargo-hauler-flight.mjs +705 -66
- package/artifact/claude/bin/cargo-hauler.mjs +2240 -307
- package/artifact/claude/hooks/after-tool-shell-after-264de610.mjs +1162 -0
- package/artifact/claude/hooks/before-tool-shell-before-9212219e.mjs +4021 -0
- package/artifact/claude/hooks/event-route-session-start.mjs +2 -2
- package/artifact/claude/hooks/event-route-stop.mjs +2 -2
- package/artifact/claude/hooks/hooks-flight.mjs +187 -3434
- package/artifact/claude/hooks/hooks.json +1 -1
- package/artifact/claude/mcp/mcp-hauler-2b8242f9-flight.mjs +840 -3408
- package/artifact/claude/mcp/mcp-hauler-2b8242f9.mjs +707 -68
- package/artifact/claude/mcp-apps/dashboard.html +54 -6
- package/artifact/claude/scripts/hauler.mjs +2077 -319
- package/artifact/claude/skills/cargo-hauler/SKILL.md +19 -9
- package/artifact/claude/skills/hauler-dashboard/SKILL.md +5 -3
- package/artifact/codex/.codex-plugin/plugin.json +1 -1
- package/artifact/codex/INSTALL.md +1 -1
- package/artifact/codex/bin/cargo-hauler-flight.mjs +705 -66
- package/artifact/codex/bin/cargo-hauler.mjs +2240 -307
- package/artifact/codex/hooks/after-tool-shell-after-264de610.mjs +1167 -0
- package/artifact/codex/hooks/before-tool-shell-before-9212219e.mjs +4026 -0
- package/artifact/codex/hooks/event-route-session-start.mjs +2 -2
- package/artifact/codex/hooks/event-route-stop.mjs +2 -2
- package/artifact/codex/hooks/hooks-flight.mjs +187 -3434
- package/artifact/codex/hooks/hooks.json +1 -1
- package/artifact/codex/mcp/mcp-hauler-2b8242f9-flight.mjs +840 -3408
- package/artifact/codex/mcp/mcp-hauler-2b8242f9.mjs +707 -68
- package/artifact/codex/mcp-apps/dashboard.html +54 -6
- package/artifact/codex/scripts/hauler.mjs +2077 -319
- package/artifact/codex/skills/cargo-hauler/SKILL.md +19 -9
- package/artifact/codex/skills/hauler-dashboard/SKILL.md +5 -3
- package/artifact/cursor/.cursor-plugin/plugin.json +1 -1
- package/artifact/cursor/INSTALL.md +1 -1
- package/artifact/cursor/bin/cargo-hauler-flight.mjs +705 -66
- package/artifact/cursor/bin/cargo-hauler.mjs +2240 -307
- package/artifact/cursor/hooks/after-tool-shell-after-264de610.mjs +1204 -0
- package/artifact/cursor/hooks/before-tool-shell-before-9212219e.mjs +4063 -0
- package/artifact/cursor/hooks/event-route-session-start.mjs +2 -2
- package/artifact/cursor/hooks/event-route-stop.mjs +2 -2
- package/artifact/cursor/hooks/hooks-flight.mjs +187 -3434
- package/artifact/cursor/hooks/hooks.json +1 -1
- package/artifact/cursor/install.mjs +1 -1
- package/artifact/cursor/mcp/mcp-hauler-2b8242f9-flight.mjs +840 -3408
- package/artifact/cursor/mcp/mcp-hauler-2b8242f9.mjs +707 -68
- package/artifact/cursor/mcp-apps/dashboard.html +54 -6
- package/artifact/cursor/scripts/hauler.mjs +2077 -319
- package/artifact/cursor/skills/cargo-hauler/SKILL.md +19 -9
- package/artifact/cursor/skills/hauler-dashboard/SKILL.md +5 -3
- package/artifact/portable/INSTALL.md +1 -1
- package/artifact/portable/bin/cargo-hauler-flight.mjs +705 -66
- package/artifact/portable/bin/cargo-hauler.mjs +2240 -307
- package/artifact/portable/install.mjs +1 -1
- package/artifact/portable/mcp/mcp-hauler-2b8242f9-flight.mjs +706 -67
- package/artifact/portable/mcp/mcp-hauler-2b8242f9.mjs +706 -67
- package/artifact/portable/mcp-apps/dashboard.html +54 -6
- package/artifact/portable/plugin.json +1 -1
- package/artifact/portable/scripts/hauler.mjs +2077 -319
- package/artifact/portable/skills/cargo-hauler/SKILL.md +19 -9
- package/artifact/portable/skills/hauler-dashboard/SKILL.md +5 -3
- package/dist/bin/cargo-hauler-flight.mjs +705 -66
- package/dist/bin/cargo-hauler.js +2240 -307
- package/dist/bin/hauler.js +2077 -319
- package/package.json +1 -1
- package/artifact/claude/hooks/event-route-tool-after.mjs +0 -106595
- package/artifact/claude/hooks/event-route-tool-before.mjs +0 -106595
- package/artifact/codex/hooks/event-route-tool-after.mjs +0 -106595
- package/artifact/codex/hooks/event-route-tool-before.mjs +0 -106595
- package/artifact/cursor/hooks/event-route-tool-after.mjs +0 -106595
- package/artifact/cursor/hooks/event-route-tool-before.mjs +0 -106595
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- cfb8d3c: Make coverage attachment fire in practice, say why it did not, and let a compile-only `cargo test --no-run` ride a running `cargo test`.
|
|
8
|
+
|
|
9
|
+
- A `test --no-run` / `bench --no-run` submitted while a `test` / `bench` with the same packages, target selection, features, and profile is in flight in the lane attaches to it and is released — `done`, exit 0, with the leader's compile time so far as its saved compute — the moment the leader prints Cargo's `Finished` line, while the leader goes on running its tests. The leader's test filters, `--test-threads`, and `--no-fail-fast` do not disqualify the rider; a `--lib` rider never attaches to a `--test foo` leader, or a named-target rider to a default-target one. A leader whose build fails requeues the rider through the existing failed-stronger path; a leader that never reports its build finished (`--quiet`, `CARGO_HAULER_OVERLAP_EXECUTION=0`) releases it at settlement. A rider that arrives after the leader's build already finished is refused (`leader-build-finished`) and runs its own, mostly fresh, cargo at once. Both rows carry `buildFinishedAtMs`. (#88)
|
|
10
|
+
- Coverage gates widened where safe: flags the daemon does not model (`--locked`, `--offline`, `--message-format`, …) and arguments after `--` disqualify a `check` riding a `build` / `check` only when they differ between the two requests; `check --tests` rides `build --tests` / `build --all-targets`; a `--bin X` / `--example X` rider rides a `--bins` / `--examples` leader. `check` still never rides `clippy` (a lint failure would misreport the check), a `-p` rider is still not proven by a leader compiling its unnamed default package set, and `--tests` does not stand in for a named `--test X`. (#89)
|
|
11
|
+
- Every refused attachment is logged at debug level (`attach rejected`) with the gate — `subcommand`, `opaque-arguments`, `passthrough`, `compile-surface`, `packages`, `targets`, `channels`, `leader-build-finished` — a one-line detail, and both tickets; the status report counts one refusal per request under `metrics.attach_rejections`, keyed by the nearest gate any leader reached (`hauler status --json`, `hauler_status`). (#89)
|
|
12
|
+
- d5ae3e9: Show what the hauler costs, not only what it saves: queue wait vs run time per metrics window, the compile/execute split per command, and kache store pressure. (#92)
|
|
13
|
+
|
|
14
|
+
- Each metrics window (`1h`, `24h`, `all`) now reports total queue wait against total run time for leaders, and splits the wait by cause: *lane-bound* (a same-lane leader was still compiling — before its `Finished` line or exit), *permit-bound* (every admission permit was held and no same-lane compile was to blame), and *other* (admission holds, `--after` prerequisites, scheduling latency). The classification is a pure sweep over ledger rows, run against the daemon's current permit count; the tile states that assumption, since runs admitted under an earlier cap are classified against today's.
|
|
15
|
+
- With `buildFinishedAtMs` on the row, the by-command split adds compile vs execution p50s for `test`/`run`/`bench` leaders, and the window reports the lane time the execution-phase hand-back released. Pure compiles have no split and say nothing.
|
|
16
|
+
- The kache panel (`hauler status`, `hauler_status`, and the dashboard) surfaces store pressure: blob bytes recorded in the index against `local_max_size` (from `KACHE_MAX_SIZE` or kache's `config.toml`; "limit unknown" names why when it cannot be read), the last GC from `gc_stats.json` — when, how long, what it evicted — with any `gc: skipping eviction` warnings matched from kache's `auto-gc.log`/`daemon.log` during that run, and `key_ms` mean/p95 over the events tail. Warnings appear when the store is over its limit or the last GC declined or skipped evictions. Missing or unparsable files render as unavailable with their reason, never as an empty store.
|
|
17
|
+
- Protocol additions are optional fields (`metrics.windows[].waitSplit`, `handBack`, `runTotalMs`, `waitTotalMs`, `bySubcommand[].phases`, `kache.pressure`); clients reading an older daemon see the tiles as unavailable rather than as zeros.
|
|
18
|
+
- ec9bf65: Fold more queued work into one Cargo run: compile batches with a `--` trailer, and test runs whose filters differ.
|
|
19
|
+
|
|
20
|
+
- `cargo build` / `check` / `clippy` requests with the same arguments after `--` now batch: `cargo clippy -p a -- -D warnings` and `cargo clippy -p b -- -D warnings` run as `cargo clippy -p a -p b -- -D warnings`, the trailer once. Such runs are demultiplexed like any other compile (the `--message-format` rewrite goes before the `--`), so under `-D warnings` a participant whose own units compiled cleanly is released as done when another participant's warnings fail the composite; the rest rerun alone. Trailers that differ, or a trailer on one side only, still keep the runs apart, as do differing `--all-targets` / `--tests` / `--features`. (#86)
|
|
21
|
+
- `cargo test` requests naming different packages fold even when their bare name filters differ: `cargo test -p a -- f1` and `cargo test -p b -- f2` run as `cargo test -p a -p b --no-fail-fast -- f1 f2`, the union of packages with the union of filters. An identical set of `--test-threads=N`, `--nocapture`, `--quiet` (or `-q`) after `--` folds too, carried once from the leader; a mismatched set does not, and `--exact`, `--skip`, `--ignored`, `--include-ignored`, `--list`, `--format`, `--logfile`, or any other harness flag keeps a run out of composites. `--lib` runs fold like `--test NAME` runs; the target selection still has to match. Requests naming the same packages still need the same filters, and unfiltered runs fold only with unfiltered runs. A folded participant inherits a composite failure only when it asked for every package and every filter the composite ran; otherwise it reruns alone. (#87)
|
|
22
|
+
- c27662d: Estimate compile and execute separately, and keep a realistic queue ETA when a live head overruns.
|
|
23
|
+
|
|
24
|
+
`hauler status` reports `compileEstimateMs`, `executeEstimateMs`, and the current `phase` on a ticket. Followers waiting on a `test`/`nextest`/`bench`/`run` head see only the remaining execution time in `queue.waitEtaMs` (zero once overlap has handed the lane back), and queued tickets ahead of them count only their compile when overlap will hand the lane back. A head that has passed `CARGO_HAULER_STALL_ESTIMATE_FACTOR` times its estimate but is still burning CPU or printing is flagged `estimateState: overrun` with the intent's p90 (`p90Ms`), and its followers see `queue.headEstimateState: overrun`; its remaining time is re-estimated from that p90 — never less than one more estimate's worth — instead of contributing zero to the queue. A silent no-CPU head stays `stalled`. Filtered `--test <name>` runs with no history of their own borrow a same-package neighbor's timing before falling back to the crate-wide prior.
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- f01833d: Cut what the hooks cost every shell tool call. The `tool/before` and `tool/after` hooks are now declared handlers compiled into standalone entries (`hooks/before-tool-shell-before-*.mjs`, `hooks/after-tool-shell-after-*.mjs`) instead of event routes that loaded the whole rendering runtime before looking at the command. `tool/before` reads `tool_input.command` and answers `continue` at once when it has no `cargo`, `hauler`, or `conductor` word (word-boundary aware; `mycargo` and `CARGO_HOME=… ls` do not match, `~/.cargo/bin/cargo`, `cargo-hauler`, and `echo cargo` do — a doubtful command takes the full path). `tool/after` runs that test plus one bounded 500 ms socket ping (`session-completed` with the session's hook-state cursor, no Effect runtime), and loads the telemetry and notification code only when the command was cargo-related or the daemon reported finished tickets; with no daemon it exits quietly with no output. Rewrite, `cargo clean` denial, `hauler exec` pass-through, and completion notices are unchanged. Measured with `/usr/bin/time -v`, median of 5, Claude `ls -la` envelope: `tool/before` 0.05 s / 48 MB and `tool/after` 0.05 s / 49 MB, against 0.54 s / 143 MB and 0.56 s / 144 MB for the 0.5.0 wrappers without a shared runtime (0.10 s / 64 MB with one, per #90); a `cargo test -p foo` envelope is 0.05 s / 49 MB either way. (#90)
|
|
29
|
+
|
|
30
|
+
## 0.5.0
|
|
31
|
+
|
|
32
|
+
### Minor Changes
|
|
33
|
+
|
|
34
|
+
- b80d9db: Cut the latency the hauler adds on a shared target directory, and stop charging lane wait to attaching.
|
|
35
|
+
|
|
36
|
+
- A lane now hands its slot to the next request as soon as a `test`, `nextest`, `bench`, or `run` leader prints Cargo's `Finished … target(s)` line: Cargo drops the build-directory lock there, so the next compile overlaps the leader's test run instead of waiting for it. The leader keeps its admission permit until it settles. `hauler status` and the dashboard list such leaders under the lane's `executing` tickets, and the ledger stamps `buildFinishedAtMs` on the leader and its riders. `CARGO_HAULER_OVERLAP_EXECUTION=0` restores strict one-process-per-lane.
|
|
37
|
+
- Default admission permits scale with the machine: one per eight cores, clamped between the previous five and sixteen. `CARGO_HAULER_MAX_CONCURRENT` still overrides.
|
|
38
|
+
- The cost model times edited and cached runs of an intent separately, and floors an edited intent that has only ever been timed cached at the crate compile priors, so a recompiling test run no longer reaches the head of the lane on a seconds-long estimate; unedited runs no longer teach the per-crate compile priors.
|
|
39
|
+
- Saved latency for riders is measured from the later of the rider's creation and the leader's start: time queued behind a leader that had not started is lane wait the rider would have paid alone, not a cost of attaching. Existing ledgers recompute the column once on open (`user_version` 2); the dashboard's all-time latency tile changes accordingly.
|
|
40
|
+
|
|
3
41
|
## 0.4.9
|
|
4
42
|
|
|
5
43
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -130,35 +130,89 @@ followed by another statement, `elif`, and `function name { … }` — are left
|
|
|
130
130
|
untouched and run as plain Cargo rather than risk emitting a changed
|
|
131
131
|
command.
|
|
132
132
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
133
|
+
Both shell hooks run on every shell tool call, so they decide cheaply before
|
|
134
|
+
they do anything else. The `tool/before` entry reads `tool_input.command` and
|
|
135
|
+
answers `continue` for a command with no `cargo`, `hauler`, or `conductor`
|
|
136
|
+
word in it (word-boundary aware: `mycargo` and `CARGO_HOME=… ls` are not
|
|
137
|
+
matches, `~/.cargo/bin/cargo`, `cargo-hauler`, and `echo cargo` are — a false
|
|
138
|
+
negative would bypass the broker, so anything that looks like a mention takes
|
|
139
|
+
the full path). Only a matching command evaluates the parser and the rewrite.
|
|
140
|
+
The `tool/after` entry runs the token test and one bounded socket ping to the
|
|
141
|
+
daemon (the `session-completed` request with the session's hook-state cursor,
|
|
142
|
+
500 ms, no Effect runtime); it loads the telemetry and notification code only
|
|
143
|
+
when the command was cargo-related or the daemon reported finished tickets. A
|
|
144
|
+
non-cargo call with nothing finished — or no daemon at all — exits with no
|
|
145
|
+
output. Measured with `/usr/bin/time -v` on a Claude `ls -la` envelope, the
|
|
146
|
+
compiled entries take ~50 ms wall and ~49 MB RSS, against ~100 ms and 64 MB
|
|
147
|
+
for the 0.4.8 event-route wrappers with a shared runtime available and
|
|
148
|
+
~560 ms and 144 MB without one.
|
|
149
|
+
|
|
150
|
+
A lane is keyed by workspace root and resolved target directory. It compiles
|
|
151
|
+
one job at a time — Cargo's own build-directory lock would serialize them
|
|
152
|
+
anyway. Once a `test`, `nextest`, `bench`, or `run` leader reports its build
|
|
153
|
+
finished, Cargo has dropped that lock, so the lane hands its slot to the next
|
|
154
|
+
request and that compile overlaps the leader's test run
|
|
155
|
+
(`CARGO_HAULER_OVERLAP_EXECUTION=0` restores strict one-at-a-time). Different
|
|
156
|
+
lanes may run concurrently after acquiring one of the global admission
|
|
157
|
+
permits. `CARGO_HAULER_MAX_CONCURRENT` controls the machine-wide permit count;
|
|
158
|
+
the default is one permit per eight cores, clamped between five and sixteen,
|
|
159
|
+
since the shared jobserver already bounds compile parallelism and the pressure
|
|
160
|
+
arms defer admission under load. Attached requests (riders) do not hold
|
|
161
|
+
permits; the admission meter counts permit holders and reports riders
|
|
162
|
+
separately.
|
|
139
163
|
|
|
140
164
|
Within a lane, the daemon can reduce work in three ways:
|
|
141
165
|
|
|
142
166
|
1. **Identity attachment:** a byte-identical request attaches to an in-flight
|
|
143
167
|
run.
|
|
144
168
|
2. **Coverage attachment:** a narrower `check` attaches to a compatible
|
|
145
|
-
`build` or `check` that covers its package and target scope
|
|
169
|
+
`build` or `check` that covers its package and target scope (`--tests`
|
|
170
|
+
under `--tests` or `--all-targets` included); a compile-only
|
|
171
|
+
`test --no-run` / `bench --no-run` attaches to a running `test` / `bench`
|
|
172
|
+
with the same packages, target selection, features, and profile — the
|
|
173
|
+
leader's test filters and `--test-threads` select what runs, not what
|
|
174
|
+
compiles — and is released as soon as the leader prints its `Finished`
|
|
175
|
+
line rather than when its tests end. Flags the daemon does not model
|
|
176
|
+
(`--locked`, `--offline`, …) and arguments after `--` disqualify a pair
|
|
177
|
+
only when they differ between the two requests. `check` never rides
|
|
178
|
+
`clippy`: a lint failure would misreport the check.
|
|
146
179
|
3. **Batch folding:** compatible queued compile or test requests are combined
|
|
147
180
|
into one invocation.
|
|
148
181
|
|
|
182
|
+
A request that could not attach is logged at debug level with the gate that
|
|
183
|
+
refused it (`subcommand`, `opaque-arguments`, `passthrough`,
|
|
184
|
+
`compile-surface`, `packages`, `targets`, `channels`,
|
|
185
|
+
`leader-build-finished`) and both tickets, and `hauler status --json`
|
|
186
|
+
counts refusals per gate under `metrics.attach_rejections` (the nearest
|
|
187
|
+
miss when several leaders were considered).
|
|
188
|
+
|
|
149
189
|
Each admitted leader starts one Cargo process. Identity, coverage, and folded
|
|
150
190
|
batch requests share that process and receive its streamed output. A failed
|
|
151
191
|
stronger compile does not satisfy a coverage or compile-batch attachment; the
|
|
152
192
|
attached request returns to its lane unless its required compilation units were
|
|
153
|
-
already observed as successful.
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
`
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
193
|
+
already observed as successful. Compile batches (`build`, `check`, `clippy`)
|
|
194
|
+
require the same target selection and features, and the same arguments after
|
|
195
|
+
`--`: `cargo clippy -p a -- -D warnings` and `cargo clippy -p b -- -D warnings`
|
|
196
|
+
become `cargo clippy -p a -p b -- -D warnings`, with the trailer once. Under
|
|
197
|
+
`-D warnings` another participant's warnings fail the composite; a participant
|
|
198
|
+
whose own units compiled cleanly is still released as done, the rest rerun
|
|
199
|
+
alone. Folded tests share the composite process and output. `cargo test`
|
|
200
|
+
requests fold when their `--test` / `--lib` selection and harness flags match
|
|
201
|
+
— `--test-threads=N`, `--nocapture`, and `--quiet` are the only flags a
|
|
202
|
+
composite carries, and only when every participant asked for the same set.
|
|
203
|
+
Requests naming different packages may also differ in bare name filters:
|
|
204
|
+
`cargo test -p a -- f1` and `cargo test -p b -- f2` become `cargo test -p a -p
|
|
205
|
+
b --no-fail-fast -- f1 f2`, a run over the union of packages with the union of
|
|
206
|
+
filters (a filter for one package may also match test names in another).
|
|
207
|
+
Requests naming the same packages share no compile and still need the same
|
|
208
|
+
filters; unfiltered runs fold only with unfiltered runs; `--exact`, `--skip`,
|
|
209
|
+
`--ignored`, `--include-ignored`, `--list`, `--format`, `--logfile`, or any
|
|
210
|
+
other harness flag keeps a run out of composites. `cargo nextest run`
|
|
211
|
+
requests fold only on an identical filterset. On success every participant
|
|
212
|
+
shares the exit. When the composite
|
|
213
|
+
fails, a participant inherits that failure only if it named every package and
|
|
214
|
+
every filter the composite ran; otherwise the failing tests may belong to
|
|
215
|
+
another participant's package or filter, so it is requeued and runs alone
|
|
162
216
|
(cargo's test output does not attribute failures to packages). The leader
|
|
163
217
|
keeps the composite exit, as compile-batch leaders do.
|
|
164
218
|
|
|
@@ -172,7 +226,9 @@ stdout.
|
|
|
172
226
|
|
|
173
227
|

|
|
174
228
|
|
|
175
|
-
The scheduler estimates run cost from per-intent EWMA history
|
|
229
|
+
The scheduler estimates run cost from per-intent EWMA history, split into a
|
|
230
|
+
compile phase and an execution phase once the ledger has seen
|
|
231
|
+
`buildFinishedAtMs` on that intent. It can also use
|
|
176
232
|
per-crate timing data from kache. Lower-cost work, requests with more attached
|
|
177
233
|
callers, dependency-unblocking work, and recently edited packages receive a
|
|
178
234
|
lower scheduling score. Waiting time lowers the score further so broad work
|
|
@@ -227,14 +283,14 @@ own `-j` flag or `CARGO_BUILD_JOBS` always wins over both.
|
|
|
227
283
|
|
|
228
284
|
| Capability | Behavior |
|
|
229
285
|
| --- | --- |
|
|
230
|
-
| Work sharing | Identical requests attach, covered checks attach, and compatible queued compile or test requests fold. |
|
|
286
|
+
| Work sharing | Identical requests attach, covered checks and compile-only `test --no-run` requests attach, and compatible queued compile or test requests fold. |
|
|
231
287
|
| Lane isolation | A workspace-root and target-directory pair is serialized independently from other lanes. |
|
|
232
288
|
| Admission | Per-core load, Linux CPU PSI, Linux memory PSI and `MemAvailable`, macOS VM pressure, configured thresholds, and the global permit cap control new starts. |
|
|
233
289
|
| Parallelism | One daemon-owned jobserver FIFO shared by every spawned Cargo; a per-run `CARGO_BUILD_JOBS` grant only when the FIFO could not be armed. |
|
|
234
|
-
| Scheduling | EWMA estimates, optional kache priors, fan-out, dependency topology, recent edits, and request age determine lane order; `--after cc-N` holds a request until the named tickets settle. |
|
|
290
|
+
| Scheduling | Per-phase EWMA estimates (compile vs execute), optional kache priors, fan-out, dependency topology, recent edits, and request age determine lane order; `--after cc-N` holds a request until the named tickets settle. |
|
|
235
291
|
| Persistence | Tickets, output tails, timings, outcomes, and savings are stored in SQLite; every leader run's whole combined output is kept on disk as `<state dir>/tickets/<ticket>.log`. |
|
|
236
292
|
| Caller output and status | Output streams to attached callers; late callers receive buffered replay. After 30 seconds without output, the client emits a progress heartbeat every 15 seconds with lane queue position, the lane-head ticket, and an aggregate wait ETA. |
|
|
237
|
-
| Wait escalation | A queued request waiting longer than the larger of twice its own estimate and ten minutes is flagged as delayed; running jobs silent for more than five minutes show a quiet-duration hint. A running job past three times its estimate whose process tree has burned no CPU and printed nothing for ten minutes is flagged `stalled`; only a stalled job whose submitting connection is gone is killed automatically. |
|
|
293
|
+
| Wait escalation | A queued request waiting longer than the larger of twice its own estimate and ten minutes is flagged as delayed; running jobs silent for more than five minutes show a quiet-duration hint. A live head past three times its estimate that is still burning CPU or printing is flagged `estimateState: overrun` (its followers see `queue.headEstimateState`) and contributes its history p90 remaining — never less than one more estimate's worth — to the queue ETA instead of zero. A running job past three times its estimate whose process tree has burned no CPU and printed nothing for ten minutes is flagged `stalled`; only a stalled job whose submitting connection is gone is killed automatically. |
|
|
238
294
|
| Daemon status | `running`, `stopped`, or `unresponsive`: a socket that exists but does not answer within its budget is reported as unresponsive, never as stopped. |
|
|
239
295
|
|
|
240
296
|
### Tickets and long-running requests
|
|
@@ -287,7 +343,12 @@ macOS; other platforms do not detect stalls). A ticket is flagged `stalled`
|
|
|
287
343
|
when its elapsed time exceeds `CARGO_HAULER_STALL_ESTIMATE_FACTOR` (3) times
|
|
288
344
|
its estimate, the tree's CPU time has not changed for
|
|
289
345
|
`CARGO_HAULER_STALL_IDLE_MS` (ten minutes), and it printed nothing in that
|
|
290
|
-
window.
|
|
346
|
+
window. A head that has crossed that estimate multiple but is still burning
|
|
347
|
+
CPU or printing is flagged `estimateState: overrun` instead (with the
|
|
348
|
+
intent's `p90Ms`, and `queue.headEstimateState: overrun` on the tickets
|
|
349
|
+
behind it): the queue ETA uses the intent's p90 remaining — never less than
|
|
350
|
+
one more estimate's worth — rather than zero, and agents can background it
|
|
351
|
+
without treating it as deadlocked. `hauler status`, `hauler_status`, and the dashboard show `stalled`
|
|
291
352
|
with the idle duration; `hauler result` and `hauler_result` answer `ticket
|
|
292
353
|
looks stalled (no CPU for Nm) — hauler kill cc-N`; `hauler await` heartbeats
|
|
293
354
|
say the same. Riders of a stalled leader report the leader's stall and its
|
|
@@ -318,7 +379,7 @@ send (`outputPath`, `after`), so a plain version difference alone does not
|
|
|
318
379
|
break `status`, `result`, or `await`; finish or `hauler kill` what is in
|
|
319
380
|
flight before restarting if the work matters.
|
|
320
381
|
|
|
321
|
-
The `tool/after`
|
|
382
|
+
The `tool/after` hook checks the session's background tickets — `--bg`,
|
|
322
383
|
`hauler_request`, and synchronous requests the client converted to a ticket
|
|
323
384
|
— and, on the first tool call after one finishes, adds its result to the
|
|
324
385
|
agent context. A foreground ticket streamed its exit to the shell the agent
|
|
@@ -385,6 +446,17 @@ reports the slowest crates by profile (`<KacheStats>`). Without that index,
|
|
|
385
446
|
estimates come from the daemon's EWMA history. A missing or incompatible index
|
|
386
447
|
is reported as unavailable and never rejects a request.
|
|
387
448
|
|
|
449
|
+
The same panel surfaces store pressure: blob bytes recorded in the index
|
|
450
|
+
against kache's `local_max_size` (from `KACHE_MAX_SIZE` or
|
|
451
|
+
`$XDG_CONFIG_HOME/kache/config.toml`; when neither applies the panel says
|
|
452
|
+
"limit unknown" and why), the last GC from `gc_stats.json` beside the index
|
|
453
|
+
— when it ran, how long it took, what it evicted, and any `gc: skipping
|
|
454
|
+
eviction` warnings from kache's `auto-gc.log`/`daemon.log` during that run —
|
|
455
|
+
and `key_ms` mean/p95 over the tail of the events sidecar. Warnings appear
|
|
456
|
+
when the store is over its limit or the last GC declined or skipped evictions;
|
|
457
|
+
a missing or unparsable file renders as unavailable with its reason, never as
|
|
458
|
+
an empty store.
|
|
459
|
+
|
|
388
460
|

|
|
389
461
|
|
|
390
462
|
## Install
|
|
@@ -470,7 +542,8 @@ Per-host notes and hook timeouts are in [docs/install.md](docs/install.md).
|
|
|
470
542
|
| --- | --- | --- |
|
|
471
543
|
| `CARGO_HAULER_STATE_DIR` | Per-user cache directory | Unix socket or Windows named pipe source, SQLite ledger, daemon log, pid lock, `hook-state.json`, `hook-events.jsonl`, and the per-ticket output logs under `tickets/`. No legacy alias. |
|
|
472
544
|
| `CARGO_HAULER_CARGO_BIN` | `$CARGO_HOME/bin/cargo` | Cargo binary for daemon-started work; bare `cargo` is the last fallback. Never resolved through `PATH`. Read from the daemon's own environment (export it where the daemon starts, or before `hauler daemon start`); clients do not forward it. |
|
|
473
|
-
| `CARGO_HAULER_MAX_CONCURRENT` |
|
|
545
|
+
| `CARGO_HAULER_MAX_CONCURRENT` | cores ÷ 8, clamped to 5–16 | Global admission permits for Cargo processes across all lanes; an integer >= 1. |
|
|
546
|
+
| `CARGO_HAULER_OVERLAP_EXECUTION` | `1` | Hand a lane to its next request once a `test`/`nextest`/`bench`/`run` leader reports its build finished, overlapping the next compile with the leader's execution phase. `0` keeps a lane strictly one process at a time. |
|
|
474
547
|
| `CARGO_HAULER_JOBS_GRANT` | `max(4, cores / max concurrent)` | `CARGO_BUILD_JOBS` added to each Cargo process only while the shared jobserver FIFO is not armed; an armed daemon injects `MAKEFLAGS` instead and leaves `CARGO_BUILD_JOBS` unset. `0` disables injection. |
|
|
475
548
|
| `CARGO_HAULER_JOBSERVER` | `auto` | Machine-wide fifo jobserver for daemon-spawned cargo: `auto` arms it only when the host `make` is 4.4+ (or absent) because older makes reject `--jobserver-auth=fifo:` in build scripts; `fifo` forces it on, `off` disables it (per-run `CARGO_BUILD_JOBS` grants apply instead). |
|
|
476
549
|
| `CARGO_HAULER_LOAD_THRESHOLD` | Disabled | Per-core one-minute load threshold for deferring new admissions. |
|
|
@@ -529,9 +602,14 @@ unset, the daemon reads kache's configured local store from
|
|
|
529
602
|
document names the one in use (`state dir …` in the header; `stateRoot` in
|
|
530
603
|
`--json`), so a `CARGO_HAULER_STATE_DIR` change is visible on the next
|
|
531
604
|
command rather than discovered from an empty ledger.
|
|
532
|
-
- Test
|
|
533
|
-
|
|
534
|
-
|
|
605
|
+
- Test execution is never shared by coverage: a `test`, `nextest`, or `bench`
|
|
606
|
+
that runs tests attaches only by identity or batch folding. Only a
|
|
607
|
+
compile-only `test --no-run` / `bench --no-run` rides a running `test` /
|
|
608
|
+
`bench`, and it is released by the leader's build alone. Folded `test` and
|
|
609
|
+
`nextest` requests receive the composite output, and a composite may run
|
|
610
|
+
more than one participant asked for (another package, another name
|
|
611
|
+
filter); its failure is inherited only by participants that asked for
|
|
612
|
+
everything it ran, the rest rerun alone.
|
|
535
613
|
- The `cargo clean` guard probes the daemon for 250 ms. Active work denies
|
|
536
614
|
the clean; an idle daemon brokers it; a daemon that accepts but does not
|
|
537
615
|
answer in time is busy, so the clean is brokered and the lane serializes
|
|
@@ -550,9 +628,9 @@ unset, the daemon reads kache's configured local store from
|
|
|
550
628
|
<summary><strong>How the app is built</strong> — agent-bundle application structure, testing, and development (click to expand)</summary>
|
|
551
629
|
|
|
552
630
|
The plugin is an [agent-bundle](https://github.com/ScriptedAlchemy/agent-bundle)
|
|
553
|
-
application: six MCP tools, a routed CLI,
|
|
554
|
-
browser dashboard, all rendered from one
|
|
555
|
-
layout. This section is for contributors; using cargo-hauler needs none of it.
|
|
631
|
+
application: six MCP tools, a routed CLI, two hook routes plus two declared
|
|
632
|
+
shell hooks, two skills, and a browser dashboard, all rendered from one
|
|
633
|
+
component library through one shared layout. This section is for contributors; using cargo-hauler needs none of it.
|
|
556
634
|
|
|
557
635
|
### Application structure
|
|
558
636
|
|
|
@@ -569,7 +647,8 @@ src/
|
|
|
569
647
|
mcp/hauler/tools/*.tsx hauler_status, _log, _last, _await, _result, _request
|
|
570
648
|
mcp/hauler/apps/dashboard.tsx the MCP App (ui://cargo-hauler/dashboard.html)
|
|
571
649
|
cli/*.tsx, cli/daemon.ts the routed `cargo-hauler` CLI, same components
|
|
572
|
-
events/{session/start,
|
|
650
|
+
events/{session/start,stop}.tsx rendered hook routes
|
|
651
|
+
hooks/fast-path/ the declared tool/before and tool/after shell hooks
|
|
573
652
|
skills/cargo-hauler/SKILL.md, skills/hauler-dashboard/SKILL.tsx
|
|
574
653
|
scripts/hauler.ts the `hauler` process entry hooks rewrite cargo to
|
|
575
654
|
daemon/, client/, hooks/, shim/, lib/ the broker and its libraries
|
|
@@ -600,6 +679,24 @@ through one layout, the way a page framework's `layout.tsx` wraps every page:
|
|
|
600
679
|
|
|
601
680
|
Event routes are host protocol responses and are never wrapped.
|
|
602
681
|
|
|
682
|
+
#### The shell hooks (`src/hooks/fast-path/`)
|
|
683
|
+
|
|
684
|
+
`tool/before` and `tool/after` are not rendered routes. They are declared in
|
|
685
|
+
`agent-bundle.config.ts` under `hooks` as handler modules
|
|
686
|
+
(`shell-before.ts`, `shell-after.ts`), which the framework compiles into
|
|
687
|
+
standalone entries — `hooks/before-tool-shell-before-<hash>.mjs`,
|
|
688
|
+
`hooks/after-tool-shell-after-<hash>.mjs` — that carry no React, no Flight
|
|
689
|
+
worker, and no Effect. Each entry decides on the raw command first
|
|
690
|
+
(`tokens.ts`; `session-ping.ts` for the completion ping) and reaches the
|
|
691
|
+
rewrite (`before-shell.ts`) or the telemetry and notification code
|
|
692
|
+
(`after-shell.ts`) through a deliberate dynamic `import()`, the one place in
|
|
693
|
+
the codebase that imports lazily. The handler contract has no `allow`
|
|
694
|
+
outcome, so a fully brokered rewrite writes the host's own allow shape
|
|
695
|
+
(`allow-output.ts`) instead of `continue` + `updatedInput`, which would make
|
|
696
|
+
the host prompt for the rewrite. Everything else — `continue`, `deny` with a
|
|
697
|
+
reason, `additionalContext` — goes through the generated wrapper's
|
|
698
|
+
projection.
|
|
699
|
+
|
|
603
700
|
#### The daemon provider (`src/providers/hauler-daemon.ts`)
|
|
604
701
|
|
|
605
702
|
One request-context provider mounts `providers.haulerDaemon` for every tool,
|
|
@@ -683,9 +780,9 @@ the same filter as its `session` field). Results carry
|
|
|
683
780
|
| `tool:hauler/hauler_request` · `cli:request` | submit a background request | `RequestDocument` |
|
|
684
781
|
| `cli:daemon` | `run` / `start` / `stop` / `status` | plain JSON, exit code from the result |
|
|
685
782
|
| `event:session/start` | new session | daemon state and the no-kill rule as context |
|
|
686
|
-
| `event:tool/before` | shell tool about to run | rewrites `cargo …` to `hauler exec --session … --host … -- cargo …`; denies `cargo clean` during in-flight builds, brokers it while the daemon is too busy to answer |
|
|
687
|
-
| `event:tool/after` | shell tool finished | injects finished background-ticket results once per session |
|
|
688
783
|
| `event:stop` | agent stopping | holds the stop while a foreground ticket is pending (bounded, re-deniable) |
|
|
784
|
+
| `hooks.beforeTool` (`src/hooks/fast-path/shell-before.ts`) | shell tool about to run | `continue` without loading anything for a non-cargo command; otherwise rewrites `cargo …` to `hauler exec --session … --host … -- cargo …`, denies `cargo clean` during in-flight builds, brokers it while the daemon is too busy to answer |
|
|
785
|
+
| `hooks.afterTool` (`src/hooks/fast-path/shell-after.ts`) | shell tool finished | one bounded completion ping per call; injects finished background-ticket results once per session |
|
|
689
786
|
|
|
690
787
|
#### Skills
|
|
691
788
|
|
|
@@ -703,6 +800,18 @@ render MCP Apps. It shows contention and admission, in-flight and queued
|
|
|
703
800
|
work, metrics windows, optional kache data, lanes, and history, with a live
|
|
704
801
|
output drawer per ticket.
|
|
705
802
|
|
|
803
|
+
Each metrics window also reports queue wait against run time for leaders,
|
|
804
|
+
with the wait split by cause: *lane-bound* (a same-lane leader was still
|
|
805
|
+
compiling — before its `Finished` line or exit), *permit-bound* (every
|
|
806
|
+
admission permit was held and no same-lane compile was to blame), and *other*
|
|
807
|
+
(admission holds, `--after` prerequisites, scheduling latency). The
|
|
808
|
+
classification is a pure sweep over ledger rows (`src/daemon/wait-split.ts`)
|
|
809
|
+
run once per status refresh against the daemon's current permit count, which
|
|
810
|
+
the tile states; runs admitted under an earlier cap are classified against
|
|
811
|
+
today's. With `buildFinishedAtMs` on the row, the by-command split adds
|
|
812
|
+
compile vs execution time for test/run/bench leaders and the window reports
|
|
813
|
+
the lane time the execution-phase hand-back released.
|
|
814
|
+
|
|
706
815
|
### Testing
|
|
707
816
|
|
|
708
817
|
```sh
|
|
@@ -714,7 +823,7 @@ artifact build, at the harness proof levels:
|
|
|
714
823
|
|
|
715
824
|
| Level | Suite | What it proves |
|
|
716
825
|
| --- | --- | --- |
|
|
717
|
-
| route-unit | `routes`, `layout`, `streaming`, `events` | documents, shell metadata, Suspense fallbacks and settled values, lineage attribution, event decisions |
|
|
826
|
+
| route-unit | `routes`, `layout`, `streaming`, `events` | documents, shell metadata, Suspense fallbacks and settled values, lineage attribution, event decisions (the shell hooks are unit-tested in `tests/hook-fast-path.test.ts` and against their compiled entries in `tests/hooks-simulate.test.ts`) |
|
|
718
827
|
| cli-dispatch | `cli-dispatch`, `layout` | argv through the routed CLI shell; Markdown wrapped by the shell, `--json` bare |
|
|
719
828
|
| script-dispatch | `script-dispatch` | the `hauler` entry through its `main` envelope as its own process |
|
|
720
829
|
| mcp-in-memory | `mcp-surface`, `layout` | tool names, `outputSchema`, the dashboard resource link, `_meta.hauler`, and a live fixture broker over the in-memory transport |
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"hooks":[{"event":"
|
|
1
|
+
{"hooks":[{"event":"afterTool","id":"hook:after-tool:shell-after:264de610","name":"after-tool-shell-after-264de610","path":"claude/hooks/after-tool-shell-after-264de610.mjs","target":"claude","timeout":10},{"event":"beforeTool","id":"hook:before-tool:shell-before:9212219e","name":"before-tool-shell-before-9212219e","path":"claude/hooks/before-tool-shell-before-9212219e.mjs","target":"claude","timeout":10},{"event":"sessionStart","id":"hook:event-route:session-start","name":"event-route-session-start","path":"claude/hooks/event-route-session-start.mjs","target":"claude","timeout":5},{"event":"stop","id":"hook:event-route:stop","name":"event-route-stop","path":"claude/hooks/event-route-stop.mjs","target":"claude","timeout":900},{"event":"afterTool","id":"hook:after-tool:shell-after:264de610","name":"after-tool-shell-after-264de610","path":"codex/hooks/after-tool-shell-after-264de610.mjs","target":"codex","timeout":10},{"event":"beforeTool","id":"hook:before-tool:shell-before:9212219e","name":"before-tool-shell-before-9212219e","path":"codex/hooks/before-tool-shell-before-9212219e.mjs","target":"codex","timeout":10},{"event":"sessionStart","id":"hook:event-route:session-start","name":"event-route-session-start","path":"codex/hooks/event-route-session-start.mjs","target":"codex","timeout":5},{"event":"stop","id":"hook:event-route:stop","name":"event-route-stop","path":"codex/hooks/event-route-stop.mjs","target":"codex","timeout":900},{"event":"afterTool","id":"hook:after-tool:shell-after:264de610","name":"after-tool-shell-after-264de610","path":"cursor/hooks/after-tool-shell-after-264de610.mjs","target":"cursor","timeout":10},{"event":"beforeTool","id":"hook:before-tool:shell-before:9212219e","name":"before-tool-shell-before-9212219e","path":"cursor/hooks/before-tool-shell-before-9212219e.mjs","target":"cursor","timeout":10},{"event":"sessionStart","id":"hook:event-route:session-start","name":"event-route-session-start","path":"cursor/hooks/event-route-session-start.mjs","target":"cursor","timeout":5},{"event":"stop","id":"hook:event-route:stop","name":"event-route-stop","path":"cursor/hooks/event-route-stop.mjs","target":"cursor","timeout":900}]}
|