Agentic Workflows

This guide covers building automated and AI-agent-driven workflows on top of deployed sandboxes. Every command used here supports --format json for deterministic parsing — no regex required.

Prerequisites

  • heyvm installed (Quickstart)
  • A Heyo cloud account (sign up at heyo.computer)
  • Logged in via heyvm login or have a JWT token available

Why sandboxes for agents

AI agents and automation scripts need isolated environments to run untrusted code, install arbitrary packages, and expose services — without affecting the host. Deployed sandboxes provide:

  • Isolation: Each sandbox is a full VM with its own filesystem, network, and process tree
  • Public URLs: Bind ports to https://<subdomain>.heyo.computer in one step
  • Programmatic control: JSON output on every command, plus a REST API for direct integration
  • Ephemeral by design: TTL-based auto-cleanup prevents resource leaks

1. One-command deployment

The deploy command handles the entire workflow — archive, create, bind ports, and health check — in a single call:

heyvm deploy ./my-app \
  --name my-agent-sandbox \
  --port 8080 \
  --start-command "python3 -m http.server 8080" \
  --health-path /health \
  --size-class small \
  --format json
{
  "id": "a1b2c3d4-...",
  "name": "my-agent-sandbox",
  "status": "running",
  "archive_id": "ar-x7y8z9",
  "urls": [
    {
      "subdomain": "abc123",
      "hostname": "abc123.heyo.computer",
      "url": "https://abc123.heyo.computer",
      "port": 8080,
      "is_public": true
    }
  ]
}

Deploy options

FlagDescription
--port <PORT>Port to expose publicly (repeatable)
--start-command <CMD>Long-running process to keep the sandbox alive
--health-path <PATH>HTTP path to poll for readiness (e.g. /health)
--health-timeout <DUR>How long to wait for health check (default: 120s)
--size-class <CLASS>micro, mini, small, medium, or large
--region <REGION>US or EU
--env <KEY=VALUE>Environment variable (repeatable)
--setup-hook <CMD>Shell command to run after mount (repeatable)
--ttl-seconds <SEC>Auto-destroy after this many seconds
--privateRequire authentication on bound ports
--format jsonMachine-readable output

2. Execute commands remotely

Run commands inside a deployed sandbox and get structured output:

heyvm exec my-agent-sandbox --format json -- python3 -c "print('hello')"
{
  "exit_code": 0,
  "stdout": "hello\n",
  "stderr": ""
}

Use --timeout to set a deadline:

heyvm exec my-agent-sandbox --format json --timeout 30s -- ./run-tests.sh

3. List and discover sandboxes

Enumerate all sandboxes — including deployed ones — with their bound URLs:

heyvm list --format json
[
  {
    "id": "a1b2c3d4-...",
    "name": "my-agent-sandbox",
    "status": "running",
    "image": "ubuntu:24.04",
    "is_deployed": true,
    "region": "US",
    "urls": [
      {
        "subdomain": "abc123",
        "hostname": "abc123.heyo.computer",
        "url": "https://abc123.heyo.computer",
        "port": 8080,
        "is_public": true
      }
    ]
  }
]

This solves the "deploy from Machine A, connect from Machine B" problem — agents can discover sandbox URLs without manual copy-paste.

4. Wait for readiness

Inside the sandbox (port check)

Wait for a port to be listening inside the sandbox:

heyvm wait-for my-agent-sandbox 8080 --path /health --timeout 60s --format json
{
  "ready": true,
  "port": 8080
}

External URL (public endpoint check)

Wait for a public URL to be routable through the proxy:

heyvm wait-for --url https://abc123.heyo.computer/health --timeout 60s --format json
{
  "ready": true,
  "url": "https://abc123.heyo.computer/health"
}

Use --url mode when you need to verify end-to-end connectivity through the proxy, not just that a process is listening inside the sandbox.

5. Bind additional ports

If you need to expose a port after deployment:

heyvm bind my-agent-sandbox 3000 --format json
{
  "subdomain": "def456",
  "hostname": "def456.heyo.computer",
  "url": "https://def456.heyo.computer",
  "port": 3000,
  "is_public": true
}

6. Clean up

heyvm stop my-agent-sandbox --format json
{
  "id": "a1b2c3d4-...",
  "status": "stopped"
}

Alternatively, set --ttl-seconds during deploy and let sandboxes expire automatically.

Example: Python agent script

This script deploys a sandbox, waits for it, runs a command, and retrieves the result:

import subprocess
import json

def heyvm(*args):
    """Run a heyvm command and return parsed JSON."""
    result = subprocess.run(
        ["heyvm", *args, "--format", "json"],
        capture_output=True, text=True, check=True,
    )
    return json.loads(result.stdout)

# Deploy an app with a health endpoint
deploy = heyvm(
    "deploy", "./my-app",
    "--name", "agent-worker",
    "--port", "8080",
    "--start-command", "python3 server.py",
    "--health-path", "/health",
    "--ttl-seconds", "3600",
    "--size-class", "small",
)
sandbox_id = deploy["id"]
public_url = deploy["urls"][0]["url"]
print(f"Deployed: {public_url}")

# Run a command inside the sandbox
result = heyvm("exec", sandbox_id, "--", "python3", "-c", "import sys; print(sys.version)")
print(f"Python version: {result['stdout'].strip()}")

# Clean up when done
heyvm("stop", sandbox_id)

Example: Bash automation

#!/usr/bin/env bash
set -euo pipefail

# Deploy and capture the output
DEPLOY=$(heyvm deploy ./my-app \
  --name "ci-sandbox-$$" \
  --port 8080 \
  --start-command "npm start" \
  --health-path /health \
  --ttl-seconds 1800 \
  --format json)

SANDBOX_ID=$(echo "$DEPLOY" | jq -r '.id')
PUBLIC_URL=$(echo "$DEPLOY" | jq -r '.urls[0].url')

echo "Sandbox $SANDBOX_ID running at $PUBLIC_URL"

# Run integration tests against the public URL
heyvm exec "$SANDBOX_ID" --format json -- npm test \
  | jq -r '.stdout'

# Tear down
heyvm stop "$SANDBOX_ID"

Example: Multi-sandbox orchestration

Agents can manage pools of sandboxes for parallel workloads:

import subprocess
import json
from concurrent.futures import ThreadPoolExecutor

def heyvm(*args):
    result = subprocess.run(
        ["heyvm", *args, "--format", "json"],
        capture_output=True, text=True, check=True,
    )
    return json.loads(result.stdout)

def deploy_worker(task_id):
    deploy = heyvm(
        "deploy", "./worker",
        "--name", f"worker-{task_id}",
        "--port", "8080",
        "--start-command", f"python3 worker.py --task {task_id}",
        "--health-path", "/health",
        "--ttl-seconds", "600",
        "--size-class", "micro",
    )
    return deploy["id"], deploy["urls"][0]["url"]

# Spin up 5 workers in parallel
with ThreadPoolExecutor(max_workers=5) as pool:
    workers = list(pool.map(deploy_worker, range(5)))

for sandbox_id, url in workers:
    print(f"Worker {sandbox_id}: {url}")

# Later: check status of all sandboxes
sandboxes = heyvm("list", "--all")
for sb in sandboxes:
    if sb["name"].startswith("worker-"):
        print(f"  {sb['name']}: {sb['status']}")

Using the REST API directly

For tighter integration, agents can call the heyvm API server directly instead of shelling out to the CLI. Start the API server:

heyvm --api --port 3000

Then use HTTP requests:

import requests

API = "http://localhost:3000"

# Create a sandbox
sandbox = requests.post(f"{API}/sandboxes", json={
    "name": "api-agent",
    "image": "ubuntu:24.04",
    "start_command": "python3 -m http.server 8080",
    "open_ports": [8080],
    "ttl_seconds": 3600,
}).json()

# Execute a command
result = requests.post(
    f"{API}/sandboxes/{sandbox['id']}/execute",
    json={"command": "python3", "args": ["-c", "print('hello')"]},
).json()
print(result["output"])  # "hello\n"

# Upload a file
requests.post(
    f"{API}/sandboxes/{sandbox['id']}/files/upload",
    json={
        "mount_path": "/workspace",
        "file_path": "script.py",
        "content": "cHJpbnQoJ2hlbGxvJyk=",  # base64
    },
)

# Clean up
requests.delete(f"{API}/sandboxes/{sandbox['id']}")

See API Mode for the full endpoint reference.

Authentication for agents

CLI authentication

The simplest approach: run heyvm login once on the machine where the agent runs. The CLI stores credentials and uses them automatically.

Token-based authentication

For CI/CD or ephemeral environments, pass a JWT token explicitly:

export HEYO_ARCHIVE_TOKEN="your-jwt-token"
heyvm deploy . --port 8080 --format json

Or per-command:

heyvm deploy . --port 8080 --token "$HEYO_TOKEN" --format json

Private endpoints

Use --private to restrict endpoint access to your account. Clients authenticate via Bearer token:

curl -H "Authorization: Bearer $HEYO_TOKEN" https://abc123.heyo.computer/api/data

See Private Endpoints for details on the authentication flow.

Best practices

  • Always use --format json — text output is for humans and may change between versions
  • Set a TTL — agents should always set --ttl-seconds to prevent leaked sandboxes from running indefinitely
  • Use --health-path — the deploy command will wait for your app to be ready before returning, so the URL is usable immediately
  • Prefer deploy over individual commands — one command instead of five means fewer failure points and no intermediate state to parse
  • Use list --format json for discovery — agents on different machines can find sandbox URLs without out-of-band coordination
  • Name sandboxes deterministically — use --name with a convention like agent-<task-id> so you can find and clean up sandboxes later