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

Two paths through one engine

This lesson is about a piece of the library's shape rather than a feature. The policy engine has two front doors, they enforce different rules, and knowing which one you are standing at saves a long afternoon.

Everything so far has gone through intercept_tool_execution, which asks check_violation. There is a second path, built around a request object, and it asks a different method.

Example
from agent_control_plane import PolicyEngine
from agent_control_plane.policy_engine import ResourceQuota

policy = PolicyEngine()
policy.add_constraint("support", ["lookup_order"])
policy.set_quota("support", ResourceQuota(agent_id="support", max_requests_per_minute=2))
print(policy.get_quota_status("support")["max_requests_per_minute"])

A quota of two calls a minute is now set for this agent. The obvious expectation is that the gate from lesson 4 starts refusing the third call.

It does not

Example
from agent_control_plane import AgentKernel

kernel = AgentKernel(policy_engine=policy)
for attempt in range(1, 5):
    verdict = kernel.intercept_tool_execution("support", "lookup_order", {})
    print(attempt, "allowed" if verdict is None else "blocked")

Four calls, four allowed, with a limit of two sitting right there in the engine. The interception path never looks at quotas.

The path that does

Quotas live on the other path, which works on an ExecutionRequest: an object carrying who is asking, what kind of action it is, and the parameters.

Example
import datetime
from agent_control_plane.agent_kernel import ExecutionRequest, AgentContext, ActionType

def request():
    context = AgentContext(agent_id="support", session_id="s",
                           created_at=datetime.datetime.now())
    return ExecutionRequest(request_id="r", agent_context=context,
                            action_type=ActionType.API_CALL, parameters={},
                            timestamp=datetime.datetime.now())

for attempt in range(1, 5):
    print(attempt, policy.validate_request(request()))

Now the limit bites. validate_request returns a pair: whether it passed, and a reason when it did not.

Which rules live on which path

Ruleintercept_tool_executionvalidate_request
Role allow-listEnforcedIgnored
Argument checks from lesson 6EnforcedIgnored
Conditions from lesson 15EnforcedIgnored
Quotas and rate limitsIgnoredEnforced
Custom rules from lesson 8IgnoredEnforced

Neither path is complete. The one built around tool names and arguments is the one a tool-calling agent needs; the one built around request objects carries the counting. A real gate calls both, which is what lesson 30 does.

Nothing in the library warns you about this. Setting a quota and watching it be ignored produces no error, no log line and no failed call — just a limit that quietly does nothing.

ActionType has seven members and none of them is a tool call: they are FILE_READ, FILE_WRITE, CODE_EXECUTION, API_CALL, DATABASE_QUERY, DATABASE_WRITE and WORKFLOW_TRIGGER. You pick whichever describes the tool best, and that choice is what the quota's action-type filter matches on.

Try it yourself
  • Set max_requests_per_minute to 1 and find the first refusal.
  • Call get_quota_status after each request and watch the counter move.
  • Pass allowed_action_types=[ActionType.FILE_READ] to the quota and see an API_CALL request refused for a different reason.

You understood something today that you didn't yesterday.