> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sigmic.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Streaming Events

> Understanding Server-Sent Events (SSE) for real-time task execution

# Streaming Events

Real-time task events are delivered via Server-Sent Events (SSE) through the `GET /api/v1/tasks/:id/stream` endpoint. This is the recommended way to receive live updates as the AI agent processes your request.

## Architecture

The API uses a **fire-and-forget** pattern:

1. **`POST /tasks`** returns JSON immediately with the task ID
2. **`GET /tasks/:id/stream`** is the unified SSE channel for all events

Task execution runs in the background. You can connect, disconnect, and reconnect to the stream at any time without affecting execution.

## SSE Format

Events are delivered in the standard SSE format:

```
event: {event_type}
data: {json_data}

event: {event_type}
data: {json_data}
```

Each event consists of:

* **event** - The event type identifier
* **data** - JSON payload with event details

Events are separated by double newlines (`\n\n`).

## Stream Phases

The stream delivers events in three phases:

### Phase 1: History Replay

All saved conversation messages are emitted as `history_message` events.

### Phase 2: History Complete

A single `history_done` event signals that history replay is complete and indicates whether the task is still running.

### Phase 3: Live Events (if running)

If the task is currently executing, the stream delivers catch-up state followed by real-time events until the task completes.

## Event Types

### `history_message`

A replayed conversation message. Emitted for each saved message at the start of the stream.

```json theme={null}
{
  "id": "msg-1",
  "role": "user",
  "content": "Analyze this CSV file",
  "createdAt": "2026-01-26T15:50:54.870Z",
  "attachedFiles": [
    { "name": "data.csv", "path": "uploads/data.csv" }
  ]
}
```

### `history_done`

Signals that all history messages have been sent.

```json theme={null}
{
  "messageCount": 4,
  "isStreaming": true
}
```

| Field          | Description                                                                 |
| -------------- | --------------------------------------------------------------------------- |
| `messageCount` | Number of history messages emitted                                          |
| `isStreaming`  | `true` if the task is running (live events follow), `false` if task is idle |

### `status`

Processing status updates indicating task progress.

```json theme={null}
{
  "status": "processing",
  "message": "Task created: c37c1d78-4d23-4e53-949a-ddd775f25e01"
}
```

**Status values:**

| Status       | Description                          |
| ------------ | ------------------------------------ |
| `connecting` | Establishing connection to the agent |
| `processing` | Task is being processed              |
| `retrying`   | Retrying after a temporary failure   |

### `thought`

Agent's thinking and reasoning process. Useful for understanding how the AI approaches the task.

```json theme={null}
{
  "subject": "Analyzing the problem",
  "description": "I need to calculate 2+2 which equals 4"
}
```

### `content`

Text content from the agent's response. May be delivered in chunks.

```json theme={null}
{
  "text": "The answer is ",
  "isPartial": true
}
```

| Field       | Description                                               |
| ----------- | --------------------------------------------------------- |
| `text`      | The content text                                          |
| `isPartial` | `true` if more content is coming, `false` for final chunk |

### `tool_call`

Indicates a tool is being executed by the agent.

```json theme={null}
{
  "callId": "call_123",
  "toolName": "read_file",
  "status": "executing",
  "args": {"path": "/uploads/data.csv"}
}
```

**Tool call status values:**

| Status              | Description                                            |
| ------------------- | ------------------------------------------------------ |
| `pending`           | Tool call queued                                       |
| `executing`         | Tool is currently executing                            |
| `awaiting_approval` | Waiting for manual approval (when `autoExecute=false`) |

### `tool_result`

Result of a completed tool execution.

```json theme={null}
{
  "callId": "call_123",
  "status": "success",
  "output": "File contents here..."
}
```

### `error`

Error event indicating something went wrong.

```json theme={null}
{
  "message": "Task execution failed",
  "code": "EXECUTION_ERROR"
}
```

### `done`

Task completion event with the final response.

```json theme={null}
{
  "finalResponse": "The answer to 2+2 is 4."
}
```

<Note>
  The `done` event signals that the current execution turn is complete. For completed tasks, the stream ends after this event. For ongoing conversations, you can send follow-ups and reconnect.
</Note>

## Subagent Events

When the AI agent delegates work to specialized subagents, these events track the subagent's lifecycle and activity. Each subagent session begins with `subagent_start` and ends with `subagent_end`, with `subagent_thought` and `subagent_tool` events in between.

### `subagent_start`

Emitted when the agent delegates work to a subagent.

```json theme={null}
{
  "agentName": "code-writer",
  "displayName": "Code Writer",
  "description": "Writes and edits code files",
  "objective": "Create a utility function for date formatting"
}
```

| Field         | Description                                 |
| ------------- | ------------------------------------------- |
| `agentName`   | Unique identifier for the subagent          |
| `displayName` | Human-readable name                         |
| `description` | What the subagent does                      |
| `objective`   | The specific task delegated to the subagent |

### `subagent_thought`

Reasoning text from a running subagent.

```json theme={null}
{
  "agentName": "code-writer",
  "text": "I need to check the existing date utilities before writing a new one"
}
```

| Field       | Description                          |
| ----------- | ------------------------------------ |
| `agentName` | Which subagent produced this thought |
| `text`      | The thought/reasoning text           |

### `subagent_tool`

Tool usage within a subagent. Emitted at both the start and end of each tool call.

```json theme={null}
{
  "agentName": "code-writer",
  "status": "end",
  "toolName": "write_file",
  "output": "File written successfully"
}
```

| Field       | Description                                           |
| ----------- | ----------------------------------------------------- |
| `agentName` | Which subagent is using the tool                      |
| `status`    | `start` when the tool begins, `end` when it completes |
| `toolName`  | Name of the tool being used                           |
| `args`      | Tool arguments (present on `start` events)            |
| `output`    | Tool output (present on `end` events)                 |

### `subagent_end`

Emitted when a subagent finishes execution.

```json theme={null}
{
  "agentName": "code-writer",
  "terminateReason": "GOAL",
  "result": "Created formatDate() in src/utils/date.ts"
}
```

| Field             | Description                                                     |
| ----------------- | --------------------------------------------------------------- |
| `agentName`       | Which subagent finished                                         |
| `terminateReason` | `GOAL` (success), `TIMEOUT`, `MAX_TURNS`, `ERROR`, or `ABORTED` |
| `result`          | Summary of what the subagent accomplished (on success)          |
| `error`           | Error message (when `terminateReason` is `ERROR`)               |

## Parsing SSE in Code

### JavaScript

```javascript theme={null}
async function streamTask(taskId) {
  const response = await fetch(`${BASE_URL}/api/v1/tasks/${taskId}/stream`, {
    headers: { 'Authorization': `Bearer ${API_KEY}` }
  });

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let buffer = '';

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    buffer += decoder.decode(value, { stream: true });
    const events = buffer.split('\n\n');
    buffer = events.pop() || '';

    for (const event of events) {
      if (!event.trim() || event.startsWith(':')) continue;

      const lines = event.split('\n');
      const eventType = lines[0]?.replace('event: ', '');
      const dataLine = lines[1]?.replace('data: ', '');

      if (!dataLine) continue;
      const data = JSON.parse(dataLine);

      // Handle event based on type
      console.log(eventType, data);
    }
  }
}
```

### Python

```python theme={null}
import json
import requests

def stream_task(task_id):
    response = requests.get(
        f'{BASE_URL}/api/v1/tasks/{task_id}/stream',
        headers={'Authorization': f'Bearer {API_KEY}'},
        stream=True
    )

    event_type = None
    for line in response.iter_lines():
        if not line:
            continue

        line = line.decode('utf-8')
        if line.startswith('event:'):
            event_type = line.replace('event: ', '')
        elif line.startswith('data:'):
            data = json.loads(line.replace('data: ', ''))
            # Handle event based on type
            print(event_type, data)
```

## Connection Handling

<AccordionGroup>
  <Accordion title="Heartbeat events">
    The server sends periodic heartbeat comments (`:heartbeat`) to keep the connection alive. These can be safely ignored by SSE parsers.
  </Accordion>

  <Accordion title="Reconnection">
    Disconnect and reconnect to the stream at any time. The stream replays full conversation history, then catches up to the current state. The task continues executing in the background regardless of stream connections.
  </Accordion>

  <Accordion title="Background execution">
    Closing the stream connection does NOT cancel or stop the task. Execution continues in the background. Use `DELETE /api/v1/tasks/:id` to explicitly cancel a task.
  </Accordion>

  <Accordion title="Timeouts">
    Tasks may take several minutes to complete. Ensure your HTTP client has appropriate timeout settings. The 15-second heartbeat keeps the connection alive.
  </Accordion>
</AccordionGroup>

## Polling as an Alternative

If you cannot use SSE streaming (e.g., due to infrastructure limitations), you can poll the task endpoint to get real-time progress via the `output` field.

### Output Structure

When polling `GET /api/v1/tasks/{id}`, the response includes an `output` object that works the same way for both running and completed tasks:

```json theme={null}
{
  "output": {
    "content": "Here's what I found...",
    "updatedAt": "2026-01-26T15:51:02.123Z",
    "thoughts": [
      { "subject": "Analyzing", "description": "Looking at the files..." }
    ],
    "toolCalls": [
      {
        "callId": "call_123",
        "toolName": "read_file",
        "status": "success",
        "output": "file contents..."
      }
    ]
  },
  "pendingApprovals": [
    {
      "callId": "call_456",
      "toolName": "write_file",
      "args": { "path": "output.csv" }
    }
  ]
}
```

| Field              | Description                                                             |
| ------------------ | ----------------------------------------------------------------------- |
| `output.content`   | Accumulated text response (partial while running, final when completed) |
| `output.updatedAt` | ISO timestamp when the output was last updated                          |
| `output.thoughts`  | Agent's thinking process                                                |
| `output.toolCalls` | All tool executions with their current status                           |
| `pendingApprovals` | Tool calls awaiting approval (for `autoExecute=false` tasks)            |

<Note>
  Use the task's `status` field to determine if `output.content` is partial (when `status` is `running` or `awaiting_approval`) or final (when `status` is `completed`, `failed`, or `cancelled`).
</Note>

### Polling Example

```javascript theme={null}
async function pollWithProgress(taskId) {
  const API_BASE = 'https://api.sigmic.ai';
  let lastUpdatedAt = null;

  while (true) {
    const response = await fetch(`${API_BASE}/api/v1/tasks/${taskId}`, {
      headers: { 'Authorization': 'Bearer sigmic_your_key_here' }
    });

    const { data: task } = await response.json();

    // Display progress when output has been updated
    if (task.output && task.output.updatedAt !== lastUpdatedAt) {
      lastUpdatedAt = task.output.updatedAt;
      console.log('Content:', task.output.content);
      console.log('Last updated:', task.output.updatedAt);
      if (task.output.toolCalls) {
        console.log('Tools:', task.output.toolCalls.map(t => `${t.toolName}: ${t.status}`));
      }
    }

    // Check for pending approvals
    if (task.pendingApprovals?.length > 0) {
      console.log('Pending approvals:', task.pendingApprovals);
    }

    // Check for completion
    if (['completed', 'failed', 'cancelled'].includes(task.status)) {
      console.log('Final response:', task.output?.content);
      return task;
    }

    // Poll every 500ms
    await new Promise(r => setTimeout(r, 500));
  }
}
```

### SSE vs Polling

| Approach          | Pros                                             | Cons                                        |
| ----------------- | ------------------------------------------------ | ------------------------------------------- |
| **SSE Streaming** | Real-time updates, history replay, lower latency | Requires SSE support, persistent connection |
| **Polling**       | Works everywhere, simpler implementation         | Higher latency, more API calls              |

<Tip>
  Use SSE streaming when possible for the best user experience. Fall back to polling with the `output` field when SSE is not available.
</Tip>
