Skip to main content
Some rules apply only when a request attribute has a given value. The attribute can be the user’s country, tenant, user tier or channel. Agent Control expresses this as a condition on the attribute, combined with the rule itself. This example uses the country (jurisdiction) as the attribute. An assistant must not return a German IBAN to a user in Germany, or a US Social Security number to a user in the US. To use another attribute, replace the jurisdiction field and its values.

What This Demonstrates

  • Attribute-based rules: each rule applies only when the request’s jurisdiction matches.
  • Two ways to author the same policy:
    • One control per attribute value, here one per country.
    • One composite control with a branch for each value.
  • Baseline controls that apply to every country, next to the country controls.
  • Two ways to send the jurisdiction with each check:
    • In step.context, with evaluate_controls() or the HTTP API.
    • As a function argument, with the @control() decorator.
No LLM or API key is needed. The scripts use fixed text in place of a model answer.

Quick Start

Prerequisites

  1. Start the Agent Control server: make server-run from the repo root.

Run the Demo

Each script creates its agent and controls, then runs the scenarios. It prints PASS or FAIL for each check, and exits non-zero if any check fails. The scripts are safe to re-run.

Send the Jurisdiction

The caller must send the jurisdiction with each check. Use an ISO 3166-1 alpha-2 code, such as DE or US. If the country code arrives in a request header, such as x-ai-jurisdiction: DE, the caller copies it into step.context.jurisdiction. Agent Control does not map request headers into the step. context holds request metadata, separate from the data that the controls check. Controls select the value with the path context.jurisdiction.
Gateways and non-Python clients send the same field in the body of POST /api/v1/evaluation:

As a decorator argument

@control() builds the step from the function’s arguments, and it cannot set step.context. To use the decorator, pass the jurisdiction as a function argument and select it with input.jurisdiction.
This works only for tool steps and custom step types, where input is the dict of all arguments. For an llm step, input is a single string, so the path input.jurisdiction does not resolve.
The controls below use the context path. For the decorator path, make two changes in each control, or the controls never match and every request is allowed:
  • Set "step_types" to ["tool"].
  • Set the jurisdiction selector path to "input.jurisdiction".

Author the Controls

Each country rule is an and of two conditions:
  • The jurisdiction is in a list, checked with the list evaluator.
  • The rule matches, here a regex on the output.

Option 1: One control per country

Write a similar control for each country, for example US with a Social Security number pattern.

Option 2: One composite control

Put each country’s and branch inside one or:
jurisdiction_in() and output_matches() are small helpers in the example scripts. They return the leaf conditions shown in Option 1.

Which option to use

Baseline and Country Controls

Most policies combine two kinds of rules:
  • Baseline controls apply to every country. They have no jurisdiction condition.
  • Country controls apply only to some countries. They include the jurisdiction condition.
Keep them as separate controls. A baseline control is a plain control:
The baseline and the country controls run together on each check. A baseline control also applies when the request has no jurisdiction.
Agent Control evaluates the controls in parallel and stops the remaining evaluations after the first deny match. If a baseline control and a country control both match, result.matches usually names only one of them.
To tell the two kinds apart, use a naming convention or tags, for example ["baseline"] and ["country", "DE"].

Scenarios

Both authoring options give the same decisions. The example runs them side by side, with the baseline control on both:

Missing Jurisdiction

If a request has no jurisdiction, no country rule matches. Only baseline controls apply, so the request is allowed unless a baseline control matches. Agent Control has no built-in default jurisdiction.
If you need a strict default, add a control that matches when the jurisdiction is missing or not covered:
Keep this control in sync with the country controls:
  • The not list must name every country that has a rule.
  • The or must repeat every country rule.
For example, if you add an FR control but do not add its rule to the or, a request with no jurisdiction that matches the FR rule is allowed.

Files

  • attribute_controls_context.py - Sends the jurisdiction in step.context with evaluate_controls()
  • attribute_controls_decorator.py - Sends the jurisdiction as a @control() function argument

Source Code

View the complete example with all scripts and setup instructions: Attribute-Based Controls Example