Stop giving AI agents production keys: new control planes and approval gates for irreversible actions
I’m Denis Shokhirev, an Agentic AI Systems Architect based in Freiburg im Breisgau, Germany, and at DennisCraft AI Studio I ship production AI systems for DACH B2B clients on Claude, Supabase, n8n, Doppler, and self-hosted Postgres. The ugly production moment is not when an agent writes a mediocre answer; it’s when the same agent holds a real production key and is one tool call away from deleting records, emailing customers, changing access, or triggering a payout. If your agent runtime has dir
I’m Denis Shokhirev, an Agentic AI Systems Architect based in Freiburg im Breisgau, Germany, and at DennisCraft AI Studio I ship production AI systems for DACH B2B clients on Claude, Supabase, n8n, Doppler, and self-hosted Postgres. The ugly production moment is not when an agent writes a mediocre answer; it’s when the same agent holds a real production key and is one tool call away from deleting records, emailing customers, changing access, or triggering a payout.
If your agent runtime has direct access to production secrets, you collapsed reasoning and authority into one component. That is the core design mistake. Teams usually notice it late: the demo looks good, the agent can “do things,” and then everyone starts adding warnings to the system prompt. But prompts are not enforcement. An LLM can propose actions; it should not automatically own the credentials for irreversible ones.
The real problem is authority, not intelligence
LLMs are very good at selecting plausible next steps from context. They are not policy engines, and they do not naturally understand legal finality, financial exposure, or operational blast radius. OWASP’s LLM Top 10 2025 explicitly calls out excessive agency and insecure output handling as production risks when model output is allowed to trigger real actions: https://genai.owasp.org/llm-top-10/
I also recommend reading Anthropic’s documentation on tool use and safe system design. The consistent pattern is clear: keep tool permissions narrow, validate outside the model, and treat the model as a planner rather than the final authority. Source: Anthropic Docs, 2025, https://docs.anthropic.com/
Where direct keys fail in practice
| Action class | Example | Why it hurts | Safer pattern |
|---|---|---|---|
| Database mutation | UPDATE or DELETE in Postgres | Data loss, broken audit trail | Signed action + approval gate |
| Customer communication | Outbound email or ticket closure | Contract and reputation risk | Preview + human approval |
| Privilege changes | Role or scope update | Access escalation | Policy service only |
| Financial operations | Refund, invoice, payout | Money movement is hard to reverse | Two-step approval |
The pattern is simple: give the agent the ability to suggest an action, not the authority to execute it directly in production.
A practical control plane for agent actions
The most useful design shift is to separate your system into three planes.
1. Reasoning plane
This is where Claude or another model reads context, selects tools, and proposes an action. It can access read-only data, schemas, and approved action definitions. It should not hold long-lived production secrets.
2. Policy and approval plane
A separate service receives a proposed action, normalizes it to a known schema, scores its risk, and decides one of three outcomes: auto-approve, require human review, or deny. The decision can depend on environment, amount, record count, target resource, reversibility, and user identity.
3. Execution plane
This is the only layer allowed to hold short-lived credentials and call production APIs. It does not execute arbitrary text from the model. It executes a typed action contract after validation, idempotency checks, and audit logging.
from dataclasses import dataclass
from typing import Literal
@dataclass
class ProposedAction:
action_type: Literal["send_email", "run_sql", "issue_refund"]
payload: dict
reason: str
actor_id: str
environment: Literal["staging", "prod"]
def approval_required(action: ProposedAction) -> bool:
if action.environment == "prod":
return True
if action.action_type in {"run_sql", "issue_refund"}:
return True
if action.action_type == "send_email" and action.payload.get("recipient_count", 1) > 5:
return True
return FalseThis is not overengineering. It is the same separation of duties you already apply to payments, infrastructure, and database administration.
Approval gates need data, not vibes
A common anti-pattern is a natural-language rule such as “if the action looks risky, ask a human.” That fails because “looks risky” is not machine-enforceable. The model must produce a structured intent, and the gate must evaluate it against explicit policy rules.
Minimum fields for a production gate
I want at least these fields on every proposed action: action_type, target_resource, environment, initiator, expected impact, reversibility, idempotency_key, and a human-readable diff. For SQL, estimate affected rows. For email, include recipients and final body preview. For config changes, include before/after values.
approval_rules:
- action_type: run_sql
environment: prod
require_human: true
- action_type: send_email
environment: prod
recipient_count_gt: 5
require_human: true
- action_type: issue_refund
amount_eur_gt: 0
require_human: true
- action_type: update_user_role
environment: any
require_human: trueThis can live in Postgres or Supabase and be called from n8n without a huge platform effort. In regulated environments, the audit trail matters as much as the action itself.
What to put around the agent today
You do not need a mythical vendor product to improve this. A stable setup can be built with standard tools.
Secrets isolation
Use Doppler or another secret manager so only the execution service can read production credentials. The model runtime should never hold the keys that can mutate production directly.
Static analysis and secret scanning
Use semgrep, bandit, and gitleaks in CI. bandit catches insecure Python patterns. gitleaks finds leaked credentials. semgrep is useful for custom policy checks in glue code, especially around unsafe SQL execution and permissive tool handlers.
python -m pip install bandit
bandit -r app/
docker run --rm -v "$PWD:/repo" semgrep/semgrep semgrep \
--config=p/security-audit /repo
gitleaks detect --source . --verboseDatabase role separation
In Postgres, split roles aggressively: read_only_agent, action_executor, and human_admin. The agent only gets read access. Any write path goes through a stored procedure or an execution service with policy checks and logging.
What counts as irreversible in real B2B systems
Many teams only think about DELETE statements. That is too narrow. In enterprise systems, irreversibility is often legal, financial, or procedural. Sending a customer-facing email with the wrong promise is not fixed by a database rollback. Changing a KYC status, closing an operations incident, exporting personal data, or editing a retention policy can all create obligations far beyond the original API call.
That is why I treat “reasoning traces” and “audit records” as different artifacts. The model’s chain of thought is not your accountability layer. Your accountability layer is a signed action request, a policy decision, an approval record, and an execution log.
FAQ
Is human-in-the-loop enough?
Not by itself. Human review only works when the reviewer sees a structured action with a clear diff, target, and impact. Free-form summaries from the agent are not a reliable approval surface.
Can I allow write access in staging?
Yes. Staging autonomy is often useful. Just do not let that become an excuse to blur the boundary between staging and production authority.
What if the action is reversible?
Then you may auto-approve more of them, but only if reversibility is defined at the system level and backed by idempotency, logging, and a tested rollback path. Do not let the model decide what “reversible” means.
Do small teams need a separate policy service?
Yes, even if it starts as a thin Python service with a Postgres table of rules. The key is architectural separation, not organizational size.
What is the first fix for an existing agent?
Inventory every action the agent can trigger today, label irreversible ones, and remove production secrets from the agent runtime first. Then add an execution layer and approval gate around those actions.
Which stage in your LLM pipeline catches the most issues in production today: static analysis, runtime sandboxing, or human approval before execution? I’d genuinely like to compare notes.
I run a free 30-min stack audit for DACH founders building AI in regulated markets. DM me on LinkedIn or write to @ger_dennis_ai.
I build agents like this for businesses in Germany and the EU.
Tell me in two sentences what eats your team’s time – reply within 24 hours.
Turn your process into an AI system
Production quality. DACH B2B focus.