Skip to main content
Longshot uses Modal to run coding agents in isolated, ephemeral sandboxes. Each task gets its own short-lived sandbox with a full development environment.

Ephemeral Sandbox Model

Longshot uses an ephemeral sandbox architecture:
  1. Task Assignment: Orchestrator assigns a task to the worker pool
  2. Sandbox Creation: A new Modal sandbox is spawned with the task payload
  3. Execution: The sandbox clones the repo, runs the coding agent, and produces a result
  4. Cleanup: The sandbox terminates immediately after task completion
This model provides:
  • Complete isolation between tasks
  • Clean slate for every agent run
  • No state pollution from previous tasks
  • Automatic resource cleanup

Basic Configuration

Configure sandbox resources in .env:
.env
# Docker image tag for sandboxes
SANDBOX_IMAGE_TAG=latest

# CPU cores per sandbox
SANDBOX_CPU_CORES=4

# Memory per sandbox in MB
SANDBOX_MEMORY_MB=8192

# Idle timeout before sandbox is terminated (seconds)
SANDBOX_IDLE_TIMEOUT=300

Resource Limits

Suitable for simple tasks (documentation, config files):
.env
SANDBOX_CPU_CORES=2
SANDBOX_MEMORY_MB=4096
Start with Standard (4 cores, 8GB) and adjust based on your workload. Monitor task execution times in the dashboard to identify if more resources are needed.

Sandbox Image

The sandbox image is defined in infra/sandbox_image.py and includes:

Base Tools

  • Node.js 22.x LTS - For TypeScript/JavaScript projects
  • Python 3.12 - For scripting and Python projects
  • Git - Version control operations
  • pnpm - Fast package manager

Development Tools

  • curl, wget - HTTP requests
  • jq - JSON processing
  • tree - Directory visualization
  • ripgrep (rg) - Fast code search
  • build-essential - C/C++ compilation tools

Longshot Components

  • @longshot/sandbox - The worker agent runtime
  • @longshot/core - Shared utilities
  • @mariozechner/pi-coding-agent - Coding agent SDK (v0.52.12)

Git Configuration

Sandboxes are pre-configured with git identity:
git config --global user.name "Longshot Bot"
git config --global user.email "[email protected]"
git config --global init.defaultBranch main
You can customize these in .env:
.env
GIT_COMMIT_NAME=Longshot Bot
GIT_COMMIT_EMAIL=[email protected]

Custom Sandbox Images

To add tools or dependencies to the sandbox image:

1. Edit infra/sandbox_image.py

Add your customizations to create_agent_image():
infra/sandbox_image.py
def create_agent_image() -> modal.Image:
    image = (
        modal.Image.debian_slim(python_version="3.12")
        .apt_install(
            "git", "curl", "wget", "jq", "tree",
            "build-essential", "ca-certificates", "gnupg",
            # Add your packages here:
            "postgresql-client",
            "redis-tools",
        )
        # ... rest of the image definition
    )
    return image

2. Install Language Runtimes

Example: Adding Go support:
.run_commands(
    "wget https://go.dev/dl/go1.21.5.linux-amd64.tar.gz",
    "tar -C /usr/local -xzf go1.21.5.linux-amd64.tar.gz",
    "rm go1.21.5.linux-amd64.tar.gz",
)
.env({"PATH": "/usr/local/go/bin:/usr/local/bin:/usr/bin:/bin"})

3. Rebuild and Deploy

After modifying the image:
# Build packages
pnpm install
pnpm build

# Test the image
modal run infra/sandbox_image.py

# Deploy to Modal
modal deploy infra/main.py

Sandbox Lifecycle

1. Creation

When a task is assigned, spawn_sandbox.py creates a new sandbox:
sb = modal.Sandbox.create(
    app=app,
    image=image,
    timeout=2400,        # 40 minute max lifetime
    workdir="/workspace",
)

2. Task Setup

The orchestrator writes task.json into the sandbox:
{
  "task": {"id": "task-001", "branch": "worker/task-001", ...},
  "systemPrompt": "You are a coding agent...",
  "repoUrl": "https://github.com/org/repo.git",
  "llmConfig": {"endpoint": "...", "model": "gpt-4o", ...}
}

3. Repository Clone

The sandbox clones the target repository:
git clone https://x-access-token:[email protected]/org/repo.git /workspace/repo
Note: Full clone (not --depth 1) is used so git diff against the start SHA works correctly.

4. Branch Setup

Two modes:
Creates a new feature branch:
git checkout -b worker/task-001

5. Agent Execution

The sandbox runs the worker agent:
node /agent/worker-runner.js
This:
  • Reads task.json
  • Initializes the Pi coding agent
  • Executes the task (read code, make changes, run tests)
  • Writes result.json with the outcome

6. Push Changes

If files were modified, the sandbox pushes the branch:
git push origin worker/task-001

7. Termination

The sandbox terminates automatically, releasing all resources.

Timeout Configuration

Worker Timeout

Maximum time for a task execution:
.env
# Default: 1800 seconds (30 minutes)
WORKER_TIMEOUT=1800
If a worker exceeds this timeout:
  1. The sandbox is terminated
  2. The task is marked as failed
  3. The dashboard shows a “TIMEOUT” event
Set WORKER_TIMEOUT lower than Modal’s sandbox timeout (2400s) to allow graceful cleanup.

Sandbox Idle Timeout

Modal terminates idle sandboxes after this duration:
.env
# Default: 300 seconds (5 minutes)
SANDBOX_IDLE_TIMEOUT=300
With the ephemeral model, SANDBOX_IDLE_TIMEOUT rarely triggers since sandboxes terminate immediately after task completion.

Monitoring Sandbox Activity

The dashboard shows real-time sandbox activity:

Worker Progress Events

As the agent works, progress events are streamed:
  • “Cloning repo…”
  • “Installing deps…”
  • “Reading codebase…”
  • “Writing implementation…”
  • “Running tests…”
  • “Committing changes…”
These appear in:
  1. The Activity Feed (Tab to switch)
  2. The Planner Tree (under each running task)

Sandbox Phase Events

Key lifecycle events:
[spawn] sandbox created for task task-001 (2.3s)
[spawn] repo cloned for task task-001 (8.7s)
[spawn] branch created for task task-001: worker/task-001
[spawn] starting worker agent for task task-001
[worker:task-001] Agent initialized, analyzing task...
[spawn] pushed branch worker/task-001 to origin
[spawn] task task-001 completed: complete
[spawn] sandbox terminated for task task-001

Troubleshooting

Sandbox Creation Slow

If sandbox creation takes >10 seconds:
  1. Cold start: First sandbox in a Modal session is slower
  2. Image size: Large custom images take longer to pull
  3. Modal capacity: Check Modal dashboard for regional issues
Solution: Use modal.Image layer caching to speed up subsequent starts.

Out of Memory Errors

If tasks fail with OOM errors:
  1. Increase SANDBOX_MEMORY_MB
  2. Optimize your build process (e.g., use pnpm install --prod)
  3. Split large tasks into smaller subtasks

Git Push Failures

If the sandbox can’t push branches:
  1. Verify GIT_TOKEN has push access
  2. Check repository permissions
  3. Ensure branch name doesn’t conflict with existing branches

Worker Timeout Issues

If tasks frequently timeout:
  1. Increase WORKER_TIMEOUT
  2. Check if the agent is stuck in a loop (review logs)
  3. Verify the task is achievable (not too complex)
  4. Check if tests are hanging
Ensure your Modal token is configured:
# Login to Modal
modal token new

# Verify authentication
modal profile current

Advanced: Custom Worker Runner

The worker runner (packages/sandbox/src/worker-runner.ts) can be customized:

Adding Pre-Task Hooks

worker-runner.ts
// Before agent execution
await runCommand("pnpm install", repoPath);
await runCommand("pnpm build", repoPath);

Custom Tool Integration

Extend the agent with custom tools:
import { AgentSession } from "@mariozechner/pi-coding-agent";

const session = new AgentSession({
  // ... config
  tools: [
    // Add custom tool definitions
    {
      name: "run_e2e_tests",
      description: "Run end-to-end tests",
      execute: async () => {
        return await runCommand("pnpm test:e2e", repoPath);
      },
    },
  ],
});

Environment Variables in Sandboxes

Pass environment variables to sandboxes via the task payload:
// In orchestrator
const payload = {
  task,
  systemPrompt,
  repoUrl,
  llmConfig,
  env: {
    DATABASE_URL: process.env.DATABASE_URL,
    API_KEY: process.env.API_KEY,
  },
};

Best Practices

Resource Allocation

  • CPU: 4 cores is sufficient for most TypeScript/JavaScript projects
  • Memory: 8GB handles most builds; increase for large monorepos
  • Timeout: 30 minutes is generous; most tasks complete in 5-15 minutes

Image Optimization

  • Keep base image minimal; add tools only when needed
  • Use layer caching for faster sandbox starts
  • Pre-install common dependencies in the image (not in sandbox runtime)

Cost Management

  • Monitor sandbox hours in Modal dashboard
  • Use appropriate resource limits (don’t over-provision)
  • Consider SANDBOX_IDLE_TIMEOUT for non-ephemeral workflows

Next Steps

Merge Strategies

Configure how branches are merged

Debugging

Debug sandbox execution issues

Build docs developers (and LLMs) love