Stop Your Agents From Breaking Production
Dan Luu's recent notes on agentic coding highlight a critical gap: we are deploying agents that can write code, delete files, and deploy services without adequate checks. The industry rush to "autonomy" ignores the cost of failure. One bad prompt can wipe a database.
You do not need a complex new framework to fix this. You need a guardrail. This tutorial builds a simple "Human-in-the-Loop" interceptor for any Python-based agent. It forces the model to classify action risk and blocks high-consequence moves until a developer approves them.
Step 1: Define Action Categories
First, stop treating all tool calls as equal. Reading a file is low risk. Deleting a table is high risk. Define a strict schema for your agent's output.
Create a risk_classifier function. It takes the intended action and returns a risk score (0-10).
`` def classify_risk(action: str, target: str) -> int: destructive_keywords = ["delete", "drop", "rm", "format", "overwrite"] if any(k in action.lower() for k in destructive_keywords): return 10 if "write" in action.lower() or "update" in action.lower(): return 5 return 1python
`
Do not rely on the LLM to decide its own risk level. Models optimize for helpfulness, not safety. They will rationalize dangerous actions if you ask them to self-evaluate. Use deterministic string matching for the initial filter.
Step 2: Implement the Interceptor
Wrap your agent's tool execution logic. Before the code runs, check the score. If the score exceeds a threshold (e.g., 5), pause execution and request input.
` def execute_with_guardrail(action, target): score = classify_risk(action, target) if score > 5: print(f"[BLOCKED] High risk action detected: {action} on {target}") approval = input("Type 'YES' to approve this destructive action: ") if approval.strip() != "YES": raise PermissionError("Action denied by user.") # Proceed with actual execution run_tool(action, target)python
`
This adds 2 seconds to the workflow but prevents 100% of accidental deletions. Integrate this into your agent loop. If you use LangChain or AutoGen, wrap the Tool class, not the chat logic.
Step 3: Log Every Decision
Silent failures are useless. When an agent triggers a guardrail, log the context, the proposed action, and the user response. Store this in a separate audit_log.json.
` import json from datetime import datetime def log_decision(action, score, approved): entry = { "timestamp": datetime.now().isoformat(), "action": action, "risk_score": score, "approved": approved } with open("audit_log.json", "a") as f: f.write(json.dumps(entry) + "\n")python
`
Review these logs weekly. If you see the same safe action triggering false positives, adjust your keyword list. If you see near-misses, tighten the threshold.
Common pitfalls
Trusting the LLM to self-regulate: Never ask the agent "Is this safe?" It will say yes to complete the task. Hardcode the rules.
Blocking too much: If you set the threshold to 1, nothing gets done. Start at 5 (write operations) and tune based on your logs.
Ignoring async flows: If your agent runs in a background worker, input()` will hang the process. Use a webhook or a database flag for approval in production environments.
Hardcoding keywords only: String matching misses context. "Drop the mic" is safe; "Drop the users table" is not. Combine keyword checks with a secondary LLM call specifically trained to detect intent if your use case is complex.
Next step
Once your guardrails are stable, integrate automated testing for your agent's decision tree using the benchmarking approach described in [Dan Luu's notes on agentic coding](https://danluu.com/ai-coding/).