Skip to content

Headless agent quickstart

A headless agent has no conversation. You package code as an image, describe it in a short agent.yaml, and PAL Runtime picks a machine, builds the sandbox, runs it, streams its logs and cleans up. This guide runs the smallest of the four demos in examples/headless-agents/ and takes about 15 minutes. It is for anyone who wants a background worker that runs under an isolation class instead of on the host.

A run on the microVM tierFrom a scheduled AgentSpec to streamed output: quota, placement, boot, secrets, egress, model calls and the journal.

1. POST /v1/runtime/schedule

Straight to the control plane on :8080 (the gateway does not serve /v1/runtime). An AgentSpec: image, isolation, limits, egress rules, secret refs, idleTimeoutSecs. A JWT or API key; the tenant comes from the credential, never the body.

From the repo root, in separate terminals:

Terminal window
make dev-infra # Postgres + Redis (MinIO is not started)
make run-runtime-manager # runtime-manager on :50054
make run-scheduler # scheduler on :50055 / :8085
make run-api-runtime # control plane wired to the scheduler, :8080

Docker on the host is enough for the trusted and standard classes. Export an API token as PAL_API_TOKEN for the REST calls below.

An agent.yaml declares the image, the isolation class, limits and any egress or secrets. This is examples/headless-agents/01-hello/agent.yaml: a first-party script that writes to stdout, so it uses trusted and no network.

apiVersion: pal.dev/v1
kind: AgentSpec
metadata:
name: hello
labels:
owner: pal-team
spec:
image_digest: pal/demos/hello@sha256:0000000000000000000000000000000000000000000000000000000000000001
isolation: trusted # first-party, no package loading
limits:
vcpu: "100m" # 0.1 vCPU
memory: "64Mi"
timeout: 30s
scratch_size: "16Mi"
network: none # stdout only, no egress
secrets: []
egress_rules: []
idempotent: true

The image is pinned by digest, not a tag, so the runtime runs exactly the bytes you named.

Your code Class
First-party, signed trusted
Your own code (the usual choice) standard
Loads internet packages or drives a browser untrusted
Adversarial input hostile

Demo 02 (web-scraper) uses untrusted because it installs packages from PyPI. The full decision tree is in Isolation.

Terminal window
pal run examples/headless-agents/01-hello/agent.yaml \
--input '{"name": "Ada"}' # add --follow to stream logs after scheduling

The CLI posts the parsed spec to POST /v1/runtime/schedule. A successful call returns HTTP 201 with a vmId.

  • "stub": true in the response means the control plane is on the stub scheduler and nothing was spawned. Set PAL_SCHEDULER_GRPC_ADDR; make run-api-runtime does this.
  • Over your per-tenant quota, the call returns HTTP 402 instead.

For debugging, pal vm exec <id> -- <cmd> runs a command in the VM, and --idle-timeout N auto-suspends an idle sandbox.

Stream the harness log lines over SSE, then read the instance and its recent audit events:

Terminal window
curl -N http://localhost:8080/v1/runtime/vms/<vmId>/logs \
-H "Authorization: Bearer $PAL_API_TOKEN"
curl http://localhost:8080/v1/runtime/vms/<vmId> \
-H "Authorization: Bearer $PAL_API_TOKEN"

The dashboard at localhost:3001/runtime shows the same instance with its log stream and resource usage, and lets you exec in. With an OTel collector wired (Observability) you get one trace per spawn. Aggregate counters are at GET /v1/runtime/metrics.

A workload that exits cleanly is torn down automatically. To stop one early:

Terminal window
curl -X DELETE "http://localhost:8080/v1/runtime/vms/<vmId>?grace=30s" \
-H "Authorization: Bearer $PAL_API_TOKEN"

The instance drains for the grace period, the manager tears it down, and the scheduler marks it terminated. States and transitions are in microVM lifecycle.

  • Idle-suspend suits long-lived exec sandboxes. A workload VM that runs one command and exits gains nothing from idleTimeoutSecs.
  • The stub scheduler fabricates VM ids and spawns nothing. The control plane refuses to start with it in production.

Next: Invoke an agent. For a sandbox that works on real data with egress denied by default, see examples/data-analyst.