CLI
pal is a single static Go binary that boots the local stack, scaffolds and runs agents, gates CI on evals, drives sandbox VMs, sets up the personal agent and installs the data plane in your cloud. It is for anyone who works from a terminal. Just looking? pal demo needs Docker and no API key. Building your own? pal dev boots the stack and pal onboard walks you through a first agent. Personal agent users start with pal setup.
| Area | Commands |
|---|---|
| Get started | demo, onboard, setup, doctor, dev |
| Build | init, agents (incl. agents dev), deploy, hooks |
| Ship safely | freeze, promote, approve, reject, rollback, canary, promotions |
| Govern an agent | agents policy, agents identity, agents audit |
| Run and observe | runs, logs, test, eval, debug, support |
| Sandbox VMs | run, vm, sandbox |
| Cloud | infra |
| Account | login, whoami, logout |
Install
Section titled “Install”Source lives at packages/cli/cmd/pal. The command below builds it under the name pal, so every command on this site works as written.
# Build from the repocd packages/cli && go build -o "$(go env GOPATH)/bin/pal" ./cmd/palpal --versionStarts the self-contained demo (kiosk mode): Docker only, no API key. Each visitor gets a private workspace with sample agents that is deleted after 30 minutes; answers are recorded replays from a local fixture server. Run it from inside a PAL checkout. It builds and starts Postgres, Redis, the control plane, the dashboard and the replay server as the compose project pal-demo, waits for /healthz and the demo page (up to 5 minutes), then opens http://localhost:3001/kiosk.
Flags: --no-open (don’t open the browser), --down (stop and delete all demo data). Host ports default to 8080 and 3001; override with KIOSK_API_PORT and KIOSK_WEB_PORT. See Demo.
pal demo # start (first run builds images)pal demo --no-open # start without opening the browserpal demo --down # stop and delete all demo dataonboard
Section titled “onboard”First run. On a terminal it is a conversation with PAL’s onboarding agent (the same one as the dashboard): say what you want, and it gets a working key (typed at a hidden prompt, never into the chat), designs the agent, creates it on your go-ahead and runs it. --steps uses the step-by-step wizard instead (health, sign-in, key test, design, confirm, streamed first run); it also takes over, labelled, when the agent can’t reach the model. With --yes or a non-terminal stdin it never prompts (CI). Flags: --provider (openai|anthropic), --api-key, --description, --steps. See Quickstart.
pal setup (PAL, the personal agent) works the same way: a conversation by default, --steps for the wizard, and consent only ever from your own keypress.
pal onboard # conversationpal onboard --steps # step-by-step wizardpal onboard --yes --provider anthropic --api-key "$KEY" \ --description "Triage support tickets" # CIAgentic first run for the personal agent: the terminal twin of the dashboard’s /pal/onboarding. The control plane owns the state and returns the step to resume, so the CLI, the dashboard and the server agree. Steps are welcome, channels, voice, trust, profile and done (your first Brief). Each step advances only on a real result: a live bridge check, a saved setting read back, your explicit confirmation. Consent is never given non-interactively.
Flags: --yes / -y never prompts; it reports where setup stands and does not change consent. --apply writes the setup into this Mac’s bridge config: confirmed facts and relationships into the owner profile, and quiet hours and the voice setting into bridge.env. Both files are backed up first. Bridge addresses come from PAL_BRIDGE_URL, PAL_IMESSAGE_BRIDGE_URL and PAL_DASHBOARD_URL, and tokens from PAL_BRIDGE_TOKEN and PAL_IMESSAGE_BRIDGE_TOKEN. See Set up PAL.
pal setup # interactive, resumes where you left offpal setup --apply # also write the result into the local bridge configpal setup --yes # report progress without promptingdoctor
Section titled “doctor”Runs readiness checks against the local stack, one pass/fail line each. Exits non-zero if any hard check fails. Run it before you report a bug. The Sandbox runtime section, which never fails the run, reports whether the API uses a real scheduler or the stub, whether the scheduler and runtime manager are reachable, and whether KVM and Firecracker are present. Each failing check prints its fix.
pal doctorBoots the local stack: Postgres, Redis and MinIO (Docker), the control-plane API (host Go process), the Next.js dashboard, and the WhatsApp and iMessage bridges (iMessage is skipped off macOS). Logs interleave in one terminal. Flags: --infra-only, --with-whatsapp=false, --with-imessage=false, --no-open, --dashboard-port (3001), --api-port (8080).
pal dev # start everythingpal dev down # stop infra containers (--volumes also deletes data)pal dev logs # tail all infra logspal dev logs postgres # tail one serviceScaffold a new agent directory with agent.yaml, src/index.ts, package.json and tsconfig.json. Without --template an interactive picker appears. Templates: basic (default), research, tool-use, connector, personal, scheduled, approval.
pal init my-agentpal init my-agent --template researchagents
Section titled “agents”Manage agents. Aliased as agent.
pal agents create --name my-agent --description "Triage tickets" [--label team=support]pal agents listpal agents get my-agentpal agents delete my-agent [--force]
# Development inner loop — watches the agent dir and re-publishes on changepal agents dev my-agent [--dir ./my-agent] [--input '{"prompt":"hi"}'] [--eval] [--yes]agents dev (also pal agent dev) is the agent development inner loop: it watches the local directory for changes to agent.yaml or code files, re-publishes the agent version on each change, runs a smoke input (--input), and streams the run events. --eval also runs the agent’s eval suite after each publish.
agents policy / identity / audit
Section titled “agents policy / identity / audit”Every registered agent is a principal with a signed identity and a least-privilege policy. Background: Agent policy and identity.
pal agents policy get <name> # -o json for the raw documentpal agents policy set <name> -f policy.json # replace; if the file has a stale "version" the server answers 409pal agents policy propose <name> [--apply] # least-privilege proposal with a rationale per fieldpal agents identity show <name>pal agents identity rotate <name> # new key generation; old tokens stop verifyingpal agents identity revoke <name> [--reason "..."] # the agent's runs fail admissionpal agents audit <name> [--event policy_denied] [--limit 50] # also verifies the hash chainpropose saves nothing unless you pass --apply. With --apply it writes the proposal, reads it back and reports “verified” only if the read-back equals the proposal; otherwise it exits non-zero.
Manage runs: create, list, inspect, cancel.
pal runs create --agent my-agent --input '{"topic":"AI"}' [--stream]pal runs list [--agent my-agent] [--status succeeded]pal runs get <run-id>pal runs cancel <run-id> [--reason "..."]Stream a run’s journal events. For dev-stack containers use pal dev logs.
pal logs <run-id> [-f] # journal events; -f keeps the stream opendebug / support
Section titled “debug / support”The debug agent root-causes a failed run, an incident or a failing eval case and proposes fixes; the support agent answers questions from your own data and the docs. See Debugging and support. A proposal is applied only when you answer y to it on a terminal; --json and non-terminal runs never apply anything.
pal debug run <run-id> [-q "why did this start failing?"] [--json]pal debug incident <incident-id>pal debug eval <agent> <case>pal support "why are my runs getting 402?" [--run <run-id>] [--json]pal support escalate "the canary keeps rolling back" [--run <id> | --incident <id>]Run an agent’s eval suite and compare against the branch baseline. Exits non-zero with a regression summary when the score drops below the baseline. Wire it into CI to gate merges. --set-baseline pins the run as the branch baseline. --rehearse replays past failed or low-score production runs instead of a suite (--window, default 7d). Runs execute asynchronously on the control plane, so each case waits for its run to finish; --timeout (default 2m) bounds that wait per case, and a failed or timed-out run scores as a failed case with its error.
pal test --agent my-agent --suite factual-accuracypal test --agent my-agent --suite factual-accuracy --against last-greenpal test --agent my-agent --rehearse --window 24hfreeze / promote / approve / rollback
Section titled “freeze / promote / approve / rollback”Ship an agent version through sandbox, staging and prod, with deterministic gates and a human approval for prod. The full flow, gates and audit trail are in Promotion.
pal freeze <agent> [--label v7] # snapshot the working copy as an immutable versionpal promote <agent> --to sandbox|staging|prod [--version V] [--canary N] \ [--max-error-rate 0.10] [--max-cost-ratio 1.5] [--min-eval-score S] [--min-runs 20] \ [--reason "..."] [--run-eval <suite-id>] [--require-approval] [--json]pal approve <promotion-id> [--note "..."] # human owner/admin onlypal reject <promotion-id> [--note "..."]pal rollback <agent> [--env prod] [--note "..."] # idempotentpal canary evaluate <agent> # run the canary guards nowpal promotions list [--status pending_approval] [--agent A]pal promotions envs <agent> # what each environment servespal promotions audit [--agent A] # append-only decision trailpal promote contract # JSON tool contract for the onboarding agentpromote, approve, reject and rollback exit non-zero when the request is blocked, so CI and scripts cannot treat “blocked” as success. --run-eval retries once after running the named suite hermetically against the target environment.
Hermetic, LLM-judged evals run on the control plane with mocked tools (Evaluations). pal test is the older runner that executes a suite through real runs.
pal eval run --agent support --suite core [--samples 3] [--set-baseline] [--branch main] [--case NAME] [--max-cost 1.00] [--judge-model reasoning-large] [--json]pal eval generate --agent support [--goal "..."] [--max-cases 12] [--out draft.json] [--approve [-y]]pal eval approve --file draft.jsonpal eval calibrate --agent support [--rubric R] [--limit 20]run exits non-zero when any case fails or the run regressed against the branch baseline. generate never saves a suite without --approve or a later eval approve.
sandbox
Section titled “sandbox”Promote an agent to sandbox, run its workload in a real microVM and record the evidence.
pal sandbox run <agent> --spec agent.yaml [--exec "/bin/sh -c uname"] [--input '{...}'] [--keep] [--wait 2m] [--version V] [--json]It exits non-zero if the VM never ran or the command failed, and refuses a stub scheduler because nothing would have run. --exec is execve-style: no shell unless you pass /bin/sh -c .... The VM is terminated unless --keep.
Schedule and manage headless sandbox VMs. pal run takes a spec file; vm (aliases vms, runtime) manages what it started.
# Schedule a headless agent from a spec filepal run agent.yaml [--input '{...}'] [--follow] [--idle-timeout 600]
# VM subcommandspal vm list [--state running] [--limit 50]pal vm get <vm-id>pal vm logs <vm-id> # SSE log stream from the harnesspal vm stop <vm-id> [--grace 30]pal vm pause <vm-id> # snapshot + stop, frees the quota slotpal vm resume <vm-id> # restore under the same vm idpal vm fork <vm-id> --count 3 # N copies; the server caps N (100 by default, PAL_MAX_FORK_COUNT, ceiling 1000)pal vm exec <vm-id> -- ls /tmp # exec into a running VM (-t/-i for a PTY)pal vm ls <vm-id> [path] # list /workspacepal vm cat <vm-id> <path> # print a file (1 MiB cap)pal vm put <vm-id> ./f.txt /workspace/f.txt # write a file (1 MiB cap)pal vm quota get # current quota + today's usagepal vm quota set --max-concurrent 10 --max-cost 50 # owner onlypal vm cluster # node load + warm-pool capacity (owner only)pal vm validate --kubeconfig ~/.kube/sandbox.yaml # operator harness: gVisor/Kata isolation on a real clusterexec talks to the runtime manager directly; point it elsewhere with --manager-addr (default $PAL_MANAGER_ADDR). vm validate shells scripts/validate-cluster.sh (run it from inside the repo; needs kubectl), marks legs as skipped, not failed, when a RuntimeClass is absent, and takes --context and --report too.
deploy
Section titled “deploy”Reads agent.yaml from the current directory, creates the agent on the control plane (or updates its system prompt and model if it already exists), saves the optional workflow graph, then calls POST /v1/agents/{name}/deploy. Manifest keys used: name (required), description, model, system_prompt, labels (set on create only; the update endpoint cannot change description or labels) and workflow. Other keys are ignored. Uses REST, so pass the HTTP address or let it derive one from the gRPC address.
Flags: --json (print the API response; same as -o json), --dry-run (one read-only lookup, reports create or update, changes nothing).
Exit codes: 0 ok, 3 unauthorized (401/403), 4 quota or budget (402), 5 not found (404), 6 server error (5xx), 1 anything else.
$ cat agent.yamlname: demo-botdescription: demomodel: autosystem_prompt: You are terse.
$ pal --api-url http://localhost:8080 deploy --json{ "agent": "created", "agentName": "demo-bot", "deployedAt": "...", "environment": "cloud", "id": "<agent-id>", "status": "live", "tenantId": "<tenant-id>", "url": "http://localhost:8080/v1/agents/demo-bot/a2a/invoke"}Manage inbound webhook triggers: a public POST /v1/hooks/{id} URL bound to an agent, so GitHub, Sentry, PagerDuty or any HMAC-signing sender starts a run. Aliased as hook and triggers. See Webhook triggers.
create prints the signing secret once; --secret-stdin reads one from stdin instead (Sentry and PagerDuty generate their own). --template maps the payload into run input. update needs at least one of its flags; --enable / --disable and --template / --clear-template are mutually exclusive, and a disabled trigger answers deliveries with 404.
pal hooks create <agent> [--provider generic|github|sentry|pagerduty] [--template '{"pr":"{{payload.number}}"}'] [--secret-stdin]pal hooks list <agent>pal hooks get <trigger-id>pal hooks update <trigger-id> [--enable | --disable] [--template JSON | --clear-template] [--secret-stdin]pal hooks delete <trigger-id>Manage MCP servers: browse the catalog, register your own remote server, attach servers to agents, and store their credentials. Tokens are read from stdin and never echoed; pal mcp creds shows metadata only. stdio catalog servers need --vm (a running sandbox VM id).
pal mcp serverspal mcp add <slug> --url https://host/mcp --auth none|bearer|oauth [--name NAME]pal mcp remove <slug>pal mcp attach <agent> <slug> [--vm <vmId>]pal mcp detach <agent> <slug>pal mcp tools <agent> <slug>pal mcp token <slug> < token.txtpal mcp login <slug> [--scope S] [--client-id ID] [--no-wait]pal mcp credsGenerate Terraform for the data plane on AWS, GCP or Azure (EKS, GKE, AKS). Only install does real work: it writes the Terraform files and prints the next steps (terraform init && plan && apply); it does not provision anything. It needs --tenant-id (or PAL_TENANT_ID).
pal infra install --cloud aws --tenant-id "$TENANT" [--region us-east-1] [--cluster-name NAME] \ [--existing-cluster NAME] [--instance-type TYPE] [--node-count 3] [--output-dir DIR] [--firecracker]login / whoami / logout
Section titled “login / whoami / logout”pal login [--email --password] # authenticate to the control planepal whoami # show current authenticated userpal logout # clear stored credentialsA pal login session is a dashboard session JWT: it lasts PAL_JWT_TTL (12 hours by default) with no refresh, and it ends early when that user signs out anywhere (POST /auth/logout revokes all of the user’s sessions) or their password or role changes. pal logout only clears the locally stored credentials. For automation (CI, cron, scripts), use an API key (--api-key / PAL_API_KEY, created under Settings > API keys with only the scopes the job needs): keys do not expire every 12 hours and are revoked individually.
Global flags
Section titled “Global flags”--api-url string Control plane address (default: $PAL_API_URL or localhost:50051, gRPC)--api-key string API key (default: $PAL_API_KEY)--tenant-id string Tenant ID (default: $PAL_TENANT_ID)-o, --output string Output format: text or json (default text)--rest Use REST instead of gRPC (or set PAL_USE_REST)