feat(codemode): interrupt all pending work at program return

Replace the two-class completion lifecycle with one rule: once the
program returns, every promise still running - race losers, fail-fast
stragglers, and fire-and-forget calls alike - is interrupted rather
than awaited. Work whose completion matters must be awaited by the
program.

Waiting for un-awaited work before interrupting observed leftovers
could hold the execution open or deadlock it outright: a fire-and-
forget chain awaiting a race loser, or queued work needing tool-call
permits the losers occupied, blocked a drain that only the pending
interruption could unblock. Rejections that settled un-awaited before
the return still surface as Success.warnings diagnostics.

Give warnings their own output byte budget equal to maxOutputBytes so
a budget-consuming value can never starve runtime diagnostics, and
state the lifetime rule in the model-facing instructions. Port ten
curated Test262 combinator cases (duplicate members, already-settled
inputs, ordering, resolve/reject flattening) and cover the timeout-
during-cleanup path with a never-settling slow-cleanup harness tool.
This commit is contained in:
Aiden Cline 2026-07-09 23:22:28 -05:00
parent d785803dd6
commit 4bc90593df
9 changed files with 328 additions and 76 deletions

View file

@ -67,18 +67,20 @@ Every sandbox promise starts eagerly on a run-once fiber owned by the whole Code
async functions, `Promise.all`, `Promise.allSettled`, `Promise.race`, `Promise.resolve`, and `Promise.reject`. Nested
functions therefore cannot end the lifetime of work they started. Independent aggregate batches overlap, and rejection
is observed at the eventual `await`. `Promise.race` uses native non-cancelling settlement semantics: its first result
wins while losers continue running. At normal completion CodeMode drains unobserved promises to empty (fire-and-forget
work finishes, and its failures become `Success.warnings` diagnostics), then interrupts whatever remains active - all
of it observed (race losers, fail-fast `Promise.all` stragglers), and since the program has returned, no future await
can exist. An unobserved promise that itself awaits an observed loser transitively keeps that loser alive through the
draining phase; interruption applies only to work nothing unobserved still depends on. A fatal program failure or host interruption closes the execution promise scope and interrupts its active
fibers instead. A timeout does the same, except that a value the program already returned is preserved alongside a
`TimeoutExceeded` warning rather than discarded. At most eight tool calls execute concurrently.
wins while losers continue running. At normal completion CodeMode interrupts everything still running - race losers,
fail-fast `Promise.all` stragglers, and fire-and-forget calls alike: the program has returned, so no future await can
exist, and work whose completion matters must be awaited by the program. Waiting for any class of leftover instead
would let it hold the execution open, or deadlock it when queued work needs tool-call permits the leftovers occupy.
Rejections that settled un-awaited before the return become `Success.warnings` diagnostics. A fatal program failure or
host interruption closes the execution promise scope and interrupts its active fibers instead. A timeout does the
same, except that a value the program already returned is preserved alongside a `TimeoutExceeded` warning rather than
discarded. At most eight tool calls execute concurrently.
The public execution-policy knobs are `timeoutMs`, `maxToolCalls`, and `maxOutputBytes`. The package supplies no
defaults because budgets are host policy. The interpreter also enforces fixed internal boundaries for tool-call
concurrency and data nesting depth. `maxOutputBytes` bounds retained payload bytes, not the complete rendered message;
fixed truncation notices and host-added framing are intentionally outside the budget.
warning diagnostics have an equal separate budget so a large value cannot starve them, and fixed truncation notices and
host-added framing are intentionally outside the budgets.
### Data, files, and failures