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 on the arguments themselves

Lesson 15 read the world around the call. This lesson reads the call, which is where the refund cap finally becomes a policy rule rather than an if in your own code.

A condition's first field is a path, not just a name. Prefix it with args. and it reads from the arguments the agent chose.

Example
from agent_control_plane import PolicyEngine
from agent_control_plane.policy_engine import ConditionalPermission, Condition

policy = PolicyEngine()
policy.add_conditional_permission("support", ConditionalPermission(
    tool_name="issue_refund",
    conditions=[Condition("args.amount", "lte", 100)]))
policy.set_agent_context("support", {})

for amount in [40, 4000]:
    print(amount, policy.check_violation("support", "issue_refund", {"amount": amount}))

The cap from lesson 8 without writing a validator function, and on the interception path this time rather than the request path. This is the version that a tool-calling agent actually passes through.

set_agent_context is still needed even with an empty dictionary, because the engine looks the role's context up while building what the condition reads.

Deeper paths

Dots go as far down as the arguments do.

Example
policy.add_conditional_permission("support", ConditionalPermission(
    tool_name="send_email",
    conditions=[Condition("args.message.priority", "ne", "urgent")]))
policy.set_agent_context("support", {})

for priority in ["normal", "urgent"]:
    args = {"message": {"priority": priority, "body": "hello"}}
    print(f"{priority:7}", policy.check_violation("support", "send_email", args))

Each dot is a dictionary lookup. If any step is missing the path yields nothing, and a condition reading nothing is false, so the permission is refused.

That is the reverse of the YAML engine in lesson 11, where a missing field made the rule not match and the default let the call through. Here a missing attribute denies. Same library, same idea, opposite outcome, which is worth writing on a sticky note if you use both.

Both at once

Conditions in one permission can mix the two sources freely: something about the world, and something about this particular call.

Example
policy.add_conditional_permission("support", ConditionalPermission(
    tool_name="issue_refund",
    conditions=[Condition("customer_status", "eq", "verified"),
                Condition("args.amount", "lte", 100)]))
policy.set_agent_context("support", {"customer_status": "verified"})

for amount in [40, 4000]:
    print(amount, policy.check_violation("support", "issue_refund", {"amount": amount}))

A verified customer, a small amount, allowed. A verified customer asking for four thousand, refused. That single rule is most of what a refund policy needs to say.

Try it yourself
  • Swap lte for lt and test the boundary at exactly 100.
  • Read args.order_id with in against a list of two order ids.
  • Ask for a refund with no amount key at all and confirm it is refused.

Little by little, you're building something great.