Skip to main content

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

FromToTrigger
liveidleNo activity for idleTimeoutSeconds
idleliveAny activity (user message)
idleendedNo activity for 2 × idleTimeoutSeconds, reason idle_timeout
live / idleendedTotal age exceeds maxSessionDurationSeconds, reason max_duration
live / idleendedExplicit end, reason user_ended / admin_ended
live / idleendedTransfer to another agent, reason transfer
live / idlepausedExplicit pause
pausedliveExplicit 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

ParameterTypeRequiredDescription
statestringNoactive (default — live + idle + paused), live, idle, paused, ended, or all.
limitintegerNo1–500. Default 100.
offsetintegerNoPagination.

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

CodeStatusWhen
session-cap-reached429User already at maxConcurrentSessionsPerUser with this agent
max-duration-reached410Session 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 idlepaused. Ended sessions return 400.

Scope: agents:write


POST /api/v1/agents/:agentId/sessions/:id/resume

Flip pausedlive 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 → idle when lastActivityAt is older than idleTimeoutSeconds.
  • Flips idle → ended when lastActivityAt is older than 2 × idleTimeoutSeconds.
  • Flips live | idle → ended when total age exceeds maxSessionDurationSeconds.

Bounded at 200 rows per tick.

See also