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/:id/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. |
cursor | string | No | meta.nextCursor of the previous page; omit for the first page |
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
},
"meta": { "requestId": "req_abc123" }
}
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/:id/sessions
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 run (POST /agents/:agentId/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. The conversation is kept.
Scope: agents:write
Response 200 OK: the session.
No request body. The recorded reason is user_ended when the session's own
user ends it and admin_ended when the agent's manager ends someone else's.
Ending a session does not change its lastActivityAt.
POST /api/v1/agents/:agentId/sessions/:id/pause
Flip live or idle → paused. Ended sessions return 400.
Scope: agents:write
Response 200 OK: the session.
POST /api/v1/agents/:agentId/sessions/:id/resume
Flip paused → live and refresh lastActivityAt.
Scope: agents:write
Response 200 OK: the session.
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" }
Response 200 OK: the session, ended.
Session policy
An agent's session policy (idleTimeoutSeconds, maxSessionDurationSeconds, maxConcurrentSessionsPerUser (null for unlimited), autoArchiveAfterDays, allowPerSessionModelOverride) is part of its config: read it with GET /api/v1/agents/:id/config and change it with PATCH /api/v1/agents/:id/config { "sessionPolicy": { … } } (owner or admin).
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