Skip to content

E8: decouple turn-concurrency measurement from the sandbox tier (spec amendment + driver arms) #254

Description

@pdettori

Summary

E8 currently measures turn concurrency coupled to the sandbox tier, and its parameterisation makes
the harness knee unreachable. Two arms are wanted instead:

  • a capacity arm that measures the supervisor + worker tier's turn concurrency and throughput with
    the sandbox tier deliberately non-binding and non-competing;
  • the existing realism arm, unchanged, which keeps the calibrated duty and answers "does it still
    hold up when tools cost something".

The sandbox tier's own capacity is P4's subject
(docs/specs/2026-09-09-p4-microvm-sandbox-design.md §7.2 E10, §7.3 E11), so E8 should stop competing
with it. This is a scope change against P6's spec as written, not just a driver tweak — §5.2
deliberately keeps the sandbox tier as a watched variable. The spec must be amended, not quietly
ignored, or the next run passes preconditions that no longer mean anything.

Blocked on #250 landing (→ #253). Do not open the PR for this before then.

Why the current methodology cannot answer the decoupled question

Three independent defects, from the calibrated run recorded in
docs/serverless-harness/2026-09-14-p6-vm-density-findings.md, which lives outside this repo — the per-rung tables and the derivations below are reproduced in the first comment, so this issue is self-contained:
knee_floor=32, saturated=no, bound=not-observed, duty 0.0735, 960/960 at c=32, p50 1530–1539 ms,
p95 1546 → 2457 ms, worker loop lag ~11 ms flat.

1. The tail we measured belongs to the sandbox tier. Three containers at ~78% busy at c=32
(32 × 0.0735 = 2.35 sandbox-equivalents of 3). The signature confirms it: p50 flat within 9 ms and loop
lag flat while p95 rises 1.59× is queueing at a shared downstream with few servers, not worker
saturation.

2. The sandbox work consumes the CPU under measurement. ~2.35 of 8 cores run git inside the tier
we want to exclude. Adding containers fixes queueing but not this.

3. The stub's latency, not the machine, sets the ladder's reach. ~1.42 s of the 1.537 s turn is the
stub's programmed wait (SH_STUB_TTFT_MS=300, SH_STUB_TOKEN_DELAY_MS=12, SH_STUB_OUTPUT_TOKENS=64).
An in-flight turn is mostly a sleeping promise costing single-digit ms of CPU, so 32 in-flight turns
load the worker tier at a few percent and the ladder tops out at the admission cap W×S=32 — a config
choice. Remove the sandboxes entirely and the result would still be bound=not-observed, because the
cap binds first.
This is the defect that matters most and it is independent of the sandbox tier.

Corollary to fix in the reporting: "88% of ideal throughput" is computed against 32 ÷ 1.537 s, a
denominator that is mostly sleep. It measures how well the harness hides a fixed wait — a
concurrency-plumbing check, not a capacity ceiling. It must not be reported as one.

Part 1 — Spec amendment

docs/specs/2026-09-08-p6-vm-process-manager-design.md

1a. §5.2 (line 535) — split E8 into two arms

State the arms explicitly, each with its own purpose, preconditions and reporting rules:

Arm Sandbox tier Stub profile Answers
capacity non-binding and non-competing (trivial exec) fast how many concurrent turns the supervisor + workers push, and where the worker tier knees
realism calibrated duty (e6-ocp) 300/12/64 whether the density figure survives a real tool cost

Record for each arm that its number is not the other's: the capacity arm is an upper bound on the
harness tier and not a deployable density; the realism arm is the deployable one and is sandbox-shaped
by construction.

§5.2 today reasons: "whether the sandbox tier holds the duty of its pinned basis (§2.3) under this load
is an open question — which is also why each run records which basis it used. Without these two metrics
a sandbox-bound run would be reported as a harness density limit."
Keep that reasoning verbatim for the
realism arm. For the capacity arm, invert its conclusion: the sandbox tier is held out of the way on
purpose, and the guard becomes a utilisation ceiling rather than a pool floor.

Add to the §5.2 metric table: per-turn worker CPU (worker CPU-seconds ÷ turns served), attributing a
knee to "the worker tier is actually full" as distinct from "the stub is slow". This is the metric whose
absence makes the current record ambiguous.

1b. §5.4 (line 600) — duty calibration is per-arm

§5.4 currently requires the stub calibrated to a duty basis. Amend so the requirement binds the
realism arm, and the capacity arm is required to record its (near-zero) duty explicitly so the
arm can never be mistaken for a density claim. Keep "measure duty, never model it" for both — that
discipline is not what is being relaxed; see the wrong-model failure documented in
deploy/vm/tests/workload-duty.test.sh.

1c. §2.3 (line 97) — unchanged, but cite it from both arms

The three-row basis table stays as is. Note only that the capacity arm takes no row, and that
"no basis" is a distinct recorded value from "basis unrecorded" — the same distinction
check_sandbox_floor already makes between an unobserved pool and an empty one.

1d. Note the P4 seam

Cross-reference P4 §7.2/§7.3 so the division of labour is legible from both sides. P4 §7.3 already
guards this boundary from its side, listing "lease saturation one tier up — a harness-side refusal
misread as a VM-tier limit"
as a per-rung metric. E8's capacity arm is the mirror of that.

Part 2 — Driver changes

2a. deploy/vm/prepare-workload.sh — a trivial-exec mode

Keep exactly one exec per turn so the relay hop, lease acquire/release, transport framing and exec
plumbing all still run per turn — that work is the harness's and must stay on the path. Only the
command's cost goes away.

turn_cmd() (line 173) builds the git chain from a repeat count. Add a mode that emits a trivial
command (e.g. true) and skips calibration entirely — there is nothing to converge. It must still print
an SH_STUB_TOOL_INPUT in the same shape (line 395) and still assert an exec was actually recorded, via
the existing counting path (turn_cmd_counting, line 245): a capacity arm whose tool call silently
never reaches a sandbox is the /turn-ran-tools-locally defect again, and the counting probe is what
catches it.

Record the resulting duty (~0.005) rather than suppressing it.

2b. deploy/vm/e8-density.sh — invert the precondition for the capacity arm

check_sandbox_floor() (line 142) asserts sandbox_pool_size >= ceil(W × S × duty) via
duty_basis_sandbox_floor (lib-vm.sh:402experiments/src/basis.ts sandboxFloor). For the
capacity arm that is the wrong direction. Add a ceiling check:

  • keep the "pool never observed" refusal exactly as it is — it catches a turn that bypassed pool
    selection, and that hazard is identical in both arms (see the two documented failures in the comment
    at lines 120–134);
  • replace the floor comparison with a utilisation ceiling: refuse the run if measured sandbox
    utilisation exceeds ~25%, computed from the recorded sandbox_cpu delta (lines 205, 284) against
    wall time × container count, so it is measured rather than predicted from a duty the arm does not
    have.

Both arms must fail loudly rather than proceed; a capacity arm running at 78% sandbox utilisation
produces the number this issue exists to stop producing.

2c. Two stub profiles

SH_STUB_TTFT_MS / SH_STUB_TOKEN_DELAY_MS / SH_STUB_OUTPUT_TOKENS are already env knobs, so this is
a parameter, not new machinery. Capacity arm: near-zero latency (suggest TTFT 10 ms, token delay 1 ms,
tokens unchanged) so turn duration approximates the harness's own cost and concurrency can climb to
where the event loop queues. Realism arm: 300/12/64 unchanged.

assert_stub_pinned (lib-vm.sh:279) already refuses a supervisor pointed at a different stub than the
one the driver is reading. Extend the same idea to the profile: the run's recorded profile must match
what /profile returns, so an arm cannot be labelled "capacity" while measuring the slow stub.

2d. Get the admission cap out of the way

Not the W/S sweep that was previously (correctly) vetoed. That veto held because the cap bound and
nothing else could saturate, so another sweep bought another honest not-observed. Here the cap stops
being the variable: set SH_WORKERS × SH_TURNS_PER_WORKER well past the expected knee (several hundred
admitted) and sweep offered concurrency upward until the knee appears. KAGENTI_SANDBOX_CAP
(deploy/vm/env/supervisor.env.example) must rise with it or leases become the new artificial cap — the
exact confusion 2b guards against.

2e. Per-turn worker CPU

Add the worker-side counterpart to sandbox_cpu: worker process CPU-seconds per rung, reported as
CPU-seconds ÷ turns served. Recorded per rung beside loop_lag_p99 / rss_bytes (lines 275–276) and
folded into the attribution block (line ~397) so a knee can be attributed to real worker CPU.

Part 3 — Two open questions to resolve while implementing

Loop lag may lack the resolution to be the capacity arm's headline diagnostic. It read ~11 ms flat
at every rung including c=1, where one turn is in flight and the tier is nearly idle. That looks like a
sampling or timer artefact rather than real delay. Since §5.2 makes it the metric that attributes a knee
to worker CPU, verify it against a deliberately CPU-starved worker before trusting it. Unverified
suspicion, not a finding — the sampling implementation was not inspected.

file_op_p95_ms is still NaN (e8-density.sh:277, acknowledged at line 459: no counter exists
anywhere). It matters less for the capacity arm, but it is the metric that would let the relay hop be
subtracted from a turn that keeps an exec on the path. Decide whether to build it now or record the gap
again.

Free check to do first, before any code moves

Read sandbox_cpu and rss_bytes out of the existing calibrated run record and confirm defect 2
quantitatively — that ~2.35 of 8 cores went to the sandbox tier. The driver already records both per
rung, so this needs no new run and no hardware. If the number disagrees with the arithmetic, this
issue's premise needs revisiting before the amendment is written.

Acceptance

  • P6 §5.2 / §5.4 amended: two arms, per-arm preconditions, per-arm reporting rules, P4 cross-reference
  • prepare-workload.sh trivial-exec mode, one exec per turn, exec presence still asserted, duty recorded
  • check_sandbox_floor gains a measured utilisation ceiling for the capacity arm; the unobserved-pool refusal is untouched
  • Stub profile recorded and pinned per arm; capacity profile fast
  • Admission cap and lease cap raised past the expected knee; offered concurrency swept
  • Per-turn worker CPU recorded per rung and used in attribution
  • Reporting no longer presents "% of ideal throughput" as a capacity figure when the ideal is stub-sleep-bound
  • The free check above run and its result recorded
  • Guard tests under deploy/vm/tests/ extended: an arm mislabelled against its stub profile, and a capacity arm over the utilisation ceiling, must both be refused

Base the PR on feat/p6-experiments after #250#253 lands.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions