Classic painting used as the article cover
← Back to blog

AGENT SECURITY

Agent Offboarding and Decommissioning

Retiring an agent is a sequence, not one click. The revocation call per provider, how long a token keeps working after you revoke access, what the AI Act requires of the logs, and the check that proves the agent is actually dead.

•Jul 8, 2026•Updated Sep 29, 2026•16 min
OffboardingLifecycle ManagementSecurity

TL;DR

An agent that's no longer used doesn't stop being able to act. It just stops being watched. That's the entire risk of skipping decommissioning: the access doesn't expire on its own, and nobody files a ticket to complain that an idle agent is still idle.

Provisioning has a clear owner and a clear moment, someone wants the agent, so someone builds it. Retirement has neither by default, which is exactly why it's the stage almost every lifecycle skips.

We think the sharpest failure inside decommissioning is treating "removed" as "revoked." Deleting an agent's connection in your own tooling clears your copy of a credential. RFC 7009 gives you a real revocation endpoint for a reason, and calling it is a separate act from deleting a row.

The gap has a measurable width. Microsoft's own guidance is that after you disable a user and revoke their sessions, an app using access tokens keeps working "when the access token expires", and Entra's default access token lifetime is a random 60 to 90 minutes.

The fix is a sequence: mark it retired, tell whoever depends on it, strip its permissions, revoke what it holds at the source, stop anything still scheduled, keep the logs for as long as the regulation says, update who's accountable, then prove the credentials no longer authenticate.

Overview

Every agent reaches the end of its useful life. A project wraps up. A workflow gets redesigned. A newer agent replaces an older one. A vendor relationship ends. The owner leaves. An experiment quietly fails.

When that moment arrives, the agent should be decommissioned, not abandoned: removed from operation completely and verifiably, so nothing it once held stays active. The control frameworks already ask for this on human accounts. NIST SP 800-53 AC-2(3) requires disabling accounts within an organization-defined period when they are "no longer associated with a user or individual" or have been inactive, and AC-2 requires notifying account managers when "accounts are no longer required" or a user is terminated. Nothing in either control says the account has to belong to a person.

What makes agents harder is that one agent is usually several identities at once: a registry record, a cloud service account, OAuth grants held per user at three SaaS vendors, a key in a vault, a schedule in a job runner, rows in an audit store. Retirement has to reach all of them, and only some live where you can see them.

The distinction that matters: "removed from the dashboard" and "no longer able to act" are not the same claim, and only the second one is decommissioning.

Why It Gets Skipped

Offboarding gets skipped for the same reasons it's dangerous. An agent that's no longer used generates no complaints. Nobody files a ticket because an agent stopped being invoked. The workflow moves on, and the agent fades from attention while retaining everything it was granted.

There's also a structural reason. Provisioning has a clear owner and a clear moment. Decommissioning has neither, and the person who'd benefit from retiring an agent cleanly is rarely the person who notices it's idle.

The result is accumulation. Take a marketing agent still holding write access to an ad platform, a pipeline agent whose service account never expired, and a procurement assistant tied to a vendor the company stopped paying months ago. None of the three is a bug. Each is a grant that outlived its reason.

When Offboarding Begins

Offboarding shouldn't depend on someone remembering. The triggers are predictable, and we'd wire each one to a signal that already exists somewhere in the enterprise:

  • The agent is replaced. A newer agent takes over the same workflow.
  • The workflow changes. The business process the agent supported was redesigned or eliminated.
  • The owner leaves. The business or technical owner departs and no one inherits the agent.
  • The vendor or integration is removed. A SaaS platform the agent depended on is no longer in use.
  • The experiment ends. A pilot concludes without graduating to production.
  • The agent goes idle. Usage drops to zero for a sustained period.

The last one is the only trigger you have to build yourself, and the cloud providers have shown what the signal looks like. IAM tracks last use per credential: GetAccessKeyLastUsed returns a LastUsedDate, the Region where the key was last used and the ServiceName of the last service requested, and the credential report carries the same per key. AWS is blunt about the answer: "Ideally, you delete credentials if they are no longer needed."

Wire an equivalent field into your agent inventory and idleness stops being a judgment call. Without it, "nobody uses that agent" is a rumour.

The Decommissioning Sequence

Proper decommissioning is a sequence. Each step closes off a different way the agent could remain a liability after it's supposed to be gone, and the order matters: announce the change before you break things, and confirm access is gone before you call the agent retired.

deleting the connection
only clears your copy

1. Mark retired
status changes in the registry

2. Notify dependents
before anything breaks

3. Remove permissions
every surface, not just the obvious one

4. Revoke credentials
at the issuing system, not just locally

5. Stop automations
schedules, webhooks, triggers

6. Retain logs
per retention policy

7. Update ownership
who retired it, and why

8. Verify
credentials no longer authenticate

still valid at the provider
until revoked there

Figure 1 — The eight-step sequence, in order. Step 4 is where "removed" quietly gets mistaken for "revoked," and step 8 is the only one that produces evidence.

1. Mark as Retired

Change the agent's status in the inventory first. Marking it deprecated and then retired creates a durable record that it's no longer approved to operate, and gives every downstream review, audit and access check one signal to act on. An agent that still reads active after it's been turned off creates ambiguity, and ambiguity is what lets stale access survive.

yaml
# Registry record after decommissioning
agent_id: fin-close-reconciler-02
status: retired
retired_on: 2026-07-08
retired_by: paulina@agenticfabriq.com
reason: replaced by fin-close-reconciler-03
permissions: revoked
credentials: revoked          # see verification block below
schedules: stopped
logs: retained (policy: finance-7y)

2. Notify Dependents

If anyone depends on the agent, tell them before it disappears. An agent supporting a live workflow can't simply be switched off; teams may need migration time, or a window to confirm a replacement behaves correctly.

Consider a quarterly close reconciliation agent used by finance. Even with a newer agent ready, finance needs to confirm the new one produces matching numbers first. Notification turns a surprise into a planned cutover, and it surfaces hidden dependencies: someone always replies that their report quietly relies on the agent everyone thought was unused.

3. Remove Permissions

Permissions are what made the agent powerful. Retiring an agent while leaving its access in place removes the convenience and keeps the risk.

Removal has to be exhaustive, because agents accumulate access across surfaces administered by different teams: SaaS scopes and connected-app grants, API access, database roles, file stores, source repositories and CI, chat memberships, ticketing tools, internal service-to-service authorizations.

Two things make this slower than it looks. Removing a role is not instantaneous downstream: Microsoft notes that Entra app provisioning "typically runs automatically every 20-40 minutes", so a deprovisioning event fired at 09:00 may not have reached a SaaS application by 09:30. And identity reuse does not carry permissions with it. Google is explicit that if you delete a service account and create a new one with the same name, "the new service account is treated as a separate identity; it does not inherit the roles granted to the deleted service account."

Permission removal is eventually consistent, and the window is measured in tens of minutes. Treat the gap between "revoked in the directory" and "refused by the application" as part of the incident timeline, not as an implementation detail.

4. Revoke Credentials

Permissions define what the agent is allowed to do. Credentials are the keys it holds. Deleting a connection inside your own tooling removes only your stored copy of the key.

OAuth gives you a real answer here, and it is a separate HTTP call. RFC 7009 defines a revocation endpoint where "the client requests the revocation of a particular token by making an HTTP POST request", specifies that implementations "MUST support the revocation of refresh tokens and SHOULD support the revocation of access tokens", and adds the cascade you actually want: if the token is a refresh token, the server "SHOULD also invalidate all access tokens based on the same authorization grant". Read the response carefully, because the spec deliberately collapses two cases: the server returns 200 "if the token has been revoked successfully or if the client submitted an invalid token". A 200 is not proof you revoked something. A 503 is proof you didn't, and the spec says the client "must assume the token still exists".

Here is what the call actually is, per provider, for the credentials an agent most often holds.

What the agent holdsWhat deleting your record doesWhat actually invalidates itWhat success looks like
Google OAuth refresh tokenclears your copy onlyPOST https://oauth2.googleapis.com/revoke with token=200 OK
Entra refresh tokens and sessionsnothing at the providerRevoke-MgUserSignInSession, plus AccountEnabled:$falseno new tokens issued; existing access tokens survive
Entra access tokennothingnot directly revocable; Continuous Access Evaluation is the near-real-time pathexpiry, at a random 60 to 90 minutes, or 20 to 28 hours under CAE
Slack tokenclears your copy onlyauth.revoke{"ok": true, "revoked": true}
GitHub OAuth grantclears your copy onlyDELETE /applications/{client_id}/grant204, and every token for that user and app goes with it
Google Cloud service accountnothingdisable, then deletekey stops authenticating; undelete stays possible for "30 days or less"
Directory account via SCIMnothingDELETE /Users/{id}204 No Content, then 404 for all later operations on it

Table 1 — The revocation call per credential type, read from each vendor's own reference page.

Three rows deserve a second look.

The Entra access-token row is the one that breaks people's mental model of revocation. Microsoft's emergency-revocation guidance says that after you disable the account and revoke sessions, "For applications using access tokens, the user loses access when the access token expires", and the default lifetime is "a random value ranging between 60-90 minutes (75 minutes on average)", rising to 20 to 28 hours under Continuous Access Evaluation. So "revoked" at 14:00 can mean "still acting" at 14:40.

Slack's auth.revoke does less than its name suggests. It revokes the token, but Slack notes that revoking a bot user token "will not uninstall the bot user or the app", though it does deactivate the bot user and remove its channel memberships. If the goal is that the app is gone, apps.uninstall is the call.

Google Cloud's advice on service accounts is to reach for the smaller hammer first: "Google recommends disabling the service account instead of deleting it", partly because a delete is only reversible for 30 days and partly because bindings elsewhere may still point at it. We'd follow that order for agents too: disable, confirm nothing broke, delete once the retention question is settled.

you delete the connection

nothing called at the provider

expires, eventually, or never

POST /revoke, auth.revoke, DELETE grant

refresh token invalid

last access token expires

verified dead

Active

LocalDeleted

Orphaned

RevokeCalled

DeadRefresh

DeadAccess

Figure 2 — Two paths out of "active". Only the lower one ends in a state you can verify, and even that one has a tail as long as the access-token lifetime.

Delegated access is the last trap. An agent acting on behalf of individual users holds a separate grant per user, minted at consent time and invisible in any single admin console. A legal review agent with tokens scoped to twelve attorneys needs twelve revocation calls, driven from your own inventory of grants, because no provider knows the agent is being retired.

5. Stop Automations

An agent can keep running long after people stop invoking it. Scheduled jobs, event triggers and webhooks all execute on their own.

Take an IT operations agent running a nightly log-cleanup routine. Users stopped interacting with it months ago, but the cron trigger never knew. Every night it still wakes up, authenticates and acts on production. Until the schedule is disabled, "no one uses it anymore" is false. Webhooks are the sneakier half: an inbound event can wake a retired agent when nothing is scheduled, and the subscription usually lives at the provider rather than in your codebase.

6. Retain Logs

Retiring an agent doesn't mean erasing the record of what it did. Decommissioning removes the agent's ability to act, not the evidence of what it already did.

The retention floors are written down. Article 12 of the EU AI Act requires that high-risk systems "shall technically allow for the automatic recording of events (logs) over the lifetime of the system", to support identifying risk situations, post-market monitoring and deployer monitoring. Article 19 tells providers to keep those logs "for a period appropriate to the intended purpose" and, as a floor, "at least six months" unless other Union or national law says otherwise. Article 26(6) puts the same floor on deployers for the logs under their control, and financial institutions fold it into the record-keeping their sectoral law already imposes.

NIST SP 800-53 AU-11 sets the same expectation without a number, requiring records be retained for an organization-defined period "to provide support for after-the-fact investigations of incidents and to meet regulatory and organizational information retention requirements". Six months is a legal floor and nowhere near an operational one. Six months after a sales forecasting agent is gone, someone in finance may still need to reconstruct which numbers it touched.

So the retirement record names the retention class, and deleting the agent must not cascade into deleting its audit trail. That cascade is easy to build by accident when the agent's ID is a foreign key.

7. Update Ownership

Ownership records should reflect the new reality: who retired the agent and why, or, if it was replaced, a successor that is registered, reviewed and assigned its own owner before the old one goes.

The handoff is also the moment to check that the successor inherited only the access it needs. Cloning a scope set is the fastest way to carry years of accumulated permissions into a brand-new identity, and because the new agent works immediately, nobody looks.

8. Verify It Worked

Steps one through seven are process. This one is evidence, and it's the only step that can fail loudly.

Verification means re-authenticating with the credential you believe is dead and asserting that it fails. The specs already define the clean signals: a SCIM DELETE should be followed by 404 for "all operations associated with the previously deleted resource"; a revoked OAuth token should produce invalid_grant rather than a fresh access token; a disabled service-account key should fail to mint a token at all.

python
# Run after the sequence, not as part of it. Any success here is a finding.
async def assert_decommissioned(agent_id: str) -> list[str]:
    findings = []
    for cred in inventory.credentials_for(agent_id):   # includes per-user grants
        if await provider(cred).still_authenticates(cred):
            findings.append(f"{cred.provider}:{cred.id} still authenticates")
    for sched in inventory.schedules_for(agent_id):
        if await runner.is_enabled(sched):
            findings.append(f"schedule {sched.id} still enabled")
    if await audit.events_since(agent_id, retired_at) > 0:
        findings.append("audit shows activity after retirement")
    return findings

Run it on a delay. Because access-token expiry and provisioning sync both lag, a check at T+0 tells you less than one at T+2 hours, and the activity query in the last block is what catches a schedule you missed. We think this is the step worth automating first, because it is the only one whose output is evidence rather than intent.

Then make the sequence repeatable: tie it to events that already exist, so an HR termination or a closed vendor contract flags the affected agents automatically, and require an artifact per step. A checklist item with no output is a claim.

The Invisible Access Path

The risk of poor offboarding is simple to state: abandoned agents become invisible access paths into enterprise systems.

They keep permissions long after their purpose disappears, hold credentials still valid at the provider, have no owner to notice trouble, and may keep firing on a schedule. OWASP's LLM06:2025 Excessive Agency describes the same shape from the design side, naming excessive permissions and excessive autonomy as root causes and recommending that extensions run "within individual user contexts with minimal privileges". A retired agent whose grants were never unwound is that category arrived at by neglect rather than design.

An identity with live credentials, broad access and no owner is a better target than a forgotten human account, because it was built to act autonomously and is expected to.

An abandoned agent is not a harmless leftover. It's a credentialed, capable actor that nobody's governing, exactly the kind of access path nobody would approve if someone proposed it on purpose.

Where Fabriq Fits

Agentic Fabriq holds credentials per user in a vault and attaches them server-side at the moment of the call, so an agent never handles the secret it acts with. Refresh runs behind that boundary with last-used tracking per connection, which is the field the idle-agent trigger needs and the one most inventories lack. Every call carries two identities, the agent and the acting user, into an audit record, so the log that survives retirement can say who the agent was acting for.

What it does not do is reach into the provider on your behalf. Clearing a vault copy is not the same act as invalidating a grant upstream, so the POST to https://oauth2.googleapis.com/revoke, the auth.revoke, the DELETE /applications/{client_id}/grant are still yours to make. Table 1 is the list, and nothing in the vault shortens it.

Frequently Asked Questions

Does deleting an integration revoke the token? No. Deleting your record removes your copy. RFC 7009 exists because invalidating the token is a separate request to the authorization server's revocation endpoint, and only that request tells the provider to stop honouring it.

How long does an agent keep working after I revoke its access? Until its current access token expires, unless the provider supports near-real-time invalidation. Entra's default access token lifetime is "a random value ranging between 60-90 minutes (75 minutes on average)", and 20 to 28 hours for long-lived tokens under Continuous Access Evaluation. Downstream SaaS deprovisioning can lag further: Entra app provisioning "typically runs automatically every 20-40 minutes".

What's the difference between removing permissions and revoking credentials? Permissions are the authorization decision, held by the resource; credentials are the bearer artifact, held by the agent. Both are required, and our view is that most programs do the first and assume the second. Removing a role stops new authorizations, but not always a token already minted, because many services check a token's signature and scopes rather than re-reading the directory.

Should I disable or delete a service account? Disable first. Google recommends "disabling the service account instead of deleting it", and a deleted account can only be undeleted for "30 days or less". Delete once you're confident nothing else references it, and remember that recreating the same name "does not inherit the roles granted to the deleted service account".

How do I find agents nobody uses any more? From last-used telemetry, the way the cloud providers do it for keys. Give each agent connection the equivalent of IAM's LastUsedDate and idleness becomes a query rather than a hunch.

Does revoking a refresh token kill the access tokens too? It should, but check. RFC 7009 says the authorization server "SHOULD also invalidate all access tokens based on the same authorization grant", which is a recommendation rather than a requirement. GitHub is one of the providers that goes further at the grant level: DELETE /applications/{client_id}/grant "will also delete all OAuth tokens associated with the application for the user."

How long do I have to keep a retired agent's logs? At least six months if the system is high-risk under the EU AI Act, under Article 19 for providers and Article 26(6) for deployers, and longer wherever sectoral law or your own AU-11 retention period says so. Removing the agent must not remove its audit trail.

If Agents Can Be Provisioned, They Must Be Deprovisioned

Provisioning and decommissioning are two halves of one discipline. One grants identity, scope and credentials; the other reclaims them. A program that does only the first accumulates risk with every agent it launches, because the access an agent carries doesn't expire on its own.

If you take one thing from this, take step 8. Every other step is a change you made. That one is the only place the system tells you whether the change landed.

If agents can be provisioned, they must also be deprovisioned. Clean retirement isn't the unglamorous end of the lifecycle, it's the only part that produces proof.

Sources

All URLs read 2026-09-29.