Contextual Policies

Policies control what your Goalrail can and cannot do. They intercept every action (tool calls, LLM requests, file operations) and decide in real time whether to allow, ask for approval, or deny.

Why “contextual”?

Most agent frameworks offer static rules: allow this tool, deny that one. Goalrail policies are different. They are stateful and dynamic: each policy maintains its own state across the entire session and makes decisions based on what has happened so far.

This is what enables policies that static systems simply cannot express:

Every policy evaluation receives the full session context: cumulative cost, tool call history, data classification labels, and custom state you define. This makes Goalrail's policy system fundamentally more powerful than the static security controls built into individual coding agents.

What you can enforce

For example: cap spending, require approval before destructive actions, restrict access to repos or services, block PII, or route models. See Builtin Policies for the full list.

How policies work

Each policy returns one of three decisions:

DecisionWhat happens
ALLOWThe action proceeds.
ASKThe action pauses until the user approves or rejects it.
DENYThe action is blocked with an error message.

Policies are checked in order. The first policy to return a decision wins. No opinion (None) passes to the next policy.

Available policies

Goalrail provides builtin policies for common guardrails (cost budgets, approval gates, access controls, PII blocking, model routing). You can also write your own custom policies in Python and register them on the server to make them available:

# server config (config.yaml)
policy_modules:
  - myorg.policies

Start the server with that file:

goalrail server -c config.yaml

The -c flag is short for --config. Without it, local servers look for ~/.goalrail/config.yaml, and Docker deployments look for /data/config.yaml. This makes your custom policies discoverable alongside the builtins. See Custom Policies for how to write and register them.

Three levels

To apply a policy, you pick one (builtin or custom) and add it at one of three levels. Each level has a different scope and priority:

LevelWhoScopeHow to addPriority
SessionEnd userCurrent session only, not persistentAsk your Goalrail, or use the UI settings panelFirst
Goalrail configDeveloperEvery session using this Goalrailpolicies block in the Goalrail YAMLSecond
Server-wideAdminEvery agent and session on the serverServer config YAML or REST APILast

An admin can set a company-wide cost cap, a developer can restrict which repos the Goalrail writes to, and an end user can add extra approval gates. All three levels are enforced simultaneously.

Adding a policy

Session level: ask your Goalrail (recommended)

The easiest way to add a policy is to ask your Goalrail directly. Describe what you want in plain language:

You: Add a policy that asks me before running any shell commands.

You: Limit this session to $5 of LLM spend.

You: Block access to all GitHub repos except myorg/frontend.

Your Goalrail picks the right policy from the available registry (both builtin and any custom policies registered on the server), configures the parameters, and asks for your approval. Once you approve, the policy takes effect immediately on the next turn of the current session.

You can also browse and toggle policies from the settings panel in the web UI without typing anything in chat.

Goalrail config level: declare in YAML

For policies that should always apply, declare them in the policies block of your Goalrail config. These take effect every time the Goalrail starts.

policies:
  approve_file_ops:
    type: function
    handler: goalrail.policies.builtins.safety.ask_on_os_tools
  rate_limit:
    type: function
    handler: goalrail.policies.builtins.safety.max_tool_calls_per_session
    factory_params:
      limit: 50

Here rate_limit caps the total number of tool calls a single session can make (50 in this example), a guard against runaway agent loops and surprise cost. Once the cap is reached, further tool calls are denied for the rest of the session.

Policies without parameters use just handler. Configurable policies add factory_params. Multiple policies are evaluated in the order declared.

Server-wide level: server config

Server-wide policies apply to every session on your deployed server. Add them to your server config YAML (~/.goalrail/config.yaml on a laptop, /data/config.yaml in Docker):

policies:
  session_cost_guard:
    type: function
    function:
      path: goalrail.policies.builtins.cost.cost_budget
      arguments:
        ask_thresholds_usd: [1.0]
        max_cost_usd: 5.0
  user_daily_cost_guard:
    type: function
    function:
      path: goalrail.policies.builtins.cost.user_daily_cost_budget
      arguments:
        ask_thresholds_usd: [10.0, 25.0]
        max_cost_usd: 50.0

Server-wide policies use the function: {path, arguments} format (not handler / factory_params). Pass an explicit server config with goalrail server -c config.yaml (--config is the long form), or place it at the default server config path. Restart the server after editing.

Server-wide policies can also be managed at runtime via the REST API:

MethodEndpointDescription
POST/v1/policiesCreate a policy
GET/v1/policiesList all policies
PATCH/v1/policies/{id}Update a policy
DELETE/v1/policies/{id}Remove a policy