一键导入
ax-provider-sandbox
Use when modifying agent sandbox isolation -- Docker, Apple Container (macOS), or k8s providers in src/providers/sandbox/
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Use when modifying agent sandbox isolation -- Docker, Apple Container (macOS), or k8s providers in src/providers/sandbox/
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Use when modifying logging, error handling, or diagnostic messages — logger setup, transports, error diagnosis patterns in src/logger.ts and src/errors.ts
Use when modifying the trusted host process — server orchestration, message routing, IPC handler, request lifecycle, event streaming, file handling, plugin loading, or agent delegation in src/host/
Use when modifying the sandboxed agent process — runner, IPC client, local/IPC tools, tool catalog, prompt building, or identity loading in src/agent/
Use when modifying IPC protocol between host and agent — schemas, actions, length-prefix framing, or Zod validation in ipc-schemas.ts and ipc-server.ts
AX project architecture and coding skills - use sub-skills for specific subsystems (agent, host, cli, providers, etc.)
Use this skill when the user asks for a capability the agent doesn't yet have — a new integration (Linear, GitHub, Notion, Slack, etc.), a new workflow, a new tool, or "can you check our team's X" where X is a service the agent has no existing connector for. Creates a proper AX skill under .ax/skills/<name>/ with the right frontmatter, credentials, and domain allowlist so the capability becomes permanent after admin approval. Use this skill whenever you'd otherwise be tempted to improvise with one-off scripts, `npm install`, or `execute_script` to reach an external service.
| name | ax-provider-sandbox |
| description | Use when modifying agent sandbox isolation -- Docker, Apple Container (macOS), or k8s providers in src/providers/sandbox/ |
Sandbox providers isolate agent processes with zero network access (by default), no credentials, and mount-only filesystem access. Each provider implements SandboxProvider from src/providers/sandbox/types.ts and exports create(config: Config).
Agents run INSIDE their containers and execute tools locally. Each agent gets a single /workspace directory (rw). In Docker/Apple mode it's bind-mounted from the host. In k8s mode it's backed by a PVC (persistent across pod restarts) or emptyDir (ephemeral).
SandboxConfig -- passed to spawn():
| Field | Type | Notes |
|---|---|---|
| workspace | string | The single /workspace directory (rw mount) |
| ipcSocket | string | Unix socket path for IPC |
| timeoutSec | number? | Process timeout |
| memoryMB | number? | Memory limit |
| cpus | number? | CPU limit |
| command | string[] | Command + args to execute |
| pvcName | string? | PVC name for persistent workspace (k8s only) |
| workspaceSizeGi | number? | PVC size in GiB (k8s only, default: 10) |
| extraEnv | Record<string, string>? | Additional env vars for sandbox pod (e.g. IPC tokens) |
| requestId | string? | Chat-turn correlation ID; propagated into provider logs as reqId so a single grep <reqId> reconstructs the pod lifecycle |
Note: Identity files are sent via stdin payload (loaded from git by the host). Skills live in .ax/skills/ in the git workspace — the agent reads them directly from the filesystem after git clone.
SandboxProcess -- returned by spawn():
| Field | Type | Notes |
|---|---|---|
| pid | number | Process/synthetic PID |
| exitCode | Promise<number> | Resolves when process exits |
| stdout | ReadableStream | Standard output (dummy in k8s -- response comes via HTTP) |
| stderr | ReadableStream | Standard error |
| stdin | WritableStream | Standard input |
| kill() | () => void | Kill the process/pod |
| bridgeSocketPath | string? | Host-side socket for reverse IPC bridge (Apple containers) |
| podName | string? | Pod name for HTTP work delivery (k8s) |
SandboxProvider: spawn(config), kill(pid), isAvailable(), plus:
| Field | Type | Notes |
|---|---|---|
| deletePvc? | (pvcName: string) => Promise<void> | Delete a PVC by name (k8s only). Used during agent deletion. |
canonical-paths.ts)Every agent gets a single /workspace directory as its CWD and HOME. The old three-directory split (scratch/agent/user) has been removed.
| Canonical Path | Mount | Purpose |
|---|---|---|
/workspace | rw | Single workspace directory, agent HOME/CWD |
/workspace/bin | rw | Prepended to PATH -- agents can install CLI tools here |
In Docker and Apple Container mode, /workspace is bind-mounted from the host. In k8s mode, /workspace is backed by a PVC (persistent across pod restarts) or emptyDir (ephemeral). PVC-backed workspaces mean installed tools and packages survive pod restarts.
Identity files are sent via stdin payload from git. Skills live in .ax/skills/ in the git workspace.
canonicalEnv(config) builds:
AX_IPC_SOCKET -- real host path for IPCAX_WEB_PROXY_SOCKET -- path to web proxy Unix socket (same dir as IPC socket, web-proxy.sock)AX_WORKSPACE -- canonical root (/workspace)PATH -- /workspace/bin prepended to system PATHnpm_config_cache, XDG_CACHE_HOME -- redirected to /tmpAX_HOME -- /tmp/.ax-agentEach provider also injects AX_REQUEST_ID (from SandboxConfig.requestId) when present. The agent runner reads it at module load and binds the last 8 chars as reqId on its top-level logger so a single grep <reqId> joins host + sandbox provider + agent runner logs. Injected by docker/apple/k8s providers alongside their -e/env list construction (NOT via canonicalEnv, which doesn't see requestId).
createCanonicalSymlinks(config) is retained for backward compatibility but is now a no-op -- returns the workspace path and an empty cleanup function.
| Name | File | Platform | Isolation |
|---|---|---|---|
| docker | docker.ts | Linux / macOS | Container, --network=none (default), --cap-drop=ALL, optional gVisor |
| apple | apple.ts | macOS (Apple Silicon) | Lightweight VM via Virtualization.framework, no shared kernel |
| k8s | k8s.ts | Kubernetes | Pod-based sandbox with HTTP IPC |
Shared helpers in utils.ts: exitCodePromise, enforceTimeout, killProcess, checkCommand, sandboxProcess.
Each agent gets a single /workspace directory. No separate scratch/agent/user directories.
Host-side (Docker/Apple/subprocess):
/workspace is a host directory bind-mounted into the container (rw).K8s mode:
/workspace is backed by a PersistentVolumeClaim (PVC) when pvcName is set on SandboxConfig./workspace uses an emptyDir (ephemeral, lost when pod terminates).docker.ts)Uses docker run with:
--network=none always (no container ever needs network -- workspace lifecycle runs host-side)--memory, --cpus, --pids-limit resource limits--cap-drop=ALL, --security-opt no-new-privileges, --read-only root-v host:canonical:mode)AX_DOCKER_RUNTIME=gvisor)AX_DOCKER_IMAGE (default: ax/agent:latest)apple.ts)Uses Apple's container CLI (Virtualization.framework):
--publish-socket bridges IPC across the VM boundary via virtio-vsockAX_IPC_LISTEN=1), host connects via bridge socketbridges/ subdirectory to prevent cleanup conflictsbridgeSocketPath returned on SandboxProcess so host knows where to connectAX_CONTAINER_IMAGE (default: ax/agent:latest)k8s.ts)Kubernetes pod-based sandbox (no k8s Exec/Attach):
SessionPodManager manages session-long pod reuse. Falls back to cold start.pvcName is set, /workspace is backed by a PVC. Installed tools and packages persist across pod restarts.SessionPodManager) and IPC:
SessionPodManager/internal/ipc (HttpIPCClient)agent_response IPC action (over HTTP)AX_HOST_URL tells the agent to use HttpIPCClient instead of Unix socketsAX_IPC_TOKEN) passed via extraEnvpodName returned on SandboxProcess for HTTP work deliveryreadOnlyRootFilesystem, runAsNonRoot (uid 1000), capabilities: drop ALL, automountServiceAccountToken: falseK8S_RUNTIME_CLASS, empty string to disable)| Env Var | Default | Purpose |
|---|---|---|
| K8S_NAMESPACE | ax | Target namespace |
| K8S_POD_IMAGE | ax/agent:latest | Container image |
| K8S_RUNTIME_CLASS | gvisor | Runtime class (empty to disable) |
| K8S_IMAGE_PULL_SECRETS | (none) | Comma-separated secret names |
| WARM_POOL_ENABLED | true | Enable warm pool claiming |
| WARM_POOL_TIER | light | Tier to claim from |
| AX_HOST_URL | (set by host) | Host service URL for HTTP staging (http://ax-host.{namespace}.svc) |
Warm pool claiming is handled by SessionPodManager, which manages session-long pod reuse via HTTP. The separate warm-pool-client.ts has been removed.
src/agent/local-sandbox.ts)Agent-side tool execution with host audit gate protocol:
sandbox_approve -> host audits the operation, returns {approved: true/false}sandbox_result -> host logs outcome (best-effort, fire-and-forget)All file operations use safePath() for path traversal prevention. Bash commands run via execFileSync('sh', ['-c', command]) with timeout and buffer limits.
In k8s mode, workspace persistence is handled by PVCs (PersistentVolumeClaims):
pvcName is set on SandboxConfig, the /workspace volume is backed by a PVC.SandboxProvider.deletePvc().utils.ts includes EPERM handling for tsx-wrapped agents:
tsx src/agent/runner.ts -- tsx wrapper may throw EPERM when parent sends SIGTERM/SIGKILLnode dist/agent/runner.js -- standard signal handlingenforceTimeout(): Sends SIGTERM first, then SIGKILL after grace period. Wraps both in try/catch to handle EPERM gracefullysrc/providers/sandbox/<name>.ts implementing SandboxProvider.create(config: Config).PROVIDER_MAP in src/host/provider-map.ts.spawn() passes minimal env via canonicalEnv(config).--network=none or equivalent -- security invariant (unless config.network is true for three-phase orchestration)./workspace (rw) and IPC socket dir.config.cpus and config.memoryMB resource limits.config.extraEnv for per-turn env var injection.tests/providers/sandbox/.--network=none when config.network is true. Apple passes --network default. Always check which phase you're in.bridgeSocketPath). This is opposite to Docker/subprocess where the agent connects to the host's IPC server.--publish-socket forwarding fails when the container-side socket path is on a tmpfs mount. That's why Apple provider doesn't use --read-only.bridges/ subdirectory. If they shared the IPC server directory, container runtime cleanup could delete proxy.sock.HttpIPCClient (set via AX_HOST_URL). Streams are dummy PassThrough -- response comes via HTTP IPC agent_response. Work dispatch also uses HTTP via SessionPodManager.enforceTimeout() handles this with try/catch..ax/skills/ in the git workspace. Don't add separate filesystem mounts for identity or skills.web-proxy.sock lives in the same directory as the IPC socket (already mounted into containers). canonicalEnv() computes the path from dirname(config.ipcSocket). No extra mount needed.server-k8s.ts passes AX_WEB_PROXY_URL pointing to a k8s Service (ax-web-proxy.{namespace}.svc:3128). Network policy allows pods to reach the proxy service./workspace is backed by a PVC in k8s mode. State persists across pod restarts. Pod idle timeout is 5 minutes since the PVC preserves everything.exited flag.node_modules/.bin/tsx) not npx inside sandboxes.src/providers/sandbox/types.ts -- SandboxConfig, SandboxProcess, SandboxProvider interfacessrc/providers/sandbox/canonical-paths.ts -- Canonical path constants, env builders, symlink helperssrc/providers/sandbox/docker.ts -- Docker container provider (--network=none, gVisor optional)src/providers/sandbox/apple.ts -- Apple Container provider (VM-based, --publish-socket IPC bridge)src/providers/sandbox/k8s.ts -- Kubernetes pod provider (HTTP IPC, SessionPodManager)src/providers/sandbox/utils.ts -- Shared sandbox helpers (exitCodePromise, enforceTimeout, etc.)src/agent/local-sandbox.ts -- Agent-side local tool execution with host audit gatesrc/host/provider-map.ts -- Static allowlist (sandbox: docker, apple, k8s)tests/providers/sandbox/ -- Tests: k8s, docker, apple, subprocess, k8s-warm-pool, k8s-ca-injection, canonical-paths, utils