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

A policy in a file

Everything in part 2 was policy written in Python. That means a rule change is a code change, a deployment and a review by somebody who reads Python. This part moves the rules into files.

The declarative layer lives in agent_os.policies, in the same core distribution you already installed. A policy is a YAML document with a name and a list of rules.

yaml
version: "1.0"
name: refunds
description: What the support agent may do with money
rules:
  - name: block_big_refunds
    condition:
      field: amount
      operator: gt
      value: 100
    action: deny
    message: Refunds over 100 need a human
defaults:
  action: allow

One rule. It has a condition, an action and a message, and the document has a default for when nothing matches. Save it as refunds.yaml.

Loading it

Example
from agent_os.policies.schema import PolicyDocument

policy = PolicyDocument.from_yaml("refunds.yaml")
print(policy.name, "|", len(policy.rules), "rule")
print(policy.rules[0].name, policy.rules[0].condition.operator.value,
      policy.rules[0].condition.value)

The document is validated as it loads. Fields become typed objects, so operator comes back as an enum rather than the string you wrote.

That validation is worth more than it looks. In lesson 16 you will meet the Python policy engine accepting an operator name it does not understand and silently denying everything. The YAML layer refuses to load instead, which is the better of the two failures.

Asking it about a call

An evaluator holds one or more documents and answers questions about a context. The context is a flat dictionary describing the call.

Example
from agent_os.policies.evaluator import PolicyEvaluator

evaluator = PolicyEvaluator(policies=[policy])
for amount in [40, 4000]:
    decision = evaluator.evaluate({"amount": amount})
    print(amount, decision.allowed, decision.matched_rule, "|", decision.reason)

The decision carries more than a yes or no. It names the rule that fired and repeats the message from the file, so a refusal explains itself in words a human wrote.

The forty pound refund matched nothing, so the document's default applied, and the reason says exactly that rather than pretending a rule allowed it.

Two engines, one library

This is a different engine from part 2. It does not know about roles, it does not share the allow-list, and nothing connects the two automatically. Lesson 30 wires this evaluator into the gate from lesson 4 by hand, which is the supported way to use both.

Try it yourself
  • Change value: 100 to value: 10 and run the same script.
  • Delete the message line and see what the reason says instead.
  • Add a second rule blocking refunds on order B22 and check both still fire.

Every expert started right here.