Scoring a call instead of naming it
Every rule so far has been a yes or a no about a named thing. A risk policy is the other shape: a number for how dangerous this call looks, and thresholds for what to do about it.
A risk policy sets the boundaries. The score itself comes from you.
from agent_control_plane import PolicyEngine
from agent_control_plane.policy_engine import RiskPolicy
policy = PolicyEngine()
policy.set_risk_policy("refunds", RiskPolicy(
max_risk_score=0.5, require_approval_above=0.7, deny_above=0.9))
print(policy.risk_policies["refunds"].deny_above)Three thresholds, and only one of them refuses on its own. deny_above is the hard stop; the other two are numbers your code reads to decide whether to ask a human or to log more loudly.
Scoring a call
Risk is checked on the request path from lesson 7, so the call becomes an ExecutionRequest first.
import datetime
from agent_control_plane.agent_kernel import ExecutionRequest, AgentContext, ActionType
context = AgentContext(agent_id="support", session_id="s", created_at=datetime.datetime.now())
request = ExecutionRequest(request_id="r", agent_context=context,
action_type=ActionType.DATABASE_WRITE, parameters={},
timestamp=datetime.datetime.now())
for score in [0.3, 0.95]:
print(score, policy.validate_risk(request, score))Below the deny threshold it passes; at 0.95 it does not. Every risk policy you have set is checked and any one of them can refuse, so several policies together act as the strictest of them.
Where a score comes from
The library does not decide what is risky for your shop. A workable first score is a number per kind of action, adjusted by what the call is asking for.
WEIGHTS = {"lookup_order": 0.1, "send_email": 0.3, "issue_refund": 0.6, "delete_order": 0.9}
def score(tool, args):
base = WEIGHTS.get(tool, 0.5)
if args.get("amount", 0) > 100:
base += 0.3
return min(base, 1.0)
for tool, args in [("lookup_order", {}), ("issue_refund", {"amount": 40}),
("issue_refund", {"amount": 4000})]:
print(f"{tool:14}", round(score(tool, args), 2))A big refund scores higher than a small one without anybody writing a rule about the number 100 twice. That is the appeal of scoring: one dial to move when the shop gets more cautious, instead of thirty thresholds to edit.
The cost is that a score explains itself badly. When a refusal says 0.91, nobody can tell which part of the call was expensive. Use thresholds for the broad shape and named rules for the things you would have to justify to a regulator.
- Set
deny_above=0.5and see which of the three calls survive. - Add a second risk policy with a lower threshold and check that the stricter one wins.
- Give
scorean extra term for an unverified customer and rerun it.
Slow is fine. Stopping is the only problem.