Skip to content

feat(e11): a persistent-connection Go Exec client (#294) - #296

Merged
pdettori merged 24 commits into
rossoctl:mainfrom
pdettori:feat/294-e11-go-exec-client
Sep 20, 2026
Merged

pdettori merged 24 commits into
rossoctl:mainfrom
pdettori:feat/294-e11-go-exec-client

Conversation

@pdettori

@pdettori pdettori commented Sep 19, 2026 •

Copy link
Copy Markdown
Member

Closes #294.

Stacked on #293 (fix/291-e11-driver-artifact), not on main — it builds on that PR's
driver-control arm and its repaired sampler. Review or merge #293 first.

Why

#291 deferred its item 3 — replacing grpcurl with a persistent-connection client — on the
grounds that committing before measuring would be a guess. #293's control arm supplied the
measurement, on bare metal, with no relay, no Redis, no worker and no VMM behind it:

c tput/s p95 coresBusy /72
8 241.6 44 ms 14.52
64 231.6 753 ms 64.13

The driver alone peaks at c=8 and then declines, burning 64 of 72 cores at c=64 with its
own p95 of 753 ms. That is the same knee position and curve shape §E11 published for both real
arms. The published density knee is reproduced by an arm with no backend at all.

What this adds

remote-worker/cmd/exec-driver — one process per rung, one grpc.ClientConn brought to READY
before any slot starts, and c goroutines in place of c bash subshells. Four small files:
cause classification (a deliberate mirror of grpc_exec_record's ordered grep chain), the rung
plan and its refusals, the issuer, and the binary.

It writes the same per-slot <ms> <status> <cause> lines the bash path wrote, so warmup
trimming, percentile, execErrorsByCause, the sampler bracket and the record writer are
untouched — not by claim, but because the file format is the interface.

Opt-in. SH_E11_EXEC_CLIENT defaults to grpcurl. It stays the reference until both clients
have run on one host, which is #294's own acceptance criterion, and every rung record carries
execClient so two ladders can never be silently mixed.

One substantive edit to the reference path, stated plainly. The bash path's driving mechanism
is unchanged — one grpcurl process per Exec, the subshell loop moved into an else branch and
nothing more. But grpc_exec_record's latency arithmetic was changed: it recorded whole
milliseconds, and the Go client's per-Exec latency is sub-millisecond, so at the Go client's measured
rates every recorded value truncated to 0 and p95Ms collapsed with it. Both paths now record
three decimal places, computed from integer microseconds with no floating point. They had to move
together: one path fractional and the other integer would make the comparison a rounding artifact.

The consequence worth knowing: grpcurl ladders published before this change carry integer p95Ms,
and new ones carry fractional.
The spec and the runbook say so too.

Converge stays on grpcurl and drivingModel stays closed-loop-per-slot — both stated
non-goals, and both asserted by tests rather than left to trust.

What this does NOT include

The authoritative comparison. That belongs on the host that produced the table above, and is
not run here. docs/notes/e11-go-exec-client-comparison-runbook.md has the reference numbers, the
two invocations, and the checks to make before quoting anything — including that the Go arm needs
its sampler cadence lowered rather than its Exec count raised, because at matched Exec counts its
rungs are ~50x shorter and the driver refuses a rung that produced zero sampler ticks.

§E11's conclusions therefore stay marked under repair. A sweep run today would measure the
driver's knee again.

Indicative local numbers only

Both clients against the null-responder on a 10-core laptop, identical Exec counts (2000/slot):

c grpcurl Exec/s go Exec/s gap
1 38.4 2133.6 55.6x
4 183.9 11289.9 61.4x
8 286.5 13975.7 48.8x

Not the 72-core comparison the acceptance criteria name: no /proc sampling, so no
hostCpuFraction, coresBusy or p95 at all; mix reduced to one command; n=1; and
e11-density.sh itself never ran. It shows both clients work and the per-Exec gap is large. It
says nothing about where any knee sits.

Corroboration worth noting for whoever books the rig: grpcurl's rates here land close to the
published metal driver-control figures (56.8 / 171.6 / 241.6) despite 10 cores versus 72 — which
is what you expect if the bound is per-call process spawn rather than the host.

One defect found and not fixed here

packages/sandbox-relay/src/relay.ts's routeExec yields an in-stream ExecEvent.error and then
returns a gRPC OK status, so grpcurl exits 0 and the bash path counts a failed Exec toward
throughput, into the p95 distribution, and never into execErrorsByCause. Filed as #295.

Deliberately left split: the Go client classifies it correctly, the reference path is unchanged,
and on driver-control — the only arm #294 measures — the divergence is exactly zero, because
the null-responder never emits an ExecError. Fixing relay.ts here would move the reference path
mid-comparison. Every rung record discloses which behaviour produced it, on both paths, and #295
records that the fix must land before any cross-client comparison on the container or microvm arms.

Verification

gofmt, go vet, 16 Go tests, shellcheck -x -S warning on both shell files, all four
deploy/microvm suites, tsc --noEmit and vitest for experiments — all clean.

The seam is proven end to end by a test that drives the real write_rung_plan into the real
exec-driver against the real null-responder and reads the times files back: e11-density.sh
needs /proc, cgroups and Linux, so that test is the only off-rig proof the boundary works, and it
needs none of them.

Several checks were mutation-tested rather than trusted — shortening runSlot's loop, swapping the
two disclosure arms, removing the Go branch's pids+= — each producing exactly the attributable
failures it should. Six vacuous guards were found and fixed along the way, most of them in the
plan's own text.

🤖 Generated with Claude Code

@pdettori

Copy link
Copy Markdown
Member Author

Review: Approved, one fix requested before quoting metal numbers

I stacked-verified this against #293 (83fd348 is a real ancestor), ran go vet/gofmt -l/go test ./... on remote-worker (clean), ran the full deploy/microvm/tests/e11-density.test.sh including the real seam test that builds and runs the actual exec-driver/null-responder binaries (449 checks, 0 failures), ran shellcheck -x -S warning and shellcheck -x -o check-unassigned-uppercase -S warning on both touched files (0 findings each), and diffed EXPERIMENTS.md / predictions.json / e10-lifecycle.sh / packages/sandbox-relay against the stated mandate (additions-only, byte-identical, untouched, untouched). All 21 commits are DCO-signed with Assisted-By: Claude, no Co-Authored-By.

Please fix before the metal run: sub-millisecond truncation collapses p95/knee on the Go arm

remote-worker/cmd/exec-driver/drive.go (oneExec's fail closure and the success path) computes:

ms: time.Since(t0).Milliseconds()

an int64 truncation to whole milliseconds. This PR's own indicative numbers put the Go client at ~0.47ms/Exec at c=1 and ~0.07ms/Exec at c=8 against the null-responder — so on the driver-control arm almost every recorded ms will be 0. That's not just a cosmetic loss of precision, it degrades two downstream metrics to the point of being uninformative:

  • percentile() in deploy/microvm/e11-density.sh does sort -n over these integers with no floor — a non-empty, near-all-zero file legitimately yields p95Ms=0 (it's not refused).
  • detectKnee in experiments/src/sharing.ts sets bound = baseline.p95Ms * degradeX. With a 0 baseline, bound is 0, so healthy = cur.p95Ms <= 0 && ... — the detector becomes a step function on the exact tick where per-Exec latency first rounds up to 1ms, not a real health signal.
  • coldAcquireRate (threshold 50ms by default) will be pinned at 0 for the whole Go-arm ladder — not wrong, just uninformative.

coresBusy/hostCpuFraction (the sampler's independent host-CPU signal) is not affected by this, since it doesn't come from Exec latency. So this doesn't need to block running the driver-control comparison — the coresBusy delta at high c is still meaningful without this fix. But please land a fix before anyone quotes a p95 or knee number from the Go arm, since right now those numbers would be measuring the millisecond clock's resolution, not the driver.

Suggested fix, and it looks cheap: record fractional milliseconds (e.g. %.3f) on both paths so the two remain comparable. require_numeric's regex (\-?[0-9]+(\.[0-9]+)?) already accepts decimals, and percentile's sort -n handles them fine — I didn't find anything else that assumes integer ms.

Worth a runbook note, not a blocker: sampler cadence vs. PSS-walk frequency

docs/notes/e11-go-exec-client-comparison-runbook.md is right that matching Exec count and lowering SAMPLE_INTERVAL_MS/SAMPLE_SLICE_MS is the way to keep both arms' Exec counts comparable, and it correctly notes SAMPLE_SLICE_MS (not just SAMPLE_INTERVAL_MS) is the real cadence floor. One thing it doesn't call out explicitly: SAMPLE_LOW_EVERY throttles the pgrep+smaps_rollup PSS walk relative to tick rate, and tick 1 always samples PSS regardless — so pushing tick rate up to the ~70Hz needed for 10 ticks in a ~140ms Go-arm window also pushes PSS/pgrep sampling to an absolute rate well past the ~1Hz-ish envelope the #291 fix was sized to avoid perturbing. Since none of p95/throughput/coresBusy actually require matched Exec counts to be individually valid, sizing to a common wall-clock window instead (letting Exec count differ) would sidestep that risk entirely. Not asking for a code change — just suggest adding a line to the runbook naming this tradeoff before someone cranks cadence past the point where PSS sampling itself becomes the next artifact.

Everything else I went looking for came back clean

  • cause.go's classification order mirrors grpc_exec_record's grep chain exactly (confirmed by diff and by TestCauseForPrefersTheEarlierRuleWhenTwoMatch's overlapping-input case).
  • req_id disjointness is enforced (plan.go:validateReqIDRanges) and tested both at the boundary and server-side.
  • The sandbox-relay: an in-stream ExecError returns a gRPC OK status, so a failed Exec counts as a success #295 relay defect is genuinely zero-difference on driver-control: null-responder only ever sends ExecEvent_End, never .Error, and packages/sandbox-relay is untouched by this PR or fix(e11): repair the density instrument's measurement artifacts (#291) #293.
  • SH_E11_EXEC_CLIENT defaults to grpcurl and is validated before any build step.
  • No live extract_fn/shellcheck traps in the touched files; the seam test that builds and runs the real Go binaries against the real null-responder passes.
  • Constraints held: set -uo pipefail as the driver's first set line, drivingModel unchanged, converge_slot never touches EXEC_CLIENT, EXPERIMENTS.md additions-only, e10-lifecycle.sh/its test/predictions.json byte-identical to #293.

Bottom line: land the fractional-ms fix (small, both paths) before quoting any p95/knee number off the metal run. Everything else here is solid enough to run the driver-control comparison as-is for the coresBusy signal.

Assisted-By: Claude Code

pdettori added a commit to pdettori/moca that referenced this pull request Sep 19, 2026
Review on rossoctl#296 found the Go exec-driver's per-Exec latency is
sub-millisecond, and int64 whole-ms truncation was rounding it to 0
almost every time -- collapsing p95/knee/coldAcquireRate signal on the
Go arm. Fix both timing paths together so their output stays byte-
identical in format:

- bash grpc_exec_record (deploy/microvm/e11-density.sh) now computes
  microseconds and formats ms to three decimals using only parameter
  and arithmetic expansion -- no command substitution, preserving the
  rossoctl#291 item 2 fork-free guarantee (verified against the reviewer's
  test vectors and re-checked by the detector test).
- Go execOutcome (remote-worker/cmd/exec-driver/drive.go) is renamed
  ms -> us (microseconds) and formatted via integer %d.%03d, no
  floating point.

Downstream consumers (require_numeric, percentile, cold_count) already
tolerate decimals and are untouched.

Also: tighten the seam test and fixture that assumed integer ms, tighten
drive_test.go's format assertion to the three-decimal shape, and add
TestDriveRecordsSubMillisecondLatencyRatherThanZero to prove a sub-ms
Exec is no longer recorded as exactly 0.

Docs: disclose the format change in the E11 comparison runbook and the
Go exec client design spec, and add a runbook note on the
SAMPLE_LOW_EVERY/cadence tradeoff when chasing 10 sampler ticks on a
fast client.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
@pdettori

Copy link
Copy Markdown
Member Author

Both points addressed in 57152f3. Thank you — the truncation finding is a real defect, and the
evidence for it was sitting in this PR's own indicative table. I had the 0.469 ms/Exec figure in
front of me and did not follow it through to p95Ms.

Fractional milliseconds, both paths

Recorded latency is now %d.%03d milliseconds, computed from integer microseconds with no
floating point
, on both clients — so the two stay exactly comparable:

  • drive.go — execOutcome's field is renamed ms → us so the unit is unambiguous, filled from
    time.Since(t0).Microseconds(), and formatted in line().
  • e11-density.sh — grpc_exec_record does the same.

One constraint shaped the bash side: that function is asserted to contain no command substitution
at all
— the #291 item-2 no-fork-in-the-timed-window guarantee — so $(printf '%.3f' …) was not
available. The construction is arithmetic and parameter expansion only:

frac=$((1000 + us % 1000))
ms="$((us / 1000)).${frac#1}"

1000 + us % 1000 lands in 1000..1999, so ${frac#1} is the remainder zero-padded to three
digits. Both no-fork assertions still pass unchanged:

ok:   ...and its body runs no command substitution -- not even one avoided fork
ok:   ...while still using $((...)) arithmetic expansion for the ms computation

You were right that nothing downstream needed changing — I re-verified each: require_numeric's
\-?[0-9]+(\.[0-9]+)?, percentile's sort -n, and cold_count's awk '$1>=t'.

Before/after, driving the real binaries against the real null-responder (8 Execs, c=1). Every
one of these lines read 0 ok - before the fix:

1.836 ok -
1.689 ok -
0.431 ok -
0.324 ok -
0.271 ok -
0.209 ok -
0.205 ok -
0.204 ok -

percentile()'s own arithmetic over them gives p95Ms = 1.689 where it previously gave 0. (The
first two calls are connection warmup settling to ~0.2 ms — the low-c startup inclusion the
runbook already discloses.)

Tests: the seam test now pins the three-decimal shape rather than tolerating it; the
"no command substitution" fixture was updated to the new arithmetic so it stays representative; and
TestDriveRecordsSubMillisecondLatencyRatherThanZero is new — it would have failed before this
commit and is what stops the regression coming back. 17 Go tests pass, Total failures: 0 across
449 checks, shellcheck clean on both files.

Records written before this change carry integer ms; that is noted in the spec and the runbook.

Cadence versus the PSS walk

Added to the runbook rather than changed in code, as you suggested. It now names SAMPLE_LOW_EVERY
and the always-sample-on-tick-1 behaviour, states the tradeoff plainly — cranking cadence to reach
10 ticks inside a ~140 ms window can make the pgrep + smaps_rollup walk the next artifact,
well past the ~1 Hz envelope #291 was sized for — and gives your alternative: size both arms to a
common wall-clock window and let the Exec counts differ, since p95, throughput and coresBusy
are each individually valid without matched counts. Matched counts were a cleanliness choice of
mine, not a correctness requirement, and the runbook now says so and leaves the operator to choose.

🤖 Generated with Claude Code

pdettori added a commit to pdettori/moca that referenced this pull request Sep 19, 2026
Clears PR rossoctl#293's merge conflict (review finding 1, the only must-fix). Merged rather
than rebased on purpose: PR rossoctl#296 is STACKED on this branch, so force-pushing a rebase
would orphan 83fd348 from rossoctl#296's history and force a second rebase there. A merge keeps
the 18 reviewed commits byte-for-byte, needs no force-push, and follows this repo's own
precedent (227ebda, "Merge rossoctl/main into E13").

Three conflict hunks, all resolved by keeping both sides:

1. cleanup_on_exit - main added E11_IN_CLEANUP=1 so kill_relay_by_port can tell a
   trap-driven teardown (log and continue) from a between-arm one (die); this branch
   added stop_host_sampler. Both are needed and neither displaces the other. The flag is
   set first, before any stop_*_stack as main's comment requires, and the sampler is
   stopped before the rm -rf of E11_TMPDIR as this branch's comment requires - the
   sampler polls for a stop file under that root, so after the rm it would spin forever
   writing to a deleted path. Verified: kill_relay_by_port reads ${E11_IN_CLEANUP:-} and
   returns cleanly when it was never set.

2. preflight - this branch made the tool and hardware checks conditional on the
   configured arm set (rossoctl#291 item 5), and main added require_tool ss for
   kill_relay_by_port. `ss` is folded INSIDE the container-or-microvm branch rather than
   above it, because kill_relay_by_port is called from stop_container_stack and
   stop_microvm_stack only: the driver-control arm runs no relay and tears its responder
   down by pid. Requiring it unconditionally would re-block exactly the
   SH_E11_ARMS=driver-control run that item 5 exists to unblock.

3. tests - both sides appended an independent section at the same point (the
   driver-control arm here, kill-by-port teardown on main). Both kept.

Also correcting the record for review finding 4, which a merge cannot fix by rewriting
history: commit b652c51's trailer says "Refs rossoctl#291 item 3". Issue rossoctl#291's numbered list has
item 3 as the deferred persistent-connection client (now tracked as rossoctl#294); the converge
barrier that commit implements is item 6. Design spec section 3 does cover it, so the
mislabel is spec-section vs issue-item. b652c51 stays as written - it is public and
rossoctl#296 is stacked on it.

Gates on the merged tree: e11-density.test.sh 0 failures; shellcheck clean at -S warning
and -o check-unassigned-uppercase on both changed shell files; go build, gofmt -l (empty),
go vet, go test ./... all clean; make test-deploy 0. EXPERIMENTS.md's §E11 caveat survives
alongside main's new E12/E13 sections, blockquote integrity intact.

Refs rossoctl#291.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
@pdettori

Copy link
Copy Markdown
Member Author

Automated review notes

Adds remote-worker/cmd/exec-driver, a persistent-connection Go alternative to the bash+grpcurl Exec client, gated behind opt-in SH_E11_EXEC_CLIENT=go (default remains grpcurl). The Go code is well-tested, defensively validates its plan input, and a genuine bash/Go integration test exercises the real boundary. No secrets, no .claude/.vscode changes, no CI workflow changes.

⚠️ Blocking: PR is currently unmergeable

GitHub reports mergeable_state: "dirty". The recorded base (f20b0225) predates #293's merge into main (now e68ab80):

  • The diff shown on the Files tab (~11,200 lines) is inflated by ~6,500 lines of already-merged fix(e11): repair the density instrument's measurement artifacts (#291) #293 content, reappearing only because the base is stale. The true incremental diff (main...PR-head, verified locally with git merge-base/git diff) is 15 files, +4666/-42.
  • There's a real, if trivial, append/append conflict in deploy/microvm/tests/e11-density.test.sh (both main via fix(e11): repair the density instrument's measurement artifacts (#291) #293 and this branch appended a new test section at the same spot, around line 2536 — the SH_E11_EXEC_CLIENT section vs. main's "between-arm relay teardown kills by PORT" section). git merge-tree confirms deploy/microvm/e11-density.sh and deploy/microvm/EXPERIMENTS.md (also touched on both sides) auto-merge cleanly — only the test file needs manual resolution.

Suggested fix: git fetch origin main && git rebase origin/main, keeping both appended test blocks. This resolves the conflict and shrinks the PR's displayed diff to its true size.

Suggestions (non-blocking)

  • remote-worker/cmd/exec-driver/cause.go:17 — causeFor is a hand-maintained mirror of grpc_exec_record's bash grep chain, kept in sync only by a DRIFT GUARD test, not a single source of truth. Worth a follow-up (e.g. a shared data file) so a future edit to one side can't silently desync the other — which would defeat the PR's own goal of comparable ladders.
  • remote-worker/cmd/exec-driver/drive.go:199 — On a rung with multiple failing slots, only the first (by index) error is returned; the rest are silently dropped even though errs holds all of them. Not a correctness bug (the rung is refused either way), but loses diagnostics. Consider errors.Join over all non-nil entries.

Nit

  • remote-worker/cmd/exec-driver/drive.go:136,141 — times.Close()/errFile.Close() errors are discarded. Low risk since Flush() is checked just before, but a close-time write failure would go unnoticed.

Author: pdettori (MEMBER — maintainer)
Commits: 40, all signed off (DCO passes)
CI status: passing (DCO is the only check configured)

Note: this is posted as a plain comment, not a formal GitHub review — GitHub blocks self-review (REQUEST_CHANGES/APPROVE) on your own PR via the API.

🤖 Generated with Claude Code

The driver-control arm PR rossoctl#293 added supplied the measurement issue rossoctl#291
deferred item 3 on: on metal the driver alone peaks at c=8, burns 64 of 72
cores at c=64, and carries 753ms of the published microVM arm's 1686ms p95.
The remaining per-Exec execve is grpcurl, which re-parses the proto and opens
a fresh TCP connection and HTTP/2 session per call.

The design: a new remote-worker/cmd/exec-driver holding one grpc.ClientConn
for a whole rung and c goroutines in place of c bash subshells, writing the
existing `<ms> <status> <cause>` per-slot lines so aggregation, percentiles
and the record writer are untouched. Opt-in behind SH_E11_EXEC_CLIENT so the
bash path stays the reference until the two are compared on one host.

Records one finding beyond the issue: the relay yields ExecEvent.error and
then returns a gRPC OK status, so grpcurl exits 0 and an ExecError-failed Exec
counts toward throughput and never reaches execErrorsByCause. It cannot affect
this issue's comparison (the null-responder only sends End), so the Go path
classifies it correctly and discloses the divergence.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
Nine tasks, each TDD and independently reviewable: the cause mirror, the
rung plan and its refusals, the issuer and binary, client selection and
build, write_rung_plan, the phase-2 branch, the execClient record field,
end-to-end seam closure, and the docs plus runbook.

The grpcurl subshell loop is reproduced verbatim in the branch task and a
step asserts it came out byte-identical: it is the reference the Go client
is compared against, so tidying it would invalidate the comparison.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…l#294)

extract_fn ends a function at the first bare } at column 0 and only skips
an embedded python block spelled with a double quote. write_rung_plan uses
a single quote, so its python dict's column-0 closing brace truncated the
extraction to 7 lines of 40 -- verified empirically against the real
helper. Task 5's extractability check and all of Task 8 would have failed.

Indenting the closing delimiters costs nothing (Python accepts it) and
avoids touching a helper a dozen other sections of that suite depend on.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…#294)

Same six causes, same order. A client-side deadline lands in unknown on
both paths, as grpcurl's own timeout message does, so execErrorsByCause
stays comparable between the two clients.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…l#294)

Slot identity travels in the plan rather than being recomputed in Go: the
three derivations live in one place each in e11-density.sh so two slots
cannot drift into sharing a workspace or a req_id space.

validateReqIDRanges refuses touching req_id spaces before any Exec is
timed. The relay demultiplexes by req_id and a collision once left a run
wedged for 33 minutes, which is a failure mode no measurement survives.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…soctl#294)

The connection is brought to READY before any slot starts, so the first
Exec of a rung pays no more than the last: grpcurl's per-call TCP and
HTTP/2 setup was inside every measured latency.

Streams are drained to io.EOF rather than stopped at End, because grpcurl
drains them. An in-stream ExecEvent.error is recorded as a FAILED Exec:
the relay yields it and then returns a gRPC OK status, so grpcurl exits 0
and the bash path counts a failed Exec toward throughput. The
null-responder never sends one, so being right here costs the
driver-control comparison nothing.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
dialReady's only non-READY exit was ctx being done, and drive was called
with context.Background() from main.go, so a persistently unreachable
target (down relay, wrong port, firewalled host) cycled
CONNECTING -> TRANSIENT_FAILURE -> CONNECTING forever. grpcurl's -max-time
covered dial as well as call, so the bash path this replaces fails fast
where this hung -- and run_density_rung waits on this child, so the hang
stalled the whole ladder, not one rung.

Fix is scoped to the dial: drive derives a dial-only context bounded by
the plan's existing CallDeadlineS and passes that to dialReady. The Exec
phase after the dial stays on the caller's unbounded context, since a
rung-level timeout would cap legitimate slow rungs at high c, which is
the regime being measured.

TestDriveFailsWhenTheTargetIsUnreachable now calls drive with
context.Background(), as main.go does, sets CallDeadlineS=1 so the
bounded dial fails in about a second, and asserts on wall-clock elapsed
time so the test cannot pass by accident if the internal bound is later
removed.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…ault (rossoctl#294)

Opt-in, because rossoctl#294's acceptance is a comparison: the bash path has to
stay the reference until both have run against the null-responder on one
host with nothing else changed. An unrecognised value is refused rather
than defaulted, since the value is stamped into every rung as execClient
and a mislabelled ladder gets compared against the wrong table.

build_exec_driver is a no-op unless the Go client was selected, so a
grpcurl run cannot fail over a binary it never invokes.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…ossoctl#294)

Round 1 review found two checks whose names claimed a property they could
not detect:

- The refusal-message check used 'grpcurl-go' as the invalid value, and
  die() echoes the bad value back verbatim, so the input itself satisfied
  the 'names both accepted values' grep regardless of what the message
  said. Switched to 'xyzzy', which shares no substring with either
  accepted value, and split the check into one assertion per value plus a
  non-vacuousness proof that a message quoting only the bad value fails
  the 'grpcurl' check.

- The 'main builds the Exec driver after preflight' check only counted
  occurrences of build_exec_driver in main's body; it never compared
  positions, so the two calls in the wrong order still passed. Replaced
  with a positional comparison (preflight's line number is less than
  build_exec_driver's) plus a non-vacuousness proof with the operands
  reversed.

Also fixed a stray 'above' in a shellcheck disable comment that should
have said 'below', matching the rest of the file's convention.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
Everything travels through argv and json.dumps does every escape: bash
passes each array element as one argv entry, so a workspace key or path
containing a quote, backslash or tab cannot be mis-split, and JSON
escaping stays in the one place this driver already puts it.

slot_workspace_key is called once per slot and feeds both the pre-escaped
form the grpcurl path interpolates, and the raw form the plan carries.
Deriving it twice is how the two clients would drift into sending
different keys.

Also updates two pre-existing test assertions that this change legitimately
invalidates: the "req_id base from ONE helper" count (2 -> 3, a new but
non-drifting call site in the plan-building loop) and the phase-split
converge/exec probe (EXEC_CLIENT is read by run_density_rung and was
unset under set -u in that isolated harness, so grpcurl is now passed
explicitly there, matching the real script's default).

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…ctl#294)

Both branches push onto the same pids array and are reaped by the same
wait loop, so the guarantee that a rung whose slots were not all
measuring the same thing is never recorded holds identically for both.

The refusal branches because the failure differs: one Go process drives
all c slots, so a non-zero exit means the rung has no trustworthy
timings at all, and reporting it as 1 of c slots would understate it.

The fork guard now also flags python3 between the wall stamps, so a
future edit cannot move write_rung_plan into the timed window.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…ossoctl#294)

The existing "both paths are waited on through the same pids array" check
only counted the shared wait-loop line, which stays at 1 whether or not
the Go branch ever pushes its pid onto the array. That check alone could
not catch a dropped pids+= in the Go branch: the process would still be
forked but never waited on, so wall_t1 would be stamped without blocking
on it, corrupting the rung's wall time and throughput, and exec_failures
would never see a Go-side failure -- the same defect class this issue
exists to remove.

Add a count scoped to the timed window (2, not 3: phase 1's converge loop
has its own pids+= outside the window), a non-vacuousness pair proving
the detector can report either 2 or 1, and a positional check that the
Go invocation is immediately followed by its own enqueue, since two
enqueues in the window do not by themselves prove they sit in different
branches.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
Without it a go-driven ladder and a grpcurl-driven one are
indistinguishable JSON, and comparing them is the entire reason the second
client exists. Same class of defect c953c98 fixed for stale rungs, one
level up.

proxyLimitations gains execErrorStatus on BOTH paths: a limitation that
appears only on the path that does not have it is not a disclosure. On
grpcurl it records that an ExecError-failed Exec counts as a success; on
go, that it does not, and that the two therefore disagree on the real arms
while agreeing exactly on driver-control.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…l#294)

The if/else that chose driver_control_note/exec_error_note by EXEC_CLIENT
lived inline in run_density_rung, where no test could reach it -- the
record-writer harness hardcodes both strings directly into its fixture,
bypassing the branch entirely. A swap between the two would have made
every go-driven record assert something false about its own
trustworthiness (that an ExecError is recorded as ok), with a passing
suite.

Extract the branch into driver_control_note_for and exec_error_note_for,
pure functions of the client argument, matching the exec_client_label
idiom this file already documents. Drive both under both clients, and
assert path-specific content plus that the two paths actually differ, not
just that both are non-empty.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
The real write_rung_plan, the real exec-driver binary, the real
null-responder, and the times files read back. e11-density.sh itself needs
/proc, cgroups and Linux, so this is the only place the seam can be
verified on a development machine -- and it needs none of them.

Skips visibly when go is absent. A silent skip is how a seam test stops
testing anything.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…ssoctl#294)

Fix round 1 review findings on the seam-closure test:

- The build-failure path of the seam section deleted its own evidence:
  a compiler error only ever appeared as one line, "FAIL: both binaries
  build", because $seam_dir (and build.log with it) is removed
  unconditionally at the end of the section. Dump build.log on failure,
  mirroring the pattern already used for the driver-failure path.

- The background null-responder was only killed on the section's normal
  exit path. A SIGINT or CI timeout between start and the kill/wait pair
  left a gRPC listener bound to 127.0.0.1:18447 indefinitely. Trap
  INT/TERM/EXIT to kill it, clearing the trap after the normal teardown
  so it cannot fire twice or mask a later signal. No trap previously
  existed at the top level of this test file to clobber.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…book (rossoctl#294)

The §E11 banner already said the driver-control subtraction was what would
decide whether the re-run can separate backend from driver. It has decided:
an arm with no relay, no worker and no VMM reproduces the published c=8
knee and burns 64 of 72 cores at c=64. Appended rather than rewritten,
because the banner's own rule is that the numbers stay as the record of
what the broken instrument produced.

Section E11 stays under repair until both clients have run against the
null-responder on the host that produced the table. The runbook carries
the reference numbers, the two invocations, and the checks to make before
quoting anything -- including deriving SH_E11_COLD_LATENCY_MS from E10 and
confirming hostCpuSamples is not 1.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
Replace committed placeholder text with the real measured comparison
table (2000 Execs/slot on both arms; Go 49x-61x faster per Exec than
grpcurl depending on concurrency). Rewrite the calibration narrative to
disclose that the calibrated 18000-per-slot count was abandoned for the
paired run because it makes grpcurl the binding cost (18003 / 38.4 Exec/s
would need ~470s at c=1), while keeping the four calibration data points
as evidence. Correct an overclaimed causal reading of the c=4 dip: grpcurl
shows the same non-monotonic shape despite opening a fresh connection per
call, so a fixed Go-side startup cost cannot be the explanation; ground
the low-c caveat in first principles and disclose n=1. Note that the
paired run's 2000/slot was scaled up from the brief's illustrative 200.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
Applies all seven review items from the whole-branch review as one
consolidated wave (no second wave planned):

- Rewrite the runbook's "Before quoting any number" section: sampler
  cadence (not Exec count) is the knob to fix a low hostCpuSamples
  count, raising ITERS_PER_SLOT for the Go arm alone is wrong, a
  cadence that still can't reach 10 ticks gets two labelled Go
  ladders, "the sampler can't see the Go client's window" is treated
  as a finding, and the ZERO-host-samples refusal is named by its
  quoted text.
- Note in the spec, EXPERIMENTS.md, main.go, and e11-density.sh's
  header that the 753ms/1686ms figures come from different drivers at
  different ITERS_PER_SLOT (200 vs 20), a mismatch that runs in this
  branch's favor.
- Reword e11-density.sh's go-case control note to mark the
  "tighter"-comparative claim UNMEASURED, grounded in the actual
  mechanism difference (JSON-to-/dev/null vs stream.Recv()-decode);
  the grpcurl case is untouched.
- Add an EXPERIMENTS.md bullet for the relay's
  routeExec-yields-in-stream-error-then-OK defect (not yet tracked by
  an issue) and a runbook sentence sequencing the relay fix before any
  cross-client comparison on container/microvm.
- Split the seam test's trap so INT/TERM re-exit 130 instead of being
  swallowed by an EXIT-only handler.
- Move the Go client's plan file from $E11_TMPDIR to $RESULTS so it
  survives a refused rung, and add a rung-header line to
  $RESULTS/e11-exec-driver.log before wall_t0 is stamped.
- Four minor fixes: a missing blank blockquote continuation line in
  EXPERIMENTS.md; reword "replaced it with" to "added ... as an
  opt-in alternative" (grpcurl stays default); fix drive.go's
  empty-message ExecEvent.error misclassification with a dedicated
  sawErr bool plus a new drive_test.go case; extend the disclosure
  safety check to reject $, backtick, and backslash alongside the
  apostrophe.

Gates: shellcheck -x -S warning on both scripts (exit 0), full bash
test suite (Total failures: 0), gofmt/go vet/go test -v on
./cmd/exec-driver/ (16/16 pass), Prettier --write then --check on all
touched Markdown (stable, "(unchanged)"), and manual confirmation
every EXPERIMENTS.md blockquote line still starts with `>` after
Prettier.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…ssoctl#294)

The final review asked that the relay defect this branch discloses but does
not fix be tracked rather than described. It is now issue rossoctl#295, referenced
from the spec's finding, EXPERIMENTS.md's under-repair list, the runbook's
sequencing note, and both execErrorStatus disclosure strings -- so a reader
holding a rung record can reach the defect that explains it.

The two disclosure strings stay free of apostrophes, dollars, backticks and
backslashes: they are interpolated through a double-quoted bash string into
python source, and the suite now asserts all four characters are absent.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…nsourced figure (rossoctl#294)

Three accuracy gaps the fix wave's re-review found in the prose an operator
reads while running the comparison.

SAMPLE_SLICE_MS is the actual tick floor and the runbook never named it:
host_sampler_loop computes slices_per_tick as
SAMPLE_INTERVAL_MS/SAMPLE_SLICE_MS floored at 1, so once SAMPLE_INTERVAL_MS
reaches SAMPLE_SLICE_MS (default 100ms) lowering it further does nothing.
Ten ticks inside a sub-200ms Go window needs both well below 100ms, so the
guidance to lower cadence was not actionable as written.

The zero-sample refusal still advised raising ITERS_PER_SLOT while the
runbook said that is wrong for a paired comparison -- the tool contradicted
the doc it points at, at the moment an operator hits it. It now names the
cadence knobs and the SAMPLE_SLICE_MS floor, and says raising ITERS_PER_SLOT
is fine for a single ladder but not when comparing two clients.

driver_control_note_for's go arm carried "roughly 50x smaller" with no
derivation anywhere in the tree; it resembled the laptop run's 49-61x
throughput ratio, a different metric on different hardware. Struck. The
UNMEASURED hedge did not cover a specific number, and this branch's own
standard is that an inaccurate disclosure is worse than none.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
Review on rossoctl#296 found the Go exec-driver's per-Exec latency is
sub-millisecond, and int64 whole-ms truncation was rounding it to 0
almost every time -- collapsing p95/knee/coldAcquireRate signal on the
Go arm. Fix both timing paths together so their output stays byte-
identical in format:

- bash grpc_exec_record (deploy/microvm/e11-density.sh) now computes
  microseconds and formats ms to three decimals using only parameter
  and arithmetic expansion -- no command substitution, preserving the
  rossoctl#291 item 2 fork-free guarantee (verified against the reviewer's
  test vectors and re-checked by the detector test).
- Go execOutcome (remote-worker/cmd/exec-driver/drive.go) is renamed
  ms -> us (microseconds) and formatted via integer %d.%03d, no
  floating point.

Downstream consumers (require_numeric, percentile, cold_count) already
tolerate decimals and are untouched.

Also: tighten the seam test and fixture that assumed integer ms, tighten
drive_test.go's format assertion to the three-decimal shape, and add
TestDriveRecordsSubMillisecondLatencyRatherThanZero to prove a sub-ms
Exec is no longer recorded as exactly 0.

Docs: disclose the format change in the E11 comparison runbook and the
Go exec client design spec, and add a runbook note on the
SAMPLE_LOW_EVERY/cadence tradeoff when chasing 10 sampler ticks on a
fast client.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…lures (rossoctl#294)

Both from PR rossoctl#296's review notes.

drive() returned only the lowest-indexed slot's error and silently dropped
the rest, so an operator debugging a dead rung had to re-run it once per
slot to learn that three failed for three different reasons. errors.Join
over all non-nil entries; it returns nil on an empty slice, so the success
path is unchanged.

runSlot discarded both Close errors. Flush already pushes bufio's buffer
into the file, but the write-back can still fail at Close, and a silently
truncated times file would then be aggregated as though it were complete.
Both closes now report through a named return, and the err file's -- which
carries diagnostics only -- is reported only when nothing worse happened,
so it cannot mask a real rung error.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
@pdettori
pdettori force-pushed the feat/294-e11-go-exec-client branch from 57152f3 to af1c366 Compare September 19, 2026 14:25
@pdettori

Copy link
Copy Markdown
Member Author

Rebased onto main (now e68ab80, with #293 merged) and both suggestions applied. Head is
af1c366.

Blocking item resolved

mergeable is now true, and the displayed diff has collapsed to its true size:

before after
base f20b0225 (pre-#293) e68ab80
changed files 19 15
additions/deletions ~11,200 shown +4682 / −42

That matches the +4666/−42 you computed locally, plus the 16 lines of the two fixes below.

On the conflict: your diagnosis was exactly right — append/append in
deploy/microvm/tests/e11-density.test.sh, main's "between-arm relay teardown kills by PORT"
section against this branch's SH_E11_EXEC_CLIENT section, both appended at the same spot. Resolved
by keeping both, main's first. e11-density.sh and EXPERIMENTS.md auto-merged cleanly, as
git merge-tree predicted.

I checked one thing before rebasing that would have changed the approach: whether 83fd348 was
still an ancestor of main. #293 landed as a merge commit rather than a squash, so it is — meaning
the rebase replayed only this branch's 22 commits rather than trying to re-apply 18 already-merged
ones under new SHAs. A backup ref (57152f3) is kept locally in case anything needs recovering.

Post-rebase verification on the combined tree, since main's newer driver code (kill_relay_by_port,
the changed stop_*_stack functions) now sits alongside this branch's: gofmt and go vet clean,
17 Go tests pass, shellcheck -x -S warning clean on both touched shell files, and all seven
deploy/microvm suites green — including the e12/e13 suites that arrived with #293 and which
this branch had never been tested against before.

Both suggestions applied

drive.go — every failing slot, not just the first. errors.Join over all non-nil entries.
It returns nil on an empty slice, so the success path is unchanged. You were right that the rung is
refused either way; the cost was making someone re-run a rung once per slot to discover that three
failed for three different reasons.

drive.go — close-time write failures no longer discarded. Both Close() calls now report
through a named return. Flush() pushes bufio's buffer into the file, but the write-back can still
fail at Close, and a silently truncated times file would then be aggregated as though it were
complete. The err file's close — diagnostics only — is reported only when nothing worse happened,
so it cannot mask a real rung error.

On the cause.go drift suggestion

Agreed, and deliberately not done here. causeFor is a hand-maintained mirror of
grpc_exec_record's grep chain held together only by the drift-guard test, and you are right that a
future one-sided edit could silently desync it — which would defeat this PR's own goal. A shared
source of truth is the correct fix and it is a bigger change than this PR should carry: it touches
the reference path's classification, which everything here has been careful not to disturb while the
two-client comparison is outstanding. Happy to file it as a follow-up alongside #295 if you want it
tracked.

One correction to the notes

CI here is more than DCO — check, lint, deploy-scripts, proto, microvm-gates, codeql and
Trivy all run. They were mid-flight when I posted this; Trivy/trivy-scan had already reported
success. mergeable_state reads blocked because branch protection on main requires one
approving review, which GitHub will not accept from the PR author via the API — the limitation you
already noted, not a remaining conflict.

🤖 Generated with Claude Code

@pdettori

Copy link
Copy Markdown
Member Author

CI on af1c366: 13 checks, 13 success, 0 otherwise — check, lint, shellcheck, hadolint, proto, deploy-scripts, microvm-gates, CodeQL, codeql (javascript-typescript), dependency-review, Trivy, trivy-scan, DCO.

microvm-gates and deploy-scripts are the two worth naming: they exercise this branch against the post-rebase tree, where main's newer kill_relay_by_port and stop_*_stack changes now sit alongside these. That combination had never been tested before the rebase.

mergeable: true, and mergeable_state stays blocked only on the one required approving review that GitHub will not accept from the PR author. Nothing outstanding from my side.

@huang195 huang195 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mamma mia, Paolo — this is a beautiful piece of instrument work. Finding that the published c=8 knee belonged to the driver, and then proving it with an arm that has no backend at all, is the kind of result most people would have shipped as a sweep re-run instead. And the discipline around it is what makes it trustworthy: grpcurl stays the default, the times-file format is the interface rather than a claim, execClient is on every rung so two ladders can't silently mix, and the relay ExecError divergence is disclosed on both paths instead of only on the one that has it. Perfetto.

The seam test earns its keep — real write_rung_plan → real exec-driver → real null-responder, and it needs neither /proc nor Linux. Verified independently while reviewing:

  • remote-worker/go.mod is go 1.26.0, so drive's per-iteration i/s capture is sound (that comment is load-bearing — on a pre-1.22 directive it would be a race).
  • cause.go's switch mirrors grpc_exec_record's grep chain key-for-key and order-for-order.
  • validateReqIDRanges' off-by-one is right: next == prev+calls really does collide the next slot's converge with the previous slot's last Exec, and TestLoadPlanReqIDBoundaryIsExact pins it in both directions.
  • The new fractional-ms field survives everything downstream — percentile sorts with sort -n, cold_count and throughput are awk, require_numeric already accepted %.4f. Nothing breaks.

Four non-blocking items inline. Two more for the description rather than the code:

  • The base-branch note is stale. #293 merged at 13:17Z today, so GitHub now shows 23 commits / 15 files — all yours. The "read this before reviewing the file list" block can go.
  • "the bash path is byte-identical in substance … additions only" no longer holds. 77aaec8 replaced grpc_exec_record's ms=$(( … / 1000 )) with the three-decimal form. That is the right call — both paths must record the same precision or the comparison is against a rounding artifact — but it is a substantive edit to the reference path, and it means published grpcurl ladders carry integer p95Ms while new ones carry fractional. Worth stating outright rather than leaving the old claim standing.

And one minor, outside the diff so I can't pin it inline: e11-density.sh:540's require_tool grpcurl reason still says "every arm drives its Exec RPCs through grpcurl". On the Go path grpcurl is genuinely still required — converge stays on it — but the justification now misleads whoever debugs a SH_E11_EXEC_CLIENT=go run.

Bellissimo work, and §E11's conclusions staying marked under repair is the honest call. Approving.


Assisted-By: Claude Code

Comment thread remote-worker/cmd/exec-driver/drive.go Outdated
}
}()

w := bufio.NewWriter(times)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion — the comment above says writes are "flushed once at the end", but bufio.NewWriter takes the default 4096-byte buffer, so that is true only while a slot's whole output fits in it.

At the documented ITERS_PER_SLOT=200 a slot writes ~203 lines × ~11 B ≈ 2.2 KB — fine. At the 2000/slot this PR's own local-numbers table used it is ~22 KB, so bufio flushes ~5 times inside the timed window: the write syscall the comment says was removed is back, just at 1/370th the rate.

Sizing it from the plan makes the comment true at any count:

w := bufio.NewWriterSize(times, 32*calls+4096)

(calls is already computed just below — you'd want to hoist it one line.) Small thing, but the whole binary exists to keep driver cost out of that window.

// resolution rather than a real signal. This must fail against the pre-fix code (ms field "0")
// and pass against the fix (a non-zero fractional value).
func TestDriveRecordsSubMillisecondLatencyRatherThanZero(t *testing.T) {
subMsShape := regexp.MustCompile(`^0\.[0-9]{3}$`)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion — ^0\.[0-9]{3}$ pins the whole-millisecond part to 0, not just the fractional precision. One Exec against the in-process fake crossing 1 ms — a loaded CI runner, a GC pause, a scheduler hiccup — fails this test with "does not have the sub-millisecond shape 0.XXX", which is the opposite of the bug it exists to catch.

The truncation defect is fully caught by the two properties that don't depend on wall-clock luck:

if !msFieldShape.MatchString(msField) { ... }   // three decimal digits present
if msField == "0.000" { ... }                    // and not truncated to zero

msFieldShape is already in this file. Against the pre-fix code the field was the bare integer 0, so it fails the first check — the mutation-test property you were after holds without the flake.

Comment thread remote-worker/cmd/exec-driver/drive.go Outdated
if _, err := fmt.Fprintln(w, o.line()); err != nil {
return fmt.Errorf("writing to times file %s: %w", s.TimesFile, err)
}
if o.errMsg != "" {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit — gating the err-file write on o.errMsg != "" drops exactly the case the code just went out of its way to classify correctly.

An in-stream ExecEvent.error with an empty message (the one TestDriveClassifiesAnEmptyMessageInStreamErrorAsFailure covers) records err / unknown in the times file and writes nothing to the err file — which is verbatim the state plan.validate's own errFile refusal warns about: "a failing Exec's message would be lost and execErrorsByCause would say unknown with nothing to look at".

Gating on the status instead makes the two agree:

if o.status == "err" {
    _, _ = fmt.Fprintf(errFile, "req %d: %s\n", s.ReqBase+1+uint64(i), o.errMsg)
}

An empty line under a req id at least tells the operator the relay sent an error with no message, rather than leaving them to infer it from a times file.

if [ "$seam_rc" -eq 0 ]; then
# An ephemeral-ish port well away from the driver's defaults (8444/8445), so a stray relay
# or responder from another run cannot answer this test's Execs.
seam_port=18447

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit — the fixed port is a small flake surface. Two concurrent runs of this suite on one host, or a listener left behind by a SIGKILL (the new INT/TERM trap covers cancellation, but not -9 or an OOM kill), both surface as the null-responder came up: no — with the stale responder happily answering on 18447 and no hint in the output that the port was the problem.

If null-responder accepts --listen 127.0.0.1:0 and logs the bound address, taking the port out of responder.log alongside the existing serving sandbox.v1.SandboxExec wait would remove the class entirely. If it doesn't, no action needed — worth a look since the wait loop is already parsing that file.

…ctl#294)

Five non-blocking items from PR rossoctl#296's second review, all applied:

- drive.go: size the times-file bufio.Writer from the plan
  (32*calls+4096) rather than taking the default 4096B buffer, so the
  "flushed once at the end" comment holds at any ITERS_PER_SLOT, not
  only at the documented 200/slot default. At the 2000/slot this PR's
  own local-numbers table used, the default buffer flushed mid-loop
  and put a write syscall back inside the timed window.
- drive_test.go: TestDriveRecordsSubMillisecondLatencyRatherThanZero
  no longer pins the whole-millisecond part of the ms field to "0",
  which could flake on a loaded CI runner, a GC pause, or a scheduler
  hiccup. It now reuses the package's msFieldShape (three decimal
  digits present) plus an explicit "not truncated to 0.000" check --
  the same two properties, without the wall-clock dependency.
- drive.go: gate the err-file write on status == "err" rather than
  errMsg != "", so an in-stream ExecEvent.error with an empty message
  (TestDriveClassifiesAnEmptyMessageInStreamErrorAsFailure) still gets
  a line in the err file instead of being silently dropped -- the
  exact state plan.validate's errFile refusal warns about. Extended
  that test to assert the err file gets one line per failing Exec.
- e11-density.test.sh: the seam test's null-responder now binds
  127.0.0.1:0 instead of a fixed port (18447), removing both the
  concurrent-run and stale-listener (SIGKILL/OOM) flake classes. The
  bound address is parsed out of the responder's own log line and
  used as the plan's target, with a check that parsing did not come
  back empty.
- e11-density.sh: reworded the require_tool grpcurl reason (and the
  block comment above it) so it says grpcurl is required on every
  path because converge always uses it directly, not because the
  timed Exec loop does on the Go path.

Gates: gofmt/go vet/go test clean (17/17 pass) in remote-worker/cmd/
exec-driver; shellcheck -S warning clean on both e11-density.sh and
its test file; full e11-density.test.sh run reports Total failures: 0
with the seam section's checks all ok:. Proved the sub-millisecond
test fix (item 2) still catches the original truncation bug by
temporarily reverting drive.go's line() to the old %d-whole-ms form
(via a file copy, never git stash/checkout): the test failed as
expected, then the file was restored byte-identical to before.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
@pdettori

Copy link
Copy Markdown
Member Author

Grazie — and thank you for going after the parts that undercut themselves. All seven items
addressed in 0054a58.

Two of the four inline catches were this branch's own additions contradicting their own purpose, which
is the most useful kind of review to get:

The buffer wasn't sized for the runs this PR documents. You're right that bufio.NewWriter's
default 4096 makes "flushed once at the end" true at ITERS_PER_SLOT=200 and false at the 2000/slot
my own local-numbers table used — about five flushes inside the timed window, in a binary whose
entire reason for existing is keeping syscalls out of it. Now bufio.NewWriterSize(times, 32*calls+4096)
with calls hoisted, and the comment says it is sized from the plan so the claim holds at any count
rather than only at the documented default.

The sub-millisecond test was a flake pointing the wrong way. ^0\.[0-9]{3}$ pinned the
whole-millisecond part to 0, so one Exec crossing 1 ms on a loaded runner would have failed the very
test meant to catch truncation. Now the two luck-independent properties: msFieldShape (already in
that file, so no second regex) for three decimal digits, and != "0.000" for not-truncated. I proved
it still catches the defect rather than assuming — reverted line() to the old %d whole-ms form
behind a file copy, confirmed the test fails with "0" does not have three decimal digits, restored,
and confirmed git diff showed no revert residue.

The err-file gate dropped exactly the case the code had just fixed. Gating on o.errMsg != ""
meant an empty-message ExecEvent.error recorded err/unknown in the times file and wrote nothing
to the err file — verbatim the state plan.validate's own errFile refusal warns about. Now gated on
o.status == "err", with a test asserting the line appears.

Ephemeral port. Verified before changing anything that null-responder --listen 127.0.0.1:0 binds
and logs on 127.0.0.1:54560, so this wasn't conditional after all. The seam test now binds :0,
parses the bound address out of the same serving sandbox.v1.SandboxExec line the wait already polls,
and refuses an empty parse rather than handing the driver a blank target. The stale-listener and
concurrent-run classes are gone, including the -9/OOM case the trap can't cover.

require_tool grpcurl's reason now says converge always uses grpcurl regardless of
SH_E11_EXEC_CLIENT and that the Go path only replaces the timed Exec loop. A block comment two lines
above carried the same misleading claim, so that was reworded too.

Both description items

The stale base-branch note is removed — #293 merged, and the PR now shows its true 15 files.

And you were right to push on the other one: "byte-identical in substance … additions only" no longer
held, and I'd left it standing. The description now states plainly that the driving mechanism is
unchanged but grpc_exec_record's latency arithmetic was substantively edited, why both paths had
to move together, and the consequence — grpcurl ladders published before this change carry integer
p95Ms, new ones carry fractional.
That belonged in the description, not just the spec.

Gates after all of it: gofmt/go vet clean, 17 Go tests pass, shellcheck -x -S warning clean on
both shell files, Total failures: 0.

One housekeeping note: the fix commit initially picked up a stray Co-Authored-By trailer alongside
Assisted-By, which this repo's CLAUDE.md forbids. Caught before pushing and corrected via a soft
reset — same tree, message only. All 24 commits now carry Assisted-By and Signed-off-by alone.

🤖 Generated with Claude Code

@pdettori

Copy link
Copy Markdown
Member Author

CI proto failure: not from this branch, and re-run

proto failed on 0054a58. I chased it rather than re-running blind, and it is a pre-existing
timing flake in internal/vmpool, not something this branch caused.

The failure:

--- FAIL: TestAnExecWaitingOnTheRunGateCanStillBeCancelled/timeout (2.20s)
    serialize_test.go:208: condition not met within 2s

Why it cannot be this branch's: the diff changes zero Go files outside
remote-worker/cmd/exec-driver/ — no go.mod, no go.work, nothing under internal/. There is no
causal path from a new package under cmd/ to a test in internal/vmpool. proto also passed on the
previous head af1c366, and 0054a58 only touched drive.go, drive_test.go, e11-density.sh and
its test file.

What it actually is. waitFor (replenish_test.go:184) is a fixed 2-second wall-clock
deadline
polling every 1 ms, and it fails with a bare t.Fatal("condition not met within 2s") —
no mention of which condition or what value it observed, which is why the failure reads more alarming
than it is. The failing assertion is one of two such waits on pool state right after a fake-clock
advance.

Measured, not assumed. 20 runs on unmodified rossoctl/main: all pass. 60 runs on this branch
(3 × 20): all pass. I did reproduce it locally once, while the machine was busy — and that single
observation briefly led me to the wrong conclusion, that a local repro ruled out load. It doesn't;
one sample can't distinguish the two. The 60 subsequent passes are what settle it.

I have re-requested the job. If it goes green, nothing here needs changing.

Worth fixing in main, separately. The helper is load-sensitive by construction: a hard 2 s budget
on CI runners of unknown contention, and a message that tells you nothing about what didn't happen.
Two cheap improvements, neither belonging in this PR:

  • make the deadline overridable (or scale it), so a slow runner doesn't read as a logic failure;
  • have waitFor take a description and report the observed value, so condition not met within 2s
    becomes liveCount never reached 1 (last saw 0).

Happy to file that as a follow-up if you want it tracked — say the word and I will, alongside #295.

🤖 Generated with Claude Code

@pdettori

Copy link
Copy Markdown
Member Author

proto re-run: success. All 13 checks green on 0054a58, and mergeable_state is now clean.

As expected from the 80 local runs (20 on main, 60 on this branch), the failure was the waitFor flake and not this diff. The two helper improvements I suggested above still stand as a follow-up for main whenever someone wants it tracked.

Nothing outstanding from my side — the branch is ready to merge. The authoritative 72-core driver-control comparison remains the one thing this PR deliberately does not contain; docs/notes/e11-go-exec-client-comparison-runbook.md is the procedure, and §E11 stays marked under repair until it runs.

@pdettori

Copy link
Copy Markdown
Member Author

Both follow-ups filed:

  • vmpool tests: waitFor has a hard 2s budget and an undiagnosable failure message, so load flakes read as regressions #297 — vmpool tests: waitFor's hard 2 s budget and undiagnosable message. Includes the measurements (20/20 on main, 60/60 on this branch), and two details that make it worth more than a re-run: the helper has 19 call sites across five test files, not one, and it surfaces in the proto job — which runs go test ./..., so the failure looks like a codegen problem and is not.
  • #298 — the bash/Go cause-vocabulary mirror, from @huang195's review. On checking, the gap is slightly wider than the review assumed: cause_test.go references e11-density.sh only in a comment and hardcodes its expectations, and the bash suite has no cause assertion at all — the check labelled DRIFT GUARD covers execTimeoutS, not the causes. So the mirror rests on a comment and reviewer diligence today. The issue proposes a cross-language drift test as the first move, since unlike a shared data file it closes the risk without touching the reference path and can land before the metal comparison rather than after.

Neither belongs in this PR. Nothing outstanding here — all 13 checks green on 0054a58, mergeable_state: clean.

pdettori added a commit to pdettori/moca that referenced this pull request Sep 22, 2026
…not density

The re-run's position reproduces and its instrument repairs hold, but every
microVM ladder in this section swept offered concurrency `c` while the worker
admitted only 4 concurrent Execs (`session.DefaultConcurrency = 4`, which
`e11-density.sh` never overrode). Slots and `c` are confounded throughout, so
no conclusion drawn above `c = 4` can separate a property of the design from a
property of that constant.

The section's own arithmetic exposes it: it derives a ~4.5 VMs/s replenishment
supply and offers it to explain a flat ~62 Exec/s plateau, 14x apart. Four
slots at the measured 61.4 ms per Exec predicts 65.1 Exec/s against a measured
62.99, and predicts every other rung within a few percent. The p95 column
doubling per doubling of `c` is the same fact read as latency — queueing
against a fixed-capacity server.

Purely additive; no measurement is removed. Separated explicitly:

- Survives: the rossoctl#293 sampling and rossoctl#296 Go-client repairs, "it is not the
  driver's knee" (driver share under 0.4%), "no CPU ceiling was reached" (now
  explained — 4.81 of 72 cores because only 4 Execs were admitted), and the
  container arm, which at ~1940 Exec/s is nowhere near a 4-slot cap.
- Does not survive: the `coldAcquireRate` argument (#306 — wrong by 13x at
  c=8, ~250x above), `bound = replenishment` as a genuine crossing, and the
  `D` sweep as evidence. "Rate, not depth" is independently correct, but with
  4 Execs in flight no value of `D` could have changed the outcome, so that
  sweep is what a slot-capped ladder must produce either way.
- Left open: c=8 peaks at 80.62 Exec/s, ~4.9 effective slots, which a 4-slot
  cap cannot produce. Unexplained.

With slots raised to match `c`, the same host reaches 577.55 Exec/s at 64
slots. Resolves rossoctl#291 as investigated with a stronger outcome than "it holds":
the knee reproduces and its cause is known.

Refs rossoctl#291 rossoctl#305 #306

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
pdettori added a commit that referenced this pull request Sep 22, 2026
…the re-run

The 2026-09-17 caveat asked a falsifiable question: does E11's c=8 knee stay
there once driver cost is removed and host CPU is sampled under load. It does.
So the caveat's items are resolved rather than retracted, and this section's
conclusion was right while only its evidence was wrong.

Purely additive: no published number is removed. This section's own policy is
that "the numbers stay ... the record of what the broken instrument produced",
and the re-run is defined by comparison against them.

Adds `### Issue #291 -- the re-run`, on the same bare-metal host and golden
snapshot as the existing E10/E11 tables, with PR #293's repaired sampling and PR
#296's Go Exec client:

- knee = 8 on BOTH real arms, by the same p95-doubling criterion this section
  used for its published knee. The microVM knee is sharper than published:
  throughput now peaks at c=8 and declines, where the published ladder peaked at
  c=16. The container knee was reproduced twice, at ITERS_PER_SLOT 80 and 2000.
- It is not the driver's knee. driver-control's own knee is 4 (Go) or 32
  (grpcurl), never 8, and the Go driver is under 0.4% of the microVM arm's p95 at
  every rung -- against up to 23.7% had it been grpcurl, which quantifies the
  confound the 2026-09-18 note suspected.
- bound = replenishment on the microVM arm is a GENUINE crossing, at the same c
  as the knee. A D sweep (2/4/8) then shows the knee does not move while standbys
  go 52 -> 439 and Sigma PSS 0.324 -> 1.443 GB, so the bound is the serialized
  200ms replenishment RATE, not pool capacity -- standby-depth sizing is not the
  lever.
- "No CPU or memory ceiling was reached" becomes a real finding instead of a
  restatement of the sampling bug: the true under-load means are 0.067 and 0.093
  against crosses('cpu')'s 0.9, from 20-284 ticks per rung.
- Sealed prediction 3 scores `supported` from a threshold derived from the same
  client that produced the ladder (94ms = go-client microVM c=1 p95 82.0 + E10
  rung 3 restore 23.06/2), not from the grpcurl era's 124-133ms.
- Issue #295 stays open and is recorded as such; it confounded nothing here.

Also records two corrections to this section as published, rather than leaving
them to be rediscovered:

- The container arm's `bound: "cpu"` is attributeBound's documented fallback, not
  a crossing. Peak hostCpuFraction is 0.093 against a 0.9 threshold. It means
  "nothing bound", not CPU.
- The driver-control table in the 2026-09-18 note does not reproduce past c=1.
  Same host, client and settings now give 1910.8 Exec/s at p95 39.26ms at c=64
  where that note recorded 231.6 at 753ms on the same ~64 busy cores; c=1 agrees
  closely. Logged as an open discrepancy, explicitly not as a diagnosis.

And the three instrument defects the run found: the dying-VMM PSS refusal that
aborted the ladder (fixed in PR #299), that SH_E11_VMM_PROC_PATTERN cannot
usefully be scoped and must be left unset, and that the in-rung sampler has no
zero-PSS refusal (unfixed, same shape as #291's own item 1).

predictions.json is untouched and its hash pin still passes. Every table figure
was cross-checked against the committed rung records.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

E11: replace grpcurl with a persistent-connection Go client (issue #291 item 3, now mandatory)

2 participants