pi-extension-opensandbox

Server-side Pi extension factory that routes built-in tools to a remote OpenSandbox sandbox

Packages

Package details

extension

Install pi-extension-opensandbox from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:pi-extension-opensandbox
Package
pi-extension-opensandbox
Version
0.1.7
Published
Sep 15, 2026
Downloads
857/mo · 650/wk
Author
okliuxing
License
unknown
Types
extension
Size
142.4 KB
Dependencies
1 dependency · 2 peers

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

Pi OpenSandbox Extension

pi-extension-opensandbox is a server-side Pi extension factory. It takes an OpenSandbox configuration, runs that sandbox for the Pi session, and routes Pi's bash, read, write, edit, ls, find, and grep tools to it. The extension has no CLI flags and does not read environment variables.

Embed

Create a factory from a validated configuration and supply it as an inline extension:

import { createOpenSandboxExtension } from "pi-extension-opensandbox";

const extensionFactories = [{
  name: "opensandbox",
  factory: createOpenSandboxExtension({
    config: sandboxConfig,
    onSandboxEvent: async (event) => {
      // event.phase is "ready" or "released"; the event carries the sandbox identity.
      if (event.phase === "ready") trackSandbox(event.sandboxId);
    },
  }),
}];

Pass extensionFactories to createAgentSessionServices. Bind extensions before prompting and dispose the enclosing AgentSessionRuntime in a finally block. Disposal emits session_shutdown, where the lifecycle policy decides what happens to the sandbox.

Options:

  • config — sandbox configuration (see below).
  • onSandboxEvent — called on every lifecycle phase: ready when the sandbox is usable, released when the extension disposed of it (with the effective mode). Events carry sandboxId, workspace, createdByExtension, and resumed.
  • createSandbox — overrides the sandbox factory (defaults to the OpenSandbox client) so embedding services and tests can inject their own client.

Sandbox lifetime

One Pi session holds exactly one sandbox. The extension creates or connects it on session_start from config, routes the built-in tools and the interactive shell to it, renews its TTL while the session lives, and disposes of it on session_shutdown according to the lifecycle policy.

The sandbox is therefore not a named resource that an agent chooses between. Needing a second, differently-provisioned environment means starting a second Pi session: this factory is invoked once per session, so every session gets its own sandbox with its own configuration.

The extension attaches no meaning to what a sandbox is used for. Whatever an embedding service needs to identify a sandbox belongs in config.metadata, which is passed to OpenSandbox untouched; the extension only adds its own pi-extension and pi-session-id keys.

Failing to create the sandbox fails session_start. If the ready event callback throws, the sandbox is cleaned up (killed when the extension created it) and the session is left without a sandbox rather than half-connected.

Configuration

The factory config requires domain. protocol defaults to http; apiKey is optional and should be supplied by the embedding service, not an untrusted caller.

Creation requires exactly one source:

{
  "domain": "sandbox.internal:8080",
  "image": "opensandbox/code-interpreter:v1.1.0",
  "workspace": "/workspace",
  "volumes": [{
    "name": "task-workspace",
    "pvc": { "claimName": "task-workspace-pvc" },
    "mountPath": "/workspace"
  }]
}

Use snapshotId instead of image to create from a snapshot. A volume has exactly one pvc or ossfs backend and an absolute mount path. PVCs support createIfNotExists, deleteOnSandboxTermination, storageClass, storage, and accessModes. OSSFS supports bucket, endpoint, version, options, accessKeyId, and accessKeySecret.

To operate an existing sandbox, provide only sandboxId with connection settings:

{
  "domain": "sandbox.internal:8080",
  "sandboxId": "sbx_123",
  "workspace": "/workspace"
}

Sandbox.connect() cannot add an image, snapshot, or mounts. The factory rejects those create-only fields for a connected sandbox. A paused sandbox is resumed before the agent begins, and a reused sandbox is renewed once before its renewal timer starts.

Lifecycle policy

lifecycle.onSessionShutdown decides what happens to a sandbox when the session shuts down:

value effect
kill kill the sandbox (default)
pause pause the sandbox; a later session resumes it by passing its sandboxId
keep leave the sandbox running and only close the transport
{
  "domain": "sandbox.internal:8080",
  "image": "opensandbox/code-interpreter:v1.1.0",
  "lifecycle": { "onSessionShutdown": "keep" }
}

Only a sandbox the extension created is affected; a sandbox connected through sandboxId is only disconnected.

Safety

timeoutSeconds defaults to 3600 and must be a positive safe integer. The extension passes it to OpenSandbox without imposing a duration cap; the server's configured maximum still applies. The sandbox renews every half TTL or one hour, whichever is shorter, setting expiration to the renewal time plus timeoutSeconds. Renewal failures are logged and retried at the next interval. Requests never overlap, and shutdown stops renewal and awaits the pending request before pause, kill, or disconnect. Worker crashes stop renewal; this is not durable task recovery.

Remote-tool startup failures are fail-closed: no tool or interactive shell command falls back to the worker host. Metadata keys under opensandbox.io/ are rejected. Network egress is default-deny; networkAllowlist becomes explicit allow rules.

Development Checks

pnpm --dir packages/pi-extension-opensandbox check

Run the renewal regression tests from this package directory (mock timers and sandbox clients; no live services required):

node --import tsx --test scripts/renewal.test.mjs