Where the audit trail went
The kernel has a method called get_audit_log. After nineteen lessons of allowed and refused calls it returns an empty list, and this lesson is about why.
Run a few calls through the gate and then ask the kernel what it recorded.
from agent_control_plane import AgentKernel, PolicyEngine
policy = PolicyEngine()
policy.add_constraint("support", ["lookup_order"])
kernel = AgentKernel(policy_engine=policy)
kernel.intercept_tool_execution("support", "lookup_order", {"order_id": "A17"})
kernel.intercept_tool_execution("support", "delete_order", {})
print("entries:", len(kernel.get_audit_log()))Two decisions, one of them a refusal, and nothing was written down. The list is real and the method works; interception simply does not put anything in it.
What does fill it
The in-memory list is written to by the kernel's own bookkeeping, like opening a session, rather than by the gate.
kernel.create_agent_session("support")
for entry in kernel.get_audit_log():
print(entry["event_type"], sorted(entry["details"]))So the method is not broken and the list is not unused. It is simply not where tool-call decisions go.
Where they actually go
Tool-call decisions go to an audit logger passed in when the kernel is built. Without one, the kernel checks the policy, returns a verdict and forgets.
kernel = AgentKernel(policy_engine=policy)
print("audit_logger:", kernel.audit_logger)None, because nothing was passed. Every if self.audit_logger inside the gate is therefore false, and every recording step is skipped without complaint.
Lesson 20 builds the thing that goes in that slot. It is worth the lesson: a governance layer that cannot say what it did is a governance layer nobody can be asked to trust.
- Call
create_agent_sessiontwice and watch the list grow. - Add a policy rule with
add_policy_ruleand check whether that is recorded. - Read
kernel.audit_logdirectly and confirm it is the same list the method copies.
This is what real progress feels like.