
AI Agent Ops: How to Monitor, Audit, and Control Agents in Production
TL;DR: „An agent in production needs three things: Observability (what is it doing?), Audit (what has it done?), and Control (how do I stop it?). Here's the operational playbook."
— Till FreitagIn 30 Seconds
Governance defines the rules. But rules without enforcement are worthless. This article covers the operational side: how to monitor agents in production, make every decision traceable, and intervene within seconds when needed.
The Problem: Agents Are Not APIs
An API is deterministic: same input, same output. An agent is non-deterministic: it decides which tools to call, in what order, and with what parameters. That makes traditional monitoring useless.
| API | Agent | |
|---|---|---|
| Behavior | Deterministic | Non-deterministic |
| Call chain | Known | Dynamic |
| Failure mode | Exception → Retry | Bad decision → Side effects |
| Monitoring | Status code + latency | Reasoning trace + tool calls + outcome |
What we need: a monitoring stack that captures not just whether something happened, but why it happened.
The Three Pillars of Agent Ops
1. Observability: What Is the Agent Doing Right Now?
Observability for agents goes beyond metrics. You need the complete reasoning trace:
@dataclass
class AgentTrace:
trace_id: str
timestamp: datetime
input_hash: str # Never log plaintext
reasoning_steps: list[ReasoningStep]
tool_calls: list[ToolCall]
model_used: str
routing_decision: str # local / cloud
total_tokens: int
total_latency_ms: float
outcome: str # success / error / timeout / killed
@dataclass
class ToolCall:
tool_name: str
input_hash: str
output_hash: str
sandbox_type: str # container / wasm / kernel
latency_ms: float
status: str # success / error / timeout
side_effects: list[str] # ["db_write", "email_sent", ...]Critical: Every tool call must declare its side effects. That's the only way to reconstruct what the agent actually changed after the fact.
Dashboards That Matter
Forget vanity metrics. These four metrics define whether an agent is viable in production:
| Metric | What It Shows | Threshold |
|---|---|---|
| Tool-Call Rejection Rate | How often is a tool call rejected by policy? | >5% → Policy too strict or agent hallucinating |
| Privacy Route Ratio | Share of queries on the local path | Industry-dependent, but trackable |
| Reasoning Depth | Average steps to reach an answer | >10 → Agent might be stuck in loops |
| Side Effect Rate | How often does the agent trigger write actions? | Every write action needs an audit entry |
2. Audit: What Has the Agent Done?
Audit is not the same as logging. Logging is for developers, audit is for compliance. The difference:
| Logging | Audit | |
|---|---|---|
| Audience | Developers | Compliance, management, regulators |
| Content | Technical details | Decisions and their rationale |
| Retention | Days to weeks | Years (GDPR: up to 10 years) |
| Immutability | Nice-to-have | Required |
| Format | Freeform | Structured, machine-readable |
Audit Trail Architecture
class AuditTrail:
def __init__(self, storage: AuditStorage):
self.storage = storage
def record(self, event: AuditEvent):
"""Append-only. No update, no delete."""
signed_event = self._sign(event)
self.storage.append(signed_event)
def _sign(self, event: AuditEvent) -> SignedAuditEvent:
"""Cryptographic signature for immutability."""
previous_hash = self.storage.get_last_hash()
event_hash = hash_chain(previous_hash, event)
return SignedAuditEvent(
event=event,
hash=event_hash,
previous_hash=previous_hash,
)
@dataclass
class AuditEvent:
timestamp: datetime
agent_id: str
action_type: str # "tool_call" | "routing" | "escalation"
decision: str # What was decided?
policy_basis: str # Which policy drove the decision?
data_classification: str # sensitive | non_sensitive
model_path: str # local | cloud
outcome: str
human_override: bool # Was there manual intervention?Hash chain: Each audit entry references the hash of the previous entry. Tampering with a single entry breaks the chain – comparable to a mini-blockchain for agent decisions.
What Must Be Audited?
Not everything. But at minimum:
- Every routing decision – Which model was chosen and why?
- Every write tool call – DB writes, emails, API calls with side effects
- Every escalation – When did the agent hand off to a human?
- Every policy violation – Including blocked attempts
- Every manual intervention – Kill switch, override, config change
3. Control: How Do I Stop the Agent?
This is the part most people forget. Starting an agent is easy. Stopping it in a controlled manner is the real challenge.
Kill Switch Hierarchy
Level 0: Soft Stop
→ Agent finishes current task, accepts no new ones
→ Graceful shutdown
Level 1: Immediate Stop
→ Running tool calls are aborted
→ Pending side effects are rolled back (where possible)
→ Agent enters quarantine mode
Level 2: Emergency Kill
→ Process terminated immediately
→ All containers stopped
→ Network access revoked
→ Incident createdImplementation
class AgentController:
def __init__(self, agent_id: str):
self.agent_id = agent_id
self.state = AgentState.RUNNING
async def soft_stop(self, reason: str):
"""Level 0: Agent finishes current task."""
self.state = AgentState.DRAINING
await self._notify_agent("stop_accepting")
await self._wait_for_current_task(timeout=60)
self.state = AgentState.STOPPED
self._audit("soft_stop", reason)
async def immediate_stop(self, reason: str):
"""Level 1: Immediate stop with rollback."""
self.state = AgentState.STOPPING
await self._cancel_running_tools()
await self._rollback_pending_effects()
self.state = AgentState.QUARANTINED
self._audit("immediate_stop", reason)
async def emergency_kill(self, reason: str):
"""Level 2: Emergency abort."""
self.state = AgentState.KILLED
await self._kill_process()
await self._stop_all_containers()
await self._revoke_network()
self._create_incident(severity="critical", reason=reason)
self._audit("emergency_kill", reason)Automatic Triggers
Kill switches should not only be manually triggerable:
AUTO_KILL_TRIGGERS = [
# Agent stuck in a loop
Trigger(
condition="reasoning_steps > 20 in single task",
action="soft_stop",
reason="Possible reasoning loop detected",
),
# Too many write actions in a short time
Trigger(
condition="side_effects > 10 in 60 seconds",
action="immediate_stop",
reason="Abnormal side effect rate",
),
# Attempt to send sensitive data to cloud
Trigger(
condition="privacy_violation_attempt",
action="emergency_kill",
reason="Privacy policy violation",
),
# Unknown tool call
Trigger(
condition="tool_call not in allowed_tools",
action="immediate_stop",
reason="Unauthorized tool call attempted",
),
]Human-in-the-Loop: Where Humans Must Intervene
Not every decision needs a human. But some do:
Agent Decision
└── Is it a write action?
├── No → Automatic
└── Yes
└── Does it involve sensitive data?
├── No → Automatic with audit
└── Yes → Human approval required
└── Is it reversible?
├── Yes → Approval by 1 person
└── No → Approval by 2 people (four-eyes)Approval Gates
class ApprovalGate:
def __init__(self, required_approvers: int = 1):
self.required = required_approvers
async def request_approval(
self,
action: str,
context: dict,
timeout_minutes: int = 30,
) -> ApprovalResult:
ticket = await self._create_approval_ticket(action, context)
# Wait for approval or timeout
result = await self._wait_for_approvals(
ticket,
required=self.required,
timeout=timeout_minutes * 60,
)
if result.timed_out:
return ApprovalResult.DENIED # Timeout = denial
return resultImportant: Timeout always means denial. An agent waiting for approval that never comes is just as dangerous as one without approval.
Alerting: The Right Signals
Too many alerts are as bad as none. The art is alerting only on real problems:
| Alert Level | Trigger | Action |
|---|---|---|
| Info | Agent completed task with unusually many steps | Dashboard entry |
| Warning | Privacy route ratio deviates >20% from average | Slack notification to agent owner |
| Error | Tool call failed after 3 retries | PagerDuty to on-call |
| Critical | Privacy violation attempt or kill switch triggered | PagerDuty + Incident + Agent stopped |
The Complete Stack
┌─────────────────────────────────────────────┐
│ Alerting │
│ (PagerDuty / Slack / monday.com Incidents) │
├─────────────────────────────────────────────┤
│ Control Plane │
│ Kill Switches │ Approval Gates │ Policies │
├─────────────────────────────────────────────┤
│ Audit Trail │
│ Hash Chain │ Compliance Reports │ Retention│
├─────────────────────────────────────────────┤
│ Observability │
│ Traces │ Metrics │ Dashboards │ Logs │
├─────────────────────────────────────────────┤
│ Agent Runtime │
│ Privacy Router │ Sandbox │ Tool Execution │
└─────────────────────────────────────────────┘Integration with Existing Tools
Agent Ops doesn't need to be built from scratch. Existing tools can be adapted:
| Function | Tool | Adaptation |
|---|---|---|
| Traces | OpenTelemetry | Custom spans for reasoning steps |
| Metrics | Prometheus / Grafana | Agent-specific metrics |
| Audit | Append-only DB (e.g., EventStore) | Schema for agent events |
| Alerts | PagerDuty / OpsGenie | Agent-specific severity rules |
| Incidents | monday.com Service | Automatic ticket creation |
| Approval | Slack / monday.com | Approval workflows |
Checklist: Agent Production Readiness
Before an agent goes to production, these points must be met:
- Observability: Complete reasoning trace is captured
- Audit trail: Append-only, signed, with hash chain
- Kill switch: All three levels implemented and tested
- Automatic triggers: Loop detection, rate limiting, policy enforcement
- Human-in-the-loop: Approval gates for sensitive write actions
- Alerting: Graduated (Info → Warning → Error → Critical)
- Privacy routing: Sensitive data stays local (Privacy Router)
- Sandboxing: Tool calls isolated (Container/WASM/Kernel)
- Rollback plan: How is a faulty agent run reversed?
- Incident playbook: Who gets notified when and what happens next?
Conclusion
Building an agent is the easy part. Running it in production – securely, auditably, controllably – is the real work. The three pillars of Observability, Audit, and Control are not optional. They're the prerequisite for deploying autonomous agents in any enterprise.
Three takeaways:
- Observability ≠ Logging – You need the complete reasoning trace, not just status codes and latency
- Audit trails must be immutable – Hash chain, cryptographically signed, append-only
- Kill switches need three levels – Graceful, Immediate, Emergency – with automatic triggers
→ Autonomous AI Agents: Governance Framework → Building a Privacy Router with OpenClaw → Agent Sandboxing: Containers vs. WASM vs. Kernel → The 5 Building Blocks of an AI Agent → Get in touch







