Agent Governance Toolkitagent-governance-toolkit 4.1.0 · Python 3.10+
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
23 small wins to finish your pathNext lesson

Conditions, and the field that is not there

Lesson 10 used one operator. There are ten, and one thing they all agree on is what happens when the field is missing, which is not what most people expect.

A condition is three things: a field to read from the context, an operator, and a value to compare against.

yaml
rules:
  - name: only_known_tools
    condition:
      field: tool
      operator: not_in
      value: ["lookup_order", "issue_refund"]
    action: deny
    message: Unknown tool

in and not_in take a list. matches takes a regular expression. contains asks whether the value appears inside what the context holds, which reads backwards the first time.

Example
from agent_os.policies.schema import PolicyDocument
from agent_os.policies.evaluator import PolicyEvaluator

policy = PolicyDocument.from_yaml("tools.yaml")
evaluator = PolicyEvaluator(policies=[policy])
for tool in ["lookup_order", "delete_order"]:
    print(f"{tool:14}", evaluator.evaluate({"tool": tool}).allowed)

A regular expression

Example
policy = PolicyDocument.from_yaml("email.yaml")
evaluator = PolicyEvaluator(policies=[policy])
for address in ["customer@shop.example", "partner@example.net"]:
    print(f"{address:24}", evaluator.evaluate({"to": address}).allowed)

matches uses a search rather than a full match, so the pattern does not have to describe the whole string. Anchor it when you mean the whole string, which is what the dollar sign is doing here.

The field that is not there

Now the part that catches people. Run the refund policy from lesson 10 against a context that never mentions an amount.

Example
policy = PolicyDocument.from_yaml("refunds.yaml")
evaluator = PolicyEvaluator(policies=[policy])

print("with amount:   ", evaluator.evaluate({"amount": 4000}).allowed)
print("without amount:", evaluator.evaluate({"tool": "issue_refund"}).allowed)

A rule that exists to deny large refunds does not deny anything when the context forgot to include the amount. A missing field is treated as no match, so the rule is skipped and the default allows the call.

A deny rule is only as reliable as the context you build. If the code assembling that dictionary misses a key, or spells it total instead of amount, the rule silently stops protecting you. Lesson 26 replays fixtures against policies for exactly this reason.

This is the opposite of how part 2 behaved. There, an unknown role was denied everything; here, an unknown field is ignored. Two engines in one library, with two different ideas of what to do when they are not sure.

Try it yourself
  • Spell the field Amount with a capital and watch the rule stop firing.
  • Change the operator to lte and swap the action, so the rule describes what is allowed instead.
  • Try contains on the to field with the value example.net and compare it with the regular expression.

Little by little, you're building something great.