Allowed only when the context agrees
An allow-list answers may this agent ever refund?. Often the real rule is may it refund this, now?. That needs the policy to read something outside the call.
The mechanism is a conditional permission: a tool name plus a list of conditions that all have to hold. The conditions read from a context dictionary you set on the role.
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("customer_status", "equals", "verified")]))
policy.set_agent_context("support", {"customer_status": "verified"})
print(policy.check_violation("support", "issue_refund", {}))The customer is verified and the call was refused anyway. Nothing raised, nothing was logged, and the condition that should have passed did not.
The reason
equals is not an operator. The engine compares the operator string against a list of names it knows, and when none of them match it falls through to returning False. An unrecognised operator therefore means the condition can never be true.
policy = PolicyEngine()
policy.add_conditional_permission("support", ConditionalPermission(
tool_name="issue_refund",
conditions=[Condition("customer_status", "eq", "verified")]))
policy.set_agent_context("support", {"customer_status": "verified"})
print("verified:", policy.check_violation("support", "issue_refund", {}))
policy.update_agent_context("support", {"customer_status": "new"})
print("new: ", policy.check_violation("support", "issue_refund", {}))With eq it behaves. The operators are eq, ne, gt, lt, gte, lte, in, not_in and contains, and every one of them is the short form.
A condition that grants
There is a second surprise here, and it runs the other way. Adding a conditional permission also adds the tool to the role's allow-list.
policy = PolicyEngine()
print("before:", policy.check_violation("support", "issue_refund", {}))
policy.add_conditional_permission("support", ConditionalPermission(
tool_name="issue_refund", conditions=[Condition("customer_status", "eq", "verified")]))
policy.set_agent_context("support", {"customer_status": "verified"})
print("after: ", policy.check_violation("support", "issue_refund", {}))No add_constraint was called and the tool is now usable. A conditional permission is a grant with a condition attached, not a restriction on a grant made elsewhere.
That matters when you read a policy back. A tool appearing in state_permissions may have been put there by a conditional permission rather than by anyone writing an allow-list, so the two have to be read together.
All, or any
Several conditions default to needing all of them. Pass require_all=False and any one is enough.
policy.add_conditional_permission("support", ConditionalPermission(
tool_name="send_email",
conditions=[Condition("customer_status", "eq", "verified"),
Condition("manager_present", "eq", True)],
require_all=False))
policy.set_agent_context("support", {"manager_present": True})
print(policy.check_violation("support", "send_email", {}))Only the second condition holds and the call is allowed. With the default of require_all=True it would have been refused.
- Put
equalsback and add a second, correct condition, to see one bad operator sink the whole permission. - Use
inwith a list of allowed statuses instead ofeq. - Remove the context entirely with
set_agent_context("support", {})and find out which way a missing attribute fails here.
Every expert started right here.