A platform that records these ten things about each agent can answer the questions that matter when something goes wrong: when was it last alive, when does its authority lapse, who renews it, what can wake it, what can it touch, who else can it call, and who owns it. Nothing here is novel. It's housekeeping, written down so it can be checked.
| Field | Type | Meaning |
|---|---|---|
agent_idrequired | string | Stable identifier. Survives renames and redeploys. |
ownerrequired | string | A named person or on-call rotation accountable for this agent. Empty means orphaned, and orphaned is a finding, not a default. |
issued_at | date-time | When the current credential was issued. Rotation age starts here. |
last_run_atrequired | date-time | Last time the agent actually executed. Dormancy is measured from here, not from deployment. |
expires_atrequired | date-time | When the agent's authority lapses unless renewed. Must be a real date. "Never" is not a value; it is the absence of a control. |
revoke_on_idle_days | integer | Idle threshold after which credentials are revoked automatically, regardless of expires_at. Dormancy revokes; re-enabling is a human decision. |
renewed_by | string | Who or what may extend expires_at. A person, a role, or a signed approval flow. If the agent can renew itself, write that down and expect the question. |
trigger_signingrequired | enum | required · optional · none. Whether the agent refuses unsigned wake-up calls. |
trigger_sources | string[] | Allowlist of principals, services, or networks that may invoke the agent. An empty list with trigger_signing: none means anyone who finds the endpoint. |
reachrequired | object[] | Each system the agent can touch: {system, tier, mode}. tier is public · internal · sensitive · regulated. mode is read · write · delete. This is the blast radius, written in advance. |
delegates_to | string[] | Other agents this one can invoke. Delegation to an agent with different permissions is how a wake becomes a chain. |
Nine named fields plus reach's three sub-fields. Ten is a round number; the point is that none of them are optional in spirit.
expires_at. Platforms may default it (90 days from issued_at is reasonable) but may not leave it empty.now − last_run_at exceeds revoke_on_idle_days, the credential is revoked and the agent moves to a disabled state. Triggers may still arrive; they find nothing to act with.expires_at produces a record: who, when, how long, why. Silent auto-renewal defeats the field.trigger_signing: none with any external source in trigger_sources (or an empty list) should fail a conformance check, not merely warn.reach, that is an incident, however benign the action.owner is reported at creation, not at audit.An agent with no live credential has nothing to act with. That is the whole control.
{
"agent_id": "invoice-reconciler",
"owner": "m.chen",
"issued_at": "2026-07-01T00:00:00Z",
"last_run_at": "2026-08-14T03:10:00Z",
"expires_at": "2026-09-29T00:00:00Z",
"revoke_on_idle_days": 60,
"renewed_by": "finance-platform-oncall",
"trigger_signing": "required",
"trigger_sources": ["svc://erp-events", "cron://nightly"],
"reach": [
{ "system": "erp-ledger", "tier": "regulated", "mode": "write" },
{ "system": "object-store/invoices", "tier": "sensitive", "mode": "read" }
],
"delegates_to": ["vendor-lookup"]
}
Read it the way the inventory would: last run August 14, idle window 60 days, so this agent's authority lapsed on October 13 and it has been overdue since. Signed triggers only, so it cannot be woken from outside. The credential should already be revoked. If it is not, that is the finding.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://agentshelflife.com/spec/v0.1/agent-expiration.schema.json",
"title": "Agent Expiration v0.1",
"type": "object",
"required": ["agent_id", "owner", "last_run_at", "expires_at", "trigger_signing", "reach"],
"additionalProperties": true,
"properties": {
"agent_id": { "type": "string", "minLength": 1 },
"owner": { "type": "string", "minLength": 1 },
"issued_at": { "type": "string", "format": "date-time" },
"last_run_at": { "type": "string", "format": "date-time" },
"expires_at": { "type": "string", "format": "date-time" },
"revoke_on_idle_days": { "type": "integer", "minimum": 1 },
"renewed_by": { "type": "string" },
"trigger_signing": { "type": "string", "enum": ["required", "optional", "none"] },
"trigger_sources": { "type": "array", "items": { "type": "string" } },
"reach": {
"type": "array",
"items": {
"type": "object",
"required": ["system", "tier", "mode"],
"properties": {
"system": { "type": "string" },
"tier": { "type": "string", "enum": ["public", "internal", "sensitive", "regulated"] },
"mode": { "type": "string", "enum": ["read", "write", "delete"] }
}
}
},
"delegates_to": { "type": "array", "items": { "type": "string" } }
}
}
Six questions a platform can answer yes or no to. A platform that answers yes to all six has given its agents a shelf life.
expires_at. No nulls, no sentinel "never."revoke_on_idle_days is enforced by the platform, not by a reminder email.reach is blocked or alerted.The inventory accepts a flattened CSV with these columns. Dates may be given as YYYY-MM-DD or as days ago.
agent_id, owner, group, last_run_at, credential_state, trigger, reach_count, reach_tier, reach_mode, delegates_to
trigger collapses trigger_signing + trigger_sources into one of scheduled · signed · internal_unsigned · external_unsigned. reach_* summarizes the worst entry in reach. delegates_to is a count. group is any cohort label you want the portfolio view to split on.
Food has a printed date because the thing inside changes whether or not anyone looks at it. Authorization is the same. A credential issued in July is not the same object in October: the person who approved it may have moved on, the system it reaches may have changed sensitivity, the trigger that wakes it may now be reachable from places it wasn't. The date on the label is a forcing function for a question nobody wants to schedule: is this still supposed to exist?
Good housekeeping, hygiene, manners for machines. Pick the metaphor you like. The control is the same: authority evaporates by default, and keeping it alive is a decision someone signs.