Normandy.Behaviours.PolicyEngine behaviour (normandy v2.2.0)

View Source

Contract for per-tool-call policy decisions, consulted at the dispatch chokepoint via Normandy.Behaviours.Config.to_pipeline/1.

check/2 gets the %ToolCall{} and a ctx map: config (the agent config), tool (the tool struct as registered; the model's input is never merged into it), input (the model's input after Normandy.Tools.Input.validate/2: the tool's input-schema struct, or a string-keyed map) and opts (the policy ref's opts). It returns one of:

  • {:allow, meta} — proceed; meta is opaque allow-context.
  • {:deny, info} — block; info may carry :reason, :rule_id, :rationale. The rationale is fed back into the model context by the chokepoint, so the model learns why a constraint exists.
  • {:needs_approval, info} — park for human approval (Turn.Server parks the call until it is approved or rejected).

The default impl AllowAll preserves current (allow-everything) behavior.

Summary

Callbacks

Decides whether a validated tool call may run. Return {:allow, meta}, {:deny, info} or {:needs_approval, info}; the moduledoc says what ctx holds and what each does.

Optional: checks the opts half of the policy ref when the agent is built (BaseAgent.init/1), raising ArgumentError on a malformed policy, so a mistake surfaces at configuration instead of on the first tool call.

Types

call()

@type call() :: term()

ctx()

@type ctx() :: map()

decision()

@type decision() :: {:allow, map()} | {:deny, map()} | {:needs_approval, map()}

Callbacks

check(call, ctx)

@callback check(call(), ctx()) :: decision()

Decides whether a validated tool call may run. Return {:allow, meta}, {:deny, info} or {:needs_approval, info}; the moduledoc says what ctx holds and what each does.

A denial becomes an error result for the model, with the info :reason in its text when it is a non-empty string or an atom other than nil, true and false (any other term is not shown). A :needs_approval call is parked for Turn.Session.approve/2 under Turn.Server, and denied in a shell that cannot ask. A raise, exit or throw is a denial: the call never runs un-vetted.

validate_opts!(keyword)

(optional)
@callback validate_opts!(keyword()) :: :ok

Optional: checks the opts half of the policy ref when the agent is built (BaseAgent.init/1), raising ArgumentError on a malformed policy, so a mistake surfaces at configuration instead of on the first tool call.