Resolved toward the branch: PM provisions uv/python (main's install.ps1 uv-shim salvage + its test and workflow steps dropped), the shim re-exec stays retired, package.json carries no electron-builder block (afterExtract identity stamp wired into electron-builder.config.cjs instead; after-pack.mjs keeps signing only), Desktop workspace-deps helpers stay retired. Main's scratch-dir bootstrap (export_scratch_tmp_env) is taken and re-run after profile resolution.
Post-mortem harness: forensics + live A/B for the 1,393-agent run fixes
The scripts that produced every number in tracking issue #103563 and the "Independent review (round 2)" sections of its 13 PRs. Two halves:
forensics/reads a copy of a Hermesstate.db(plus rotatedagent.log*and git) and recomputes the observed figures for any run: where the money went, per-call cache behaviour, nested-delegate timeouts, batch-join delivery delay, tool friction,/goalloop behaviour, and the post-open rework inventory. It needs no model calls and no network.live_ab/andreview_probes/exercise the real code paths of a checkout (realAIAgent, dispatch, judge, scanner, SDK) against local fake providers to show what each fix does.run.pyruns them against one or two checkouts and prints PASS/FAIL side by side.review_probes/are the probes the independent/reviewwrote; each reproduced a defect in the first version of a PR and the fixed head must pass it.
Everything is labeled OBSERVED (from usage rows / logs / git) or MODELED (a reconstruction or replay). Do not add the modeled figures to the observed ones; see §5 of #103563 for why they overlap.
Requirements
- A Hermes checkout with its venv (
.venv/bin/python). NeMo Relay is not required for the forensics; it is what the run itself used for the wire captures inlive_ab/cache_prefix_wire.py. - For forensics: a copy of
~/.hermes/state.db(never point at the live file;sqlite3 state.db ".backup copy.db"orcpwhile Hermes is idle) and, optionally, the rotated~/.hermes/logs/agent.log*. - For
--liveprobes: real credentials inHERMES_HOME(they spend cents per run).
Forensics: recompute the observed numbers for YOUR run
cd <hermes-checkout>
P=.venv/bin/python
# 1. cost buckets, depth/duration shares, context-size reconstruction, excess-cache-write proxy, cap replay
$P -m evals.postmortem.forensics.tokens --db state_copy.db [--root <session_id>] [--cap 200000 --floor 65000]
# 2. per-call cache behaviour from the logs (coverage fraction is printed first; quote nothing without it)
$P -m evals.postmortem.forensics.logcalls --db state_copy.db --logs "$HOME/.hermes/logs/agent.log*"
# 3. delegation: timeouts, orphaned children, polling, batch-join delay, truncated summaries
$P -m evals.postmortem.forensics.delegation --db state_copy.db
# 4. tool friction: hardline false blocks, foreground refusals, whole-file rewrites, output volume
$P -m evals.postmortem.forensics.tools --db state_copy.db
# 5. /goal loop: nudges, parked barrier, notification counts
$P -m evals.postmortem.forensics.goal_loop --db state_copy.db
# 6. rework inventory for a large PR (git only)
$P -m evals.postmortem.forensics.rework --repo . --base <merge-base> --open <sha-at-open> --head <merged-sha>
Each writes postmortem_out/<lane>.json and prints a summary. --root defaults to the top-level
session with the most descendants; compression-rollover children are excluded from the population so
cost buckets are disjoint. Pricing is fitted from estimated_cost_usd, so dollars match what that
Hermes recorded (an estimator, not an invoice).
Reference output (the #102117 run, state_copy.db of 2026-09-04)
| lane | prints |
|---|---|
| tokens | 1394 sessions, $19,302.59; buckets cache_write $11,159.76 · cache_read $3,587.17 · output $4,555.48; depth-2 65%; >60 min 61%; MODELED excess cache-write proxy ~$9.1k; sawtooth cap 200K: prompt volume ×0.76 (reconstructed sizes) |
| logcalls | coverage 22,489/93,284 (24.1%); median prompt 229,648, p90 408,794, >200K 59%; hit ratio 93.9%; strict plateau 22.5% of uncached input, non-advancing 74.8%; sawtooth cap 200K on real sizes ×0.498 |
| delegation | 266 orchestrators; 332 delegate_task timeouts in 234 sessions (all nested); 93 results carried; 242.6 h sleep after first timeout; those sessions' lifetime $4,034.69 (includes real work); summaries truncated 123/220; batch-join withheld child-hours root 233 / depth-1 300 / depth-2 52 |
| tools | 96,855 tool calls; hardline blocks 579 (566 "malformed" class); foreground timeout refusals 475 (303 asked 900 s); & 185, nohup 24; write_file 8,188 calls / 92.8M chars, 661 rewrites of a file read this session >20k; patch 4,623 |
| goal_loop | 5 nudges (2 within 180 s of a "waiting" turn); 34 batch notices; 48 bg-process notices; final barrier waiting_on_session=proc_… parked 201 min |
| rework | surface at open: 1,703 names / 341 modules, 951 methods / 156, 126 test defs / 52 files; 125 post-open commits (simplify 55, review-fix 39, fix 19, …) |
The two sawtooth figures differ on purpose: tokens replays reconstructed per-call sizes for all
93k calls (×0.76); logcalls replays real per-call sizes for the 24% of calls in the logs (×0.50,
the peak-concurrency window). The tracking issue quotes the second and says so.
Live A/B: what each fix does
# offline probes (fake providers, temp HERMES_HOME), main vs a branch or integration checkout:
.venv/bin/python -m evals.postmortem.run --repo /path/to/main --compare /path/to/branch
# add the probes that make real provider calls (cents):
.venv/bin/python -m evals.postmortem.run --repo /path/to/branch --live
# one PR only:
.venv/bin/python -m evals.postmortem.run --repo . --only 103492
| probe | PR | expects on the fixed head |
|---|---|---|
live_ab/hardline_scanner_matrix.py |
#103492 | 11-case matrix ALL OK (546-block class allowed; newline/;/&&/` |
review_probes/scanner_bypass_probe.py |
#103492 | public guard approved: False, 0 callbacks, harmless Bash witness not executed |
live_ab/subagent_context_cap.py |
#103513 | child trigger 200,000 on a 1M model; parent untouched |
review_probes/context_cap_probe.py |
#103513 | cap holds through repeated compression + persistence; config validation |
live_ab/nested_delegate_deadline.py |
#103486 | 40 s deadline + 75 s leaf: result delivered (main: lost) |
review_probes/deadline_probe.py |
#103486 | same through actual dispatch |
live_ab/auth_stampede.py <repo> 40 |
#103526 | 401s=0 (main: 40) |
review_probes/credential_identity_probe.py <repo> pr |
#103526 | explicit account-A key stays A (v1: became B) |
live_ab/batch_failure_notice.py |
#103549 | TASK_FAILURE_NOTICE at t+0.3 s, BATCH_FINAL after |
review_probes/notice_delivery_probe.py |
#103549 | gateway receives notice, notice, final; busy-parent final claim succeeds |
review_probes/cache_estimator_probe.py |
#103476 | preflight ≈ wire estimate; should_compress agrees |
live_ab/cache_prefix_wire.py <repo> B (live) |
#103476 | 0 mutated prefixes across 6 calls |
live_ab/goal_judge_wait.py <repo> 3 (live) |
#103534 | wait ×3 on the run's "waiting on workers" response (main: continue ×3) |
review_probes/goal_scope_probe.py |
#103496/#103534 | judge sees own processes; delegation WAIT lifts on batch return |
review_probes/goal_repaste_probe.py |
#103553 | near-whole re-paste → pointer; ship the API ≠ ship the UI |
review_probes/rewrite_hint_probe.py |
#103551 | remote backend: no host-derived hint; FIFO: returns; 460 KB repeated-line file: skipped, not 22 s |
review_probes/finalizer_schedule_probe.py |
#103507 | pytest plugin: -p evals.postmortem.review_probes.finalizer_schedule_probe --finalizer-probe=consumer-first on tests/e2e/test_relay_native_openai_stream.py → 2 passed |
Cache concurrency probe (live_ab/cache_concurrency_probe.py)
The instrument behind #104284 / #104421 and NousResearch/api#227. N concurrent real AIAgent
sessions run the same growing tool loop against a route; every call's cache_read / cache_creation,
response id, upstream provider and prefix shas are logged, and consecutive pairs are classified
ideal / stuck (previous write not visible: routing) / collapse (whole context re-written).
# ~$50 per 20x6 arm on Fable 5.1 at the 5m tier
python -m evals.postmortem.live_ab.cache_concurrency_probe --repo . --provider nous --workers 20 --calls 6 --out /tmp/p.jsonl
python -m evals.postmortem.live_ab.cache_concurrency_probe --repo . --provider nous --wire native --workers 20 --calls 6 --out /tmp/p.jsonl
python -m evals.postmortem.live_ab.cache_concurrency_probe --repo . --provider openrouter --pin anthropic --workers 20 --calls 6 --out /tmp/p.jsonl
Reference results (2026-09-05/06): Nous native wire 13.9% stuck (14–20% over 4 runs, unchanged by the
portal's provider pin or a 2 s settle); Nous chat wire 0/320; OpenRouter pinned 0/161; OpenRouter
unpinned 9.8% collapse. Use this to clear a new upstream before flipping
agent/nous_wire.py::GMI_NATIVE_WIRE_CLEARED. --ttl 1h reproduces the 2× write price
(#104168). The .summary.json carries the bad pairs with both response ids for the provider's logs.
What is NOT here, and why
- The trajectories themselves. The run's
state.dbcontains 51,956 absolute home paths, 5,341 e-mail addresses, private IPs, chat/user ids, and real-shaped API keys and JWTs in tool output. It is not publishable, and this harness is written so it does not need to be: run it on your own DB. - Hand classification. The root-cause classes of the 72 rework commits (dropped symbol vs semantic
drift vs compat fallout) were labeled by hand in the original audit;
forensics/rework.pyreproduces the mechanical inventory that labeling started from and stops there. - A single aggregate saving. By design. Each lane prints its own number with its own caveat.
Evidence bundle
The original lane reports, the independent review, and the JSON this harness recomputes on the run's DB are in a secret gist linked from #103563 (no trajectories; see "What is NOT here").
Provenance
Forensic lanes: five parallel Hermes subagents (2026-09-04), rewritten here to take --db/--root
instead of hard-coded paths. live_ab/: the primary agent's per-PR A/Bs. review_probes/: the
independent /review subagent's probes (2026-09-05), adapted to take paths from the command line;
their findings and the fixes are in each PR's "Independent review (round 2)" section.