PAL Runtime · for builders · Apache-2.0

Agents that finish, stay contained, and stay yours.

An open-source runtime for the agents you build. Write one on your laptop and run the same thing in your cloud: runs that survive a crash, code in a microVM, spend capped before the model is called, and a receipt for every run.

01 · Laptop to cloud

Design on your laptop. Ship the same agent.

The full stack runs locally with one command, including real Firecracker microVMs on an Apple Silicon Mac. An agent is one agent.yaml; each version is stored as an immutable bundle with a digest, so what you tested is what runs.

  • Point the CLI at another control plane and deploy the same file.
  • The data plane runs in your VPC (EKS, GKE, AKS or a plain host) and dials out. No inbound ports.
Firecracker on a Mac
Architecture: SDK, CLI and console talk to the control plane. The data plane runs in your VPC with the workflow engine, runtime manager, microVMs and your data. It dials out; inbound connections are refused.
  1. Describe

    pal onboard turns one sentence into a running agent. Or scaffold with pal init.

  2. Iterate

    pal agents dev watches the folder, republishes on save and streams run events.

  3. Gate

    pal test runs the eval suite against your baseline. A regression returns 422 and blocks the deploy.

  4. Deploy

    pal deploy sends the same agent.yaml to whichever control plane you’re logged in to.

The CLI is pal (pal still works as an alias). pal doctor checks health, auth, your model provider and a live run.

02 · Checkpoints and crash-resume

A crash doesn’t start the run over.

Every step is written to the run’s journal as it finishes. If a worker dies, a recovery sweep reclaims the run: finished steps come back from the journal and only the unfinished step runs again.

  • The sweep runs every 30 seconds; a lock makes sure one worker resumes a run.
  • Side effects carry an idempotency key from run, step and attempt, so a retried model call is billed once.
  • Child runs are created and driven by the engine itself, with a depth cap of 5.
Durable execution
Durable runs: four steps run and are journaled, the worker crashes on the send step, the recovery sweep reclaims the run, cached steps are skipped, send runs once, and a signed receipt verifies.

03 · Sandboxes

Untrusted code runs in a microVM you can pause, resume and fork.

Firecracker or Kata, never a bare container. Pause snapshots a VM and frees its quota slot; resume brings back the same id. Set an idle timeout and it suspends itself, then wakes on the next exec.

—

Press Spawn to start a sandbox.

Times measured on a dev laptop under nested virtualization (Apple M4 Max, Lima; 1 vCPU, 128 MiB VM; 2026-10-27), not production hardware. Method and raw numbers.

Nothing leaks.

In broker mode the VM has no network device at all. Its only way out is a broker on the host, which checks each request against the allowlist, adds credentials the guest never sees, and audits every decision.

  • Requests to hosts off the allowlist are refused and audited.
  • Injected credentials never enter the VM, its logs or its audit trail.

Broker mode is opt-in and Firecracker-only1.

Egress and secrets
Egress: agent code in a microVM with no network device reaches the host broker over vsock. A GitHub call is allowed and gets its token injected; a POST containing card numbers is blocked; an unknown host is refused. Every decision is audited.

04 · Invoke

Call it the way your system already works.

  • Request

    POST /v1/runs

    Start a run from the SDK, CLI or HTTP, then read its result.

  • Stream

    GET /v1/runs/{id}/events

    Server-sent events as steps run, end to end with no buffering.

  • Webhook

    POST /v1/hooks/{id}

    A signed URL per agent, so GitHub, Sentry or any HMAC sender can start runs.

  • Cron

    POST /v1/schedules

    Cron expressions in the timezone you choose.

  • Batch & callbackscoming soon

    —

    Many inputs in one call, with a callback when they finish.

05 · Budgets and routing

A budget before the first token.

Cap what an agent may spend. Over the cap, the run is refused with HTTP 402 and the model is never called. Code asks for a capability like reasoning-large; the router picks the vendor and fails over when one is down.

Guardrails: a run asks for reasoning-large, passes the budget check, the router fails over from a rate-limited provider, cost is recorded; the next run is refused with 402 and a regressed eval suite returns 422.
const pal = new PalClient({
  baseUrl: "http://localhost:8080",
  apiKey: process.env.PAL_API_KEY,
});

await pal.agents.create({ name: "triage" });
await pal.budgets.upsert("triage", {
  maxCostUsdPerDay: 25,
  hardFail: true,
});

await pal.runs.create({ agent: "triage", input });

TypeScript shown; Python and Go SDKs cover the same surface.

402 Payment RequiredPOST /v1/runs
{
  "error": "agent budget limit reached: daily spend would reach $25.0318 exceeding limit $25.0000",
  "reason": "daily spend would reach $25.0318 exceeding limit $25.0000"
}
No model call is made. The fields and wording are the handler’s; the dollar amounts are an example2.

06 · Telemetry

Every request and step is a span.

OpenTelemetry traces on every HTTP request, gRPC call and run step, tagged with tenant, run, step and agent version, so one filter finds one run across every service.

  • Prometheus alerts, dashboards and runbooks ship in the repo.
  • Each run’s journal streams live and replays later.
  • Off by default and free when off: no endpoint, no cost.
Observability
tracetenant · run · step · agent_version
  • POST /v1/runs
  • budget check
  • step plan · llm
  • step fetch · tool (microVM)
  • step reply · llm
  • receipt
Illustration. Span names and widths are an example.

07 · Resilience

Built to keep going, and to fail loudly.

Provider failover

Four routing strategies (cheap, fast, best, balanced). A rate-limited provider is skipped and the next one answers.

Retries that don’t double-bill

The same idempotency key rides every retry, so the provider dedups instead of charging twice.

Runs get reclaimed

A run whose worker vanished is picked up by the recovery sweep within 30 seconds and resumed, not restarted.

Heartbeats

Each VM’s harness reports every 5 seconds; one silent for 30 is marked stale.

No fake success

With no model router or VM manager wired, a step fails with a typed error instead of inventing an answer.

Refuses unsafe prod

In production, services refuse to start without their service token, scheduler or real VM manager.

08 · Security

Built for more than one tenant from day one.

Tenant isolation

Every row carries a tenant, every internal call carries it too, and row-level security policies cover every tenant table3.

Trust boundaries

Internal gRPC ports accept a tenant only from callers holding the service token.

Secrets by reference

Code refers to a secret by name; the value is resolved at execution time and never lands in logs, traces or run state.

Signed receipts

A finished run gets an Ed25519-signed receipt over its journal; anyone can verify it, and any later edit breaks the signature4.

Scoped API keys

Keys carry scopes, and a key can’t mint another key with more power than it has.

Open to audit

Apache-2.0, Go and Rust, with tests and linters gating every change in CI.

Security model

Proof, not promises

PAL runs on PAL Runtime.

The personal agent we use every day runs on this layer. Try the whole stack with Docker, no API key: the demo replays recorded model answers.

git clone https://github.com/dshakes/pal.git && cd pal && make demo
  1. 1.Without broker mode the allowlist is enforced inside the VM by a proxy, and secrets you vend to the workload do enter the guest.
  2. 2.Enforced today: per-agent daily cost, runs and tokens, and a tenant-wide daily cost cap. Workflow runs also stop mid-run at a per-run cap and at per-tool limits.
  3. 3.Policies are in place on every tenant table. Enforcement is on in production whenever PAL_APP_DB_PASSWORD is set; without it the control plane refuses to start unless you set PAL_RLS_ENFORCE=0.
  4. 4.Ed25519 once a signing key is configured; development falls back to HMAC.