Agent Sessions
Agent sessions track the lifecycle of every chat between a user and
an agent. Distinct from threads (the append-only history) — sessions
carry mutable state (live / idle / paused / ended), enforce
the agent's session policy (idle timeout, max duration, max
concurrent, auto-archive), and reserve a transport discriminator for
the streaming gateway.
All endpoints require authentication via X-API-Key header and the
appropriate scope.
Session Object
{
"_id": "sess_abc123",
"agentId": "wf_xyz789",
"userId": "usr_mn0",
"threadId": "thr_pqr",
"organizationId": "org_acme",
"appId": null,
"transport": "text",
"state": "live",
"streamingSessionId": null,
"startedAt": "2026-05-17T10:00:00Z",
"lastActivityAt": "2026-05-17T10:14:32Z",
"endedAt": null,
"endedReason": null,
"transferredTo": null,
"modelOverride": null,
"metadata": {}
}
State machine
| From | To | Trigger |
|---|---|---|
live | idle | No activity for idleTimeoutSeconds |
idle | live | Any activity (user message) |
idle | ended | No activity for 2 × idleTimeoutSeconds, reason idle_timeout |
live / idle | ended | Total age exceeds maxSessionDurationSeconds, reason max_duration |
live / idle | ended | Explicit end, reason user_ended / admin_ended |
live / idle | ended | Transfer to another agent, reason transfer |
live / idle | paused | Explicit pause |
paused | live | Explicit resume |
ended is terminal.
GET /api/v1/agents/:agentId/sessions
List sessions for an agent. Owner / admin sees every user's rows; non-owner with read access on the agent sees only their own.
Scope: agents:read
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
state | string | No | active (default — live + idle + paused), live, idle, paused, ended, or all. |
limit | integer | No | 1–500. Default 100. |
offset | integer | No | Pagination. |
Response 200 OK
{
"data": {
"rows": [
{ "_id": "sess_abc", "agentId": "wf_xyz", "userId": "usr_1", "threadId": "thr_a", "state": "live", "startedAt": "...", "lastActivityAt": "...", "endedReason": null, "transport": "text" }
],
"total": 12
}
}
GET /api/v1/agents/:agentId/sessions/:id
Fetch one session. Authorisation: the session's user OR the agent's owner OR an admin.
Scope: agents:read
POST /api/v1/agents/:agentId/sessions/upsert
Runtime activity touch. Idempotently upserts the session row,
refreshes lastActivityAt, transitions idle → live, enforces
maxConcurrentSessionsPerUser on first-touch. The agent worker calls
this on every POST /threads/:id/runs.
Scope: agents:write
Request Body
{
"threadId": "thr_pqr",
"transport": "text",
"modelOverride": null,
"streamingSessionId": null
}
Errors
| Code | Status | When |
|---|---|---|
session-cap-reached | 429 | User already at maxConcurrentSessionsPerUser with this agent |
max-duration-reached | 410 | Session exceeded maxSessionDurationSeconds |
POST /api/v1/agents/:agentId/sessions/:id/end
End a session explicitly. Idempotent.
Scope: agents:write
Request Body (optional)
{ "reason": "user_ended" }
Admin ending another user's session is auto-stamped as admin_ended
regardless of what's passed.
POST /api/v1/agents/:agentId/sessions/:id/pause
Flip live or idle → paused. Ended sessions return 400.
Scope: agents:write
POST /api/v1/agents/:agentId/sessions/:id/resume
Flip paused → live and refresh lastActivityAt.
Scope: agents:write
POST /api/v1/agents/:agentId/sessions/:id/transfer
Hand off this session to another agent. Ends the source session with
endedReason: 'transfer' and stamps transferredTo. The receiving
agent gets its own upsert on the user's next turn there.
Scope: agents:write
Request Body
{ "targetAgentId": "wf_target" }
PATCH /api/v1/agents/:agentId/session-policy
Write a fresh session policy onto the brain's function-calling node. Owner / admin only.
Scope: agents:write
Request Body
{
"idleTimeoutSeconds": 1800,
"maxSessionDurationSeconds": 14400,
"maxConcurrentSessionsPerUser": null,
"autoArchiveAfterDays": 30,
"allowPerSessionModelOverride": false
}
null for maxConcurrentSessionsPerUser means unlimited.
Reactive subscription
UI consumers use the Meteor publication for live updates:
Meteor.subscribe('agentSessions.list', agentId, 'active');
// AgentSessionsCollection.find({ agentId }) in useTracker
State transitions push automatically — no polling.
Background reaper
The Meteor server runs a 60-second setInterval that:
- Flips
live → idlewhenlastActivityAtis older thanidleTimeoutSeconds. - Flips
idle → endedwhenlastActivityAtis older than2 × idleTimeoutSeconds. - Flips
live | idle → endedwhen total age exceedsmaxSessionDurationSeconds.
Bounded at 200 rows per tick.
See also
- Agents API — agent CRUD + brain config + redeploy
- Library tool families — the seven canonical built-in families
- User-facing concepts: Session Management