For the complete documentation index, see llms.txt. This page is also available as Markdown.

Authoring policies

How to write policies that match when they should, act as intended, and don't fight your routines — patterns, anti-patterns, and a review checklist.

Context — This guide assumes the Policies concept page. It's organised as: when to reach for a policy, writing conditions, writing actions, choosing knobs, patterns, anti-patterns, checklist.

YAML examples follow manifest schema 6.1.5. Manifest and content shapes are schema-versioned and differ across runtime versions — see Versioning & compatibility.

When to create a policy

Reach for a policy when a rule is cross-cutting — it should hold across many processes, regardless of which routine (if any) is active. Policies are the agent's standing rules; they're matched on every turn and layered on top of whatever flow is running.

Create a policy when the rule is:

  • A behaviour that spans routines — "always answer in the customer's language", "never reveal another customer's data", "quote prices in EUR". You don't want to copy this into every routine; state it once as a policy.

  • A safety / compliance / escalation trigger that can fire at any point — "if the user reports an accident, hand off to a human" — independent of where the conversation is.

  • A reaction to a condition, not a sequence of steps — one situation, one response.

Reach for a routine instead when the behaviour is a procedure: an ordered, multi-step flow (collect → look up → branch → respond) with its own state. A rule that "happens to need three steps" is a routine wearing a policy's clothes — see the one-action rule below.

Writing conditions that match correctly

The condition is read by the policy matcher every turn and answered as a yes/no: does this apply to the conversation right now? Write it for that reader:

Be concrete about triggers. Name the phrasings, not just the abstract category:

condition: >
  The user mentions a driver who is under 21 years old, or asks whether
  someone under 21 can rent.

beats condition: "The driver age requirement is relevant." — abstractions make the matcher guess.

Include the negative space when a sibling rule exists. If two policies could plausibly both match, carve the boundary into the conditions themselves ("…but NOT when the user is asking about an existing booking").

Reference state explicitly. Conditions can read variables and tool results from the conversation: "player_info is not available AND the customer's full name and date of birth are present" is a perfectly good condition.

Writing actions that do what you meant

The action is a binding instruction for the turn. Four rules:

  1. Say what to do, then what not to do. Models follow positive instructions better; reserve prohibitions for the genuinely dangerous part:

  2. One policy, one rule. An action that handles four unrelated cases should be four policies — each gets its own condition, criticality, and trace visibility.

  3. Tool + speech is fine — but it must be one action. Unlike routine nodes, a policy action may call a tool and address the customer in one turn:

    The catch is single action. "Call a tool and tell the customer the outcome" is one action. But if the action is really two — e.g. ask the customer for their credentials and then authenticate them — that's a sequence with a customer turn in the middle, which a single policy can't reliably drive. Reconsider it as:

    • a routine (ask → authenticate is a two-node flow with a customer reply between them), or

    • two policies (one that prompts for credentials when they're missing; one that authenticates once they're present, gated by reevaluate_after so it fires in the same turn the credentials arrive).

    If you can't phrase a policy's action as a single sentence without an "and then", it's a procedure — author it as a routine.

  4. Your line breaks reach the model exactly as authored. Conditions and actions are rendered verbatim into the model's instructions (trailing whitespace trimmed), so YAML block style is part of the prompt:

    • Use a literal block (|) when the text has intentional structure — numbered steps, BAD/GOOD example pairs, paragraphs. Every line break you type is a line break the model reads.

    • Use a folded block (>) for a single flowing paragraph, and keep every continuation line at the same indentation. A line indented deeper than the rest keeps its literal line break and extra spaces, producing a mid-sentence break in the rendered instruction:

Choosing the knobs

Knob
Set it when

criticality: HIGH

Safety, compliance, money, hard prohibitions — rules you want flagged as mandatory. (HIGH and MEDIUM behave the same today; the level is a signal, not a conflict tie-breaker.)

criticality: MEDIUM (default)

The bulk of ordinary business rules — enforced as mandatory instructions.

criticality: LOW

Style/tone preferences the agent may deprioritise — rendered as soft guidance, so an occasional miss is acceptable.

always_match: true

The rule must hold even if the matcher would judge it irrelevant — regulatory disclaimers, absolute prohibitions. Costs prompt space every turn; budget these.

reevaluate_after: [tool]

The policy's relevance flips after that tool runs (auth, account status) — the match is re-run once the tool executes, whether it succeeds or errors.

metadata

Anything your team's tooling wants to read off traces (severity, owner, ticket).

Routine-scoped (policies: inside a routine, explicit id required)

The rule only makes sense mid-flow of that routine.

When a policy must categorically beat routines or other policies, declare it in priorities — criticality does not resolve conflicts, so it is never a substitute for a priority.

Patterns

The guard — block a specific dangerous action:

The redirector — keep the agent in its lane (see stay-on-topic in the Quickstart).

The escalator — hand off when out of depth:

The state-gated authenticator — see auth-on-identity above: fires only while unauthenticated, self-disarms via reevaluate_after.

The disclaimeralways_match: true plus a short action that appends required wording when quoting prices/terms.

Anti-patterns

Anti-pattern
Why it fails
Instead

A procedure in an action ("first ask X, then call Y, then confirm Z")

Policies have no step state; the model gets the whole script every turn and improvises its position in it

Make it a routine

Condition that needs the action's result ("the lookup shows the account is locked" before any lookup ran)

The matcher reads the conversation as-is

Split: one policy/node calls the tool; another conditions on its result

Mirror-image pairs ("if X do A" + "if not-X do B")

Doubles matcher load; the negative usually belongs in the system prompt or the first policy's action

One policy with both arms in the action

Everything always_match

Burns prompt space; dilutes the genuinely critical

Trust the matcher for conditional rules

Vague meta-conditions ("the conversation is going badly")

Unanchorable judgement → erratic matching

Name observable triggers (user swears, repeats a question 3×, asks for a human)

Versioning workflow

Policies are versioned documents; the manifest pins exact versions. The working loop:

  1. Publish the new policy version to the platform catalog.

  2. Bump the pin in the manifest (context.policies[].version).

  3. Deploy. The trace snapshot records which versions were live for every turn, so you can correlate behaviour changes with policy changes — see Observability.

Review checklist

Last updated

Was this helpful?