Skip to content

fix(sandbox+web-react): real stream errors, driveTurn delegation, skip unchanged skill re-writes#206

Merged
drewstone merged 3 commits into
mainfrom
fix/drive-sandbox-turn-detach
Jul 20, 2026
Merged

fix(sandbox+web-react): real stream errors, driveTurn delegation, skip unchanged skill re-writes#206
drewstone merged 3 commits into
mainfrom
fix/drive-sandbox-turn-detach

Conversation

@drewstone

Copy link
Copy Markdown
Contributor

Three shell/chat fixes, all proven against workcomp-agent live:

1. Surface real stream errors (web-react/chat-stream.ts). The sandbox lane sends failures as {type:'error', data:{message}} but the reader only checked top-level details/error, so every real error rendered as "Unknown stream error". Now reads data.message first — empty-output, egress, model 402 reach the operator. Fleet-wide.

2. driveSandboxTurn delegates to the SDK (sandbox/index.ts, from #205). Uses box.driveTurn instead of a blocking box.prompt().

3. Skip the deferred skill re-write when unchanged (sandbox/index.ts). Reusing a box re-wrote the whole (large) skill corpus via box.fs.writeMany unconditionally — seconds of latency per turn. Stamp the corpus content hash into box metadata at create; on reuse, skip the re-write when the hash matches. Fail-safe: a missing/mismatched hash (redeploy with new skills, or a pre-existing box) writes as before — a stale skip is never risked. Metadata-only, so existing deferred-write tests are unchanged; adds a skip test.

Verification

pnpm typecheck clean · pnpm test 2520/2520 · clean merge into main. Live-proven on workcomp-agent: (1) fixed the masked errors Drew saw; (3) targets the ~10s reuse latency behind the prewarm work.

…ocking box.prompt

driveSandboxTurn was named/documented as the detached cron/mission-step
helper but was built on box.prompt(), which awaits the WHOLE turn in
process. Every autonomous caller therefore held its worker alive for the
run's duration — the exact durability trap the helper is meant to avoid.
The stale comment even claimed "the SDK SandboxInstance has no driveTurn",
but the pinned SDK (@tangle-network/sandbox 0.10.5) ships box.driveTurn,
box.dispatchPrompt, and box.findCompletedTurn.

Route the helper through box.driveTurn: ONE settle -> poll -> dispatch
pass that runs the turn fire-and-detached server-side and returns
immediately with where it stands, never awaiting completion in process.

The old Outcome<PromptResult> contract assumed a completed result in one
call, which is blocking by construction and cannot represent a
still-running turn. Change the return to Outcome<TurnDriveResult> (the
SDK's discriminated running | completed | failed): the Outcome boundary
now separates a retryable transport throw (fail) from a settled turn
state (ok), and a deterministic `failed` turn is a state the caller
inspects rather than a swallowed error. Expose turnId + wallCapMs so
tick-based drivers get idempotent resume and unattended-run bounding.

Callers: the two in-repo consumers are unit tests, updated here. The
return-type change is a compile-time break that forces external
autonomous callers to handle `running` — failing loud is correct, since
their prior blocking usage was the bug.

Add a test proving the helper returns promptly on state:'running'
(box.prompt is never called), plus transport-throw and
turnId/wallCapMs-forwarding coverage.
…ror'

The sandbox lane sends a failure as { type:'error', data:{ message } } (mirrored
by session.run.failed), but chat-stream only read top-level details/error, so
EVERY real error rendered as the useless 'Unknown stream error'. Read
data.message first — empty_visible_output, egress, model 402 etc. now reach the
operator. Fleet-wide (every product on streamChatTurn).
…s unchanged

Reusing a box re-wrote the entire (large) skill corpus via box.fs.writeMany
UNCONDITIONALLY — seconds of latency on every turn even when nothing changed.
Stamp the corpus content hash into box metadata at create; on reuse, skip the
re-write when the hash still matches. Fail-safe: a missing/mismatched hash (a
redeploy with new skills, or a box predating this) writes as before — a stale
skip is never risked. Metadata-only (no extra exec/round-trip), so existing
deferred-write tests are unchanged; adds a skip test. 2520/2520 green.

@tangletools tangletools left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Auto-approved drewstone PR — da40046d

This PR was opened by the trusted drewstone account.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.

tangletools · auto-approval · reason: drewstone_author · 2026-07-20T01:55:11Z

@drewstone
drewstone merged commit 8ad6755 into main Jul 20, 2026
1 check 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.

2 participants