Session Lifecycle
FSM, status/activity/completion endpoints.
Session lifecycle endpoints allow workers to report execution progress, transition session state, post activity to Linear, and finalize sessions. All endpoints require Authorization: Bearer <runtimeJwt>.
State machine
Transitions are enforced server-side. Attempting an illegal transition (e.g. queued → completed) returns 409 Conflict.
Endpoint reference
Status read
GET /api/sessions/{sessionId}/status
Authorization: Bearer <runtimeJwt>Returns the current session state:
{
"sessionId": "sess_01abc...",
"status": "running",
"workerId": "wkr_01abc...",
"startedAt": "2026-06-02T12:00:00Z",
"updatedAt": "2026-06-02T12:05:00Z"
}Status update (FSM transition)
POST /api/sessions/{sessionId}/status
Authorization: Bearer <runtimeJwt>
Content-Type: application/jsonRequest body
{
"status": "finalizing",
"reason": "agent_complete"
}Valid status values and valid preceding states:
| Target status | Valid from | Description |
|---|---|---|
running | queued | Worker has started executing |
finalizing | running | Worker is wrapping up (writing artifacts, posting completion) |
completed | finalizing | Execution finished successfully |
failed | running, finalizing | Execution failed |
stopped | running | Stop signal was processed |
Post activity
Post a structured activity entry to the session. Activities are forwarded to the Linear AgentSession thread.
POST /api/sessions/{sessionId}/activity
Authorization: Bearer <runtimeJwt>
Content-Type: application/jsonRequest body
{
"type": "thought",
"content": "Analyzing the failing test case to understand the root cause.",
"metadata": {}
}| Activity type | Description |
|---|---|
thought | Internal reasoning step (not shown to end users by default) |
action | Tool invocation or file operation |
response | Message addressed to the user; transitions Linear session to complete |
error | Error condition; use for recoverable errors |
Sending a response activity is the correct way to close a Linear AgentSession. The Linear API has no explicit "close" mutation - the platform infers active → complete when it receives a response activity. See the Linear integration guide for detail.
Post progress
Post a milestone message (shown in the session detail view):
POST /api/sessions/{sessionId}/progress
Authorization: Bearer <runtimeJwt>
Content-Type: application/json{
"message": "Tests passing. Opening pull request.",
"phase": "qa"
}Post completion
Post the final completion summary. Long summaries are automatically chunked into Linear comment threads:
POST /api/sessions/{sessionId}/completion
Authorization: Bearer <runtimeJwt>
Content-Type: application/json{
"summary": "## Summary\n\nFixed the null pointer dereference in `UserService.getById`...",
"pullRequestUrl": "https://github.com/my-org/my-repo/pull/42",
"artifacts": [
{ "type": "pr", "url": "https://github.com/my-org/my-repo/pull/42" }
]
}Lock refresh
Sessions hold an issue lock (prevents parallel agents from claiming the same issue). Refresh it to extend the TTL during long-running operations:
POST /api/sessions/{sessionId}/lock-refresh
Authorization: Bearer <runtimeJwt>Call this approximately every 60 seconds during active execution. The platform automatically releases the lock when the session reaches a terminal state.
Report tool error
Post a tool execution error (e.g. a bash command that exited non-zero, a file read failure):
POST /api/sessions/{sessionId}/tool-error
Authorization: Bearer <runtimeJwt>
Content-Type: application/json{
"toolName": "bash",
"error": "Command exited with code 1: npm test",
"stdout": "...",
"stderr": "npm ERR! Test suite failed to run"
}Security scan event
Post a security scan finding. Triggers compliance review workflows when BFSI mode is enabled:
POST /api/sessions/{sessionId}/security-scan
Authorization: Bearer <runtimeJwt>
Content-Type: application/json{
"severity": "high",
"finding": "Potential SQL injection in UserRepository.search",
"file": "src/repositories/user.ts",
"line": 42
}Transfer ownership
Hand the session to another worker (used during rolling deploys):
POST /api/sessions/{sessionId}/transfer-ownership
Authorization: Bearer <runtimeJwt>
Content-Type: application/json{
"targetWorkerId": "wkr_02def..."
}Recovery packet
Fetch a crash-recovery context packet. Used by workers that restart mid-session:
GET /api/sessions/{sessionId}/recovery-packet
Authorization: Bearer <runtimeJwt>Returns serialized session state sufficient to resume an interrupted agent.
Routing provider select
Ask the platform to sample the MAB (Multi-Armed Bandit) posterior store and pick the best provider for this session. The in-process daemon calls this at session start when ROUTING_SELECTOR_ENABLED=true. The endpoint is dark by default - when routing is disabled or an explicit provider was already pinned, it returns { selectedProvider: null, source: "disabled" | "explicit" } without touching Redis so the daemon falls back to its statically resolved provider.
POST /api/sessions/{sessionId}/routing-select
Authorization: Bearer <runtimeJwt>
Content-Type: application/json{
"workType": "bug_fix",
"candidates": ["anthropic.claude-sonnet-4-5", "anthropic.claude-haiku-3-5"],
"project": "my-project"
}Response
{
"selectedProvider": "anthropic.claude-sonnet-4-5",
"source": "mab-routing"
}Routing feedback
Post a routing observation at session end. The daemon calls this to feed MAB reward signals back to the posterior store so future routing decisions improve over time. Best-effort: errors are swallowed with 200 OK so a Redis blip never fails the worker's terminal flow.
POST /api/sessions/{sessionId}/routing-feedback
Authorization: Bearer <runtimeJwt>
Content-Type: application/json{
"provider": "anthropic.claude-sonnet-4-5",
"workType": "bug_fix",
"reward": 1.0,
"taskCompleted": true,
"prCreated": true,
"qaResult": "passed",
"totalCostUsd": 0.84,
"wallClockMs": 1800000
}| Field | Type | Required | Description |
|---|---|---|---|
provider | string | Yes | Provider ID that ran the session |
workType | string | No | Work type (e.g. bug_fix, feature) |
reward | number | No | Explicit reward override (0-1). Omit to let the platform calculate from task/PR/QA signals. |
taskCompleted | boolean | No | Whether the agent completed the task |
prCreated | boolean | No | Whether a pull request was opened |
qaResult | string | No | QA pass/fail result: passed, failed, unknown |
totalCostUsd | number | No | Total cost in USD for this session |
wallClockMs | number | No | Total wall-clock duration in milliseconds |
SSE event stream
Subscribe to a real-time stream of org-wide session events. This endpoint is designed for the Rensei dashboard and requires a browser session cookie - it is not accessible with a runtime JWT.
GET /api/sessions/stream
Cookie: <session cookie>Emits Server-Sent Events (SSE) for all sessions in the authenticated user's org. Events are filtered to remove internal hook-bus messages (provider_hook_event) before reaching the client.
Event types
| Event type | Description |
|---|---|
session_created | A new session was queued |
session_status_changed | A session transitioned state (e.g. queued → running) |
session_activity | A new activity was posted to a session |
session_completed | A session reached a terminal state |
session_health_updated | Session health indicator changed |
heartbeat | Keep-alive event emitted every 15 seconds |
Example event
event: session_status_changed
data: {"type":"session_status_changed","sessionId":"sess_01abc...","orgId":"org_01abc...","timestamp":"2026-06-02T12:05:00Z","payload":{"from":"queued","to":"running","workerId":"wkr_01abc..."}}
event: heartbeat
data: {"timestamp":"2026-06-02T12:05:15Z"}Daemon workers do not need to subscribe to this stream. Workers receive session work via the poll endpoint and inbox ACK mechanism. This stream is for dashboard UI clients that display live session status.
Complete lifecycle example
const sessionId = 'sess_01abc...';
const headers = {
Authorization: `Bearer ${runtimeJwt}`,
'Content-Type': 'application/json',
};
const base = 'https://app.rensei.ai';
// 1. Transition to running
await fetch(`${base}/api/sessions/${sessionId}/status`, {
method: 'POST',
headers,
body: JSON.stringify({ status: 'running' }),
});
// 2. Post thought activities during execution
await fetch(`${base}/api/sessions/${sessionId}/activity`, {
method: 'POST',
headers,
body: JSON.stringify({ type: 'thought', content: 'Reading the issue context...' }),
});
// 3. Refresh lock periodically
setInterval(() => {
fetch(`${base}/api/sessions/${sessionId}/lock-refresh`, { method: 'POST', headers });
}, 60_000);
// 4. Transition to finalizing
await fetch(`${base}/api/sessions/${sessionId}/status`, {
method: 'POST',
headers,
body: JSON.stringify({ status: 'finalizing' }),
});
// 5. Post completion summary
await fetch(`${base}/api/sessions/${sessionId}/completion`, {
method: 'POST',
headers,
body: JSON.stringify({
summary: '## Changes\n\nFixed the bug. All tests pass.',
pullRequestUrl: 'https://github.com/org/repo/pull/99',
}),
});
// 6. Post response activity (closes the Linear AgentSession)
await fetch(`${base}/api/sessions/${sessionId}/activity`, {
method: 'POST',
headers,
body: JSON.stringify({
type: 'response',
content: 'Done! PR #99 is ready for review.',
}),
});
// 7. Mark completed
await fetch(`${base}/api/sessions/${sessionId}/status`, {
method: 'POST',
headers,
body: JSON.stringify({ status: 'completed' }),
});Related pages
- Poll & Heartbeat - picking up work from the platform
- File Reservation - pessimistic file locking during execution
- Worker Credentials - credential snapshot and rotate-stream
- Sessions - the user-facing session view