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.
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.
Prerequisites
Section titled “Prerequisites”From the repo root, in separate terminals:
make dev-infra # Postgres + Redis (MinIO is not started)make run-runtime-manager # runtime-manager on :50054make run-scheduler # scheduler on :50055 / :8085make run-api-runtime # control plane wired to the scheduler, :8080Docker on the host is enough for the trusted and standard classes. Export an API token as PAL_API_TOKEN for the REST calls below.
1. Write the spec
Section titled “1. Write the spec”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/v1kind: AgentSpecmetadata: name: hello labels: owner: pal-teamspec: 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: trueThe image is pinned by digest, not a tag, so the runtime runs exactly the bytes you named.
2. Pick the isolation class
Section titled “2. Pick the isolation class”| 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.
3. Run it
Section titled “3. Run it”pal run examples/headless-agents/01-hello/agent.yaml \ --input '{"name": "Ada"}' # add --follow to stream logs after schedulingThe CLI posts the parsed spec to POST /v1/runtime/schedule. A successful call returns HTTP 201 with a vmId.
"stub": truein the response means the control plane is on the stub scheduler and nothing was spawned. SetPAL_SCHEDULER_GRPC_ADDR;make run-api-runtimedoes 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.
4. Watch it
Section titled “4. Watch it”Stream the harness log lines over SSE, then read the instance and its recent audit events:
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.
5. Terminate
Section titled “5. Terminate”A workload that exits cleanly is torn down automatically. To stop one early:
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.
Limits
Section titled “Limits”- 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.