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

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.

Example
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.

Example
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.

Example
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.

Try it yourself
  • Set deny_above=0.5 and see which of the three calls survive.
  • Add a second risk policy with a lower threshold and check that the stricter one wins.
  • Give score an extra term for an unverified customer and rerun it.

Slow is fine. Stopping is the only problem.