Security and guardrails
Two command groups cover Agent Guardrails:
platformctl agents security- one agent's guardrails: read the settings in force, change them, drop them and inherit the project default again, read the record of what the checks decided, turn everything off.platformctl guard- the same checks pointed at text you supply, plus the project-wide default policy and the project's counters. Nothing here needs an agent to exist.
All commands here honor the global flags (--api, --project, -o) and all of them need a credential.
Command summary
| Command | What it does |
|---|---|
platformctl agents security get <agent> | The guardrails actually in force for an agent, and which layer each setting came from |
platformctl agents security set <agent> | Change an agent's guardrails (rolls a new version) |
platformctl agents security clear <agent> | Drop an agent's own settings so it inherits the project default again |
platformctl agents security attach <agent> | Point an agent at a published version of a named policy |
platformctl agents security detach <agent> | Stop using a named policy on this agent |
platformctl guard policies list | The project's named policies, with their versions and who uses them |
platformctl guard policies get <name> | One named policy, its draft, and who is attached |
platformctl guard policies create <name> | Create a named policy (a draft; nothing runs it yet) |
platformctl guard policies set <name> | Edit a named policy's draft |
platformctl guard policies publish <name> | Freeze the draft as a new immutable version |
platformctl guard policies versions <name> | The version history, newest first |
platformctl guard policies delete <name> | Delete a policy and its history (refused while attached) |
platformctl agents security verdicts <agent> | What the checks decided, most recent first |
platformctl agents security off <agent> | Turn an agent's guardrails off entirely |
platformctl guard scan | Check one piece of text and print what was decided |
platformctl guard unmask | Put real values back where a check left placeholders |
platformctl guard policy get | The project's default guardrail policy |
platformctl guard policy set | Change the project's default guardrail policy |
platformctl guard stats | How much was checked recently, and what was decided |
platformctl guard resolves its endpoint the plain way - --api, then $CAI_API, then the address saved by platformctl config set-api, then the one built into the binary. There is no CAI_GUARD_API: guardrails are served on the public API like the control plane, not on a data-plane address of their own.
So every command on this page runs from wherever you are - a laptop, a build step, a cron job - against the same public API and the same ordinary credential as platformctl list or platformctl deploy. Nothing here has to run on the platform, and nothing here needs an agent to exist. The routes behind these commands are the ones documented in the standalone API, so anything the CLI does here you can also do with curl and an API key.
The agents security group
platformctl agents security get
Prints the effective policy: the preset, whether it is enforcing, every check with its action and threshold, the refusal message, and which layer each value came from.
platformctl agents security get <agent>
No flags of its own.
platformctl agents security get research-buddy
You should see:
scope agent research-buddy
preset standard
mode enforce
version 3
resolved from platform then project then agent
refusal message I'm sorry - my security policy doesn't allow me to respond to that request.
refusal also shows nothing else
when a check cannot run structure=closed pattern=closed classifier=open semantic=open
time budget (ms) structure=50 pattern=400 classifier=500 semantic=1500
verdicts kept for 30 days
DIRECTION CHECK ACTION THRESHOLD LOCKED DETAIL
input structure block - no at most 65536 characters, invisible characters removed
input denylist block - no 0 phrases
input allowlist allow - no 0 patterns
input secrets block - yes -
input pii mask 0.6 no EMAIL_ADDRESS PHONE_NUMBER CREDIT_CARD IBAN_CODE US_SSN IP_ADDRESS; real values restored in the reply
input injection block 0.9 no -
input toxicity block 0.8 no -
input semantic on 0.5 no profile default; copyright=flag hate=block illegal=block jailbreak=block misinformation=flag privacy=flag self_harm=block sexual=block unethical=flag violent=block; borderline text: flag
output pii mask - no real values restored in the reply
output secrets block - yes -
output denylist block - no 0 phrases
output toxicity block 0.85 no -
output semantic off - no profile default; borderline text: flag
tool checks no
on tool arguments denylist=block pii=mask secrets=block
on tool results injection=block pii=mask secrets=mask
locked by an earlier layer (you can make these stricter, not weaker): /input/secrets/action /output/secrets/action
An agent nobody has configured prints preset off (nothing is checked; no latency and no cost).
A setting marked LOCKED was pinned by the platform floor or the project default. You can still make it stricter - raise a flag to a block, turn a check on, lower a threshold - you just cannot weaken it.
-o json prints the full policy view: the merged policy, plus resolved_from, locked, stored, effective, and clamped if a stronger layer overruled something you set.
platformctl agents security set
Changes the agent's own layer. Each flag changes one setting; anything you leave out keeps its current value.
platformctl agents security set <agent> [flags]
Passing no flag at all is refused, and the error lists every flag rather than the three whoever wrote it happened to remember.
The policy flags
These twenty-seven flags are shared by agents security set and guard policy set, character for character, so a reader who learned one does not have to re-learn the other.
Preset and mode
| Flag | Values | What it does |
|---|---|---|
--preset | off | standard | strict | Seeds the whole document, then any other flag in the same command overrides it. custom is refused - the platform records a policy as customised on its own, the moment you change a field away from its preset value. |
--mode | enforce | monitor | monitor runs every check and records every verdict but refuses nothing. Turn it on for a week, read verdicts, then enforce. |
What the end user is told when a request is refused
| Flag | Values | What it does |
|---|---|---|
--refusal-message | text | The exact sentence they see. Empty is refused: a refused request still has to say something. |
--refusal-include-reason | - | Appends the one-line reason the check gave. |
--refusal-include-verdict-id | - | Appends the verdict id, so they can quote it to your support desk. |
The checks. An action is allow, flag, mask or block. mask is only accepted where a value can be rewritten - personal data and credentials; on any other check it is refused with a sentence saying why rather than a bare "invalid".
| Flag | Values | What it does | Directions set |
|---|---|---|---|
--pii | action | Personal data - names, emails, card numbers | both |
--pii-entities | A,B,C | Which kinds to look for. Empty is refused: use --pii allow to turn the check off. | input |
--pii-restore | response | never | Put the real values back into the reply, or leave the placeholders in it | both |
--secrets | action | Credentials, keys and tokens | both |
--denylist-add | phrase | Refuse text containing this phrase. Repeatable. | both |
--denylist-clear | - | Empty the denylist - phrases and patterns | both |
--injection | allow | flag | block | Attempts to override the agent's instructions | input |
--injection-threshold | 0–1 | How sure the check must be before it fires | input |
--toxicity | allow | flag | block | Abusive and hateful language | both |
--toxicity-threshold | 0–1 | How sure the check must be before it fires | both |
--semantic | on | off | The slower judgement of what the text is actually asking for | input |
--semantic-profile | name | Which judgement profile to use (today: default, strict). Not validated against a closed list, because profiles are added without a CLI release. | input |
--category | KEY=ACTION | One safety category, e.g. violent=block. Repeatable. Only the categories you name are sent; the rest keep their value. | input |
--on-controversial | allow | flag | block | What to do with borderline text | both |
--tools | on | off | Also check tool arguments and tool results | - |
--output-semantic | on | off | Judge the agent's own replies as well | output |
The "directions set" column is the thing to read twice. Personal data, credentials, the denylist and toxicity are checked in both directions and these flags set both - "block credentials" almost never means "only on the way in". Prompt injection and the incoming semantic judgement exist on the incoming side only; --output-semantic is the outgoing one. Anything finer - a different threshold per direction, the allowlist, per-tool overrides - is a --policy-file away.
A threshold must be greater than 0 and at most 1. It is a confidence, not a percentage: 0 would fire on everything and anything above 1 could never fire.
When a check fails or runs out of time
| Flag | Values | What it does |
|---|---|---|
--fail-pattern | open | closed | What a failed fast check does |
--fail-classifier | open | closed | What a failed language check does |
--fail-semantic | open | closed | What a failed judgement does |
--budget-ms | TIER=MS | Time one tier of checks may take, e.g. semantic=1500. Repeatable. Tiers are structure, pattern, classifier, semantic. |
--retention-days | 1–365 | How long verdicts are kept |
open lets the request through, recorded as a concern; closed refuses it. Never an error to the caller either way.
There is no --fail-structure flag. Set fail.structure with --policy-file if you need it; --preset strict sets all four to closed.
The whole document
| Flag | Values | What it does |
|---|---|---|
--policy-file | path, or - for stdin | Send a whole policy document, for the settings no flag covers. It is the base, and flags passed alongside it land on top - the same order the platform applies a preset in. Must be a JSON object; the fields are validated by the platform, not here. |
Examples
# The recommended first move on any agent.
platformctl agents security set research-buddy --preset standard --mode monitor
# Standard with one change.
platformctl agents security set research-buddy --preset standard --toxicity flag
# Tune the injection check and keep everything else.
platformctl agents security set research-buddy --injection-threshold 0.8
# Two safety categories, and refuse borderline content.
platformctl agents security set research-buddy \
--category copyright=block --category misinformation=block --on-controversial block
# Check tool arguments and results - the indirect-injection path.
platformctl agents security set research-buddy --tools on
# Add a phrase to the denylist. Reads the current list first, so adding one
# phrase does not delete the rest.
platformctl agents security set research-buddy --denylist-add "internal project chimera"
# Refuse rather than let anything through unchecked, and give the semantic
# judgement less time.
platformctl agents security set research-buddy \
--fail-pattern closed --fail-classifier closed --fail-semantic closed \
--budget-ms semantic=800
# A whole document, plus one late override.
platformctl agents security set research-buddy --policy-file policy.json --mode monitor
Every successful set ends with:
a new revision is rolling; the running agent applies the new policy once it is ready
The stored policy changes immediately; the running agent applies it once the new version is ready, exactly like a compute or environment change.
platformctl agents security verdicts
One row per checked piece of text: when, which direction it was going, what was decided, how far the checks got, and which safety categories fired.
platformctl agents security verdicts <agent> [--decision allow|flag|mask|block] [--since 7d] [--limit 50]
| Flag | Default | What it does |
|---|---|---|
--decision | all | Show only this decision: allow, flag, mask or block |
--since | server default | How far back to look: 30m, 24h, 7d. Days and weeks are accepted (d, w), which the Go standard library's own duration parser does not do. |
--limit | 50 | Maximum verdicts to show, 1–1000. Always sent, even unchanged: a feed with no bound prints ten thousand rows the first time an agent gets busy. |
platformctl agents security verdicts research-buddy --decision block --since 7d
You should see:
TIME DIRECTION DECISION TIER CATEGORIES LATENCY ID
2026-09-16 11:31:07 tool_result block classifier - 41ms gv_01J9X1M4S6ZQ8KC2
2026-09-16 10:04:18 input block classifier - 47ms gv_01J9X1M4S6ZQ8K5B
With nothing to show:
no verdicts in this window - either nothing was checked, or the policy is off
When more rows exist than --limit allowed:
more verdicts are available; raise --limit to see them
No text and no detected value is ever recorded, so this feed is safe to read and to share. Start here before turning enforcement on: run with --mode monitor for a week, then --decision block shows exactly what would have been refused.
-o json prints {"agent", "verdicts": [...], "next_page_token"}.
platformctl agents security clear
Removes the policy stored for one agent, so the agent follows the project default and the platform floor again.
platformctl agents security clear <agent>
No flags.
platformctl agents security clear research-buddy
You should see what was removed and what is in force now — both halves, because this is the command you reach for when you are not sure which is which:
removed research-buddy's own policy (was preset off)
now in force: preset standard, mode enforce, inherited from platform then project
a new revision is rolling
An agent that had nothing of its own says so, and changes nothing:
research-buddy had no policy of its own; nothing to clear
now in force: preset standard, mode enforce, inherited from platform then project
This is the opposite of set and off, which both pin a value on the agent and so stop it following the project. It cannot escape a locked setting: dropping your own layer only moves the effective policy towards the shared ones. It does erase the agent's own settings, with no undo beyond setting them again — security get shows them under stored if you want a copy first.
See going back to the project default.
platformctl agents security off
Shorthand for set <agent> --preset off.
platformctl agents security off <agent> --yes
| Flag | Default | What it does |
|---|---|---|
--yes | false | Confirm turning every check off. Required - without it the command refuses and says what would stop. |
platformctl agents security off research-buddy --yes
You should see:
guardrails are off for research-buddy; a new revision is rolling
Without --yes:
this stops every guardrail check on research-buddy - personal data will no longer be masked
and credentials will no longer be caught, in either direction; re-run with --yes
Nothing is checked afterwards: no personal data masking, no credential check, no refusals, and no latency or cost from any of it. Your other settings are not erased - set <agent> --preset standard or --preset strict brings the checks back. This is the command to reach for when a check is refusing real work and you need the agent usable again now.
The guard group
platformctl guard scan
Check text and print the decision, why, which checks fired, and the text to use next.
platformctl guard scan [--text TEXT | --file PATH] [--direction input|output|tool_args|tool_result]
[--agent NAME] [--policy-file PATH]
| Flag | Default | What it does |
|---|---|---|
--text | - | The text to check |
--file | - | Read the text from this path. - reads stdin. |
--direction | input | Which half of the policy applies: input, output, tool_args, tool_result |
--agent | - | Judge by this agent's effective policy, and name it in the verdict - the way to ask "would my agent have refused this?" |
--policy-file | - | Judge by the policy document at this path. - reads stdin. |
Pass exactly one of --text or --file. A file's bytes are taken verbatim; stdin's trailing newline is trimmed, because it almost always came from echo or a heredoc rather than from a decision. Pass /dev/stdin as --file if you need stdin byte-exact. Text over 65536 bytes is refused locally rather than after the round trip.
Which policy is used, most specific first: --policy-file, then --agent, then the project default. Passing both --policy-file and --agent uses the document and labels the verdict with the agent - which is the "try this candidate policy as if I were that agent" case, and the reason both are allowed together.
--direction matters. An agent's reply checked as input is judged by the incoming rules.
platformctl guard scan --agent research-buddy \
--text "Email john.doe@acme.example the invoice."
You should see:
decision mask
reason Personal data was replaced with placeholders before the agent saw it.
severity safe
checks reached semantic
policy agent:research-buddy, standard, enforce, version 3
took 258ms
verdict gv_01J9X1M4S6ZQ8K3T
CHECK TIER DECISION TOOK DETAIL
pii pattern mask 34ms EMAIL_ADDRESS x1
text to use next:
Email <PII_EMAIL_1> the invoice.
mapping reference - keep it to restore the real values later ('guard unmask'),
it is not stored anywhere and cannot be reissued:
pm1.cDdmMmFiOTE.k4Zt3QqX9r2mB1wY...
On a refusal:
decision block
reason The request looked like an attempt to override the agent's instructions.
checks reached classifier (checks after this one did not run)
policy agent:research-buddy, standard, enforce, version 3
took 47ms
verdict gv_01J9X1M4S6ZQ8K5B
CHECK TIER DECISION TOOK DETAIL
injection classifier block 29ms -
text: withheld - a refused request returns no text to pass on. The caller of a
guarded agent receives the refusal message instead.
When nothing fired at all, the check table is replaced by no check fired.
Only the checks that did something, plus any that failed, are listed. A table of fourteen allow rows buries the one row that matters; the full list is in -o json.
The command succeeds whatever the verdict. Read the decision (or use -o json) to branch on it - it does not exit non-zero on a block, because a block is an answer and not a failure.
# A candidate policy against a file of real messages.
platformctl guard scan --policy-file candidate.json --direction tool_result --file poisoned-ticket.txt
# From stdin, scripted.
platformctl guard scan --agent research-buddy --file - -o json
platformctl guard unmask
Restore the values a masking check replaced, using the mapping reference it returned at the time.
platformctl guard unmask --mapping-ref REF [--text TEXT | --file PATH]
| Flag | Default | What it does |
|---|---|---|
--mapping-ref | - | The mapping reference the masking pass returned. Required. |
--text | - | The text holding the placeholders |
--file | - | Read the text from this path. - reads stdin. |
platformctl guard unmask \
--mapping-ref 'pm1.cDdmMmFiOTE.k4Zt3QqX9r2mB1wY...' \
--text "I have emailed <PII_EMAIL_1> about invoice 4471."
You should see:
I have emailed john.doe@acme.example about invoice 4471.
restored 1 value(s)
The restored text goes to stdout and the count to stderr, so a pipeline gets only the text.
When placeholders could not be accounted for:
restored 2 value(s); left 1 placeholder(s) alone - this reference does not cover them,
so they were masked in a different session or are past the per-session cap
Without --mapping-ref:
--mapping-ref is required: the real values are not stored anywhere, so restoring them
needs the reference the masking pass returned
Only placeholders the reference covers are restored; anything else is left exactly as it is and counted, so a reply that mentions a placeholder-shaped string of its own is never corrupted. The reference is bound to the project and to the session it was minted for, and it expires. A reference from another project cannot be opened here at all - not by policy, by construction - and an expired or mismatched one is refused rather than partly applied.
platformctl guard policy get
The policy every agent in this project inherits.
platformctl guard policy get
No flags of its own.
platformctl guard policy get
The output is the same shape as agents security get, with scope reading project default. To see what one agent actually runs, use platformctl agents security get <agent> - an agent can make any of this stricter.
platformctl guard policy set
Sets the policy every agent in this project inherits.
platformctl guard policy set [flags]
It takes exactly the policy flags documented above - the same twenty-seven, registered once so the two commands cannot drift apart.
platformctl guard policy set --preset standard --secrets block --retention-days 90
# A floor with locked settings needs the document form: no flag sets `locked`.
platformctl guard policy set --policy-file project-floor.json
Every successful set ends with:
each agent applies the new default on its next revision
An agent that has configured nothing of its own runs this immediately on its next version. An agent that has can make any of it stricter, never weaker - a write that tries is refused with policy_locked naming the exact setting.
platformctl guard stats
A count of what the guardrails did across this project over a window of time - how much text was checked in each direction, how it was decided, and how long the checks took.
platformctl guard stats [--window 24h]
| Flag | Default | What it does |
|---|---|---|
--window | 24h | The span to report over: 30m, 24h, 7d |
platformctl guard stats --window 7d
Read it alongside platformctl agents security verdicts <agent>: this answers "how much is happening", that answers "what happened".
--window takes a span written the way you would say it - 30m, 24h, 7d - up to 365d, which is the longest history anything keeps. Longer is refused rather than quietly reported over a shorter one.
The output is rendered as plain field-and-value pairs rather than chosen columns, deliberately: the counters are the service's own and it grows new ones - a category, a direction, a check - without a CLI release. A hand-written table would show a stale subset and look complete while doing it. Use -o json when you want to pick values out in a script.
There is no platformctl guard verdicts. The feed this CLI reads is per agent - platformctl agents security verdicts <agent>. For every agent in the project at once - including checks run through platformctl guard scan, which belong to no agent - read GET /v1/projects/{projectID}/guard/verdicts with the same credential this CLI uses.
Related
- Guardrails overview - what the checks are and what they cost.
- Policies - every field of the document these flags write.
- Verdicts and audit - reading the record.
- The standalone API - the same surface over HTTP.
- CLI overview - global flags, credentials, and endpoint resolution.
Named policies
A named policy is a guardrail document several agents can share, with a version history. See named policies for what it is for; this is the command reference.
The order matters: create → publish → attach. A draft is enforced by nothing, and publishing changes nothing an agent runs until you attach it.
platformctl guard policies list
platformctl guard policies list
No flags. VERSION reads draft only for a policy that has never been published — it cannot be attached yet. AGENTS counts who is attached.
platformctl guard policies get
platformctl guard policies get <name>
No flags. Shows the draft's preset, the newest published version, the locks, and every agent attached.
platformctl guard policies create
platformctl guard policies create <name> [--description <text>] [--lock <path>] <policy flags>
| Flag | Default | What it does |
|---|---|---|
--description | empty | What this policy is for. Shown in list. |
--lock | none | A setting path agents may not weaken, e.g. /input/secrets/action. Repeatable; replaces the list. |
Plus every policy flag agents security set takes — --preset, --pii, --injection-threshold and the rest. A create with no policy flags is refused: a policy whose draft says nothing cannot be published, so it asks for a preset now rather than failing later.
platformctl guard policies create payments-strict \
--preset strict --lock /input/secrets/action \
--description "what Legal signed off for anything touching money"
platformctl guard policies set
platformctl guard policies set <name> [--description <text>] [--lock <path>] <policy flags>
Edits the draft. Nothing an agent enforces changes, whatever is attached — a running agent enforces the version it was attached to. Same flags as create.
platformctl guard policies publish
platformctl guard policies publish <name> [--message <text>]
| Flag | Default | What it does |
|---|---|---|
--message | empty | Why this version exists. Kept in the version history. |
Freezes the draft as the next version number, for good. A draft that would weaken a setting the platform floor or the project default has locked is refused here rather than clamped later at deploy time.
platformctl guard policies versions
platformctl guard policies versions <name>
No flags. Newest first, with the publisher and message. This is the list a rollback picks from.
platformctl guard policies delete
platformctl guard policies delete <name>
No flags. Deletes the policy, its draft and every version. Refused while any agent is attached, naming them.
platformctl agents security attach
platformctl agents security attach <agent> --policy <name> [--policy-version <n>]
| Flag | Default | What it does |
|---|---|---|
--policy | none | Required. The named policy to attach. |
--policy-version | the newest published | Which published version to pin. |
The version is pinned. Publishing a newer version does not move this agent onto it — that is what stops somebody else's edit changing what your agent enforces. Attaching an older version is how you roll back.
platformctl agents security attach pay-worker --policy payments-strict --policy-version 2
You should see what was attached and what is now in force:
attached payments-strict version 2 to pay-worker
now in force: preset strict, mode enforce, from platform then project then policy
a new revision is rolling
platformctl agents security detach
platformctl agents security detach <agent>
No flags. Removes the attachment. The agent's own settings are left exactly as they are — security clear is the command that removes those. Idempotent.