Agent-mode output
TL;DR
- The
pplCLI has two surfaces — a human surface (pretty tables, interactive prompts, every verb available with explicit confirmation) and an agent surface (machine-readable JSON, lean projections, destructive commit-side verbs sealed). The same binary serves both; the active profile decides which surface answers. - Agent mode is a real execution surface, not a formatting flag. The output shape changes, the available verbs change, the safety contract changes. Switching profiles is
ppl mode <profile>(sticky) orppl --agent=<profile> <cmd>(one-shot, not a dry run). - Every agent-mode command returns one of four output envelope shapes — single-record JSON, paginated
{count, items}, mutation{ok:true, data:…}, or error{ok:false, error:{…}}on stderr. Exit code is0on success,1on error. - List commands return a lean per-record projection capped at 20 records by default, designed for LLM context efficiency. Filter with
--query=<text>(agent-only flag, per-resource matcher); raise the cap with--limit. - Destructive commit-side verbs are sealed to humans in agent mode (
ppl component promote, overwrite of released versions). Agents prepare and propose; humans commit. Going back from agent to human requires a freshppl login— no quick swap, by design.
What agent mode actually is
Agent mode is a named, persistent execution profile of the ppl CLI that changes three things at once: the output shape becomes machine-readable JSON envelopes, the available command surface drops the destructive commit-side verbs, and the session boundary prevents an agent profile from silently switching back to the human profile mid-workflow. It is a distinct execution surface, not a --json formatting flag layered on top of the human commands.
The three changes are coupled because an LLM driving the platform needs all of them together. JSON envelopes give the LLM a stable shape to parse instead of human tables it would have to scrape. Removing destructive verbs from the agent profile means the LLM cannot invoke them at all, rather than relying on the LLM to recognise and avoid them. The session boundary keeps an agent workflow inside the agent profile until a human explicitly logs back in.
These choices map to the ways an LLM running shell commands fails when the surface is built for humans. Wide tables spend context the LLM has a fixed budget for. Interactive confirmation prompts block because the LLM has no way to answer them. Destructive verbs can fire when a confirmation is misread. Long-running commands emit log noise the LLM then has to filter. The agent surface addresses each at the platform level so the LLM does not have to work around them.
The contract this asks of agent authors — whether the agent is a Claude session, a CI-driven script, or any other LLM-driven workflow — is to pick the right profile for the task, parse the envelope shape, and respect the seals. The platform then exposes a surface the LLM can drive directly instead of one it has to wrap.
Mental model — one binary, two surfaces
┌──────────────────────────────────────────────────────────────┐
│ ppl │
│ ├── human surface │
│ │ pretty tables, prompts, interactive flows │
│ │ destructive commit-side verbs callable with │
│ │ explicit confirm │
│ │ │
│ └── agent surface │
│ ppl mode <profile> OR ppl --agent=<profile> │
│ machine-readable JSON envelopes │
│ lean per-record projections │
│ per-resource --query matcher (agent-only flag) │
│ destructive commit-side verbs SEALED │
└──────────────────────────────────────────────────────────────┘
For an opening prompt, ppl mode general gives an LLM the broadest starter surface. Switch to component, backend, ci-cd, or application once the task is known.
The output envelope
Every agent-mode command writes one of four shapes:
single-record stdout : <json>
paginated list stdout: {"count":<total>,"items":[...]}
mutation stdout : {"ok":true,"data":<json>}
error stderr : {"ok":false,
"error":{"error":"<msg>",
"code":"<machine-code>",
"detail":"...",
"suggestion":"...",
"fields":{...},
"trace_id":"...",
"status_code":<int>}}
Exit code is 0 on success, 1 on error. The error envelope is nested — the outer object always has {"ok":false,"error":{…}}, and the inner object carries error (human message), code (machine-readable identifier), and detail / suggestion / fields / trace_id / status_code. Those inner slots are always present: unset string slots serialize as "" and fields as {}, so branch on whether a value is non-empty rather than on the key being present. Always check the exit code AND parse stderr; the exit code alone is not enough to distinguish error categories.
Lean list projections
Every list command returns a slim per-record projection in agent mode, not the full object. Each record carries only what an agent needs to look up a result, route to related resources, and chain into the next call — an id, a human-readable label, a status, the ids that connect it to other resources, a few counts, and the timestamps that matter. The heavy fields — full configurations, document bodies, nested graphs — are left out so responses stay small and cheap to parse.
When the full record is needed, fetch it by id with the matching get command (ppl component get <id>, ppl backend get <id>, and so on).
Paginated commands wrap the records as {"count": <total>, "items": [...]}; the rest return a bare array. The exact field set each command keeps is part of that command's contract — it prints under Output schema: in --help, which is the authoritative reference. This page explains the shape, not the per-command field lists.
Pagination and the 20-record cap
Agent-mode lists default to 20 records, and the cap applies even when --query is set. Pass --limit=N to raise it on commands that support the flag. Combine --query with --limit whenever a partial match could plausibly resolve to more than 20 rows.
The --query flag
Every searchable list command accepts --query=<text> in agent mode. The matching surface is per-resource — not a uniform substring on name/display_name:
ppl file list— matchesdisplay_nameandread_me.ppl node list— matchesdisplay_name.ppl component list,ppl backend list,ppl runtime list,ppl team list— matchnameand/ordisplay_name, with trigram similarity on some resources.
The exact matcher is documented per-command in --help.
ppl component list --query=detect-objects
ppl backend list --query=demo
ppl runtime list --query=prod
ppl file list --query=model
ppl node list --query=gpu
Three patterns that come up repeatedly when scripting against the CLI:
# 1. Component lookup by partial name
COMP_ID=$(ppl component list --query=detect-objects \
| jq -r '.items[] | select(.name=="detect_objects") | .id')
# 2. Latest version of a component
VER_ID=$(ppl component versions $COMP_ID \
| jq -r '.[-1].id')
# 3. A Runtime by display name
CAP_ID=$(ppl runtime list --query=staging \
| jq -r '.items[0].id')
--query is agent-only — it does not appear in human-mode help.
Streaming commands
ppl component publish, ppl backend deploy, and other long-running commands keep the final JSON machine-readable by default. Re-enable the full streaming log with --verbosity build|all for debugging.
Sessions and the human-only commit boundary
A ppl login opens a human session. Switching into an agent profile is ppl mode <profile> (persisted) or ppl --agent=<profile> <cmd> (per-invocation); returning to human is a fresh ppl login, not a quick swap. The asymmetry is deliberate: agent profiles seal the destructive commit-side verbs, so agents prepare and propose while humans commit. Which verbs are sealed, and why, is covered in Destructive operations and Publish semantics — this page does not restate it.
The docs registry as the agent's map
The docs registry is the canonical source of truth for what the platform exposes. Prefer it over assumptions or stale references — the registry is what changes when the platform changes; everything else may lag.
ppl docs tree # everything available
ppl docs search <substring> # path or title contains substring
ppl docs get flows/quickstart # fetch one doc
The pattern that works well for an LLM driving the platform: open with ppl docs tree to see the shape of the documentation, search when a specific topic comes up, fetch the exact flow the task needs, execute the bounded commands the flow describes. The registry is intentionally tree-shaped — discover, search, fetch, execute — so the LLM can navigate it the way a user would navigate any structured reference.
Where this fits
Agent mode makes LLM-driven workflows a first-class surface of the CLI. The output envelopes, the lean projections, the per-resource --query matcher, the destructive-operation seals, and the session boundary each exist to match how an LLM drives a real workflow. The trade-off is that the human surface and the agent surface diverge where they would otherwise be identical, in exchange for each surface being shaped for its intended caller.
The contract this asks of agent authors is to drive through it, not around it: parse the envelopes the platform returns, respect the lean projections (fetch the full record with get when needed), use --query for the search step the matcher was built for, and accept the seals on destructive verbs, surfacing candidates for human review. The contract holds as long as the agent does its part.
Related
- Quickstart — typical end-to-end agent flow.
- Install modes —
install: nodedefers package installation to deploy time. - Backend operations — every
backend <verb>and the state it leaves behind. - Destructive operations — the safety contract behind the seals.
- Publish semantics — prerelease, release, and what only humans can do.
- The lease lifecycle — the cleanup contract that makes agent experimentation safe.