Skip to content

Events

Events are the core communication protocol in Everruns. They provide real-time visibility into session execution via Server-Sent Events (SSE) streaming.

Every action during a session - from user messages to LLM responses to tool executions - emits events. This enables:

  • Real-time UI updates: Stream agent responses as they’re generated
  • Observability: Track every step of agent execution
  • Debugging: Full visibility into LLM calls, tool execution, and errors
  • Integration: Build custom UIs or monitoring tools on the event stream

Subscribe to real-time events via Server-Sent Events:

Terminal window
curl -N "https://api.everruns.com/v1/sessions/{session_id}/sse" \
-H "Authorization: Bearer $API_KEY"

Alternatively, poll for events with pagination:

Terminal window
curl "https://api.everruns.com/v1/sessions/{session_id}/events?since_id={last_event_id}" \
-H "Authorization: Bearer $API_KEY"

SSE streams include special lifecycle events for connection management:

EventDescription
connectedSent immediately when the stream is established
disconnectingSent before the server gracefully closes the connection

To prevent stale connections through proxies and load balancers, SSE connections are automatically cycled:

Stream TypeCycle Interval
Session events5 minutes
Durable monitoring10 minutes

Before closing, the server sends a disconnecting event:

{
"event": "disconnecting",
"data": "{\"reason\":\"connection_cycle\",\"retry_ms\":100}"
}

Clients should reconnect immediately using the since_id of the last received event. This ensures no events are missed during the transition.

Each SSE event includes a retry: field that hints how long clients should wait before reconnecting if the connection is unexpectedly lost:

SituationRetry Hint
Active streaming (new events)100ms
Idle (no new events)Increases with backoff up to 500ms
After disconnecting event100ms (immediate reconnect)

The EventSource API automatically uses this hint for reconnection timing.

Use the since_id query parameter to resume from the last received event:

Terminal window
curl -N "https://api.everruns.com/v1/sessions/{session_id}/sse?since_id={last_event_id}" \
-H "Authorization: Bearer $API_KEY"

Event IDs are UUID v7 (monotonically increasing by timestamp), ensuring reliable ordering and no duplicate events on reconnection.

function connectSSE(sessionId, lastEventId) {
const url = new URL(`/v1/sessions/${sessionId}/sse`, API_BASE);
if (lastEventId) {
url.searchParams.set('since_id', lastEventId);
}
const eventSource = new EventSource(url, { withCredentials: true });
eventSource.addEventListener('connected', () => {
console.log('SSE connected');
});
eventSource.addEventListener('disconnecting', (event) => {
const data = JSON.parse(event.data);
console.log('SSE disconnecting, reconnecting in', data.retry_ms, 'ms');
eventSource.close();
setTimeout(() => connectSSE(sessionId, lastEventId), data.retry_ms);
});
eventSource.addEventListener('input.message', (event) => {
const eventData = JSON.parse(event.data);
lastEventId = eventData.id;
// Handle event...
});
// Handle other event types...
eventSource.onerror = () => {
eventSource.close();
setTimeout(() => connectSSE(sessionId, lastEventId), 2000);
};
}

Events are organized into categories based on what they represent:

CategoryEventsDescription
Inputinput.messageUser messages submitted to the session
Outputoutput.message.*Agent response lifecycle (started, delta, completed)
Turnturn.*Turn lifecycle (started, completed, failed, cancelled)
Thinkingreason.thinking.*Extended thinking content (for Claude models)
Atomreason.*, act.*, tool.*Internal execution phases
LLMllm.generationFull LLM API call details
Sessionsession.*Session state changes

Every event follows this schema:

{
"id": "event_01933b5a00007000800000000000001",
"type": "turn.completed",
"ts": "2024-01-15T10:30:00.000Z",
"session_id": "session_01933b5a00007000800000000000002",
"sequence": 42,
"context": {
"turn_id": "turn_01933b5a00007000800000000000003",
"input_message_id": "message_01933b5a00007000800000000000004",
"trace_id": "turn_01933b5a00007000800000000000003",
"span_id": "abc123",
"parent_span_id": "def456"
},
"data": { /* type-specific payload */ }
}
FieldTypeDescription
idstringUnique event ID (UUIDv7 with event_ prefix)
typestringEvent type in dot notation
tsstringISO 8601 timestamp with millisecond precision
session_idstringSession this event belongs to
sequenceintegerMonotonic sequence within session (for ordering)
contextobjectCorrelation context for tracing
dataobjectEvent-specific payload

The context object provides correlation IDs for tracing:

FieldDescription
turn_idTurn this event belongs to
input_message_idUser message that triggered the turn
exec_idAtom execution identifier
trace_idOpenTelemetry-style trace ID
span_idThis event’s span ID
parent_span_idParent span for hierarchy

Long-running operations follow a lifecycle pattern:

turn.started → turn.completed
↘ turn.failed
↘ turn.cancelled

This provides clear boundaries for UI state management and error handling.

Streaming content uses delta events with accumulated state:

{
"type": "output.message.delta",
"data": {
"turn_id": "turn_...",
"delta": "Hello", // New content since last delta
"accumulated": "Hello" // Total content so far
}
}

Delta events are batched (~100ms) to reduce volume while maintaining real-time feel.

Events follow semantic versioning. The contract is defined in specs/events-contract.md.

  • Adding new event types
  • Adding optional fields to existing events
  • Adding new enum values
  1. Ignore unknown fields: Your deserializer should not fail on unknown fields
  2. Handle optional fields: Check for presence before accessing
  3. Don’t rely on field ordering: JSON field order is not guaranteed

All events in API responses are well-defined types. The server filters out any internal or unsupported events before transmission.

Full event schemas are documented in the OpenAPI specification: