feat(settings): opt in to Code Mode for new tasks - #5219
Conversation
Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
Keep the VM alive during host waits and resume through the existing Promise bridge. Patch the executor at its synchronous VM boundary, remove idle polling and the guest-writable result slot, and preserve cancellation and resource limits. Generated-by: Codex
Generated-by: Codex
Resolve the combined Host contract at compatibility epoch 146 and regenerate dependency notices. Generated-by: Codex
Keep single-slice success and cumulative timeout assertions while widening the test budget and repeated workload together. Generated-by: Codex
jackwener
left a comment
There was a problem hiding this comment.
Approve at 2fda64d5d310fe33dafb2ebd528606ebfd48d8b9. I found no blocking issues. Opt-in Code Mode is off by default, is frozen per Session at creation, and the execution-budget semantics match what the patch notes describe.
What I actually checked.
- Budget, async and cancellation. In
executionmode the budget accumulates only synchronous entries into the sandbox: the measuring wrapper returns immediately when already inside an outer entry, so reentrant calls do not double-count, and the interrupt check compares accumulated time plus the current span against the budget. No wall-clock timer is armed in this mode, which is the documented intent. Cancellation is wired into the existing host cancel/fail path and rejects the race that the resolver awaits, so a cancelled run unblocks without relying on polling. - Removal of the resolver's polling loop. This is not gated on the new mode, so I checked the default
wallpath separately: the wall deadline is enforced on the manager side by a timer that fails the run terminally, independent of the worker's await. Removing the per-tick poll therefore does not weaken the default timeout. - Settlement. The cell's promise drains every host operation it started on both the success and failure paths, looping while new operations appear, so repeated cancellation cannot accumulate unbounded host work. Nested calls now carry a step identity, are refused once the turn has yielded, and route plan and yield results through the same handlers as direct calls.
- Mode freezing. New Sessions read the setting once at creation and store the result on the Session header; turns read that frozen value. Scheduled tasks capture the mode into their execution template at creation, and the stored template is validated on decode, so changing the setting later does not rewrite existing tasks or past jobs.
- Dependency patches and install consistency. The override pinning, the patched package versions and the patch filenames agree with each other. The readable source diff is coherent and corresponds to the described behaviour.
- The large generated hunk. I extracted the embedded WebAssembly payload from both sides of that hunk and compared it directly: it is byte-identical (848,536 base64 characters, identical digest). The entire difference in that line is rebuilt worker JavaScript, which confirms the patch notes' claim that the embedded binary is unchanged.
Two non-blocking observations.
- In this mode the tool plan returns no context-budget diagnostics at all, while the single callable tool's description now embeds the full serialized catalog. The catalog still costs prompt space, but nothing reports it, so a Session in this mode loses the visibility that other Sessions keep. Consider reporting the projected tool's schema size rather than suppressing the diagnostic.
- The rebuilt worker bundle is minified, so its difference is dominated by renamed local identifiers. Beyond the binary-identity check above, that hunk is not reviewable by reading; its provenance rests on the documented rebuild procedure and the pinned upstream commit. That procedure being written down is what makes this reviewable at all, and it is worth keeping accurate.
What I did not verify. I reviewed code and hosted check results only. I did not run the test suite, a build, an end-to-end run or any sandbox execution, so I did not measure real budget accounting, cancellation timing or memory behaviour. The no-polling change assumes the guest environment exposes no timers, so that nothing becomes runnable without a bridge response; that holds for the frozen guest surface in this diff, but I did not audit the whole upstream guest environment for a timer. This change also raises eight SDK package versions alongside the feature; I did not assess those upgrades independently, and hosted checks are the only evidence for them here. All required checks are terminal on this commit: seventeen successful and one skipped, with none failing or pending.
Automated review notice. This review was produced by an automated reviewer (agent seat kabi-opus, model Claude Opus) acting on its own review identity. It is posted from a shared machine account, so the seat is named here because the account alone does not identify the reviewer. It is not an independent human review and does not substitute for one. Its scope and limits are stated above; no merge was performed.
) Thirty-one upstream commits. The one that reaches the new renderer is apache#5170, which gives the Renderer the transcript window: Main keeps a tail cache and answers page requests pass-through, `loadBefore` / `loadAfter` return a page, `loadAround` / `loadLatest` a reset, `acknowledgeTail` is new, and a batch carries `extends` / `coversFrom` / `navigation` instead of `evictedDurableSequences` / `completedOverlayMessageIds`. Also in: apache#5217's observation contract (`subscribeEvents` loses `onSeeded`; readiness follows seed consumption as the `ready` phase, and the execution projection it offers is not consumed here yet), the memory work across composer and stream (apache#5153), interactions cleared per Turn on abort/complete (apache#4562), Session bundles and external agents in main/preload (apache#5197, apache#5164), Code Mode (apache#3615, apache#5219), and the scheduled-task snooze fix (apache#5226). Resolution per the sync policy: conflicts under the old renderer's trees, packages/ui's deleted components, their stories, e2e specs and the main tests that import them stay deleted; upstream's new files in those trees are dropped (`application/contracts/settings-presentation`, `features/external-agent-settings`, `features/session-bundle`, `workhub/ui/return-button`, `model-wheel-picker`, the prompt-rail and live-turn-buffer tests, `workhub-return-rail.spec.ts`). The renderer side of apache#5217 (one live Turn per Session → a buffer keyed by Turn, `liveTurn` → `liveTurns`, `phase` gone) stays out: `packages/ui` `live-turn-projection.ts`, `transcript-projection.ts` and their tests keep ours and `live-turn-buffer.ts` is dropped; `session-event-handlers.ts` keeps ours plus upstream's display-frame scheduler. `packages/ui` `conversation-copy.ts` keeps `transcriptGap` (our gap rows use it), `transcript-row-projection.ts` is restored, `use-pending-selection.ts` goes. Astryx stays out of package.json and the lockfile; `@ai-sdk/provider-utils` moves to 5.0.40 and the `@ai-sdk/code-mode` override lands. Re-implemented for the new contract: - `lib/ported/desktop-transcript-range-store.ts` and `transcript-reading-position.ts` are re-ported from upstream head (the previous copies were format-only ports of the old versions); `TranscriptReadSupersededError` lives in the latter, and `display-frame-scheduler.ts` joins `lib/ported`. - `store/active-session-store.ts`: the window is the store's — the display follows a store subscription rather than `accept`'s return; the paging gate and `loadTranscriptHistory` are gone (the controller refuses a read against an edge it already read), `loadHistory` keeps only the gap-row indicator; `prefetchHistory` and `retainWindow` serve `useChatScroll`'s geometry-driven filling and trimming; `setReadingAnchor` only moves the bookmark; the bookmark re-anchors after a replica generation change, by sequence within a Host epoch and by Turn through the landmark index across one; a read superseded by an epoch change is not an error. - `SessionView` passes `onPrefetchHistory` / `onRetainWindow`; the gap rows and the return-to-latest button keep their explicit commands. - `bridge/sessions.ts` drops `onSeeded`. - Main tests for the range store, navigation race, overlay settlement and the two new probes are upstream's with paths under `lib/ported`; the reading-position test keeps upstream's pure-module cases (send pinning, overlay-only bookmark, superseded read) — the shell-shaped cases live with the store's tests. - `settings-sections.ts` and the copy files name core's new `external-agents` section id as a deferred page. - Ported apache#5226: an edit that leaves the schedule fields alone omits `schedule` from its patch, so the Host keeps a snoozed fire (`scheduled-task-form-payload.ts`, `ScheduleFormDialog.tsx`, a test in `scheduled-module.test.tsx`). Also in this tree, found while verifying the sync and not caused by it: a live Turn's finished steps vanished after switching to another task and back, leaving only "Working on it…". The Host re-seeds only what is still incomplete (the streaming text, pending interactions) and Main's transcript overlay is bootstrapped once per replica, so the steps that finished while the Session was on screen existed only in the renderer's live projection — which the store wiped on every selection and reseed. The projection now survives the switch (a reseed drops only the incomplete text and thinking it replays; a Turn that ended meanwhile is retired by the transcript it left behind). `test:streaming-switch` drives the real app through it with a new fake-backend scenario that settles a text step and a tool call, then holds the Turn open. The compatible-change declaration `base64-length-allocation.json` is re-pinned from 143 to the epoch this branch carries (147, upstream's own): upstream left it at the epoch of its commit and its per-commit hook never re-judged it, while our merge stages it next to the epoch bump. Its reason (Base64 byte counting in `artifact.ts` / `session-transcript.ts` without observable change) still holds against the protocol as merged. Gates: build:test + build:renderer, typecheck, biome lint and format, locale hygiene, ASF headers, renderer architecture ledger (rewritten with `--write`; the new range store's `window` local reads as environment capabilities to the checker), e2e budget, third-party notices, knip (same findings as before the merge), desktop dist tests (1599), renderer state (282), Electron smoke (44 checks, no renderer errors), core-dialogue smoke, streaming-switch smoke. `packages/storage` `workspace-identity` (git worktree ENOTEMPTY) is a parallel-run flake that passes in isolation, as is `packages/eval` `lifecycle-boundaries` (relay cancellation timing); `packages/runtime` `model-adapter-onerror` fails on this machine before and after the merge (asynchronous activity after the test ended; the file is unchanged this round). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Summary
Add an opt-in Code Mode switch under Settings → General → Task defaults. The selected Runtime Host saves the global default, and newly created tasks persist their tool mode. Changing the switch leaves existing tasks unchanged; copies and child tasks inherit their source task's mode. Scheduled agent tasks also freeze the mode in their execution template, with legacy templates using direct tools. There is no per-task UI switch.
When enabled, the model receives only
exec, with the current callable tool catalog in its description. Nested calls use the existing ToolRuntime permission checks, durable settlement, cancellation, and exclusive-tool admission. Tool search refreshes the catalog for the next cell; nested questions and plan/graph handoffs remain available. Denied sandbox tools are removed from the model-visible nested catalog.Execution boundary
Keep
@ai-sdk/code-mode, upgrading 1.0.42 → 1.0.56 and aligning the AI SDK family and provider-utils patch. Pin itsrundependency to 2.1.4 and apply two documented dependency patches: the SDK forwards an opt-in execution-time policy, and the executor enforces it at synchronous QuickJS entry points.Previously, a 30-second total deadline also aborted normal host-tool and user-input waits. Maka now uses a cumulative 30-second VM execution budget. External waits retain the VM and consume no execution budget; individual Promise completions immediately advance dependent code without a continuation batch or an extra parent-model request. Default SDK wall-time behavior remains available and unchanged.
The executor removes empty-job polling and waits for bridge responses or cancellation. It also uses the Promise handle returned by evaluation directly, removing a guest-writable global result slot. Runtime cancellation still drains started host operations before releasing cell admission. No new Worker pool, replay mechanism, or Runtime scheduling layer is added.
The patched run manifest uses Maka's Node >=22.19 baseline and removes the optional TypeScript peer used by the older-Node fallback. Supported Maka runtimes use native type stripping, so no second compiler is shipped. Attribution, generated notices, and source-header inventory are updated. The large generated patch hunk is the inline Worker with embedded WASM;
patches/run-2.1.4-source.diffprovides the readable source changes and rebuild instructions are inpatches/README.md.Host protocol compatibility epoch moves from 142 to 143.
Verification
npm ci, dependency patch application, production dependency inspection, and SDK smoke passed. Execution mode allows a long host wait; default wall mode still times out.Two independent Reviewer Deep reviews covered adversarial behavior and simplification. No P0/P1 remains. Removed the old direct-only plan/graph assertions, redundant diagnostics, idle polling, and the mutable global result slot. Retained distinct tests for permissions, cancellation, durable settlement, and execution budgets.
Known limits
Waiting invocations retain their VM and Worker/memory quota until completion or cancellation. This includes guest-created Promises that never settle; Runtime supplies a reachable cancellation signal, and existing admission and process-wide resource limits remain in force. There is no automatic liveness deadline or deadlock detector. Provider-native and explicitly non-nestable tools remain unavailable through Code Mode; execution-time mode supports asynchronous host functions, not synchronous bridge/module-loader calls. These tests establish execution and settings behavior, not a live-model quality improvement.
AI use
Tool(s) and scope: Codex implemented dependency alignment, executor patches, settings/runtime integration, and tests. Two Reviewer Deep agents performed independent reviews.
Checklist
Does this PR entail a change in behavior?