Skip to content

Measure the whole launch frame, and run an oversized continuation cold - #2013

Merged
ppXD merged 9 commits into
mainfrom
fix/measure-the-whole-launch-frame-before-sending-it
Sep 25, 2026
Merged

ppXD merged 9 commits into
mainfrom
fix/measure-the-whole-launch-frame-before-sending-it

Conversation

@ppXD

@ppXD ppXD commented Sep 23, 2026 •

Copy link
Copy Markdown
Owner

Summary

Closes three gaps in how an agent launch carries large text after the goal moved to stdin (#2008). Each fix is its own commit.

  • The deploy E2E's fake codex still read its goal from the last argv argument. backend/deploy/e2e/fake-codex is the static twin of FakeCodexCli, mounted into the real worker image. After the move to stdin the worker handed it -, it answered DONE: -, and the lane stayed green because run.sh only waited for Success. The fake reads stdin now, and run.sh asserts that the succeeded attempt's folded summary in agent_run.result_jsonb is DONE: Deploy E2E smoke task.
  • The frame check only measured stdin. A continue carries the restored session transcript in ConfigHomeFiles (captured up to 32 MiB). WriteFrameAsync enforced the 16 MiB bound only after transmissionStarted = true, which deliberately skips the netns/cgroup teardown, so the failure surfaced as a generic, retried executor-error. The frame is now encoded first and refused before transmission as the terminal SandboxArgumentTooLongException. The catch tears the isolation down, and the broker releases its slot as rejected.
  • A continuation too large for the frame runs cold instead of being refused. Refusing is right for a goal no attempt can carry, and wrong for a retry whose work can go on in a fresh conversation. The executor builds the spec, and it rebuilds the spec cold only when the spec carries a restored transcript and does not fit (NativeLaunchProtocol.FitsTheFrame). This covers the launch path and each revise round, and a model escalation already applied to the round stands.
    • The fit is measured the way the frame is built. It counts the spec, then the argv and environment the invocation carries a second time (a persona on --append-system-prompt is in both copies), plus a 64 KiB allowance for the rest. It is pinned against the invocation the runner really encodes, at the boundary.
    • So a transcript between 8 and ~16 MiB still resumes warm, as it did on main. A goal and a transcript that only overflow together run cold instead of failing.
    • The hint that tells the agent rides only the dispatched spec. Verification is graded on the goal the persisted envelope holds, because local acceptance hashes that goal against the envelope. Graded against a hinted goal, it read as a different contract and failed a locally graded run as local-context-mismatch before it launched. The same fix covers a bug already on main: the unreadable-checkpoint degrade amends the goal the same way and failed a locally graded retry identically.
    • A cold attempt leaves a durable trace once it has passed every check the executor makes before launch (local acceptance, spend). The persisted envelope drops its continuity claims, so the Room's "resumed" mark stops claiming a resume that did not happen, and the timeline says what happened, with separate wording for a launch and for a revise round (only a revise round keeps its workspace). The persisted goal is untouched.
    • The agent is told its conversation is gone, and that whatever the workspace already holds beyond the base is its own earlier work. A respawn can be checked out at the branch its earlier attempt pushed, with nothing else saying so.

Test plan

  • Drift detector for the deploy fake (Rule 12.5): SubtaskAwareFakeCliDriftTests runs the real backend/deploy/e2e/fake-codex through the real CodexHarness invocation, with the goal on stdin, and asserts the summary run.sh expects. It was red on the old fake.
  • Frame refusal, real runner host: stdin of 8 bytes plus a restored transcript that pushes the frame past 16 MiB gives sandbox_argument_too_long. The message names both carriers' sizes and never their contents, and the broker receipt settles on rejected, not indeterminate. It was red first (InvalidDataException from the late write).
  • Cold degrade, launch path, executor level (AgentRunExecutorTests). Setup: the real executor, runner and frame check, and the real Claude adapter's spec (recorded with its real argv) with only the executable swapped. A transcript of 16 MiB + 1, restored by artifact reference, runs cold and succeeds. The warm spec carried the transcript and --resume (fixture checks); the launched one has neither. Stdin ends with the hint, the persisted envelope has no resume, and the timeline note is present. Red with the degrade disabled.
  • Cold degrade on a locally graded continuation: a repo-less task with an acceptance command, whose transcript overflows, runs cold and passes acceptance. Red when the hint rides the run's own goal and verification does not use the contract.
  • A cold continuation refused for spend before launch leaves no trace: no cold note, and the envelope is unchanged. It was red before the note moved below spend admission. The revise twin — a cold round refused for spend under a capped quick lane — leaves no "continued" note either. Red with the note moved back above the round's admission.
  • Unreadable checkpoint on a locally graded task (the bug already on main): it degrades and is graded on its contract. Red without the contract-goal fix.
  • Cold degrade, revise path, executor level (AgentRunReviseLoopFlowTests). Round 0 writes a session file past the whole frame at the path Claude keeps it, and the real capture reads it. Round 1 is rebuilt cold with the whole contract and passes. Red with the degrade disabled.
  • Frame fit, pinned against the real encoded invocation (NativeLaunchFrameFitTests): with a 130,000-character persona, ASCII and < (six bytes each, in both copies), a spec exactly at the fit boundary encodes to a frame within 16 MiB, and within two allowances of it. Red when the second copy of argv and environment is not counted.
  • Unit: a transcript past half the frame still revises warm, with a fixture check that the spec really carries it. A goal and a transcript each under half but over the whole overflow, and the cold attempt fits. A goal alone is never degraded (it is the frame check's to refuse).
  • Full unit suite: 10955 passed, 0 failed. Integration (every AgentRun* suite, agent node, supervisor retry / cost cap, session resume, real harness, native launch, local acceptance, task launch, map, Room projector, supervisor decider): 807/807. Full solution build clean.
  • Deploy compose E2E (deploy-e2e.yml): green on 639bf2f (the frame and cold-degrade code it exercises is unchanged since), including the new run.sh summary assertion on a real Linux worker.

Notes

  • The stdin preflight keeps its half-frame share (LargeCarrierBudgetBytes). It is the early cut for a goal no attempt can carry, taken before a spool exists.
  • The persisted agent_run.resumed_from_agent_run_id column still records that the attempt was created to resume its predecessor. That is creation provenance and remains true; the Room's mark reads the envelope.

backend/deploy/e2e/fake-codex is the static twin of FakeCodexCli, mounted
into the real worker image for the deploy compose E2E. It still took the
goal from the last argv argument after the goal moved to stdin, so the
worker handed it "-" and it answered "DONE: -". The E2E never noticed,
because it only waited for a Success status — which a CLI handed no prompt
at all can still report.

The fake reads stdin now, and run.sh asserts the folded summary from
agent_run.result_jsonb is "DONE: Deploy E2E smoke task", so the lane checks
that the prompt crossed the real images rather than that a process exited 0.

The fake is a mirror of production behaviour with no drift detector, which
is how it drifted. SubtaskAwareFakeCliDriftTests now runs the real file
through the real CodexHarness with the goal on stdin and asserts the summary
run.sh expects — red on the old fake, green on the new one.
The stdin preflight only measured the standard input, and its comment
claimed the rest of the invocation frame always fits. It does not: a
continue carries the restored session transcript in ConfigHomeFiles,
captured up to 32 MiB, and the frame write enforced the 16 MiB bound only
after transmission was marked started. That path deliberately skips the
netns/cgroup teardown, because a failed write may be a lost ACK, and it
surfaced as "Native invocation exceeds its pipe bound." — a generic
executor error the node retries identically.

Encode the frame first, refuse it while transmission has not started, then
send the bytes already encoded. The refusal is the same terminal
SandboxArgumentTooLongException an oversized argument gets, it names the
two carriers that can grow that large without quoting either, the catch
tears the isolation down, and the broker reads EOF and releases its slot
as rejected rather than indeterminate. The stdin check stays as the early
cut that costs nothing before a spool exists; its comment now says what it
covers.
The whole-frame check refuses an invocation the pipe cannot carry as a
terminal failure. That is right for a goal that will never fit and wrong
for a retry whose restored transcript is the oversized part: the same
work can go on in a fresh conversation, and before the check a node
retry did exactly that one attempt later. Measuring the frame had turned a
recoverable retry into a finished task.

A restored transcript past its share of the frame is now dropped before
launch, on both paths that bring one: a node retry or continue, where the
bytes are first known just after the executor resolves the reference, and
the executor's own revise loop, which stays warm only when the transcript
fits. The degrade mirrors the unreadable-checkpoint one — the session id
and every "resumed from" stamp go with the bytes, and the goal is told
the conversation it was promised is not there. The frame check stays as
the backstop, now for a goal no attempt can carry.

The share is one number, NativeLaunchProtocol.LargeCarrierBudgetBytes,
used by the stdin preflight too. Also: run.sh reads the succeeded
attempt's summary, since a run won on a retry has one row per attempt,
and the frame refusal labels its counts and its config-home files as
what they are.
The cold degrade dropped a restored transcript whenever it alone
encoded past half the launch frame. That share had nothing to do with
the room the frame actually had left: a revise goal is a short delta,
so nearly all 16 MiB is the transcript's, and on main continuations
with an 8-16 MiB transcript resumed warm - after the half-frame rule
they silently lost their conversation. The opposite case was refused
outright: a goal and a transcript each under half, together over the
whole, failed terminally although a cold attempt fits.

The executor now builds the spec, and only when it carries a restored
transcript and does not fit the frame (NativeLaunchProtocol.FitsTheFrame,
with a fixed allowance for what wraps a spec) does it rebuild cold - on
the launch path and for a revise round, which keeps any model
escalation already applied. The stdin preflight keeps its half share.

A cold attempt now leaves a durable trace. The persisted envelope drops
its continuity claims, so the Room no longer marks a cold attempt as
"resumed", and the timeline says it ran cold; the persisted goal is
left alone because the contract hash covers it.

Both call sites were tested by nothing - removing either left every
suite green. Executor-level tests now drive each through the real
runner's frame check with the real Claude adapter's spec, and go red
when the degrade is disabled.
A respawn can be checked out at the branch its earlier attempt pushed,
with no other sentence saying that work is there. "You are starting
this task from the beginning" had the agent redo or overwrite its own
half-finished edits. The hint now says the conversation is gone and
that whatever the workspace already holds beyond the base is its own
earlier work - true on every path, including a fresh clone.
@ppXD ppXD changed the title Measure the whole launch frame, and make the deploy fake read stdin Measure the whole launch frame, and run an oversized continuation cold Sep 24, 2026
FitsTheFrame measured the spec and held a flat 1 MiB for everything
else. The invocation carries the spec and, a second time, the child's
argv - which appends the spec's arguments, a persona on
--append-system-prompt included - and its environment, which copies
the spec's. With a large escaped persona that second copy approached
the allowance on a 4 KiB-page host and passed it where the argv
ceiling is higher, so a spec the fit admitted was a frame the runner
refused. With a plain persona the allowance gave away ~1 MiB, so
continuations whose frame fit ran cold for nothing.

Both copies are counted now, with 64 KiB for the parts that do not
grow with the spec. Pinned against the invocation the runner really
encodes, at the boundary, for an ASCII and a worst-case persona.
The cold degrade put its hint into the run's own goal. Local
acceptance hashes that goal against the persisted envelope before
launch, so a locally graded continuation read as a different contract
and failed as local-context-mismatch, a grader fault, before any
process started - the refusal the degrade exists to replace. The
unreadable-checkpoint degrade on main amends the goal the same way and
failed a locally graded retry identically.

The hint now rides only the dispatched spec, and verification is
graded on the goal the envelope persisted, which fixes both. The cold
trace is recorded once the attempt is sure to launch, and says what
happened per path: only a revise round keeps its workspace, so only
its note says so. The launch test now records the real argv, so its
"no --resume" assertion can fail.
The cold note and the cleared envelope were written after local
acceptance but before spend admission. A continuation refused for
spend - a lost host whose dead attempt still holds the run's cap -
never started a process, yet its timeline said it had started a fresh
conversation, and its envelope no longer said it was a resume. Both
now follow admission, on the launch path and for a revise round.
Moving the revise round's cold note below its spend admission had no
test: putting it back above admission left every suite green. A cold
round refused for spend under a capped quick lane now asserts no
"continued as a fresh conversation" note, and goes red with the note
moved back.
@ppXD
ppXD merged commit 29b4c34 into main Sep 25, 2026
7 of 9 checks passed
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.

1 participant