@agentproto/sandbox
v0.13.0
Published
@agentproto/sandbox — AIP-36 SANDBOX.md reference implementation. A composable schema block defining the `sandbox` field — provider, config, command env, network egress, resource limits — for any manifest that names a compute environment for agent-issued
Maintainers
Readme
@agentproto/sandbox
AIP-36 SANDBOX.md reference implementation. A composable schema block defining the sandbox field — provider, config, command env, network egress, resource limits — for any manifest that names a compute environment for agent-issued shell commands. Sibling primitive to STORAGE.md (AIP-35); inline or ref, mirroring AIP-17 RUNNER and AIP-19 SECRETS.
Status: 0.1.0-alpha. Generated by
scripts/scaffold-aip.mjs—build()andvalidate()bodies are TODOs.
Spec: https://agentproto.sh/docs/aip-36
Usage
import { defineSandbox } from "@agentproto/sandbox"
const x = defineSandbox({
id: "my-sandbox",
description: "Short purpose.",
// ...
})Running an agent step inside a sandbox
createSandboxAgentSessionHost turns any SandboxProvider into an
AgentSessionHost (@agentproto/workflow-runtime) — the seam runWorkflow
injects for every AgentStep. It resolves the requested secrets into an env
map, boots the sandbox with that env, then connects
connectDaemonAgentSessionHost (@agentproto/worktree) to the sandbox's
exposed agentproto daemon — reusing the same daemon-backed session host a
local run would use, unchanged.
import { createSandboxAgentSessionHost } from "@agentproto/sandbox"
import { e2bSandboxProvider } from "@agentproto/sandbox-e2b"
const host = await createSandboxAgentSessionHost({
provider: e2bSandboxProvider,
spec: { provider: "e2b", config: {} },
secrets: { slugs: ["OPENROUTER_API_KEY"] },
})
try {
// await runWorkflow({ workflow, input, agents: host })
} finally {
await host.stop()
}Adapter auto-install on boot
A box's boot-time npm i -g @agentproto/cli replaces the global install and
loses the template-baked @agentproto/adapter-* packages. Providers that
accept config.installPackages (e2b, box) install those entries in the SAME
npm invocation as the CLI update; when spawning through the runtime
(spawnAgentSession), the adapter about to be launched — plus
@anthropic-ai/claude-code for the claude-code adapter — is injected there
automatically (a caller-declared pin always wins, nothing is injected for
non-sandbox spawns).
For harnesses BEYOND the spawned adapter, declare semantic slugs instead of
raw npm specs: config.installAdapters: ["hermes", "claude-code"]. Each slug
expands to @agentproto/adapter-<slug>@latest (plus that adapter's declared
boot extras, e.g. @anthropic-ai/claude-code) and merges into
config.installPackages with dedupe — a caller's explicit pin for the same
package always wins. An unknown slug still expands (the box's npm install is
the authority); an absent field changes nothing.
Billing-auth auto-passthrough (opt-in)
Set env.autoPassthrough: true on a sandbox spec and the runtime adds the
spawn's RESOLVED billing-credential env-var NAME (e.g. ANTHROPIC_API_KEY) to
env.passthrough before the box boots — so a fresh box inherits host auth
without the caller naming vars. Only the NAME is injected; the value travels
via the normal passthrough mechanism (host secrets broker → box env) and is
never read by the flag. Billing credential only (no GITHUB_TOKEN); the
caller's explicit env.passthrough entries are kept and deduped. When no
credential resolved — or the host cannot resolve the var — nothing is
injected and the spawn proceeds: the flag is a convenience, not a contract.
Lifetime: re-armed while the session works
A provider box has a lifetime (e2b: timeoutMs, 45 min by default, 1 h
account cap). It counts from the last re-arm, not from boot. While its
session is active the runtime keeps re-arming it with the same duration:
on each prompt dispatch, on streamed box events (at most once a minute), and
every 10 minutes while a turn is in flight, even one that streams nothing
for a long time (a single long tool call). An idle box is not renewed and
expires on schedule.
About 5 minutes before the deadline it knows about, the proxy acts once more. If a turn is still in flight it tries one last renewal. If the session is idle, or that renewal fails, it runs a final checkpoint while the box's filesystem can still be reached. A detached box (host-daemon restart) follows its own rule: it is asked for 1 h from the moment of detach.
Lifecycle: pause is the default teardown
Closing a session PAUSES its box by default (pause({ keepMemory: true }))
rather than killing it — any closed box stays reattachable via
sandbox.reuse / agentproto sandbox attach. A paused box still dies at its
own timeoutMs (45 min by default), so paused boxes don't accumulate
indefinitely; declare lifecycle.destroy_on for a hard kill on close.
License
MIT — see LICENSE.
