Skip to content

Sequence policies

Many risky agent behaviours look harmless one step at a time. Reading .env is normal. So is calling curl. Reading .env and then calling curl is how secrets leave a laptop. A sequence policy is a rule over the order of an agent’s actions. It fires when a pattern of tool calls and user turns happens within one session.

Shield360 checks sequence policies in three places:

  • Coding-agent hooks check each tool call before it runs. They compare it with a small per-session history kept on the developer’s computer.
  • The MCP gateway checks each gateway tool call before it runs. It compares it with a per-session window of the agent’s recent calls.
  • The after-the-fact scan runs every ten minutes. It rebuilds recent sessions from coding-agent traces and from the gateway’s call log, and records any violation that live enforcement could not see.

All three use the same engine. It is implemented natively in Go (the CLI) and in TypeScript (the platform). Both load one shared definitions file and are held to identical answers by a shared test corpus.

Violations are listed on the Violations page (open Govern → Policies & rules → Policies and select Violations), together with sensitive-data, AI-tool and prompt-injection decisions (see Violations). They are counted on the Govern → Compliance program overview and on the Home Compliance card, and each one raises a policy.violation alert.

PolicyFires whenSeverityNeeds
secret-then-egressA secret-like file is read (.env, SSH keys, cloud credentials, *.pem, kubeconfig, a keychain, terraform.tfstate…) or a credential command runs (gh auth token, security find-generic-password…), and a later call sends data to an outside destination: one that is neither internal nor allowed. It fires on the first such call after the read. It ends when the developer approves in a reply (“yes”, “go ahead”, “try again”), or after 20 more events. Test fixtures are not secrets. See Secret, then egress in detail.critical— (user turns optional)
destructive-without-confirmationA destructive command runs with no confirming user turn in the six events before it. Destructive commands include rm -rf, git push --force or +refspec, git reset --hard, DROP/TRUNCATE, DELETE without WHERE, terraform destroy, kubectl delete, helm uninstall, cloud delete APIs, and MCP tools named delete/drop/destroy/purge… A user turn confirms when it is an affirmative reply (“yes”, “go ahead”, “lgtm”…) or itself asks for the destructive action (“delete the old branches”).warninguser turns
taint-to-externalData from one tool’s result appears in the outbound arguments of a later call to a destination that is not allowlisted. Matching uses keyed fingerprints, never content (see Data flow).criticaltool results
excessive-writesThe 26th write action since the last user turn is attempted. It fires once per run of writes.warninguser turns
privilege-changesudo, chmod 777 or setuid, chown root, usermod/useradd, IAM attach/put/create, add-iam-policy-binding, (cluster)role bindings, or MCP tools that grant, attach or add permissions, roles, collaborators or members.warning—
injection-then-sensitivePrompt injection was detected in something the agent read, and a later call sends data out, deletes something, changes privileges or reads a secret.criticaltool results

Every built-in starts on, in warn. It records the violation and raises an alert, but stops nothing. You can switch each one off, or set it to block, under Sequence policies in Govern → Policies & rules → Policies. Warn lets you see what block would stop before you turn block on.

Where a runtime cannot see what a policy needs, the policy is not evaluated there live. The gateway cannot see user turns. Gemini CLI and Windsurf hooks cannot see tool results. The scan still checks these policies afterwards.

ModeLive (hooks and gateway)Scan
OffNot evaluatedNot evaluated
WarnThe call goes ahead. The violation is recorded and alerted.Recorded and alerted
BlockThe call is refused with a safe message wherever the agent lets Shield360 refuse it. Elsewhere it is recorded as a warning, marked “block not possible here”.Recorded as a warning. The call has already happened.

When a call is blocked it never runs, and it is kept in the session history as blocked. Every blocked attempt is still recorded as a violation. The built-ins ignore blocked calls when they look for the “first” egress, count writes or count the 20 events after a secret read, so retrying does not wear the policy out.

This built-in protects against the classic exfiltration pattern: an agent reads a credential and then sends it somewhere. It is tuned so that normal development does not trip it.

What counts as a secret read. Reading a secret-like path (with a read tool, or as a word of a shell command such as cat ~/.aws/credentials), or running a credential command. These are not secrets:

  • Examples and templates: .env.example, .env.sample, .env.template, *.example, *.sample, *.dist, and public keys (*.pub).
  • Test fixtures: files under a test/, tests/, testdata/, fixtures/, __fixtures__/, __mocks__/, __tests__/, spec/ or e2e/ folder, and files named like server.test.pem or key.fixture.json. For example, services/api/test/.env.test is a fixture. A .env.test at the root of a project is still a secret.
  • Home-directory credentials are never fixtures, wherever they sit: anything under .ssh/, .aws/, .config/gcloud/, .azure/, .kube/ or .gnupg/, id_rsa-style keys, keychains, .netrc, .git-credentials, .pgpass and .vault-token. A key copied to tests/.ssh/id_rsa is still treated as a key.
  • The fixture check starts below your home folder and the project folder in it, so a project that happens to be called test (~/test/.env) is not treated as a fixture folder.

The trade-off: a real credential that someone saves inside a test folder (for example a live API key in test/fixtures/.env) is not flagged by this policy. The taint-to-external policy still follows that file’s lines and tokens if they are sent out, and the agent guard’s secret scanning still applies.

What counts as an outside destination. Shield360 names where each call goes, as a host or a host and up to two path segments (an owner and a repository):

  • URLs in the arguments: curl https://app.example.com/api/x goes to app.example.com/api/x.
  • git push: the remote’s URL. The hook reads the repository’s remotes and the branch’s push remote from the local git configuration in the agent’s working folder. It never contacts the network. git push, git push origin main and git push https://… are all resolved. A local path or file:// remote is this computer.
  • gh: github.com (or --hostname), with the repository from --repo/-R, from gh api repos/<owner>/<repo>/… or orgs/<org>/…, or else the repository the working folder is in. gh gist … goes to gist.github.com. gh repo create and gh repo fork without an owner go to the developer’s own account, named as github.com only, so an organization entry does not allow them.
  • Email addresses and the MCP or gateway service’s own address, if known.

A call whose destination cannot be named, such as git push to a remote Shield360 could not read, still counts as outside.

Approving a call. When a call is blocked, the agent is told why and what to do: “Data was sent to an outside destination after reading .env.local in services/api” or “after running gh auth token”. The message names the file (base name and up to two folders) or the command, never the content. If the developer expects the call, they reply with an approval such as “yes”, “go ahead”, “try again”, “retry”, “continue” or “approved”, and the next attempt goes ahead. A new secret read after the approval starts the policy again. Under minimal content capture, the message says only “after a secret read”.

The 20-event limit. After 20 further calls and user turns (blocked calls not counted), the policy no longer links the read to a later call. A typical agent turn makes 5 to 15 calls, so this covers “read the secret, then act on it straight away,” the pattern exfiltration follows. A model keeps what it read in its context, so this limit is a usability trade-off, not a risk reduction. Beyond it, taint-to-external still finds a secret’s lines or tokens sent to an outside destination within the 64-event history, and an agent that pads its work with 20 harmless calls before sending data is a pattern to review in the violations feed.

Where user turns are not seen. The policy declares user turns optional (the # optional: user_turns comment line). The MCP gateway, which cannot see a person’s turns, still evaluates it, without the approval.

Subagents. Claude Code runs subagents (the Agent or Task tool) in the same session as their parent. Hook events fired inside a subagent carry an agent_id, so the hook keeps each subagent’s calls apart: a call is decided against the main conversation’s events plus, for a subagent, its own. A secret read by one subagent does not hold back the parent or the other subagents. A secret read by the main conversation does hold back its subagents. What a subagent hands back reaches the parent as the Agent call’s result, which taint-to-external still follows. Other agents (Cursor, Codex) give subagents their own session. The after-the-fact scan still treats a Claude Code session as one, so it can record a warning for a subagent’s read followed by the parent’s call.

Claude Code’s own coordination tools (Agent, Task, SendMessage, TodoWrite, ListAgents, TaskOutput, plan-mode and similar tools) stay on the computer. They are never outbound, even though names like SendMessage contain “send”. An MCP tool such as mcp__slack__send_message is still a message out.

AgentUser turnsRefused before a tool callTool results seenNotes
Claude CodeYesYes (PreToolUse deny)Yes
CodexYesYes (exit code 2)Yes
CursorYesShell, MCP and file reads (beforeShellExecution, beforeMCPExecution, beforeReadFile)Shell and MCPEdits are reported after they happen (afterFileEdit). They are evaluated but cannot be refused.
GitHub Copilot CLIYes (it can only observe prompts)Yes (preToolUse deny)Yes
Gemini CLIYesYes (BeforeTool deny)NoData-flow and injection policies run only in the scan.
WindsurfYesCommands, MCP and file reads (exit code 2)NoData-flow and injection policies run only in the scan.
OpenCodeYesYes (the plugin fails the call in tool.execute.before)Yes

Each session’s history is a file under the user cache directory (~/Library/Caches/shield360/sequence/ on macOS, $XDG_CACHE_HOME/shield360/sequence/ on Linux). The file is owner-only (mode 0600) and holds the last 64 classified events. It holds categories, flags, keyed fingerprints and a masked summary. It never holds arguments, results or prompts. Below full content capture, the summary is only the tool’s name.

SHIELD360_AGENT_SEQUENCE=off|warn|block overrides every policy’s mode on one computer. It also turns the built-ins on when an older platform sends no sequence policies. When the organisation asks the guard to fail closed, a session history that exists but cannot be read makes the hook refuse sensitive calls until it can be read again.

The gateway keeps each agent session’s recent calls in Redis, which every pod shares. Without REDIS_URL it keeps them in the process’s memory. A session is the client’s Mcp-Session-Id if it sends one, or otherwise the agent. A window expires an hour after its last call. In block mode, a call that violates a policy is refused with a safe error before it reaches the tool, and it is logged as denied. The gateway sees tool calls, their results and prompt-injection findings. It does not see the person’s turns.

Gateway tools are named <service>.<tool>. An MCP or API service counts as an outside destination unless it is allowlisted as mcp:<service>.

Every ten minutes (SEQUENCE_SCAN_SCHEDULE), the scan evaluates every enabled policy over:

  • Coding-agent sessions rebuilt from coding_agent.tool.call and user-prompt coding_agent.llm.turn spans. Arguments and results are read only when content capture is full; they are fingerprinted in memory and never stored. A user turn whose text was not captured counts as confirming, so an unknown turn never causes a violation.
  • Gateway sessions rebuilt from the call log (shield360_agent_tool_calls). A session is split where the agent paused for 30 minutes. Only tool names and outcomes are available here.
  • Prompt-injection detections, which are attached to the call or turn they were found in.

The scan decides only the calls of the last SEQUENCE_SCAN_LOOKBACK_MINUTES (15). It reads up to 24 hours of earlier history so that sequences which began earlier are still found. It is idempotent: a violation’s id is derived from the policy, the session and the call that completed it, which is how live enforcement derives it too. Ids already recorded are skipped. So is a live record of the same policy, session (or gateway agent) and tool within two minutes, whose call could not be named the same way.

Each violation is stored in shield360_policy_violations in your ClickHouse database and kept for seven years, like guard events. A record holds:

  • The policy id, name and severity, the rule’s message, the configured mode, and what was done: block, or warn. Warn includes block that was set but could not be enforced.
  • Where the violation was found (coding agent, MCP gateway or scan), the client or agent, the person and computer, and the session.
  • Each matched event, earliest first: its id, time, type, tool, category, flags, and a masked summary.

A summary is the tool plus one masked detail: the command, the first path, or the destination domains. Home directories become ~. Values after secret-looking names, and long tokens, are cut to four characters. Below full content capture, the summary is only the tool’s name, and ingest enforces this even for older collectors.

Arguments, results and prompts are never sent to or stored by Shield360.

Each violation raises a policy.violation alert with the policy’s severity. The alert links to the violation. Repeats for the same policy and session within an hour are counted on the open alert rather than raised again. To send them to a channel, add an Alerts raised by features rule for the kind Policy violation under Detect & respond → Alerts → Alert rules.

The language is a small, native subset of Invariant Labs’ guardrail rules, with Shield360’s own event fields and functions. For a step-by-step walkthrough of writing, testing and saving one, see Create custom policies and rules. A policy is one rule:

from invariant.detectors import prompt_injection # optional; accepted and ignored
raise "Data was sent to an outside destination after {secret.secret_source}" if:
(secret: ToolCall) -> (egress: ToolCall)
secret.secret_read
egress.external
  • raise "message" if: begins the rule. The message is what a violation and its alert say. {variable.field} in the message is filled from the event the variable matched, cut to 60 characters. The fields it can use are secret_source, tool, name, server, category, outcome, summary, role, type and id. Anything else in braces is left as written. Collectors older than version 2026.10.15-1 show the braces as written.
  • A comment line # optional: user_turns says the policy reads user turns only to let the developer approve. Runtimes that cannot see user turns (the MCP gateway) still evaluate it, without the approval. Without it, a policy that reads user turns is skipped there.
  • The pattern is the first line that starts with typed variables. Variables are joined by ->, which means anywhere earlier in the same session, or by ~>, which means immediately before. The last variable is the call being decided: a policy is checked when that call is about to run. It must be a ToolCall. A pattern has one to four variables.
  • Every other line is a condition, and all conditions must hold. Conditions can be combined with and, or and not, and grouped with parentheses. A condition can run over several lines only inside parentheses, brackets or braces.
  • Comments start with #.

When several earlier events could satisfy the rule, the nearest are chosen, and the violation shows those events.

ValueWritten as
Text"…" or '…', on one line. \n, \t, \\ and an escaped quote work as usual. A raw string, r"…", keeps backslashes as written, which suits regular expressions.
Numbers3, 2.5. Compare them with <, <=, > and >=, for example len(c.destinations) > 2.
True and falsetrue and false (or True and False).
Lists["delete_repository", "transfer_repository"], mostly with in and not in: c.name in ["delete_repository", "transfer_repository"].

None and null are not supported. A missing value, such as an argument the call does not have, is already false.

TypeMatches
ToolCallA tool call. ToolOutput is the same thing: a call and its result are one event.
MessageA user turn.
EventEither.

The last variable of the pattern must be a ToolCall. The same names test an event’s type in a condition: a is Message, a is ToolCall, a is Event.

FieldMeaning
toolThe tool name as the agent reported it (Bash, mcp__github__create_issue, github.create_issue).
name, serverThe tool’s own name and its MCP server or gateway service, both lower-case.
categoryread, search, write, execute, network, message or other.
secret_readReads a secret-like file, or dumps a keychain or cloud token. Examples, public keys and test fixtures are not secrets (see Secret, then egress in detail).
secret_sourceFor a secret read: what was read, without its content, such as reading .env in services/api or running `gh auth token` .
destructiveA destructive command or delete-style remote tool.
privilegeChanges privileges or permissions.
networkMakes a network request: a fetch tool, curl, ssh, git push, npm publish, or a command with a URL.
outboundSends data out: network, a message-style tool (send, post, email, upload, comment…), or a write through a remote tool. The agent’s own coordination tools (Agent, Task, SendMessage, TodoWrite…) are not outbound.
externalOutbound to a destination that is neither internal (localhost) nor allowlisted. An outbound call with no known destination counts as external.
destinationsWhere the call goes: a host, or a host and up to two path segments (github.com/acme/app). It includes URLs, email addresses, git push remotes and gh repositories.
writesChanges state: writes, edits, messages, destructive or privilege commands, and writing commands such as git commit, mv or npm install.
sensitiveoutbound or destructive or privilege or secret_read.
remoteAn MCP or gateway tool.
injectionPrompt injection was detected in this call’s result or arguments, or in this user turn.
confirmsFor a user turn: it approves (“yes”, “go ahead”, “try again”, “retry”, “continue”, “approved”, “lgtm”…) or asks for a destructive action.
domainsThe destination hosts found in the arguments (URLs, email addresses) and the service’s own, if known.
outcomeok, error, blocked or pending.
role, type, id, ts, summaryThe user turn’s role (user), the event type, its id, its time in milliseconds, and its masked summary.
args.<key>An argument of the call being decided. Coding-agent hooks and the MCP gateway do not keep arguments for earlier events, so there it is missing. function.name and function.arguments are Invariant’s spelling of tool and args.
==, !=, <, <=, >, >=A comparison with a missing value (an argument that is not there) is false, whatever the operator.
in, not in, containsMembership in a list, a substring in a string, or a key in an argument object.
x is tool:nameThe event is a call of name. * matches any run of characters, for example tool:mcp__github__*. Case is ignored.
x is tool:name({key: "regex", …})The same, and each named argument of the decided call matches its regular expression.
x is ToolCall, x is Message, x is EventThe event’s type. x is ToolOutput is the same as x is ToolCall, and x is Event is always true.
match(r"pattern", value)A regular-expression search. The pattern must be a literal.
len(x), lower(x)Length of a string or list; lower-case.
flows(a, b)Data from a’s result appears in b’s arguments (by fingerprint).
prompt_injection(x)x.injection. prompt_injection(x.content) is accepted too, for Invariant’s spelling. x.content means something only there: anywhere else it is always missing, because content is never kept.
count((v: Type) before x [after y] [within N] [since user] [where cond])The number of events of that type before x: after y, within the N events before x, and since the last user turn, each only when given. before is required. x and y must be variables of the pattern, and v must be a new name. N is a whole number of at least 1. where must come last and can use v and the pattern’s variables.
exists(…)count(…) > 0.

Regular expressions use the subset Go and JavaScript read the same way. Lookaround, backreferences, named groups, \A, \z, \p{…}, POSIX classes and inline flags other than a leading (?i) are refused. Matching is case-sensitive unless the pattern starts with (?i).

A policy can be at most 4,000 characters and 32 levels of nesting. Its body can have at most 20 lines: the pattern and up to 19 conditions. Conditions joined with and on one line count as one. Evaluation is bounded, and a policy that exceeds the bound matches nothing.

Invariant’s own example, as-is:

raise "External email after reading the inbox" if:
(call: ToolCall) -> (call2: ToolCall)
call is tool:get_inbox
call2 is tool:send_email({
to: ".*@evil\\.com"
})

A write to GitHub outside your organisation’s repositories. GitHub’s MCP tools usually name the owner and the repository separately (owner: "acme", repo: "app"), while other tools pass repo: "acme/app", so the rule accepts either:

raise "GitHub write outside acme" if:
(c: ToolCall)
c is tool:mcp__github__*
c.writes
not (lower(c.args.owner) == "acme" or match(r"(?i)^acme/", c.args.repo))

It fires on a write to other/app and to acme-tools/x, and on a write that names no owner or repository at all. It does not fire on reads.

Deleting more than three things in a row without asking:

raise "Several deletions without a user turn" if:
(c: ToolCall)
c.destructive
count((d: ToolCall) before c since user where d.destructive) >= 3

Each custom policy can carry up to 20 fixtures, each of 1 to 64 events, and at most 200,000 characters of fixture JSON in all. Test in the editor runs them on the server without saving anything:

[
{
"name": "secret, then curl",
"expect": "violation",
"events": [
{ "kind": "tool", "tool": "Read", "args": { "file_path": ".env" }, "result": "API_KEY=example" },
{ "kind": "tool", "tool": "Bash", "args": { "command": "curl https://example.com" } }
]
},
{ "name": "curl alone", "expect": "pass", "events": [{ "kind": "tool", "tool": "Bash", "args": { "command": "curl https://example.com" } }] }
]

A fixture is { "name", "expect": "violation" | "pass", "events": [...] }, and an event is:

FieldMeaning
kind"tool" for a tool call, "user" for a user turn. Required.
toolThe tool name as the agent reports it (Bash, mcp__github__create_issue). Required for a tool call. At most 200 characters.
serverThe MCP server or gateway service, when it is not part of the tool name. At most 100 characters.
serverDomainWhere a remote tool’s calls go (api.vendor.example). It is added to the call’s destinations and domains. At most 253 characters.
argsThe call’s arguments, as an object.
resultThe call’s result, as text, for flows() and taint-to-external. At most 8,192 characters.
textA user turn’s text, for confirms. Leave it out to model a turn whose text was not captured, which counts as confirming. At most 4,096 characters.
outcomeok, error, blocked or pending.
injectiontrue when prompt injection was found in this call or turn.

A fixture expects a violation if the policy fires on any of its calls. Fixtures run with your allowed destinations and as if every runtime could see user turns and tool results. Fixtures are stored with the policy, so use made-up values.

Rules are compiled on the server when they are validated and again when they are saved. A rule that does not compile is refused with a code and a line number, for example unknown_field on line 3.

taint-to-external and flows() never compare content. When a call returns, each line of its result is fingerprinted with HMAC-SHA256 under your organisation’s guard fingerprint key. Lines are normalised the way agent-written lines are fingerprinted for AI-code rework: trimmed, whitespace collapsed, at least 8 characters, not only punctuation. Each long token (16 or more characters, with a letter and a digit: keys, ids, hashes) is fingerprinted the same way. Each later call’s arguments are fingerprinted the same way. A shared fingerprint is a flow. Only the 16-character fingerprints are kept, and only in the session history.

A flow is found when a whole line or a whole token is carried over. Data that is reformatted, split, encoded or summarised on the way is not found.

Under Allowed destinations, below the sequence policies in Govern → Policies & rules → Policies, list the destinations where data may go without counting as leaving, one per line:

  • example.com covers example.com and all its subdomains. *.example.com means the same.
  • github.com/acme covers only that path and what is below it, on github.com and its subdomains: github.com/acme/app and api.github.com/acme/…, but not github.com/acme-tools, github.com itself or gist.github.com. Up to two path segments are allowed (github.com/acme/app). Paths compare whole segments and ignore case.
  • mcp:github allows an MCP server or gateway service when no other destination is named.

localhost, 127.0.0.1, 0.0.0.0 and ::1 are always internal. A path that could lead somewhere else (.., . or percent-encoding) is reduced to its host, so github.com/acme/../other does not match github.com/acme.

Collectors older than version 2026.10.15-1 do not understand path entries. They treat such an entry as a host that never matches, so it allows nothing there until the collector is updated.

A typical organization adds:

github.com/your-org
app.your-company.com

The first entry lets agents push to and call the GitHub API for the organization’s repositories, and nothing else on GitHub. The second lets them call your own application.

The built-ins are tuned to be quiet, but they will sometimes fire on legitimate work:

  • Secret, then egress can fire when an agent reads .env and later fetches documentation or calls a public API. It no longer fires on pushes and gh calls to destinations you allow, on test fixtures, on the agent’s own coordination tools, or after the developer approves. If it fires on legitimate work:
    1. Read the message. It names what was read (for example “after reading .env.local in services/api”), so you can tell a real secret from a false alarm.
    2. If the destination is your own, add it to allowed destinations, with a path for shared hosts such as github.com/your-org.
    3. If the file is test data, keep it under a test or fixtures folder.
    4. For a one-off, reply “go ahead” in the agent’s chat and let it retry.
    5. Consider warn rather than block for teams whose agents routinely read local configuration.
  • Destructive without confirmation looks at the six events before the command. A request made long before, followed by a lot of exploration, no longer counts. rm of a single file and a normal git push are not destructive; deleting a branch is. When prompt text is not captured, turns count as confirming, so the scan cannot find these violations below full capture.
  • Taint to external matches whole lines and long tokens. Common lines, such as a license header copied into an issue, can match. Allowlist the destinations your agents are meant to write to.
  • Excessive writes counts edits. Large refactors by an agent working autonomously will reach 26. It fires once per run and resets at the next user turn.
  • Privilege change fires on any sudo. Where agents legitimately use sudo, keep it in warn.

Every classifier and built-in lives in sequence.json. A change there is re-checked against the shared corpus of positive and negative sessions on both engines.

Shield360 does not run Invariant’s analyzer. Invariant Labs was acquired by Snyk in June 2025. The Guardrails repository (Apache-2.0) has had no substantive change since July 2025, and the hosted service shut down in January 2026. The analyzer is a Python interpreter with heavy runtime dependencies (models for its detectors, OpenAI calls). Instead, the policy language is implemented natively in Go and TypeScript and accepts the core of Invariant’s syntax. Invariant’s -> is ordering, not data flow, and Shield360’s -> means the same. Data flow is the separate flows().

InvariantShield360
raise "msg" if: with indented conditionsSupported. One rule per policy.
(x: ToolCall), (x: ToolOutput), (x: Message)Supported. ToolOutput is the call’s own result.
a -> b, a ~> bSupported, within one session. One pattern chain per rule.
call is tool:name, tool:name({arg: "regex"})Supported, with * globs. Argument patterns apply to the call being decided.
call.function.name, call.function.arguments.xSupported as aliases of tool and args.x.
match, len, in, and/or/notSupported.
from invariant.detectors import prompt_injection; prompt_injection(out.content)Supported. It uses Shield360’s prompt-injection detector.
secrets, pii, moderated, copyright, semgrep, llm…Not supported. Use secret_read, the agent guard and prompt-injection instead.
count(min=, max=): blocks, forall, list comprehensions, def, :=Not supported. Use count(…) and exists(…).
Lookahead in regular expressions (for example (?!Peter$))Not supported. Use not match(…).

A policy that uses an unsupported feature is refused with the code unsupported and the line.

PermissionAllowsShipped roles
policies.read (View agent policies)See the policies, their modes, the allowed destinations and the violations (other categories in the Violations feed also need their own page’s permission)Owner, Administrator, Compliance officer, Analyst, Security, Member, Viewer, Managed service provider
policies.manage (Manage agent policies)Turn policies on and off, change modes, write custom policies and change allowed destinationsOwner, Administrator, Security

Every route checks these permissions on the server and acts only on the caller’s current project and environment. Changes are recorded in the audit log.

  • Policies overview: every policy on one page, what each mode means, and how developers get unblocked.
  • Prompt-injection protection, whose detections the injection-then-sensitive policy builds on.
  • Create custom policies and rules: a step-by-step walkthrough for writing, testing and saving your own sequence policy, and every other kind of rule you can write.
  • Alerts: route policy.violation alerts to Slack, Teams, email, webhooks or PagerDuty with an Alerts raised by features rule under Detect & respond → Alerts → Alert rules.