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.
rules:
- name: only_known_tools
condition:
field: tool
operator: not_in
value: ["lookup_order", "issue_refund"]
action: deny
message: Unknown toolin 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.
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
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.
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.
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.
- Spell the field
Amountwith a capital and watch the rule stop firing. - Change the operator to
lteand swap the action, so the rule describes what is allowed instead. - Try
containson thetofield with the valueexample.netand compare it with the regular expression.
Little by little, you're building something great.