FailproofAI Cloud CLI
fp is a command-line client for a FailproofAI Cloud deployment. It authenticates
either as a signed-in user or with a scoped API key (§2), and every command
takes --json, so it's built to be driven by an agent.
1. Find how to invoke it
Resolve this once, then reuse it for every call:
- If
fp is on PATH (command -v fp) → use fp (it's
installed via pipx / uv tool / pip). This is the normal case.
- Else, if you're in (or under) a repo with an
fp-cloud-cli/ directory containing the
fp_cli package → run it from there with uv run fp (a local dev
build). The first run after a code change prints Building…/Installed… on
stderr — that's uv, not CLI output; ignore it.
- Else the CLI isn't available here → tell the user to install it
(
pipx install fp-cloud-cli or uv tool install fp-cloud-cli) and stop. Don't try to
reach the dashboard another way.
Don't go spelunking in the CLI source tree for flags — if you're unsure of one,
run fp <group> <cmd> --help. The source is not the documented contract
and reading it wastes effort.
Throughout this skill, fp means "whichever form you resolved."
2. The contract (the CLI enforces it, work with it, don't fight it)
Global options go BEFORE the command: fp --json events, never
fp events --json. Globals are --base-url, --org, --token,
--api-key, --json, --insecure/--secure. After the command they're a
usage error.
Two ways to authenticate, and they are not interchangeable:
|
How you supply it |
What it is |
| Session |
fp login (interactive; it emails a one-time code) |
a signed-in user, carrying that person's org memberships and permissions |
| API key |
--api-key <key>, or FP_API_KEY in the environment |
a scoped credential, carrying exactly the permissions it was granted |
A key is what you want in CI or any other non-interactive context: no browser, no
emailed code, nothing to expire mid-run.
Credential precedence, in full (resolve_auth, fp_cli/_context.py). Read it
as a ladder — the first rung that applies wins, and an explicit flag outranks
every environment variable, not just its own:
--api-key and --token together → usage error, exit 2. A silent guess
about which you meant is the one outcome worth refusing.
--api-key <key> → key mode
--token <tok> → session mode. This beats an ambient FP_API_KEY — the
flag is checked before the environment value, so "FP_API_KEY wins" is only
true between the two env vars.
FP_API_KEY → key mode
FP_TOKEN → session mode
- the saved session from
fp login → session mode
The rung that catches people is 2: exporting FP_API_KEY in CI and also
passing --token runs as that user's saved session, with their org memberships,
rather than under the scoped key you meant to audit.
A key is never written to the CLI's saved config — pass it every time, from
the environment. --api-key "" means "no override" and does not fall back to
a saved session (Click treats an empty env var as unset, so it falls to rung 4).
Some commands need a signed-in user. login, logout, orgs *, the whole
agent group, keys update, and all of policies * / fleet * /
guardrails * refuse a key with a usage error (exit 2) and make no
network call at all — there is no user to sign in, no saved active org to
switch, no private assistant thread to own, and the enforcement write routes
are root-only and deliberately absent from /v1. keys update is in that list
rather than a special case: keys:update can never be granted to a key, so it
is refused up front like the rest. The key is not the problem to fix — plan
around them rather than retrying or hunting for a flag.
Default to --json and parse it. It prints clean JSON to stdout and
nothing else. The plain output is a boxed Rich UI meant for human eyes — it
burns context with box-drawing characters and is awkward to parse. Use the
rendered output only when the user explicitly wants to look at something.
Data → stdout, status/errors/prompts → stderr. So a --json stdout
capture is pure JSON even when a status line is shown.
Branch on exit codes — don't scrape error text:
| code |
meaning |
what to do |
| 0 |
ok |
parse stdout |
| 1 |
unexpected / server error |
report it to the user |
| 2 |
usage error (bad flags/args) |
fix the command and retry |
| 3 |
can't reach the dashboard |
check base-url / connectivity |
| 4 |
no usable credential — not signed in, session expired, or the API key was rejected |
session: user must run fp login. Key: it's missing, mistyped, disabled, or belongs to another deployment — don't retry, and don't fall back to a session |
| 5 |
authenticated but missing permission |
message names the exact permission |
| 6 |
resource not found |
the named resource doesn't exist |
3. First call: confirm you're connected
Before real work, run fp --json whoami and react to the exit code:
- exit 4 → no usable credential. If the user is working from a session, tell
them to run
fp login (it emails a one-time code and prompts
interactively — you can't complete it for them, and don't fabricate a token).
If a key was supplied, the key itself was rejected — say so and stop; logging in
is not the fix, and silently switching to a session would run the command as a
different identity than the user asked for.
- base-url → the CLI defaults to the hosted product,
https://app.befailproof.ai, so a plain fp login works out of the box.
Only pass --base-url <url> (or set FP_DASHBOARD_URL) for a self-hosted
or dev deployment — a local dev stack is usually http://localhost:3000. A
scheme-less URL is rejected as a usage error (exit 2).
- exit 0 →
whoami returns the active org slug and your permissions; trust
that for the org name and to know what you're allowed to do before attempting a
gated command (don't assume a particular org slug — read it from whoami).
- In key mode,
whoami answers a different question. It still exits 0 —
whoami never errors — but it reports how you are authenticated rather than
who you are: there is no signed-in user, so it says so and names the auth mode
and the org it will act on. Read the auth mode; don't read "no user" as "not
authenticated" and don't try to log in on the strength of it. Since it isn't a
permission check either, let your first real read (fp --json list envs)
be what confirms the key works.
Multi-tenant: a user can belong to several orgs; the active one is chosen at
login. Override for a single command with the global --org <slug>
(fp --org acme sessions); change the saved default with
fp orgs switch <slug>.
⚠️ With a key, name the org explicitly. A key bound to one organization only
ever acts on that one. But a key that is not bound to a single organization
has nothing to fall back on — key mode never reads a saved active org — so the
deployment resolves it to its own default, and you get that org's data: no
error, no warning, results that look perfectly valid. If you cannot tell which
kind of key you hold, pass --org <slug> (or set FP_ORG) on every
command. Naming the org the key already belongs to is a no-op, and naming the
wrong one fails loudly instead of quietly — both better than guessing.
4. Mutations: confirm with the user FIRST
The CLI normally prompts "are you sure?" before a destructive action — but it
auto-skips that prompt whenever it isn't attached to a terminal, which is
exactly how you run it. --json skips it too. So the safety prompt will not
fire for you.
Therefore: before running any command that changes state, tell the user
plainly what will change (which resource, what value) and get an explicit OK.
Then run it. (When the user's request is the instruction to act — "create a
key called X" — state the exact command you'll run and proceed; when it's vague
or wide-blast — delete, disable a user, rotate a key, resolve an incident —
stop and confirm.)
If a create fails because the name already exists (exit 2), report that and
ask — don't rename-and-retry or rotate/regenerate the existing one. A
keys regenerate you didn't intend breaks whatever already uses that key.
State-changing commands: keys create/update/disable/regenerate,
users create/update/disable/enable, settings set,
alerts create/update/delete/test, the writing issues subcommands
(ack/assign/resolve/comment-add/comment-delete/subscribe/unsubscribe/open),
audits create/edit/delete/run and the finding-triage verbs
(ack/mute/dismiss/resolve/reopen/assign),
query create/update/delete, agent rename/delete, orgs switch, and — the
highest-consequence of the lot — policies publish/enable/disable/delete and
fleet deploy/rollback/rename, which change what is ENFORCED on production
machines. A fleet deploy replaces a machine's entire policy set, so name the
policies being dropped, not just the ones being added.
Read-only commands (§5 "Observe") never need this.
5. Command map
Pick the right group; full flags are in references/commands.md — read it when
you need a flag you don't already know.
Observe (read-only):
events — event log (light/payload-free responses by default; --search still scans payload server-side; --full or --fields payload returns the raw payload — keep bounded to a --session-id). --session-id --event-type --env --agent-id --since --search --full --all
sessions — agent runs (time/env/agent/session/status), no scores.
evals — evaluation results + scores; --aggregate for a health rollup; --score key:min..max.
errors — errored events; --aggregate for count / sessions / agents / last-seen.
usage — current org usage for its fixed 30-day metering window; needs usage:read.
list <kind> — discover valid filter values first: envs agents event_types score_filters models hooks tools error_types.
Manage (permission-gated, mutations):
keys list|show|create|update|disable|regenerate — API keys; secret shown once.
users list|show|create|update|disable|enable — referenced by email.
settings list|schema|set — fixed registry; schema shows what each key accepts.
alerts list|show|create|update|delete|test — referenced by name.
issues list|count|show|ack|assign|resolve|comment-add|comment-list|comment-delete|subscribe|subscribers|unsubscribe|open — by id (short ids accepted). One board for everything needing attention: alert breaches, hand-raised issues, and audit findings, told apart by a source of alert / manual / audit. (This group was called incidents before; the old name is gone.)
audits list|show|create|edit|delete|run|runs — scheduled sweeps, referenced by name; audits findings|finding + the triage verbs ack|mute|dismiss|resolve|reopen|assign act on a finding id. audits run <name> only queues a run (poll audits runs <name> for completion). See §8.
Enforce (cloud-managed policy, session-only — see §2):
policies list|show|publish|enable|disable|delete|test|compose — policy versions. publish mints a version from a local .mjs; test runs one against a synthetic context locally (it applies each policy's match filter, so a policy that does not cover the --event/--tool you pass is reported skipped, not run). enable/disable/delete take --yes.
fleet list|show|deploy|diff|history|rollback|rename — which machines run which policies. deploy REPLACES a machine's whole set (--add/--remove amend it, --set replaces, --create mints a deployment); it prints the plan and asks only on an interactive terminal without --json — under --json or with stdin redirected it applies immediately, so read fleet show first if you want review.
guardrails summary|timeline — what enforcement actually did; --since 1h|6h|24h|7d, --machine.
Analytics & assistant:
query list|show|create|update|delete|run|schema — saved ClickHouse SQL + ad-hoc runner (query run <name> or query run --sql "…"); query schema [table] for table layout.
agent health|models|chats|ask|show|rename|delete — built-in assistant; agent ask "…" starts a chat, --chat <short-id> continues one.
Identity: login, logout, whoami, orgs {list,switch,current,perms}, version, help.
All of login / logout / orgs — like the whole agent group, keys update, and
every policies / fleet / guardrails subcommand — are session-only: with a key
they exit 2 without calling anything (§2). whoami, version and help work either way.
6. Translating plain-English requests
Users speak in outcomes, not commands ("is anything broken?", "give CI a key",
"who has access?"). Map intent → command; when a value is fuzzy, run a discovery
command (list <kind>, whoami, a list subcommand) before committing.
| The user says… |
Reach for |
| "is anything broken / failing today?", "any errors?" |
errors --since 24h --aggregate, then errors --since 24h --all --limit 1000 to break down |
| "why did that run fail?", "what happened in session X?" |
events --session-id X --all --limit 1000 (and errors --session-id X) |
| "how are my agents doing?", "show recent runs" |
sessions --since 24h (add --status error for just failures) |
| "are the evals / quality scores ok?", "did quality drop?" |
evals --aggregate; drill with evals --score <key>:..0.5 |
| "how many events / how much traffic last week?" |
query schema then query run --sql "SELECT count() FROM events WHERE ts >= now() - INTERVAL 7 DAY" |
| "what has this org used this metering window?" |
usage (or --json usage for the complete response) |
| "is anything on fire?", "any alerts firing / open issues?" |
alerts list + issues list (and issues count) |
| "ack / look at / resolve that issue" |
issues list → issues show <id> → confirm → issues ack/resolve <id> |
| "run an audit", "what did the audit find?", "any findings to triage?" |
audits list → audits run <name> (queues) → audits runs <name> (wait for succeeded) → audits findings --audit <name>; triage with audits resolve/mute/dismiss <id> — confirm first |
| "give CI / this service an API key" |
keys create <name> --add events:add (scope to what they describe) — state it, then create; capture the one-time secret |
| "who has access?", "add / remove a teammate", "make them read-only" |
users list / users show <email> / users create/update/disable |
| "change a setting", "what can I configure?" |
settings schema (what's tunable) then settings set <key> --value … — confirm first |
| "what models can the assistant use?", "ask the assistant …" |
agent models; agent ask "…" |
| "what can I query?", "run this SQL" |
query schema / query run --sql "…" (or a saved query run <name>) |
| "what am I allowed to do?", "which org am I in?" |
whoami, orgs current, orgs perms |
If the ask is ambiguous about scope (which org, which agent, read vs. change),
resolve it with a discovery command or a quick clarifying question rather than
guessing.
7. How to actually use it (recipes)
Discover → filter → read JSON → answer in prose:
fp --json list agents # find valid agent ids
fp --json errors --since 24h --aggregate # how bad is it right now? (full-window totals)
fp --json errors --since 24h --all --limit 1000 | jq '.errors[] | {session_id, error_type}'
fp --json sessions --status error --since 7d --all --limit 1000 # which runs failed
fp --json events --session-id run-001 --all --limit 1000 # a run's timeline (light: summaries, no payload)
fp --json events --full --session-id run-001 --all | jq '.events[].payload' # that run's RAW payloads (--full, bounded)
Raw payload is opt-in — events/errors responses are payload-free by default; add
--full (or --fields payload) to get it, and always bound it to a --session-id
(the full feed is slow/OOM-prone at scale). For one event or a precise slice, read the
column directly: fp --json query run --sql "SELECT payload FROM events WHERE id = <id>"
(or WHERE session_id = '<id>'). See references/commands.md → "Getting the raw payload".
list <kind> before filtering — don't guess an env or agent id; the
discovery command tells you exactly what exists.
--since takes 24h / 7d / etc.
--all is bounded by --limit, which defaults to 50. So a bare
errors --since 24h --all silently returns only the first 50 rows (with
next_cursor: null, looking complete). For a real sweep pass a high explicit
limit: --all --limit 1000 (or higher). When you only need the totals, use
--aggregate — it covers the whole window regardless of row caps, so it's the
reliable cross-check that you pulled everything.
Triage flow: issues list → issues show <id> (read the activity
log) → confirm with the user → issues ack <id> or resolve <id>.
Investigate a regression: evals --aggregate to see which score dropped →
evals --score helpfulness:..0.5 to list the bad runs → events --session-id <id>
to see what happened inside one.
When you've pulled what you need, answer the user in prose or a small table —
don't paste raw JSON back unless they asked for it.
8. Audits — the async sweep, and how findings become issues
An audit is a scheduled sweep that analyses recent agent behaviour (errors,
runaway tool loops, leaked secrets, low eval scores, …) and emits findings.
Two things about the flow matter when driving it from the CLI:
audits run <name> is asynchronous — it only queues. A {"queued": true}
does NOT mean the run finished (the analysis can take minutes). Poll
audits runs <name> until the newest row reads succeeded (or failed) before
reading findings — don't assume results are ready on the call that queued them.
A disabled audit, or one already mid-run, refuses to queue (exit 1).
- Findings ARE issues — it's one bucket. Every finding graduates to an issue
(
source = audit) and carries its full content there, so the same problem shows
up under both audits findings and issues list. Triage is globally
consistent in both directions: audits resolve <finding-id> closes the linked
issue, and issues resolve <issue-id> on an audit issue resolves the finding —
either surface works, they never disagree. Triage a finding with
audits ack|mute|dismiss|resolve|reopen <id> (durable mute/dismiss suppress
the pattern org-wide by fingerprint; resolve leaves no suppression, so a true
recurrence reopens as new). Reads need audits:read, every mutation
audits:write (note: triaging a finding needs audits:write, not an issues:*
permission — the audit is the system of record and the issue follows it).
Typical end-to-end: audits list → audits run <name> → poll audits runs <name>
→ audits findings --audit <name> (highest priority first) → audits finding <id>
for the full write-up → confirm with the user → audits resolve <id>.
1---2name: fp-cloud-cli3description: The way to answer "how are my production AI agents doing?" and to run the team's agent-observability deployment — reach for it even on casual phrasing that names no tool. Trigger when the user wants to: • inspect agent telemetry — did agents error/fail/go flaky; sessions, events, latency, token usage, slowest models; eval/quality scores and whether quality dropped; • operate the deployment — ack/assign/resolve/mute/dismiss issues (alerts, reports, and audit findings) with notes; run and triage audits; see who has access and change roles (e.g. read-only); create or scope API keys (e.g. a push-only CI key); change settings; run saved or ad-hoc ClickHouse queries. Served by the `fp` CLI against FailproofAI Cloud. NOT for writing or designing an evaluator service / scoring logic (that's `agenteye-evaluator`), adding SDK/instrumentation to your app (that's `failproofai-sdk`, imported as `failproofai_sdk`), debugging the collector/daemon, or unrelated dev work (why a build/CI run failed, rotating non-FailproofAI Cl4---56# FailproofAI Cloud CLI78`fp` is a command-line client for a FailproofAI Cloud deployment. It authenticates9either as a signed-in **user** or with a scoped **API key** (§2), and every command10takes `--json`, so it's built to be driven by an agent.1112## 1. Find how to invoke it1314Resolve this once, then reuse it for every call:15161. If `fp` is on `PATH` (`command -v fp`) → use **`fp`** (it's17 installed via pipx / uv tool / pip). This is the normal case.182. Else, if you're in (or under) a repo with an `fp-cloud-cli/` directory containing the19 `fp_cli` package → run it from there with **`uv run fp`** (a local dev20 build). The first run after a code change prints `Building…`/`Installed…` on21 stderr — that's `uv`, not CLI output; ignore it.223. Else the CLI isn't available here → tell the user to install it23 (`pipx install fp-cloud-cli` or `uv tool install fp-cloud-cli`) and stop. Don't try to24 reach the dashboard another way.2526Don't go spelunking in the CLI source tree for flags — if you're unsure of one,27run `fp <group> <cmd> --help`. The source is not the documented contract28and reading it wastes effort.2930Throughout this skill, `fp` means "whichever form you resolved."3132## 2. The contract (the CLI enforces it, work with it, don't fight it)3334- **Global options go BEFORE the command:** `fp --json events`, never35 `fp events --json`. Globals are `--base-url`, `--org`, `--token`,36 `--api-key`, `--json`, `--insecure`/`--secure`. After the command they're a37 usage error.38- **Two ways to authenticate, and they are not interchangeable:**3940 | | How you supply it | What it is |41 |---|---|---|42 | **Session** | `fp login` (interactive; it emails a one-time code) | a signed-in **user**, carrying that person's org memberships and permissions |43 | **API key** | `--api-key <key>`, or `FP_API_KEY` in the environment | a scoped **credential**, carrying exactly the permissions it was granted |4445 A key is what you want in CI or any other non-interactive context: no browser, no46 emailed code, nothing to expire mid-run.4748 **Credential precedence, in full** (`resolve_auth`, `fp_cli/_context.py`). Read it49 as a ladder — the first rung that applies wins, and an explicit flag outranks50 *every* environment variable, not just its own:5152 0. `--api-key` **and** `--token` together → usage error, exit 2. A silent guess53 about which you meant is the one outcome worth refusing.54 1. `--api-key <key>` → key mode55 2. `--token <tok>` → session mode. **This beats an ambient `FP_API_KEY`** — the56 flag is checked before the environment value, so "`FP_API_KEY` wins" is only57 true between the two env vars.58 3. `FP_API_KEY` → key mode59 4. `FP_TOKEN` → session mode60 5. the saved session from `fp login` → session mode6162 The rung that catches people is 2: exporting `FP_API_KEY` in CI and *also*63 passing `--token` runs as that user's saved session, with their org memberships,64 rather than under the scoped key you meant to audit.6566 **A key is never written to the CLI's saved config** — pass it every time, from67 the environment. `--api-key ""` means "no override" and does **not** fall back to68 a saved session (Click treats an empty env var as unset, so it falls to rung 4).6970- **Some commands need a signed-in user.** `login`, `logout`, `orgs *`, the whole71 `agent` group, `keys update`, and all of `policies *` / `fleet *` /72 `guardrails *` refuse a key with a usage error (**exit 2**) and make **no73 network call at all** — there is no user to sign in, no saved active org to74 switch, no private assistant thread to own, and the enforcement write routes75 are root-only and deliberately absent from `/v1`. `keys update` is in that list76 rather than a special case: `keys:update` can never be granted to a key, so it77 is refused up front like the rest. The key is not the problem to fix — plan78 around them rather than retrying or hunting for a flag.79- **Default to `--json` and parse it.** It prints clean JSON to stdout and80 nothing else. The plain output is a boxed Rich UI meant for human eyes — it81 burns context with box-drawing characters and is awkward to parse. Use the82 rendered output only when the user explicitly wants to *look* at something.83- **Data → stdout, status/errors/prompts → stderr.** So a `--json` stdout84 capture is pure JSON even when a status line is shown.85- **Branch on exit codes — don't scrape error text:**8687 | code | meaning | what to do |88 |---|---|---|89 | 0 | ok | parse stdout |90 | 1 | unexpected / server error | report it to the user |91 | 2 | usage error (bad flags/args) | fix the command and retry |92 | 3 | can't reach the dashboard | check base-url / connectivity |93 | 4 | no usable credential — not signed in, session expired, **or the API key was rejected** | session: user must run `fp login`. Key: it's missing, mistyped, disabled, or belongs to another deployment — don't retry, and don't fall back to a session |94 | 5 | authenticated but missing permission | message names the exact permission |95 | 6 | resource not found | the named resource doesn't exist |9697## 3. First call: confirm you're connected9899Before real work, run `fp --json whoami` and react to the exit code:100101- **exit 4** → no usable credential. If the user is working from a session, tell102 them to run `fp login` (it emails a one-time code and prompts103 interactively — you can't complete it for them, and don't fabricate a token).104 If a key was supplied, the key itself was rejected — say so and stop; logging in105 is not the fix, and silently switching to a session would run the command as a106 different identity than the user asked for.107- **base-url** → the CLI defaults to the hosted product,108 `https://app.befailproof.ai`, so a plain `fp login` works out of the box.109 Only pass `--base-url <url>` (or set `FP_DASHBOARD_URL`) for a self-hosted110 or dev deployment — a local dev stack is usually `http://localhost:3000`. A111 scheme-less URL is rejected as a usage error (exit 2).112- **exit 0** → `whoami` returns the active org slug and your permissions; trust113 that for the org name and to know what you're allowed to do before attempting a114 gated command (don't assume a particular org slug — read it from `whoami`).115- **In key mode, `whoami` answers a different question.** It still exits 0 —116 `whoami` never errors — but it reports *how* you are authenticated rather than117 *who* you are: there is no signed-in user, so it says so and names the auth mode118 and the org it will act on. Read the auth mode; don't read "no user" as "not119 authenticated" and don't try to log in on the strength of it. Since it isn't a120 permission check either, let your first real read (`fp --json list envs`)121 be what confirms the key works.122123**Multi-tenant:** a user can belong to several orgs; the active one is chosen at124login. Override for a single command with the global `--org <slug>`125(`fp --org acme sessions`); change the saved default with126`fp orgs switch <slug>`.127128> ⚠️ **With a key, name the org explicitly.** A key bound to one organization only129> ever acts on that one. But a key that is **not** bound to a single organization130> has nothing to fall back on — key mode never reads a saved active org — so the131> deployment resolves it to its own default, and you get **that** org's data: no132> error, no warning, results that look perfectly valid. If you cannot tell which133> kind of key you hold, pass `--org <slug>` (or set `FP_ORG`) on every134> command. Naming the org the key already belongs to is a no-op, and naming the135> wrong one fails loudly instead of quietly — both better than guessing.136137## 4. Mutations: confirm with the user FIRST138139The CLI normally prompts "are you sure?" before a destructive action — **but it140auto-skips that prompt whenever it isn't attached to a terminal, which is141exactly how you run it. `--json` skips it too.** So the safety prompt will not142fire for you.143144Therefore: **before running any command that changes state, tell the user145plainly what will change (which resource, what value) and get an explicit OK.**146Then run it. (When the user's request *is* the instruction to act — "create a147key called X" — state the exact command you'll run and proceed; when it's vague148or wide-blast — delete, disable a user, rotate a key, resolve an incident —149stop and confirm.)150151If a create fails because the name already exists (exit 2), **report that and152ask** — don't rename-and-retry or rotate/regenerate the existing one. A153`keys regenerate` you didn't intend breaks whatever already uses that key.154155State-changing commands: `keys create/update/disable/regenerate`,156`users create/update/disable/enable`, `settings set`,157`alerts create/update/delete/test`, the writing `issues` subcommands158(`ack/assign/resolve/comment-add/comment-delete/subscribe/unsubscribe/open`),159`audits create/edit/delete/run` and the finding-triage verbs160(`ack/mute/dismiss/resolve/reopen/assign`),161`query create/update/delete`, `agent rename/delete`, `orgs switch`, and — the162highest-consequence of the lot — `policies publish/enable/disable/delete` and163`fleet deploy/rollback/rename`, which change what is ENFORCED on production164machines. A `fleet deploy` replaces a machine's entire policy set, so name the165policies being dropped, not just the ones being added.166Read-only commands (§5 "Observe") never need this.167168## 5. Command map169170Pick the right group; full flags are in `references/commands.md` — read it when171you need a flag you don't already know.172173**Observe (read-only):**174- `events` — event log (light/payload-free responses by default; `--search` still scans payload server-side; `--full` or `--fields payload` returns the raw payload — keep bounded to a `--session-id`). `--session-id --event-type --env --agent-id --since --search --full --all`175- `sessions` — agent runs (time/env/agent/session/status), no scores.176- `evals` — evaluation results + scores; `--aggregate` for a health rollup; `--score key:min..max`.177- `errors` — errored events; `--aggregate` for count / sessions / agents / last-seen.178- `usage` — current org usage for its fixed 30-day metering window; needs `usage:read`.179- `list <kind>` — **discover valid filter values first**: `envs agents event_types score_filters models hooks tools error_types`.180181**Manage (permission-gated, mutations):**182- `keys list|show|create|update|disable|regenerate` — API keys; secret shown once.183- `users list|show|create|update|disable|enable` — referenced by **email**.184- `settings list|schema|set` — fixed registry; `schema` shows what each key accepts.185- `alerts list|show|create|update|delete|test` — referenced by **name**.186- `issues list|count|show|ack|assign|resolve|comment-add|comment-list|comment-delete|subscribe|subscribers|unsubscribe|open` — by id (short ids accepted). **One board for everything needing attention**: alert breaches, hand-raised issues, and audit findings, told apart by a `source` of `alert` / `manual` / `audit`. (This group was called `incidents` before; the old name is gone.)187- `audits list|show|create|edit|delete|run|runs` — scheduled sweeps, referenced by **name**; `audits findings|finding` + the triage verbs `ack|mute|dismiss|resolve|reopen|assign` act on a finding **id**. `audits run <name>` only *queues* a run (poll `audits runs <name>` for completion). See §8.188189**Enforce (cloud-managed policy, session-only — see §2):**190- `policies list|show|publish|enable|disable|delete|test|compose` — policy versions. `publish` mints a version from a local `.mjs`; `test` runs one against a synthetic context locally (it applies each policy's `match` filter, so a policy that does not cover the `--event`/`--tool` you pass is reported `skipped`, not run). `enable`/`disable`/`delete` take `--yes`.191- `fleet list|show|deploy|diff|history|rollback|rename` — which machines run which policies. **`deploy` REPLACES a machine's whole set** (`--add`/`--remove` amend it, `--set` replaces, `--create` mints a deployment); it prints the plan and asks **only on an interactive terminal without `--json`** — under `--json` or with stdin redirected it applies immediately, so read `fleet show` first if you want review.192- `guardrails summary|timeline` — what enforcement actually did; `--since 1h|6h|24h|7d`, `--machine`.193194**Analytics & assistant:**195- `query list|show|create|update|delete|run|schema` — saved ClickHouse SQL + ad-hoc runner (`query run <name>` or `query run --sql "…"`); `query schema [table]` for table layout.196- `agent health|models|chats|ask|show|rename|delete` — built-in assistant; `agent ask "…"` starts a chat, `--chat <short-id>` continues one.197198**Identity:** `login`, `logout`, `whoami`, `orgs {list,switch,current,perms}`, `version`, `help`.199All of `login` / `logout` / `orgs` — like the whole `agent` group, `keys update`, and200every `policies` / `fleet` / `guardrails` subcommand — are **session-only**: with a key201they exit 2 without calling anything (§2). `whoami`, `version` and `help` work either way.202203## 6. Translating plain-English requests204205Users speak in outcomes, not commands ("is anything broken?", "give CI a key",206"who has access?"). Map intent → command; when a value is fuzzy, run a discovery207command (`list <kind>`, `whoami`, a `list` subcommand) before committing.208209| The user says… | Reach for |210|---|---|211| "is anything broken / failing today?", "any errors?" | `errors --since 24h --aggregate`, then `errors --since 24h --all --limit 1000` to break down |212| "why did that run fail?", "what happened in session X?" | `events --session-id X --all --limit 1000` (and `errors --session-id X`) |213| "how are my agents doing?", "show recent runs" | `sessions --since 24h` (add `--status error` for just failures) |214| "are the evals / quality scores ok?", "did quality drop?" | `evals --aggregate`; drill with `evals --score <key>:..0.5` |215| "how many events / how much traffic last week?" | `query schema` then `query run --sql "SELECT count() FROM events WHERE ts >= now() - INTERVAL 7 DAY"` |216| "what has this org used this metering window?" | `usage` (or `--json usage` for the complete response) |217| "is anything on fire?", "any alerts firing / open issues?" | `alerts list` + `issues list` (and `issues count`) |218| "ack / look at / resolve that issue" | `issues list` → `issues show <id>` → **confirm** → `issues ack`/`resolve <id>` |219| "run an audit", "what did the audit find?", "any findings to triage?" | `audits list` → `audits run <name>` (queues) → `audits runs <name>` (wait for `succeeded`) → `audits findings --audit <name>`; triage with `audits resolve/mute/dismiss <id>` — **confirm first** |220| "give CI / this service an API key" | `keys create <name> --add events:add` (scope to what they describe) — **state it, then create**; capture the one-time secret |221| "who has access?", "add / remove a teammate", "make them read-only" | `users list` / `users show <email>` / `users create`/`update`/`disable` |222| "change a setting", "what can I configure?" | `settings schema` (what's tunable) then `settings set <key> --value …` — **confirm first** |223| "what models can the assistant use?", "ask the assistant …" | `agent models`; `agent ask "…"` |224| "what can I query?", "run this SQL" | `query schema` / `query run --sql "…"` (or a saved `query run <name>`) |225| "what am I allowed to do?", "which org am I in?" | `whoami`, `orgs current`, `orgs perms` |226227If the ask is ambiguous about scope (which org, which agent, read vs. change),228resolve it with a discovery command or a quick clarifying question rather than229guessing.230231## 7. How to actually use it (recipes)232233Discover → filter → read JSON → answer in prose:234235```bash236fp --json list agents # find valid agent ids237fp --json errors --since 24h --aggregate # how bad is it right now? (full-window totals)238fp --json errors --since 24h --all --limit 1000 | jq '.errors[] | {session_id, error_type}'239fp --json sessions --status error --since 7d --all --limit 1000 # which runs failed240fp --json events --session-id run-001 --all --limit 1000 # a run's timeline (light: summaries, no payload)241fp --json events --full --session-id run-001 --all | jq '.events[].payload' # that run's RAW payloads (--full, bounded)242```243244- **Raw `payload` is opt-in** — `events`/`errors` responses are payload-free by default; add245 `--full` (or `--fields payload`) to get it, and **always bound it to a `--session-id`**246 (the full feed is slow/OOM-prone at scale). For one event or a precise slice, read the247 column directly: `fp --json query run --sql "SELECT payload FROM events WHERE id = <id>"`248 (or `WHERE session_id = '<id>'`). See `references/commands.md` → "Getting the raw payload".249250- **`list <kind>` before filtering** — don't guess an env or agent id; the251 discovery command tells you exactly what exists.252- **`--since`** takes `24h` / `7d` / etc.253- **`--all` is bounded by `--limit`, which defaults to 50.** So a bare254 `errors --since 24h --all` silently returns only the first 50 rows (with255 `next_cursor: null`, looking complete). For a real sweep pass a high explicit256 limit: **`--all --limit 1000`** (or higher). When you only need the totals, use257 `--aggregate` — it covers the whole window regardless of row caps, so it's the258 reliable cross-check that you pulled everything.259- **Triage flow:** `issues list` → `issues show <id>` (read the activity260 log) → confirm with the user → `issues ack <id>` or `resolve <id>`.261- **Investigate a regression:** `evals --aggregate` to see which score dropped →262 `evals --score helpfulness:..0.5` to list the bad runs → `events --session-id <id>`263 to see what happened inside one.264265When you've pulled what you need, answer the user in prose or a small table —266don't paste raw JSON back unless they asked for it.267268## 8. Audits — the async sweep, and how findings become issues269270An **audit** is a scheduled sweep that analyses recent agent behaviour (errors,271runaway tool loops, leaked secrets, low eval scores, …) and emits **findings**.272Two things about the flow matter when driving it from the CLI:273274- **`audits run <name>` is asynchronous — it only *queues*.** A `{"queued": true}`275 does NOT mean the run finished (the analysis can take minutes). Poll276 `audits runs <name>` until the newest row reads `succeeded` (or `failed`) before277 reading findings — don't assume results are ready on the call that queued them.278 A disabled audit, or one already mid-run, refuses to queue (exit 1).279- **Findings ARE issues — it's one bucket.** Every finding graduates to an issue280 (`source = audit`) and carries its full content there, so the same problem shows281 up under both `audits findings` and `issues list`. Triage is **globally282 consistent in both directions**: `audits resolve <finding-id>` closes the linked283 issue, and `issues resolve <issue-id>` on an audit issue resolves the finding —284 either surface works, they never disagree. Triage a finding with285 `audits ack|mute|dismiss|resolve|reopen <id>` (durable **mute/dismiss** suppress286 the pattern org-wide by fingerprint; **resolve** leaves no suppression, so a true287 recurrence reopens as new). Reads need `audits:read`, every mutation288 `audits:write` (note: triaging a finding needs `audits:write`, not an `issues:*`289 permission — the audit is the system of record and the issue follows it).290291Typical end-to-end: `audits list` → `audits run <name>` → poll `audits runs <name>`292→ `audits findings --audit <name>` (highest priority first) → `audits finding <id>`293for the full write-up → **confirm with the user** → `audits resolve <id>`.