← Blog

Operations · · 8 min read

Five things that keep breaking when AI agents move into production

The operator-pain pattern that keeps recurring in CISO discovery calls, with concrete scenarios and the standard fixes that don't close them.

Jonathan Haas

A user asks a customer-support agent a question that requires comparing their data to other customers' data. The agent decides that's reasonable, reads across tenant boundaries under a service account's broad access, and returns an aggregate. No PII crosses the wire, so the DLP layer never fires — the read still happened.

That's one of five operator-pain points recurring across a year of shipping agentic security in production and several months of CISO discovery calls. They're old categories of risk that autonomous agents expose in ways existing controls don't catch. Each pain has a standard "fix" that doesn't fix it, and a different layer where the fix has to land.

Cross-tenant data reach#

Agents reach across data they were never scoped to.

The shape: the agent runs under a service account. The service account has broad read access because the integration was designed around "let the agent answer questions about customer data." A user asks a question the agent decides requires comparing to other customers' data. The agent reads it. The data crosses tenant boundaries inside a single agent run. The customer who asked got an answer; the customers whose data was sampled have no record of the read in their dashboards. The DLP layer didn't catch it because no PII left the system — only an aggregate.

The standard fix is more guardrails. A content classifier on inputs, a DLP rule on outputs, a prompt-injection detector on the model. Each is useful for a different problem; none of them addresses the cross-tenant reach. The content classifier was bypassed by a legitimate question. The DLP rule had no PII to catch. The prompt-injection detector had no injection to detect. The agent decided on its own.

The fix lives at the identity layer. The agent's identity for the run is scoped to the customer the run is helping; the policy gate refuses reads of data outside that scope. The model can attempt the read; the gate refuses. The agent's loop sees the denial and proceeds without the data it can't have.

The security team finds out in the postmortem only when the fix hasn't been built — that gap between the read and the postmortem is discovery lag. When the fix is in place, the security team finds out at the moment of attempt, in the form of a denied audit row.

Destructive operations without approval#

The shape: the agent has API access that includes destructive verbs — drop tables, force pushes, deletes of resources the operator can't get back. The model decides a destructive operation is the right next step — usually for a defensible reason, sometimes for a misinterpreted one. The API call goes through because the service account has the permission. The destructive action is now done. The agent's loop continues, possibly making the next decision on top of the destroyed state.

The standard fix is to put a confirmation step in front of destructive operations: a prompt to the model asking it to confirm, or a tool that requires a "are you sure" parameter. Neither works. The model that decided to invoke the destructive operation will confirm. The "are you sure" parameter is just one more field for the agent to pass.

The fix lives at the policy gate, with the require-approval outcome. The gate evaluates the action class against the policy; destructive operations in a production scope require an operator's approval before the API call is made. The operator sees the action and either approves or denies; the agent waits. The model's confirmation is not a substitute for the operator's confirmation.

The harder design problem inside this fix is approval design — making sure the operator actually reads the approval, rather than rubber-stamping it. That's a separate post.

No kill switch in one place#

A running agent that needs to be stopped is a research project.

The shape: the agent is calling six tools. Three use the agent's primary credential; two use separate credentials minted for specific services; one is calling a third-party API on a webhook. The operator wants to stop the agent because it's misbehaving. "Stop the agent" turns into "find every credential the agent is currently using and revoke each one separately, then check whether the agent has any other credential paths the operator doesn't know about, then deal with the in-flight requests that have already been sent."

The standard fix is to redesign the credential layer so there's one credential. This is the right direction but it doesn't account for in-flight actions, sub-agents the agent has spawned, or cached credentials downstream services hold. The kill switch has to operate at the run level, not the credential level: the runtime maintains the active-runs set; revoking a run removes it from the set; the policy gate refuses subsequent actions for any run not in the set. In-flight requests fail their next gate check; sub-agents inherit the revocation.

The fix lives at the runtime control plane. The credential layer is part of the implementation, but the operator-facing surface is a single button on a single control plane that produces the right effect on every downstream credential, every sub-agent, every in-flight action.

The deeper containment design — three outcomes (stop, scope, quarantine) rather than one — is covered in Stopping a running agent.

Missing SIEM attribution#

The SIEM has the action. The chain back to a person isn't in the row.

The shape: the agent makes an API call. The API gateway logs the call and ships it to the SIEM. The SIEM row records what was called, when, and which credential was used. The credential is the agent's service account. The operator who launched the agent's run isn't on the row, because the operator's identity wasn't propagated through the credential the API gateway saw.

When the security team investigates a suspicious call months later, they pull the SIEM row. The row tells them the service account did it. The service account belongs to the agent. The agent runs on behalf of many operators. Which operator launched this particular run? The SIEM has no answer. The investigation either falls back on best-guess timestamps or escalates to forensic preservation of the runtime's own logs, if those still exist.

The standard fix is to ask the agent's authors to include the operator id in API request headers. This works when the authors remember to do it; it stops working when an upstream service drops headers or when an intermediate proxy normalizes them. The fix is brittle.

The fix lives at the identity layer. The agent runs under an identity that includes the operator as a verifiable claim; the SIEM row carries the claim end-to-end, not as a header that might be dropped but as a structured field the gateway emits as part of the action's audit row. The schema in What an agent audit row should carry is the operational form of this fix.

Scope is unknowable#

"What data can this agent actually reach?" is asked in every CISO conversation.

The shape: the security team is asked to sign off on an agent's deployment. The first question is what the agent will have access to. The team that built the agent answers with the service account's permissions. The security team asks what the agent will actually reach in practice. The team has no answer. The agent decides at runtime what to read. The set of resources actually touched is some subset of the service account's permissions, but the team can't tell which subset without running the agent in production and watching.

This is the pre-deployment version of the SIEM attribution problem. The deployment decision needs an answer to "what is the agent authorized to reach"; the existing answer is the service account's permission list, which is universally broader than what the agent actually does. The security team is being asked to approve a superset.

The standard fix is to scope the service account more tightly. This is the right direction but it doesn't survive the next prompt that requires data outside the new scope; either the agent fails, or the operator widens the scope back, or the team builds a special-case escape hatch.

The fix lives at the policy gate, with run-time scope. The agent's identity at the start of each run is scoped to the work in front of it. The pre-deployment answer to "what data can this agent reach" becomes: "the policy permits the following resource classes; each run is scoped to the work in front of it within those classes; the audit log records every resource actually touched." The deployment decision is on the policy, not on a static service account. The pre-deployment review reviews the policy.

Five gaps, three primitives#

The five pains look like five different problems on first read. The common structure is that each one is a gap between what the agent can do (the service account's broad permission) and what the agent should be allowed to do for the work in front of it.

Three primitives close that gap: identity scoped to the run with the operator behind it, runtime enforcement that evaluates every action against that scope before the action runs, and audit that records every action, every decision, and every operator behind it.

Deixic ships the three: the jobs are on What you can do, the security posture on Trust, and the implementation depth in the rest of the blog.