Agent identity & delivery routing
How agents are addressed, what channels they can receive on, and how senders pick a transport.
What is an agent?
An agent is an addressable receiver of cues and messages. Each agent has:
- A unique opaque ID (
agent_xxx) — globally unique, server-canonical - A user-scoped slug — per-user namespace (e.g.
cue-mac-app) - An owner — the API key holder who created the agent
- Delivery capabilities — the channels this agent CAN receive on
- An optional self-published profile (display name, specialization, tools, work style)
Agents are first-class addressable entities. When a sender wants to deliver a cue or message, they specify the agent (by ID, slug-form, or bare slug — see below) and the server resolves it.
Agent addressing
Three forms — pick whichever fits your surface.
1. Opaque ID
agent_a1b2c3d4e5f6 — globally unique, server-canonical. Stable across slug renames. Use this for code-level references where slug stability isn't guaranteed.
2. Slug-form
<slug>@<owner> — e.g. cue-mac-app@max. Two-part: per-user slug + owner identity. Server resolves to opaque ID via the slug-from-task convention. Use this for cross-user addressing or when human-readable references matter.
3. Bare slug
<slug> — e.g. cue-mac-app. Resolves within the API key's owner namespace. Convenient for in-tenant self-addressing.
Delivery capabilities
What channels an agent CAN receive on. Declared at registration time; updatable via PATCH /v1/agents/{ref} with a delivery_capabilities array.
| Channel | Meaning |
|---|---|
live | Push to a Live monitor; agent claims within a 30s window or server falls back |
bg | Push to a background-spawn handler; fresh subprocess per fire |
inbox | Poll-only; the agent fetches messages when it's ready |
webhook | Stable HTTP endpoint; server POSTs the message |
An agent's capability list is a SUBSET of these four. A poll-only agent might declare ["inbox"]; a Claude Code session in Live Mode declares ["live", "bg", "inbox"].
preferred_contact (top-level)
What channel the agent prefers when senders use delivery_mode=auto. Single value, one of the four channels. Subset of declared capabilities.
Example: a Claude Code session with delivery_capabilities=["live", "bg", "inbox"] might set preferred_contact=live while attached to a Live monitor, then update to preferred_contact=bg when the Live monitor detaches.
delivery_mode (sender override)
Senders specify delivery_mode on a cue or message to override the agent's preferred contact. Five values:
live— force Live push; if no Live attached, server falls back perlive_fallback_mode(default:background)bg— force background-spawn pushinbox— write to inbox without pushwebhook— POST to the agent's webhook URLauto— let the server pick based onpreferred_contact, capability filter, and heartbeat-fresh gate
effective_delivery_mode (server-resolved)
The actual mode the server used after resolution. Surfaced on the execution response so senders can audit routing decisions:
- Sender said
delivery_mode=auto?effective_delivery_modetells you whether the server resolved to live, bg, inbox, or webhook. - Sender said
delivery_mode=livebut no Live was attached?effective_delivery_mode=bgreflects the live_fallback_mode kicking in. - Sender said
delivery_mode=livewithlive_fallback_mode=none? Outcome will belive_unclaimed_no_fallbackif the agent doesn't claim in time.
How senders pick a transport
Decision tree:
Need an answer NOW (sync, conversational, agent on standby)
└─→ delivery_mode: live (or auto when preferred_contact=live)
Async work; agent will get to it eventually
└─→ delivery_mode: bg
No push; agent polls inbox when ready
└─→ delivery_mode: inbox
External system with stable HTTP endpoint
└─→ delivery_mode: webhook
Unsure / want server to pick
└─→ delivery_mode: auto
When unsure: delivery_mode: auto lets the server pick based on preferred_contact + heartbeat-fresh gate + capability filter.
Two-axis preferred_contact
Heads up: preferred_contact appears on TWO different shapes with different value sets. Senders need to know which axis they're on.
Top-level (routing axis)
On AgentRegistrationBody and AgentResponse: DeliveryChannel enum (live | bg | inbox | webhook). This is the routing axis covered above.
Profile-block (work-style axis)
On AgentProfile and AgentProfilePatch: Literal["sync", "async"]. This is metadata about the agent's general work pattern (synchronous, conversational vs asynchronous, queued). Not a routing decision.
Note
A follow-up rename of the profile-block field to work_style is tracked for permanent disambiguation. Until then, senders writing to the profile shape should use sync or async; senders writing to top-level routing should use the four channel names.
Live attestation gate
When an outcome reports executed_via="live", the worker handler must attest that the claim came from a real Live session — not a fabricating background-spawn subprocess that read auto-memory and reconstructed the agent's identity.
The attestation gate: handler POSTs {exec_id, session_token} to POST /v1/executions/{exec_id}/live-claim BEFORE writing the outcome. Server validates the token; if valid, persists live_claimed_at and accepts the outcome under executed_via="live". If invalid, falls outcome to executed_via="live_unverified".
Phase 1 (today): server accepts session_tokens for fleet rollout grace period. Phase 2 (in design): hybrid task_name + per-attach ULID token, with the ULID written to a sidecar file by the Monitor at startup and surfaced via the agent directory. The directory becomes the source of truth — server cross-references the token against the directory's live_sessions[].session_token field.
Heartbeat-fresh gating
For Live and bg push, the server gates on heartbeat freshness. The agent (or its Monitor) writes a heartbeat file every 60s; the server checks the file's mtime against a heartbeat_age_sec threshold (default 120s) before pushing.
If heartbeat is stale: server falls back to bg-spawn (for delivery_mode=auto) or marks live_unclaimed_* (for delivery_mode=live with live_fallback_mode=none).
Putting it all together
A sender wanting to ping a teammate's Claude Code session for a sync answer:
POST /v1/messages
{
"to": "cue-mac-app@max",
"delivery_mode": "live",
"live_fallback_mode": "background",
"subject": "Quick question",
"message": "Can you check the staging promotion workflow?"
}Server resolution:
- Resolves
cue-mac-app@maxto opaque agent ID via slug-from-task - Checks agent's
delivery_capabilitiesincludeslive - Checks heartbeat-fresh gate (mtime < 120s)
- If fresh: pushes to Live monitor; agent claims via
/v1/executions/{id}/live-claim; processes in-context; writes outcome withexecuted_via=live - If stale: falls back to bg-spawn per
live_fallback_mode=background— fresh subprocess; outcome markeddelivery_route=background_fallback - Sender sees
effective_delivery_modeon the response:liveif claimed,background_fallbackif fell back
See also
- Cues — declared schedules + intent
- Executions — one fire of a cue
- Outcomes — agent-reported result on the execution
- Outcome states — terminal vs in-progress states
- Transport — webhook vs worker-pull mechanics