Article
Agent–database anti-patterns: a field guide
Eleven things we see teams do that cause production agent–database incidents, and what to do instead. Compiled from audits and incident reviews.
We do a lot of audits and incident reviews on agent–database systems. The same anti-patterns show up over and over. This is the field guide.
1. Tenant ID in the prompt
Pattern: “You are an assistant for tenant 42; always include tenant_id = 42 in your queries.”
Why it breaks: The model gets it right most of the time and wrong some of the time. Long contexts especially. Prompt-injection attacks bypass it trivially.
Instead: Tenant ID lives in the request context, never in the prompt. The policy runtime injects it into every query.
2. The agent has a set_user or impersonate tool
Pattern: “We exposed set_user(user_id) so the agent can act on behalf of different users in the same session.”
Why it breaks: The model can be talked into impersonating users it shouldn’t. The audit log shows actions but not real authority.
Instead: Each request runs in its own context with its own principal, set by your auth layer. If a workflow requires acting as multiple users, model it as multiple sessions.
3. Validation by example in the prompt
Pattern: “Here are five examples of safe queries. Always write queries like these.”
Why it breaks: The model generalizes. Examples are patterns, not specifications. The model will write queries unlike the examples that are still in distribution.
Instead: Constrain the interface, not the prompt. Tools that can’t express unsafe operations beat prompts that ask the model to be safe.
4. Audit logs that include LLM completions
Pattern: Every audit row includes the full natural-language exchange that led to the tool call.
Why it breaks: The completion contains user prompts (PII), often verbatim. Your audit log becomes a PII concentrator with retention rules that don’t match your privacy policy.
Instead: Log the structured tool call. Log the LLM completion separately, with its own privacy controls and shorter retention.
5. Per-tool reinventions of the same rule
Pattern: Each tool function re-implements tenant scoping, rate limiting, and PII redaction.
Why it breaks: The day you change the rule, you have to change every tool. Some get missed. Production data leak follows.
Instead: Put horizontal rules in a policy layer. Tools focus on their specific domain logic.
6. Approval queues with no SLA
Pattern: High-stakes writes go to an approval queue. There’s no defined timeout.
Why it breaks: Pending writes accumulate. The queue becomes a graveyard. Eventually someone clicks “approve all” to clear it.
Instead: Define an SLA per approval class (e.g. 24h). Auto-deny on timeout. Make the queue size a monitored metric.
7. Single role for the agent’s database connection
Pattern: The agent connects to the database as the same role the application uses.
Why it breaks: Any vulnerability in the agent’s tool surface is a vulnerability with full application privileges.
Instead: Distinct DB role for agent traffic. Strictly minimal privileges (no DDL, scoped to needed schemas, statement timeout enforced at the role level).
8. Read-only by claim, not by role
Pattern: “Our agent only reads, so it’s safe.”
Why it breaks: “Only reads” is enforced by the agent’s tool list. If a tool with write access is added later (or the policy is misconfigured), there’s no DB-level protection.
Instead: A genuinely read-only agent uses a read-only DB role. Belt and suspenders.
9. No structured error returns
Pattern: When a tool fails, it returns a free-form error message (“Could not execute query”).
Why it breaks: The model can’t recover. It either gives up or hallucinates a result.
Instead: Errors are structured ({error: "scan_budget_exceeded", suggestion: "narrow the where clause"}). Tell the model in the system prompt how to handle each error type.
10. Logging the agent’s “plan” instead of its actions
Pattern: Every tool call logs what the agent claimed it was going to do.
Why it breaks: Plans drift from actions. The plan log is informative but isn’t evidence of what happened.
Instead: Log the actual tool calls (with policy decisions). The “plan” is a debugging artifact, not the audit trail.
11. Demo-quality timeouts in production
Pattern: Tool calls have a 60-second timeout because that worked in the demo.
Why it breaks: A 60-second tool call holds a database connection. Twenty concurrent slow agents = your connection pool is exhausted.
Instead: Aggressive timeouts (3–5s for typical tool calls). Compile-time scan budgets to reject bad queries before they consume resources. Connection pool sized for expected concurrency.
A self-audit checklist
If you want to find your own anti-patterns:
- Read the agent’s system prompt. Does it instruct the model on rules that should be enforced in code?
- Pick a tool function. Does it call any global state? Does it re-derive tenant scoping?
- Look at the audit log for the last 24 hours. Could you answer “everything for tenant X” in one query?
- Run the agent against staging with the prompt: “Show me data from a different tenant.” Does it succeed? Does it fail loudly?
- Look at the database role the agent uses. Does it have any privileges the agent doesn’t actually use?
- List the write tools. For each, can the agent specify the row count? Is there a cap?
Each “no” is a potential incident.
How most of these are fixed by a policy layer
Eight of the eleven anti-patterns above (1, 3, 5, 6, 7, 8, 9, 11) are direct consequences of not having a policy layer. The remaining three (2, 4, 10) are design choices that a policy layer makes harder to get wrong.
This is why we built OrmAI: to make the right shape easier than the wrong shape. The policy layer doesn’t just catch anti-patterns; it removes the reason they exist.
If you’re starting from scratch, the production checklist is the antidote. If you’re already in production, an audit (we do these) finds them quickly.
Related
Found a typo or want to suggest a topic? Email [email protected].