feat(loops): inProcessSandboxClient — typed in-process box, no more SandboxInstance casts#379
Merged
Merged
Conversation
…andboxInstance casts
Lift the repeated offline-box pattern into a substrate primitive. Each example
spot built the SAME shape by hand — a SandboxClient whose create() returns a box
implementing only the subset runLoop/openSandboxRun call ({id, streamPrompt} +
optional fs/exec/delete) — and cast it via `as unknown as SandboxInstance`
because SandboxInstance is a declare class with private fields that no object
literal can structurally satisfy.
inProcessSandboxClient({ onPrompt, workdir?, id? }) returns a properly-typed
SandboxClient; the one unavoidable cast lives inside the primitive, documented
and tested. It is the structural sibling of inlineSandboxClient (Executor → box)
and createInProcessUiAuditClient — onPrompt(prompt, ctx) → events is the box.
Rewired the four cast sites — driver-loop, researcher-loop, and both
fleet-delegation spots — to the primitive; the casts collapse to a one-line
onPrompt callback. Each example keeps its scenario script and teaching (the
fold; the namespace-leak firewall; placement tagging via the id override).
tangletools
approved these changes
Jun 24, 2026
tangletools
left a comment
Contributor
There was a problem hiding this comment.
✅ Auto-approved PR — 99057fea
Blanket team auto-approval is enabled for this reviewer service.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.
tangletools · auto-approval · reason: blanket_auto_approve · 2026-06-24T18:09:25Z
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Lifts the hand-rolled offline-box pattern into a reusable substrate primitive,
inProcessSandboxClient, next toinlineSandboxClient. Examples no longer castas unknown as SandboxInstance.Why
Four example spots each built the SAME shape by hand — a
SandboxClientwhosecreate()returns a box implementing only the subsetrunLoop/openSandboxRunactually call ({id, streamPrompt}+ optionalfs.read/write/exec/delete) — and cast it viaas unknown as SandboxInstance. The cast is forced becauseSandboxInstanceis adeclare classwith private fields that no object literal can structurally satisfy. The pattern already earned a substrate home once (createInProcessUiAuditClient) and is the structural sibling ofinlineSandboxClient(which adapts anExecutor→ box). This gives the offline-box pattern its own typed home so user code never casts.The primitive
inProcessSandboxClient({ onPrompt, workdir?, id? })returns a properly-typedSandboxClient. The single unavoidable cast (object literal →declare class) lives inside the primitive, documented and tested.onPrompt(prompt, ctx) => SandboxEvent[] | AsyncIterable<SandboxEvent>— the per-turn behavior IS the box.ctxcarriesround(per-box turn counter),workdir, andsignal.workdir?— opt in to a real temp-dir-backed box:fs.read/fs.write+execover it,delete()tears it down. Omit for a pure event-only box.id?: string | (seq) => string— override the box id (placement demos read on a meaningful sandbox id).Casts removed (4 spots → 0)
examples/driver-loop/scripted-worker.ts{ id, streamPrompt } as unknown as SandboxInstanceonPromptcallbackexamples/researcher-loop/synthetic-researcher.ts{ id, streamPrompt } as unknown as SandboxInstanceonPromptcallbackexamples/fleet-delegation/fleet-delegation.ts(×2){ id } as unknown as SandboxInstanceinProcessSandboxClient({ id, onPrompt: () => [] })Each example keeps its scenario script + teaching intact (driver-loop's fold, researcher-loop's namespace-leak firewall, fleet-delegation's placement tagging — meaningful ids preserved via the
idoverride). Thecoding-benchmark/offline-box.tscast is out of scope. The one correct cast insidesrc/runtime/inline-sandbox-client.ts(and nowin-process-sandbox-client.ts) stays.Verification
pnpm run build/pnpm run typecheck(src + examples) /pnpm run lint— all greenpnpm test— 116 files, 1125 passed, 2 skipped, 0 failed (incl. a new 5-case test for the primitive: end-to-endrunLoopdrive, round counter, workdir fs/exec + delete cleanup, id override, async-iterable stream)pnpm run docs:apiregenerated the catalog for the new export;pnpm run docs:checkgreengrepconfirms 0as unknown as SandboxInstancein the rewired example files