Hosted Lifecycle And Recovery
Hosted clients use one server-authoritative lifecycle for control state, committed history, live observation, semantic input, and responses.
The runtime scope is:
agentId + sessionId + threadIdThe Lifecycle
Opening or recovering a thread uses two independent reads:
GET /state
-> restore activeExecution and pendingRequests immediately
-> capture observedCursor = Generation:N
GET /events?after={appliedCursor}
-> catch up canonical events after the client's cursor
-> remain attached for future commits
-> advance appliedCursor only after projection succeedsSubmitting work remains a separate command:
POST /inputs
-> 202 { disposition, threadExecutionId, startedAt?, activeExecution? }The backend owns execution. Disconnecting an observer does not cancel the execution.
Read Control State
GET /agents/{agentId}/sessions/{sessionId}/threads/{threadId}/stateExample:
{
"observedCursor": {
"generation": 3,
"sequenceNumber": 42
},
"activeExecution": {
"threadExecutionId": "execution-123",
"agentId": "assistant",
"sessionId": "session-1",
"threadId": "main",
"status": "active"
},
"pendingRequests": []
}/state is deliberately metadata-only. It does not return event history.
observedCursoris the coherent journal boundary observed with the control state.activeExecutionrestores running and cancellation controls before history finishes loading.pendingRequestsrestores permission, clarification, continuation, and client-tool interactions.
observedCursor is not an applied cursor. A client must not skip to it without projecting the intervening events.
Catch Up And Remain Live
GET /agents/{agentId}/sessions/{sessionId}/threads/{threadId}/events?after=3:17
Accept: text/event-streamThe stream sends committed catch-up events and then waits for new commits on the same connection.
id: 3:18
event: agent-event
data: {"version":"1.0","type":"TEXT_DELTA","threadSequenceNumber":18,"text":"hello"}The server also accepts Last-Event-ID when the after query parameter is absent. It sends comment heartbeats while idle:
: heartbeatClients must maintain separate notions of received and applied progress. Reconnect only from the greatest contiguous ThreadJournalCursor whose handlers completed successfully.
- Decode events in order.
- Parse the SSE id as
generation:sequenceand verify its sequence equalsthreadSequenceNumber. - Apply the event through the same handler used for live delivery.
- Advance the applied cursor only after successful application.
- On EOF or transport failure, reconnect from the applied cursor.
If projection fails, stop applying later events. Reconnection redelivers the failing canonical fact.
Hard Journal Replacement
Hard compaction replaces the active journal and increments its generation. An observer attached to the previous generation receives a control frame and the stream ends:
event: thread-journal-rebased
data: {"previousGeneration":3,"currentGeneration":4}The client must not continue its old projection with generation 4 events. Reload /state, clear semantic state derived from generation 3, and connect with after=4:0. Active-execution and pending-interaction facts are reseeded into the replacement generation, so controls remain recoverable while the transcript rehydrates.
Submit And Track An Execution
Accepted input returns:
{
"disposition": "queued",
"threadExecutionId": "execution-123",
"startedAt": "2026-07-15T10:15:30Z"
}Only one execution can own a thread at a time. Hosting reserves ownership, commits THREAD_EXECUTION_STARTED, exposes activeExecution, executes the input, commits THREAD_EXECUTION_FINISHED, and only then clears active ownership. A conflicting submission returns 409.
Steer Or Interrupt Safely
Read /state, then submit the active execution id:
POST /agents/{agentId}/sessions/{sessionId}/threads/{threadId}/inputs
Content-Type: application/json
{
"version": "1.0",
"type": "INTERRUPTION_REQUEST",
"threadExecutionId": "execution-123",
"reason": "User cancelled the request.",
"source": "User"
}Steering uses the same route with type: "STEERING_INPUT", the same threadExecutionId, and a messages array. Possible dispositions include accepted, no_active_execution, active_execution_mismatch, active_input_not_steerable, and execution_finishing. Never automatically retry against a mismatched execution; it may be newer work started by another client.
Interactive Responses
Pending request events are canonical journal facts and are also included in /state while answerable. Send the matching response envelope through:
POST /agents/{agentId}/sessions/{sessionId}/threads/{threadId}/responsesThe request must already be answerable before its committed notification can reach observers. Reopening a client can therefore restore pending interaction UI without replaying the whole thread first.
Long Disconnects
For a laptop sleep, proxy timeout, or UI closed for hours:
- Preserve the last successfully applied cursor when possible.
- Read
/stateto restore control state and capture the current generation boundary. - Open
/events?after={generation}:{sequence}. - Apply catch-up events through the same projector.
- Continue on the same stream as commits become live.
A client with no local projection starts at after=0 and reconstructs the complete conversation incrementally. It retains semantic UI state, not the raw event graph.
Transport Responsibilities
HTTP owns commands and control state. SSE owns ordered committed observation. Each transport therefore has one lifecycle responsibility.