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 loginor 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.computerin 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
| Flag | Description |
|---|---|
--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 |
--private | Require authentication on bound ports |
--format json | Machine-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.sh3. 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 jsonPrivate 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-secondsto prevent leaked sandboxes from running indefinitely - Use
--health-path— thedeploycommand will wait for your app to be ready before returning, so the URL is usable immediately - Prefer
deployover individual commands — one command instead of five means fewer failure points and no intermediate state to parse - Use
list --format jsonfor discovery — agents on different machines can find sandbox URLs without out-of-band coordination - Name sandboxes deterministically — use
--namewith a convention likeagent-<task-id>so you can find and clean up sandboxes later