sandbox-functionswebhookscoding-agentsvolumesgit

Sandbox Functions: Keep Coding Agent Repositories Warm

Sandbox0 Team·

Coding agents need the current repository before they can do useful work.

That sounds obvious, but it is one of the easiest runtime costs to hide. Every background coding task tends to start with the same setup path:

  • clone or fetch the repository
  • check out the right branch
  • install dependencies
  • recover package caches and generated files
  • configure Git credentials
  • create an isolated workspace so parallel agents do not collide

For a small public repository, this may be acceptable. For a private monorepo, or for a platform launching many short agent tasks, it becomes part of cold start.

The problem is not only "how fast can a sandbox boot?" The better question is: how quickly can the agent receive a fresh, isolated, writable workspace?

That is where Sandbox Functions fit.

The Market Is Moving Toward Warm Repositories#

This is not a synthetic use case.

OpenAI described Codex as running each task in an isolated cloud environment preloaded with the user's repository. The later Codex app announcement emphasized built-in worktrees, cloud environments, and multi-agent workflows.

GitHub Copilot cloud agent now exposes task creation through an API for automation such as fan-out refactors across many repositories. GitHub's setup docs also make repository checkout part of the agent environment contract: if the setup workflow does not check out code, Copilot does it automatically.

Claude Code worktrees make the local version of the same need explicit: parallel sessions should run in separate worktrees so file edits do not collide, and new worktrees normally branch from the remote default branch.

Infrastructure vendors are converging on the same primitive. Runloop Code Mounts exist because mounting a repository into an agent devbox is a better product surface than telling every user to script git clone. Vercel Sandbox persistence is now on by default, with resume and fork operations for long-lived sandboxes. The GitFarm paper makes the broader systems point: repeated clone and fetch operations are a real bottleneck at monorepo scale, and pre-warmed repositories can turn repository access into a low-latency service.

For coding-agent infrastructure, a fresh repository is not just input data. It is runtime state.

What Sandbox Functions Are#

A Sandbox Function is a Sandbox Service with runtime.type: function.

Instead of starting and managing a user HTTP server, you store inline function source in the service config. When a public request matches a route, Sandbox0:

  1. applies gateway route policy such as method filtering, auth, rate limits, CORS, timeout, path rewrite, and auto-resume;
  2. forwards the normalized request to procd inside the sandbox;
  3. executes the function handler inside the sandbox runtime;
  4. returns the handler's HTTP response.

The current function runtime is Python with inline source. The sandbox image must have python3 in PATH.

That placement is the important part. A Sandbox Function is not a separate serverless island. It runs inside the sandbox, next to the workspace, tools, mounted Volumes, and network policy already used by the agent runtime.

This makes functions useful for small, event-driven pieces of runtime logic:

  • webhooks
  • repository refreshers
  • artifact indexers
  • async job triggers
  • health or status endpoints
  • lightweight callbacks from external systems

The function does not need to run a listener. It only runs when traffic arrives.

Use Case: Keep a Base Repository Volume Fresh#

The coding-agent workflow looks like this:

The base Volume is the mutable maintenance workspace. A webhook function keeps it close to the latest remote state. The handoff to coding agents is an immutable snapshot plus a commit marker, not a writable mount of the active base Volume.

When a coding-agent task starts, the control plane creates a new RWO Volume from the latest published snapshot and mounts that new Volume into the task sandbox.

The agent does not need to clone the repository from scratch. It starts from a fresh workspace that already has repository objects, selected dependencies, generated indexes, or whatever else the platform chooses to keep in the base workspace.

The operational shape is:

  1. Create a SandboxVolume for the base repository cache.
  2. Claim a maintenance sandbox from a template that includes git, python3, and a writable workspace mount such as /workspace.
  3. Attach a function service with a public route like /webhook/github.
  4. Configure a GitHub webhook to call the returned public_url.
  5. The function verifies the webhook, fetches the latest branch, and writes a commit marker into the mounted Volume.
  6. The orchestrator creates a snapshot. For an active RWO mount, Sandbox0 checkpoints the writer before the snapshot is published.
  7. The agent launcher creates a new Volume from the latest snapshot for each coding-agent task.

This distinction matters. An active RWO Volume has one writable owner, so direct fork of an actively mounted base Volume is not the handoff path. The stable handoff is the latest published snapshot. If a refresh is still in progress, the launcher can either use the previous published snapshot or wait for the next pointer.

ROX mounts are already read-only and do not need a writable flush. Active writable RWX snapshots currently return 409 Conflict, so use RWO for this repository-maintenance Volume.

A Minimal Function Service#

This example shows the service shape. In production, keep the repository URL, webhook secret, and credential behavior outside the inline source.

yaml
services: - id: github-refresh port: 49983 runtime: type: function env_vars: REPO_URL: https://github.com/acme/app.git REPO_BRANCH: main REPO_CACHE_DIR: /workspace/cache/app GITHUB_WEBHOOK_SECRET: replace-with-webhook-secret function: runtime: python handler: handler source: type: inline code: | import base64 import hashlib import hmac import json import os import subprocess import time from contextlib import contextmanager from pathlib import Path REPO_URL = os.environ["REPO_URL"] BRANCH = os.environ.get("REPO_BRANCH", "main") REPO_DIR = Path(os.environ.get("REPO_CACHE_DIR", "/workspace/cache/app")) SECRET = os.environ.get("GITHUB_WEBHOOK_SECRET", "") def header(request, name): headers = request.get("headers") or {} values = headers.get(name) or headers.get(name.lower()) or [] return values[0] if values else "" def verify_signature(raw_body, request): if not SECRET: return False expected = "sha256=" + hmac.new( SECRET.encode("utf-8"), raw_body, hashlib.sha256, ).hexdigest() actual = header(request, "X-Hub-Signature-256") return hmac.compare_digest(expected, actual) def run(args): completed = subprocess.run( args, check=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, ) if completed.returncode != 0: raise RuntimeError( "command failed: " + " ".join(args) + "\n" + completed.stderr[-4000:] ) @contextmanager def refresh_lock(lock_dir): deadline = time.monotonic() + 30 while True: try: lock_dir.mkdir() break except FileExistsError: if time.monotonic() >= deadline: raise TimeoutError(f"timed out waiting for {lock_dir}") time.sleep(0.2) try: yield finally: try: lock_dir.rmdir() except FileNotFoundError: pass def handler(request): raw_body = base64.b64decode(request.get("body_base64") or "") if not verify_signature(raw_body, request): return {"status": 401, "body": "invalid signature"} payload = json.loads(raw_body or b"{}") if payload.get("ref") != f"refs/heads/{BRANCH}": return {"status": 202, "body": "ignored ref"} REPO_DIR.parent.mkdir(parents=True, exist_ok=True) lock_dir = REPO_DIR.parent / ".refresh.lock" with refresh_lock(lock_dir): if not (REPO_DIR / ".git").exists(): run([ "git", "clone", "--filter=blob:none", "--branch", BRANCH, REPO_URL, str(REPO_DIR), ]) else: run(["git", "-C", str(REPO_DIR), "fetch", "origin", BRANCH, "--prune"]) run(["git", "-C", str(REPO_DIR), "reset", "--hard", f"origin/{BRANCH}"]) run(["git", "-C", str(REPO_DIR), "clean", "-fdx"]) commit = subprocess.check_output( ["git", "-C", str(REPO_DIR), "rev-parse", "HEAD"], text=True, ).strip() (REPO_DIR / ".sandbox0-base-commit").write_text(commit + "\n") return { "status": 200, "headers": {"content-type": "application/json"}, "body": {"ok": True, "commit": commit}, } ingress: public: true routes: - id: github path_prefix: /webhook/github methods: [POST] timeout_seconds: 120 rate_limit: rps: 1 burst: 4 resume: true

Apply the service with the CLI:

bash
s0 sandbox service update --services-file function-services.yaml -s sb_repo_cache

Function services use the sandbox function port, 49983, when applied through s0.

Then configure the GitHub webhook target as:

bash
$PUBLIC_URL/webhook/github

where PUBLIC_URL is the function service URL returned by s0 sandbox service get or the PUT /api/v1/sandboxes/{id}/services response.

Publish a Snapshot for Agent Tasks#

After the base workspace has been refreshed, publish a snapshot:

bash
s0 volume snapshot create vol_base_repo \ --name repo-main-$COMMIT \ --description "commit=$COMMIT"

Store the returned snapshot ID with the commit that the function wrote into .sandbox0-base-commit:

json
{ "volume_id": "vol_base_repo", "snapshot_id": "snap_repo_main_20260528", "commit": "7fd1a60b01f91b314f59955a4e4d4e80d8edf11d" }

For each coding-agent task, create a fresh writable Volume from that immutable snapshot:

bash
s0 volume create --snapshot-id snap_repo_main_20260528 --access-mode RWO

Then claim the coding-agent sandbox with that per-task Volume mounted at the template-declared workspace path:

json
{ "template": "coding-agent", "mounts": [ { "sandboxvolume_id": "vol_task_from_snapshot", "mount_point": "/workspace" } ] }

Each agent gets an isolated writable workspace. The base repository Volume remains the prepared maintenance workspace. Snapshot-created Volumes are copy-on-write, so unchanged data is shared until the agent writes new files.

This pattern also gives the orchestrator a useful commit marker. The agent launcher can attach the published commit and snapshot ID to the task receipt, and later explain exactly which repository state the agent used.

Why Use a Function Instead of a Listener?#

You can build the same workflow with a normal HTTP service inside the sandbox. The function form is useful when the route is small and event-driven.

A webhook refresher should not require a resident web server just to receive occasional push events. The gateway already owns public ingress, route matching, auth, rate limits, and auto-resume. procd can execute the handler on demand, then return the HTTP response.

Use a function when:

  • the route is a small event-driven callback or request-scoped handler
  • no user-managed listener should stay running
  • for normal HTTP responses, the handler can finish inside the function timeout
  • the logic benefits from running beside mounted workspace state

Use a listener-backed cmd or warm_process service when:

  • you need a long-running HTTP service
  • the process maintains in-memory state across requests
  • startup readiness matters more than per-request execution
  • cmd: first public access may start the command and wait for readiness
  • warm_process: the template should pre-start the listener before claim

Use a template warm process, not a public service, for a pure queue worker that does not expose an HTTP listener.

Auto-resume changes wake-up behavior, not runtime ownership. A public service route can wake a paused or cleaned sandbox only when both sandbox auto_resume and route resume are enabled. Route policy is evaluated before wake-up, so rejected method, auth, CORS preflight, or rate-limit requests do not restart compute. After auto-resume, a function service still executes per request with no listener to restart. A cmd service may create the command context and wait for readiness. A warm_process service expects the template listener to exist; after a hard clean, resume creates a new runtime pod rather than restoring previous in-memory process state.

For very large monorepos, the first clone may exceed a function timeout. That is fine. Seed the initial Volume through a maintenance job or template build, then use the function for incremental webhook refreshes.

Security Boundaries#

A repository webhook is still an external entrypoint into an execution environment.

The safe version of this pattern has a few rules:

  • verify the GitHub webhook signature inside the function
  • restrict route methods to POST
  • rate-limit the webhook route
  • use narrow network policy for GitHub and package registries
  • avoid broad personal access tokens in inline source or sandbox environment variables
  • prefer GitHub App installation tokens, deploy keys, or Sandbox0 credential injection when private repository access is required
  • keep the base Volume writable only by the maintenance path
  • create a fresh per-task Volume from a published snapshot instead of letting agents write directly to the base
  • record the refreshed commit, snapshot ID, and task Volume ID in the task receipt

The function owns the webhook action. The agent owns the coding task. The Volume boundary keeps those two workflows from mutating the same workspace.

Other Places Sandbox Functions Fit#

The GitHub refresh workflow is only one example.

Sandbox Functions are a good fit anywhere a sandbox needs a small public callback without a long-running listener:

  • GitLab, Linear, Jira, or Slack webhooks that enqueue sandbox work
  • refreshing documentation indexes after a docs repository changes
  • warming package-manager caches after lockfile updates
  • triggering benchmark or evaluation runs from CI
  • receiving external approval callbacks for an agent workflow
  • exposing a lightweight status route for a sandbox-hosted job
  • syncing MCP server configuration into a workspace
  • receiving artifact upload notifications from an external build system

The common pattern is the same: keep the public route thin, run the handler close to the sandbox workspace, and persist durable state in Volumes.

The Runtime Primitive#

Sandbox Functions are small, but they change the shape of sandbox integrations.

Before functions, a webhook usually meant one of two things: keep an HTTP server running inside the sandbox, or put a separate service outside the sandbox and have it call back into the sandbox API.

With functions, the sandbox itself can expose a controlled HTTP callback. The handler runs where the files and tools are, while cluster-gateway keeps ownership of public ingress.

For coding agents, that opens a practical workflow:

webhook refreshes the base workspace, snapshot publication creates a stable handoff point, and each agent starts from a fresh writable Volume instead of rebuilding the repository.

That is the kind of runtime boundary agent infrastructure needs as coding agents move from one-off demos to background, parallel, and production workflows.

Further Reading#