Skip to main content

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​

CommandWhat 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 listThe 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 scanCheck one piece of text and print what was decided
platformctl guard unmaskPut real values back where a check left placeholders
platformctl guard policy getThe project's default guardrail policy
platformctl guard policy setChange the project's default guardrail policy
platformctl guard statsHow 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

FlagValuesWhat it does
--presetoff | standard | strictSeeds 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.
--modeenforce | monitormonitor 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

FlagValuesWhat it does
--refusal-messagetextThe 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".

FlagValuesWhat it doesDirections set
--piiactionPersonal data - names, emails, card numbersboth
--pii-entitiesA,B,CWhich kinds to look for. Empty is refused: use --pii allow to turn the check off.input
--pii-restoreresponse | neverPut the real values back into the reply, or leave the placeholders in itboth
--secretsactionCredentials, keys and tokensboth
--denylist-addphraseRefuse text containing this phrase. Repeatable.both
--denylist-clear-Empty the denylist - phrases and patternsboth
--injectionallow | flag | blockAttempts to override the agent's instructionsinput
--injection-threshold0–1How sure the check must be before it firesinput
--toxicityallow | flag | blockAbusive and hateful languageboth
--toxicity-threshold0–1How sure the check must be before it firesboth
--semanticon | offThe slower judgement of what the text is actually asking forinput
--semantic-profilenameWhich judgement profile to use (today: default, strict). Not validated against a closed list, because profiles are added without a CLI release.input
--categoryKEY=ACTIONOne safety category, e.g. violent=block. Repeatable. Only the categories you name are sent; the rest keep their value.input
--on-controversialallow | flag | blockWhat to do with borderline textboth
--toolson | offAlso check tool arguments and tool results-
--output-semanticon | offJudge the agent's own replies as welloutput

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

FlagValuesWhat it does
--fail-patternopen | closedWhat a failed fast check does
--fail-classifieropen | closedWhat a failed language check does
--fail-semanticopen | closedWhat a failed judgement does
--budget-msTIER=MSTime one tier of checks may take, e.g. semantic=1500. Repeatable. Tiers are structure, pattern, classifier, semantic.
--retention-days1–365How 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

FlagValuesWhat it does
--policy-filepath, or - for stdinSend 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]
FlagDefaultWhat it does
--decisionallShow only this decision: allow, flag, mask or block
--sinceserver defaultHow 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.
--limit50Maximum 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
FlagDefaultWhat it does
--yesfalseConfirm 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]
FlagDefaultWhat it does
--text-The text to check
--file-Read the text from this path. - reads stdin.
--directioninputWhich 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]
FlagDefaultWhat 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]
FlagDefaultWhat it does
--window24hThe 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.


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>
FlagDefaultWhat it does
--descriptionemptyWhat this policy is for. Shown in list.
--locknoneA 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>]
FlagDefaultWhat it does
--messageemptyWhy 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>]
FlagDefaultWhat it does
--policynoneRequired. The named policy to attach.
--policy-versionthe newest publishedWhich 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.