Engineering

The agent sprawl problem

April 20268 min read

In January, a mid-size fintech company discovered that an autonomous agent deployed by their marketing team had been scraping competitor pricing data, storing it in an unencrypted S3 bucket, and feeding it into a report that was shared with three external partners. The agent had been running for four months. Nobody in security knew it existed.

This isn't an edge case. It's the new normal.

Gartner projects that 40% of enterprise applications will include task-specific AI agents by the end of 2026, up from less than 5% in 2025. A LangChain survey of 1,300 professionals found that 78% of organisations have agent pilots running, but only 14% have reached production scale with proper governance. The gap between those two numbers is where the risk lives.

What agent sprawl actually means

Shadow IT was the problem of the 2010s: employees signing up for SaaS tools without IT approval, creating data silos, compliance gaps, and security blind spots. Agent sprawl is the 2026 version, and it's worse.

The difference is agency. A SaaS tool stores data. An agent acts on data. It reads your database, calls your APIs, sends emails, modifies records, and makes decisions, all at machine speed, with whatever permissions its creator happened to give it.

Shadow IT was about access. Agent sprawl is about execution. When an unmanaged tool leaks data, you have a breach. When an unmanaged agent acts on data, you have an incident you might not discover for months.

The anatomy of sprawl

Interactive simulation · sprawl vs governance

The same capability, two outcomes

One high-value agent ships fast. Governance is either bypassed or embedded from day one.

Ungoverned path

Inherited credentials + fragmented routing

Risk 44

Action map

Trace 5%
MKTFINHRSUPCRMBILLDOCS
Blast radiusUnknown action scope

Known

0/1

Untracked

1

Keys

1

Detect

30d

Product Manager

Deploys pricing agent from laptop using personal API key.

Untracked agents

1

Trace uplift

+95%

Risk reduction

28 points

Notice the core shift: the exact same agent capabilities become far safer when identity, routing, and action boundaries are centralized instead of inherited ad hoc.

We've audited agent deployments at four companies in the past six months. The pattern is remarkably consistent.

Phase 1: The useful hack. A product manager builds an agent in Cursor or Claude Code to automate their weekly competitive analysis report. It works beautifully. It takes 3 minutes instead of 3 hours. They share it with the team.

The agent uses the PM's personal API keys. It runs on the PM's laptop. It stores results in a shared Google Sheet. Security has no visibility into any of this.

Why traditional security fails

Enterprise security teams have spent two decades building frameworks for managing human access to systems. Identity and Access Management (IAM), role-based access control (RBAC), single sign-on (SSO), multi-factor authentication (MFA). These frameworks assume a fundamental property: the entity being governed is a person who authenticates, makes decisions at human speed, and can be held accountable.

Agents break every one of these assumptions.

typescriptthe-identity-problem.ts
1
// Traditional IAM: a human authenticates
2
const user = await auth.verify(credentials);
3
// user.id → traceable to a person
4
// user.permissions → reviewed quarterly
5
// user.actions → logged at human-comprehensible velocity
6
7
// Agent "IAM": what actually happens today
8
const agent = new Agent({
9
// Who is this agent? A string someone typed.
10
name: "marketing-pricing-bot",
11
12
// Whose permissions does it use? The person who deployed it.
13
credentials: process.env.JANES_API_KEY,
14
15
// What can it do? Everything Jane can do.
16
permissions: "inherited",
17
18
// Who reviews its actions? Nobody.
19
monitoring: undefined,
20
});

NIST's Centre for AI Standards and Innovation (CAISI) launched an agent-specific standards initiative in February 2026, identifying three fundamental gaps in traditional identity management:

  1. Binding: agents need identities linked to the humans who authorised them, establishing a chain of accountability.
  2. Scoping: agents need task-bound, time-limited permissions, not the broad persistent access their creators have.
  3. Verification: systems that receive requests from agents need mechanisms to verify the authenticity of the authorisation behind each action.

The governance architecture

After helping four companies recover from sprawl incidents, we've converged on an architecture with four layers. Each layer addresses a distinct failure mode.

Layer 1: The agent registry

You cannot govern what you cannot see. The first requirement is a centralised inventory of every agent operating in your environment.

typescriptagent-registry.ts
1
interface AgentRegistration {
2
id: string;                  // Unique, immutable identifier
3
name: string;                // Human-readable name
4
owner: string;               // Person accountable for this agent
5
department: string;          // Organisational unit
6
purpose: string;             // What this agent does, in plain language
7
model: string;               // Which LLM it uses
8
tools: ToolPermission[];     // Every tool it can access
9
dataSources: DataSource[];   // Every data source it reads from
10
writeTargets: WriteTarget[]; // Every system it can modify
11
schedule: Schedule;          // When it runs (continuous, cron, triggered)
12
createdAt: Date;
13
lastAuditedAt: Date;
14
status: "active" | "suspended" | "decommissioned";
15
}
16
17
interface ToolPermission {
18
toolId: string;
19
operations: ("read" | "write" | "delete")[];
20
rateLimit: number;           // Max invocations per minute
21
dataClassification: "public" | "internal" | "confidential" | "restricted";
22
}

The registry isn't a spreadsheet. It's an API that enforces registration at deployment time. If an agent isn't registered, it doesn't get credentials. If its registration expires, its credentials are revoked automatically.

Layer 2: Scoped identity

Every agent gets its own identity, separate from its creator. This identity has permissions scoped to exactly what the agent needs (and nothing more) with time-bound access that requires periodic renewal.

typescriptscoped-identity.ts
1
interface AgentIdentity {
2
agentId: string;
3
// Who authorised this agent to exist
4
authorisedBy: string;
5
// When the authorisation expires
6
expiresAt: Date;
7
// Permissions are task-specific, not role-based
8
permissions: TaskPermission[];
9
}
10
11
interface TaskPermission {
12
resource: string;        // e.g., "crm.contacts"
13
actions: string[];       // e.g., ["read", "update"]
14
conditions: Condition[]; // e.g., only contacts in the agent's assigned region
15
maxActionsPerHour: number;
16
requiresApproval: boolean; // Human-in-the-loop for high-risk actions
17
}
18
19
// At runtime: every tool call is validated against permissions
20
async function executeToolCall(
21
agent: AgentIdentity,
22
tool: string,
23
action: string,
24
params: unknown
25
): Promise<ToolResult> {
26
// Check: is this agent allowed to use this tool?
27
const permission = agent.permissions.find(
28
  p => p.resource === tool && p.actions.includes(action)
29
);
30
31
if (!permission) {
32
  audit.log("denied", { agentId: agent.agentId, tool, action });
33
  throw new PermissionDenied(agent.agentId, tool, action);
34
}
35
36
// Check: has the agent exceeded its rate limit?
37
if (await rateLimiter.isExceeded(agent.agentId, tool)) {
38
  audit.log("rate_limited", { agentId: agent.agentId, tool });
39
  throw new RateLimitExceeded(agent.agentId, tool);
40
}
41
42
// Check: does this action require human approval?
43
if (permission.requiresApproval) {
44
  await requestHumanApproval(agent.agentId, tool, action, params);
45
}
46
47
// Execute and log
48
const result = await tools.execute(tool, action, params);
49
audit.log("executed", { agentId: agent.agentId, tool, action, params });
50
return result;
51
}

Layer 3: Real-time observability

Every action an agent takes is logged to an immutable audit trail with enough context to reconstruct the full decision chain after the fact. This isn't optional instrumentation. It's a governance primitive.

typescriptagent-observability.ts
1
interface AgentAuditEvent {
2
// Identity
3
traceId: string;
4
agentId: string;
5
authorisedBy: string;
6
7
// Action
8
timestamp: Date;
9
action: "tool_call" | "decision" | "data_read" | "data_write" | "escalation";
10
tool?: string;
11
input: SanitisedInput;  // PII stripped, but semantically complete
12
output: SanitisedOutput;
13
14
// Context
15
reasoning?: string;     // Why the agent chose this action
16
confidence?: number;    // Model's self-reported confidence
17
alternatives?: string[]; // What other actions were considered
18
19
// Governance
20
permissionUsed: string;
21
dataClassification: string;
22
piiDetected: boolean;
23
anomalyScore: number;   // Statistical deviation from normal behaviour
24
}

The anomaly detection layer is critical. It monitors patterns like: an agent that normally makes 50 API calls per day suddenly making 5,000. An agent accessing a data source it has never touched before. An agent's error rate spiking from 2% to 40%. These patterns trigger automatic suspension and human review before damage compounds.

Layer 4: The kill switch

Every agent must be instantly suspendable. Not "suspendable after a deploy" or "suspendable after someone finds the right Kubernetes pod." Instantly. One API call. The agent stops, in-flight actions are cancelled where possible, and all credentials are revoked.

typescriptkill-switch.ts
1
// Governance dashboard action
2
async function suspendAgent(
3
agentId: string,
4
reason: string,
5
suspendedBy: string
6
): Promise<SuspensionResult> {
7
// 1. Revoke all credentials immediately
8
await credentialStore.revokeAll(agentId);
9
10
// 2. Cancel in-flight actions where possible
11
const inflight = await actionQueue.getInFlight(agentId);
12
const cancelled = await Promise.allSettled(
13
  inflight.map(action => action.cancel())
14
);
15
16
// 3. Update registry
17
await registry.updateStatus(agentId, "suspended");
18
19
// 4. Notify stakeholders
20
await notify.send({
21
  to: [registry.getOwner(agentId), "security-team"],
22
  subject: `Agent ${agentId} suspended`,
23
  body: `Reason: ${reason}. Suspended by: ${suspendedBy}.`,
24
  severity: "high",
25
});
26
27
// 5. Immutable audit log
28
audit.log("agent_suspended", {
29
  agentId,
30
  reason,
31
  suspendedBy,
32
  inflightCancelled: cancelled.length,
33
  timestamp: new Date(),
34
});
35
36
return { agentId, status: "suspended", cancelledActions: cancelled.length };
37
}

The coordination problem

There's a failure mode that no amount of individual agent governance solves: conflicting agents. When two agents with overlapping scopes operate on the same data, they can produce contradictory outcomes without either agent doing anything individually wrong.

Interactive · coordination demo

The Inventory Conflict

Execution Trace
Inventory Stock
500 units
Procurement Budget
$10,000

Takeaway: local correctness is not system correctness. Two individually valid agents can still create destructive loops without a global conflict policy.

The solution is an explicit coordination layer: a registry of which agents can act on which resources, with built-in conflict detection.

typescriptconflict-detection.ts
1
interface ResourceClaim {
2
agentId: string;
3
resource: string;
4
operations: string[];
5
priority: number;             // Higher wins in conflicts
6
conflictStrategy: "block" | "queue" | "escalate";
7
}
8
9
function detectConflicts(
10
claims: ResourceClaim[]
11
): Conflict[] {
12
const conflicts: Conflict[] = [];
13
14
for (let i = 0; i < claims.length; i++) {
15
  for (let j = i + 1; j < claims.length; j++) {
16
    const a = claims[i];
17
    const b = claims[j];
18
    
19
    if (
20
      a.resource === b.resource &&
21
      a.operations.some(op => b.operations.includes(op))
22
    ) {
23
      conflicts.push({
24
        agents: [a.agentId, b.agentId],
25
        resource: a.resource,
26
        overlappingOps: a.operations.filter(
27
          op => b.operations.includes(op)
28
        ),
29
        resolution: a.priority !== b.priority
30
          ? "priority"
31
          : "requires_human_review",
32
      });
33
    }
34
  }
35
}
36
37
return conflicts;
38
}

The governance checklist

If you're shipping your first 10 agents to production, here is the minimum viable governance surface:

The chart above shows the percentage of sprawl incidents that each layer would have prevented, based on our analysis of 31 incidents across four companies. The registry alone prevents the majority of problems because most sprawl incidents stem from agents that nobody knew about.

The uncomfortable truth

Agent sprawl isn't a technology problem. It's an organisational problem. The technology for governance exists today: registries, scoped credentials, audit trails, anomaly detection. The challenge is that governance feels like friction, and the teams deploying agents are optimising for speed.

The companies that get this right treat agent governance the way they treat cloud governance. Not as a gate that blocks deployment, but as a platform that makes deployment safe and auditable by default. You don't ask developers to "remember to log things." You give them infrastructure that logs everything automatically.

The goal isn't to slow down agent adoption. It's to make ungoverned agents the harder path. When the governed path is faster and more reliable than the ungoverned path, sprawl fixes itself.

Sources

  • Gartner projection on enterprise agent adoption (2026 forecast).
  • LangChain survey (n=1,300) on pilot-to-production governance gaps.
  • NIST AI Risk Management Framework and CAISI agent-identity initiative materials (2026).
  • Internal incident analysis sample: 31 sprawl incidents across four companies.

Your company will have 50 agents by the end of 2026. The question isn't whether to govern them. It's whether you build the governance architecture before or after the first incident. The math strongly favours before.