Skip to content

Expose exec() on reusable createSandbox() handles #788

Description

@nakleiderer

Please expose a public exec() method on the Sandbox handle returned by createSandbox().

Today, sandbox provider handles expose exec(), and Sandcastle's internal sandbox service uses exec() for lifecycle hooks and prompt command expansion, but the reusable Sandbox returned by createSandbox() only exposes run(), interactive(), close(), and async dispose. That makes it hard for harness authors to run non-agent verification commands inside the same sandbox after an agent run.

Use case

I'm building a Sandcastle workflow where an implementer agent runs first, then the harness must run verification gates before allowing the implementer stage to finish. These commands need to run inside the same sandbox/container/worktree as the agent, not on the host.

Current workaround

The only way I've found is to wrap the sandbox provider, intercept the underlying provider handle returned by create(), and keep a reference to its exec() method. That works, but it feels like reaching around Sandcastle's public API.

Proposed API

await using sandbox = await createSandbox({
  branch: "agent/fix-42",
  sandbox: docker(),
});

await sandbox.run({
  agent: codex("gpt-5.5"),
  promptFile: ".sandcastle/prompts/implement.md",
});

const result = await sandbox.exec("bun run typecheck", {
  onLine: (line) => console.log(line),
});

if (result.exitCode !== 0) {
  throw new Error("Verification failed");
}

Suggested type:

interface Sandbox {
 // ... existing properties 

  exec(
    command: string,
    options?: {
      cwd?: string;
      onLine?: (line: string) => void;
      sudo?: boolean;
      stdin?: string;
    },
  ): Promise<ExecResult>;
}

Important behavior

cwd should default to the sandbox repo path, not the host worktree path.

For bind-mount providers those are related, but for Docker/Podman the command should run from the container-side repo path, e.g. /home/agent/workspace. For isolated providers, it should likewise run in the sandbox-side checkout.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions