# ML Intern Event System: Complete Guide to Event Types and Consumption

> Explore the ML Intern event system's 16 SSE types like ready, assistant chunk, and tool call. Learn how to consume events directly via EventSource or the SSEChatTransport.

- Repository: [Hugging Face/ml-intern](https://github.com/huggingface/ml-intern)
- Tags: deep-dive
- Published: 2026-04-24

---

**The ML Intern event system emits 16 distinct Server-Sent Events (SSE) including `ready`, `assistant_chunk`, `tool_call`, and `approval_required`, which can be consumed either directly via `EventSource` or through the `SSEChatTransport` abstraction that integrates with the Vercel AI SDK.**

The **huggingface/ml-intern** repository implements a robust event-driven architecture to communicate agent session state from backend to frontend. Understanding the **ML Intern event system** is essential for building custom UI integrations or debugging agent workflows, as every user interaction, tool execution, and state change is propagated via typed JSON payloads over SSE streams.

## Event Types Emitted by the ML Intern Event System

The backend emits events through an `asyncio.Queue` managed by the `EventBroadcaster` class in [`backend/session_manager.py`](https://github.com/huggingface/ml-intern/blob/main/backend/session_manager.py). Each event is a JSON object containing an `event_type` string and an optional `data` payload. The following 16 event types are defined in [`frontend/src/types/events.ts`](https://github.com/huggingface/ml-intern/blob/main/frontend/src/types/events.ts) and emitted throughout the agent lifecycle:

- **`ready`** – Signals that the session has been created and the agent is ready to receive input. Payload: `{ message: string }`.
- **`processing`** – Indicates the agent has started processing a turn (user message, approval, etc.). Payload: `{}`.
- **`assistant_message`** – Delivers the complete assistant response when streaming is disabled. Payload: `{ content: string }`.
- **`assistant_chunk`** – Streams a fragment of the assistant’s reply token-by-token. Payload: `{ content: string }`.
- **`assistant_stream_end`** – Marks the end of a streaming response. Payload: `{}`.
- **`tool_call`** – Notifies when the LLM requests a tool execution. Payload: `{ tool: string, arguments: Record<string, any>, tool_call_id?: string }`.
- **`tool_output`** – Returns the result of a tool execution. Payload: `{ tool: string, output: string, success: boolean, tool_call_id?: string }`.
- **`tool_log`** – Emits log lines from a running tool process. Payload: `{ tool: string, log: string, agent_id?: string, label?: string }`.
- **`tool_state_change`** – Reports status changes (running, rejected, cancelled, etc.). Payload: `{ tool: string, state: string, tool_call_id?: string, jobUrl?: string }`.
- **`approval_required`** – Requests user approval before executing one or more tool calls. Payload: `{ tools: Array<{tool:string, arguments:Record<string,any>, tool_call_id:string}>, count: number }`.
- **`turn_complete`** – Indicates the entire turn (including all tool calls) is finished. Payload: `{ history_size: number }`.
- **`compacted`** – Informs that context has been compacted (summarized) to save tokens. Payload: `{ old_tokens: number, new_tokens: number }`.
- **`error`** – Signals an unrecoverable error in the session. Payload: `{ error: string }`.
- **`shutdown`** – Sent when the session is being shut down. Payload: `{}`.
- **`interrupted`** – Indicates the user manually stopped the session. Payload: `{}`.
- **`undo_complete`** – Confirms an undo operation succeeded. Payload: `{}`.
- **`plan_update`** – Updates the internal plan of upcoming tool calls. Payload: `{ plan: [{ id:string, content:string, status:"pending"|"in_progress"|"completed" }] }`.

These events are pushed to the session’s event queue via `session.send_event(Event(event_type=..., data=...))` as implemented in [`backend/session_manager.py`](https://github.com/huggingface/ml-intern/blob/main/backend/session_manager.py) (lines 322-324, 347-348).

## How to Consume ML Intern Events

### Low-Level SSE Subscription with EventSource

For direct access to the raw event stream, subscribe to the `GET /api/events/{session_id}` endpoint using the browser’s native `EventSource` API. This endpoint is implemented in [`backend/routes/agent.py`](https://github.com/huggingface/ml-intern/blob/main/backend/routes/agent.py) (lines 605-610) and creates a subscriber via `EventBroadcaster.subscribe()`.

```typescript
import { useEffect } from 'react';

function useAgentEvents(sessionId: string) {
  useEffect(() => {
    const source = new EventSource(`/api/events/${sessionId}`);

    source.onmessage = (e) => {
      const evt = JSON.parse(e.data) as { event_type: string; data?: any };
      switch (evt.event_type) {
        case 'ready':
          console.log('Agent ready →', evt.data?.message);
          break;
        case 'assistant_chunk':
          console.log('Streaming →', evt.data?.content);
          break;
        case 'tool_log':
          console.log(`[${evt.data?.tool}]`, evt.data?.log);
          break;
        case 'error':
          console.error('Session error:', evt.data?.error);
          break;
      }
    };

    source.onerror = (err) => {
      console.error('SSE error', err);
      source.close();
    };

    return () => source.close();
  }, [sessionId]);
}

```

### High-Level Integration with SSEChatTransport

For applications using the Vercel AI SDK, the repository provides `SSEChatTransport` in [`frontend/src/lib/sse-chat-transport.ts`](https://github.com/huggingface/ml-intern/blob/main/frontend/src/lib/sse-chat-transport.ts). This class parses the SSE stream, converts chat-related events into `UIMessageChunk`s, and forwards side-channel events to a callbacks object.

```typescript
import { SSEChatTransport } from '@/lib/sse-chat-transport';

const sideCallbacks = {
  onReady: () => console.log('Ready'),
  onProcessing: () => console.log('Processing…'),
  onToolLog: (tool: string, log: string) => console.log(`Tool ${tool}: ${log}`),
  onApprovalRequired: (tools: any[]) => {
    /* Render approval UI */
  },
  onError: (msg: string) => console.error('Agent error', msg),
};

const transport = new SSEChatTransport(sessionId, sideCallbacks);

// Send a message to trigger the event stream
const stream = await transport.sendMessages({
  trigger: 'submit-message',
  chatId: 'main',
  messages: [{ role: 'user', parts: [{ type: 'text', text: 'Hello' }] }],
});

```

The transport’s internal switch block (lines 91-266 in [`sse-chat-transport.ts`](https://github.com/huggingface/ml-intern/blob/main/sse-chat-transport.ts)) handles the mapping between raw SSE events and the `SideChannelCallbacks` interface.

### Backend Event Emission

To emit custom events from the backend, access the session instance and call `send_event()` with an `Event` object:

```python
from your_module.events import Event

# Inside an async context with a Session instance `session`

await session.send_event(
    Event(event_type="my_custom_event", data={"info": "extra"})
)

```

Subscribed clients receive the payload as: `data: {"event_type":"my_custom_event","data":{"info":"extra"}}`.

## Key Source Files and Implementation Details

Understanding the following files is crucial for advanced customization of the **ML Intern event system**:

- **[`frontend/src/types/events.ts`](https://github.com/huggingface/ml-intern/blob/main/frontend/src/types/events.ts)** – Defines the `EventType` enum and TypeScript interfaces for all event payloads.
- **[`frontend/src/lib/sse-chat-transport.ts`](https://github.com/huggingface/ml-intern/blob/main/frontend/src/lib/sse-chat-transport.ts)** – Houses the `SSEChatTransport` class that manages the SSE connection and callback dispatching.
- **[`backend/session_manager.py`](https://github.com/huggingface/ml-intern/blob/main/backend/session_manager.py)** – Contains the `EventBroadcaster` class (lines 49-73) that reads from the session’s `event_queue` and fans out events to all subscribers.
- **[`backend/routes/agent.py`](https://github.com/huggingface/ml-intern/blob/main/backend/routes/agent.py)** – Implements the `/api/events/{session_id}` SSE endpoint (lines 605-610) that bridges HTTP clients to the broadcaster.

## Summary

- The **ML Intern event system** emits 16 distinct event types via Server-Sent Events to communicate agent state, tool execution, and errors.
- Events are defined in [`frontend/src/types/events.ts`](https://github.com/huggingface/ml-intern/blob/main/frontend/src/types/events.ts) and emitted through `session.send_event()` in the backend.
- Consume events directly using the `EventSource` API against `/api/events/{session_id}` for low-level control.
- Use `SSEChatTransport` for high-level integration with the Vercel AI SDK and automatic UI message chunking.
- The `EventBroadcaster` in [`backend/session_manager.py`](https://github.com/huggingface/ml-intern/blob/main/backend/session_manager.py) manages the message queue and subscriber distribution.

## Frequently Asked Questions

### What are the main event types in the ML Intern event system?

The system emits lifecycle events (`ready`, `processing`, `turn_complete`), messaging events (`assistant_chunk`, `assistant_message`, `assistant_stream_end`), tool events (`tool_call`, `tool_output`, `tool_log`, `tool_state_change`), control events (`approval_required`, `interrupted`, `undo_complete`), and error events (`error`, `shutdown`). Each event carries a typed payload defined in [`frontend/src/types/events.ts`](https://github.com/huggingface/ml-intern/blob/main/frontend/src/types/events.ts).

### How do I listen to ML Intern events from a React frontend?

Create an `EventSource` pointing to `/api/events/${sessionId}` and attach an `onmessage` handler to parse the JSON payload. Alternatively, instantiate `SSEChatTransport` from [`frontend/src/lib/sse-chat-transport.ts`](https://github.com/huggingface/ml-intern/blob/main/frontend/src/lib/sse-chat-transport.ts) with a `SideChannelCallbacks` object to receive typed events and automatic Vercel AI SDK integration.

### Can I emit custom events from the ML Intern backend?

Yes. Within any async context holding a `Session` reference, call `await session.send_event(Event(event_type="custom_name", data={...}))`. The event is immediately pushed to the session’s `event_queue` and broadcast to all connected SSE clients via `EventBroadcaster`.

### What is the difference between assistant_chunk and assistant_message?

`assistant_chunk` fires multiple times during streaming mode to deliver token-by-token updates (payload: `{ content: string }`), while `assistant_message` fires once to deliver the complete response when streaming is disabled. The `assistant_stream_end` event marks the conclusion of a streaming sequence.