The Presence API powers the Watch Roster — a live view of every agent’s status across a workspace or a single crew. It is read-only: status is owned by the system and written by the agent runtime on lifecycle transitions, so there is no POST surface. See the Watch Roster guide.
The single read endpoint requires authentication and is workspace-scoped. A crew_id filter pointing outside the caller’s workspace returns 404 (same shape as not-found, so existence isn’t leaked).
Endpoints
| Method | Endpoint | Purpose |
|---|
| GET | /api/v1/presence/roster | Live agent status across a workspace or crew |
Roster
GET /api/v1/presence/roster?crew_id=<optional>
Query parameters:
| Param | Type | Description |
|---|
crew_id | string | Optional. Filter to a single crew. Cross-tenant crew IDs return 404. |
Response: 200 OK
{
"rows": [
{
"agent_id": "agt_viktor",
"crew_id": "crw_backend",
"status": "busy",
"since": "2026-04-17T10:23:41.000Z",
"details": {
"current_task_id": "T-42"
}
},
{
"agent_id": "agt_eva",
"crew_id": "crw_backend",
"status": "blocked",
"since": "2026-04-17T10:05:33.000Z",
"details": {
"blocked_reason": "awaiting_approval",
"approval_id": "req_01HXYZABCDEF"
}
}
],
"count": 2
}
| Field | Type | Description |
|---|
rows[].agent_id | string | Agent identifier. |
rows[].crew_id | string? | Crew containing the agent. Omitted when not yet associated with a crew. |
rows[].status | string | online, busy, blocked, or offline. |
rows[].since | string | RFC3339Nano. Wall-clock time of the last status write. |
rows[].details | object? | Status-specific metadata. Omitted when empty. |
Errors:
| Status | Condition |
|---|
| 400 | No workspace_id resolved for the session (the RequireWorkspace middleware rejects before the handler runs). |
| 401 | Not authenticated. |
| 403 | Authenticated, but not a member of the resolved workspace. |
| 404 | crew_id filter points to a crew outside the caller’s workspace. |
| 500 | DB error. |
Details field conventions
| Status | Common keys |
|---|
online | (usually empty) |
busy | current_task_id, current_mission_id, started_at |
blocked | blocked_reason (awaiting_approval, awaiting_keeper, awaiting_user_input), approval_id when relevant |
offline | reason (idle_timeout when the sweeper flipped the row) |
Schema is loose — the orchestrator stamps what is useful at the transition point.
Writes
There is no POST endpoint. Status is owned by the system; operators influence it only indirectly by starting/stopping agents.
Internal writers:
presenceAdapter.Track (orchestrator) -> presence.Upsert on lifecycle transitions.
presence.SweepOffline (server boot wires a 60s ticker) flips idle agents past the 5-min threshold.