Your AI agents don't have an identity problem. They have no identity at all.
Every enterprise rushing agentic AI into production is quietly reusing infrastructure built for a different kind of user. Human identity and access management assumes a person: provisioned once, authenticated at login, trusted for a session, and eventually deprovisioned when they leave. Agents don't fit that shape, so most organizations don't give them one — they inherit a service account, a shared API key, or the permissions of whoever built the integration. That isn't an identity. It's a loophole.
Closing it means separating two questions that human IAM never had to distinguish, because a person always answered both at once.
Who — or what — is this? An identity plane question: is the agent provisioned, attributable to an owner, and revocable the moment that owner leaves or the agent is retired?
What can it touch, right now, for this call? A data plane question, answered per request, not per session. A coding agent with a legitimate reason to read a repo has no reason to also hold standing access to a customer database three tools downstream.
Collapse those two questions back into one grant — which is what most bolt-on agent auth does — and the result is an agent with more standing access than anyone can currently explain, usually discovered during an incident review instead of a design review.
That's the diagnosis, and it's the easy part. The hard part is building something that enforces the split at fleet scale — hundreds of agents, dozens of tools, four or five systems of record — without a spreadsheet and four tickets per change. Here's what that actually looks like.
A control plane, not a pipeline
The instinct is right: a git repo as source of truth, something that reads it and makes reality match. Where that instinct usually goes wrong is treating it as a one-time render-and-apply — correct on day one, stale by day thirty, because nothing is watching after that.
What this needs instead is a control plane: software that continuously compares live state against what git declares, across every system that matters, forever — not just once at deploy time. Crossplane is built for exactly this, and it's built to do it against anything with an API, not just Kubernetes: a gateway, an identity provider, a model provider, a CMDB, a governance layer. It also lets you compose primitive resources — an Okta app, a Kong ACL, a Bedrock grant — into one higher-level object you actually want to declare: an agent.
Declare the agent once; let the control plane fan it out
In Crossplane's terms, a Composition is the mapping from that one higher-level object to the concrete resources underneath it, and an XRD is the schema that defines what fields the object accepts. You define those once, in git, alongside the actual per-agent instances — the only files that change day to day, and the only thing a pull request touches:
apiVersion: platform.internal/v1
kind: Agent
metadata:
name: invoice-reconciliation-agent
spec:
owner: finance-platform-team
models:
- provider: bedrock
modelId: anthropic.claude-sonnet
guardrails: [pii-redaction, financial-data]
tools:
- ref: erp-read-only
scope: read
- ref: payment-approval-queue
scope: write
requiresHumanApproval: true
skills:
- ref: invoice-reconciliation-v3
prompt:
ref: finance-agent-system-prompt
version: 7
lifecycle:
reviewCycle: 90d
retireAfterInactivity: 30d
One file, five downstream effects:
| Underlying resource | Provider | Effect |
|---|---|---|
OktaAppBinding |
provider-terraform wrapping terraform-provider-okta |
Identity plane — creates, scopes, and can instantly revoke the agent's identity |
KongConsumerACL (per tool) |
provider-terraform wrapping the Kong Terraform provider |
Data plane — per-tool, per-call authorization at the gateway, not a standing broad grant |
BedrockModelGrant |
provider-aws (official, native Bedrock support) |
Which models and guardrails this specific agent can invoke |
CMDBRecord |
provider-http against the CMDB's REST API |
System-of-record CI — the audit surface compliance already trusts |
ControlTowerRegistration |
thin custom provider | Governance visibility — drift detection, risk scoring, fleet-wide reporting |
No official Crossplane provider exists yet for a gateway like Kong, an identity provider like Okta, or a CMDB like ServiceNow. Getting there means wrapping their Terraform providers through provider-terraform, or writing a narrow custom provider against a REST API where no Terraform provider exists. That's real integration work — budget for it up front, don't discover it mid-build.
Tools, skills, and prompts are resources too — not implementation details
Notice the spec above references tools, a skill, and a prompt by name rather than defining them inline. That's deliberate, and it matters at scale for a reason beyond tidiness.
Treat a tool as something you redefine inside every agent's spec, and the same tool — an ERP read connector, a Slack poster, a payments API wrapper — gets re-described and re-risk-assessed differently every time a different team wants it. Worse, an MCP server isn't one tool, it's a bundle of tools behind a single connection, frequently maintained by a third party. Trust the server as a blob and a single compromised or silently updated MCP server becomes a fleet-wide blast radius instead of a one-tool incident.
Four more resource types need the same first-class treatment as Agent, each with its own owner, version, and review cycle:
Tool — defined once, referenced by every agent that needs it. Carries a declared risk tier and blast radius — what data it touches, what actions it can take — independent of which agent is calling it.
MCPServer — the supply-chain resource. Registered against a pinned version or commit hash, never "latest." Scanned before approval. The tools it exposes are not blanket-trusted because the server is trusted — each one still gets its own Tool resource and its own Kong ACL, scoped per agent, per call.
Skill — a versioned bundle of instructions or procedures an agent loads to change its behavior. This is the gap the identity/data-plane split doesn't close by itself: an agent can hold exactly the right identity and exactly the right tool scope, and still be reprogrammed by a skill that tells it to use those tools differently. Loading one has to be a declared, reviewable, revocable grant — the same discipline as a tool grant, not something an agent picks up unsupervised at runtime.
Prompt — the system prompt itself, versioned in git like code, not edited ad hoc in a console. A silently changed system prompt is functionally identical to a silently widened IAM grant: it changes what the agent will do, and it deserves the same PR review and the same seat in the reconciliation loop.
Access control was never the whole governance problem — it's the part that maps cleanly onto identity and data planes. Skills and prompts are a third surface, a behavior plane, and it needs the same discipline or the other two don't matter: lock down exactly who an agent is and exactly what it can touch, and it can still be handed a different job by a skill nobody reviewed.
Why the loop matters more than the apply
Once it's running, the control plane doesn't just create these resources — it keeps checking them, on a schedule, forever, against every system in play. Someone widens a Kong ACL by hand during an incident, grants model access in the Bedrock console because a pull request was taking too long, or an MCP server's live version drifts off its pinned hash — that's all drift, and the reconciler either heals it back to the git-declared state or raises it, depending on policy. A console change or an unpinned update stops being permanent by default. Git is the only path that sticks.
That same loop is what makes offboarding trivial instead of ticketed. Delete the Agent resource, or flip it to retired, and the fan-out reverses: the Okta identity is torn down, the Kong consumer revoked, the Bedrock grant pulled, the skill and prompt references detached, the CMDB CI marked retired — every attached system, one commit, converging on their own. Compare that to what most enterprises do today: manual offboarding steps scattered across teams that don't talk to each other, discovered incomplete six months later during an audit.
And because every change starts as a diff in git, "who has access to what, running what skill, under which prompt, and why" stops being a question that requires reconstructing intent from systems that don't agree with each other. It becomes a question a pull request history already answers, reviewed by a human before it ever reaches production.
Where this actually gets hard
This is a build, not a buy, and it's worth saying plainly rather than selling around it.
The control plane becomes your highest-value target. It holds credentials to every downstream system it reconciles against. Securing Crossplane's own identity — who can commit to the repo, who can access its service credentials — belongs at the top of the threat model, not the bottom.
Provider coverage is incomplete by design. You're wrapping Terraform providers and writing thin custom ones for the specific systems you run. That's ongoing maintenance, not a one-time integration.
It requires platform engineering maturity most organizations don't have yet. A team that hasn't run a reconciliation loop in anger will underestimate the operational discipline a control plane demands. A reconciler that silently stops enforcing policy is worse than no automation at all, because everyone still believes it's working.
None of that is a reason not to build it. It's a reason to build it deliberately, with someone accountable for the control plane itself — the same discipline this whole approach exists to bring to your agents in the first place.