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

Context that changes while it runs

The context in lessons 15 and 16 was set once. Real context moves: a shift ends, a customer gets verified, a spending limit is reached. This lesson changes it between calls.

To watch that clearly the agent needs to make exactly the calls the lesson wants, in order. The agent from lesson 2 takes a script for this.

Example
from pretend_agent_governance import PretendAgent

agent = PretendAgent(script=[
    ("issue_refund", {"order_id": "A17", "amount": 40}),
    ("issue_refund", {"order_id": "B22", "amount": 40}),
])
print(agent.decide("anything at all"))
print(agent.decide("also ignored"))

With a script the message is ignored and the calls come back in order. The router is still there for the lessons that want a decision; this is for the lessons that want a fixture.

A shift that ends

Example
policy = PolicyEngine()
policy.add_conditional_permission("support", ConditionalPermission(
    tool_name="issue_refund", conditions=[Condition("shift", "eq", "open")]))
policy.set_agent_context("support", {"shift": "open"})
kernel = AgentKernel(policy_engine=policy)

for shift in ["open", "closed"]:
    policy.update_agent_context("support", {"shift": shift})
    verdict = kernel.intercept_tool_execution("support", "issue_refund", {"amount": 40})
    print(shift, "->", "allowed" if verdict is None else "blocked")

Same agent, same policy object, same tool, different answers, because something outside the call changed between them. Nothing was rebuilt.

update_agent_context merges, so keys you do not mention keep their values. set_agent_context replaces the whole dictionary, which quietly drops anything another part of your program put there.

A budget that runs out

The same mechanism does spending limits, by keeping a running total in the context and comparing the call against it.

Example
policy.set_agent_context("support", {"remaining": 100})
policy.add_conditional_permission("support", ConditionalPermission(
    tool_name="issue_refund", conditions=[Condition("remaining", "gt", 0)]))

for amount in [100, 60]:
    verdict = kernel.intercept_tool_execution("support", "issue_refund", {"amount": amount})
    print(amount, "allowed" if verdict is None else "blocked")
    if verdict is None:
        policy.update_agent_context("support", {"remaining": policy.agent_contexts["support"]["remaining"] - amount})

The second refund is refused because the first one spent the budget. The counting is yours: the engine compares, and your code is what remembers.

Note what the rule actually says. A condition compares against a fixed value, so this asks whether any budget is left rather than whether this particular refund fits inside it. A call for 500 against a remaining 40 would still pass. Checking the call against the balance needs the two numbers in one comparison, which is what a custom rule from lesson 8 is for.

This is also the freeze problem from lesson 9 in a new shape. update_agent_context is a mutating method, so a frozen engine refuses it, and a policy that needs to move context at runtime cannot be frozen. Keep the rules frozen and the counters somewhere else if you need both.
Try it yourself
  • Make the budget condition gte and see whether a refund of exactly the remaining amount goes through.
  • Freeze the engine before the loop and read the error.
  • Swap update_agent_context for set_agent_context and watch the shift key disappear.

You understood something today that you didn't yesterday.