LiveKit docs › Logic & Structure › Chat context

---

# Chat context

> How to use ChatContext to manage conversation history in your agents.

## Overview

`ChatContext` is the conversation history sent to the LLM on each turn. It holds an ordered list of items — messages and events like agent handoffs — that together define what the model knows about the current conversation.

Each agent and task maintains its own `chat_ctx`. By default, a new agent or task starts with an empty context. You can initialize it at construction time, modify it during turns, or pass it across handoffs.

### Accessing the context

Within an agent or task, the current context is available as `self.chat_ctx`:

**Python**:

```python
class MyAgent(Agent):
    async def on_enter(self) -> None:
        print(self.chat_ctx.items)

```

---

**Node.js**:

```ts
class MyAgent extends voice.Agent {
  async onEnter(): Promise<void> {
    console.log(this.chatCtx.items);
  }
}

```

The complete conversation history across all agents in a session is available on `session.history`:

**Python**:

```python
history = self.session.history

```

---

**Node.js**:

```ts
const history = this.session.history;

```

### Structure

`ChatContext` exposes an `items` list. Each item has a `type` field that determines what it represents:

| Type | Description |
| `message` | A conversation turn with a `role` (`system`, `user`, or `assistant`) and `content` (text, [images](#adding-images-and-video-frames), or instructions). |
| `function_call` | A tool invocation requested by the LLM. |
| `function_call_output` | The result returned from a tool call. |
| `agent_handoff` | Added automatically when control transfers between agents. |
| `agent_config_update` | Records a change to the agent's instructions or tools. Only available in Python. |

To get the text of a `message` type item, use `text_content` (Python) or `textContent` (Node.js). This property is only available on `ChatMessage` items.

## Core operations

These are the most commonly used `ChatContext` operations. For additional methods like `insert()` and `get_by_id()`, see the reference for [Python](https://docs.livekit.io/reference/python/livekit/agents/llm/index.html.md#livekit.agents.llm.ChatContext) and [Node.js](https://docs.livekit.io/reference/agents-js/classes/agents.llm.ChatContext.html.md).

### Creating a context

Create a `ChatContext` and add messages directly:

**Python**:

```python
from livekit.agents import ChatContext

chat_ctx = ChatContext()
chat_ctx.add_message(role="system", content="You are a helpful assistant.")
chat_ctx.add_message(role="user", content="Hello!")

```

---

**Node.js**:

```ts
import { llm } from '@livekit/agents';

const chatCtx = new llm.ChatContext();
chatCtx.addMessage({ role: 'system', content: 'You are a helpful assistant.' });
chatCtx.addMessage({ role: 'user', content: 'Hello!' });

```

### Copying a context

Use `copy()` to create a snapshot that can be passed to another agent or modified independently. By default, `copy()` includes all items — messages, function calls, handoff markers, and system (instruction) messages.

You can filter the copy with the following options:

| Option | Description |
| `exclude_instructions` | Omit system/developer messages. |
| `exclude_function_call` | Omit function calls and their outputs. |
| `exclude_handoff` | Omit agent handoff markers. |
| `exclude_empty_message` | Omit messages with no content. |
| `exclude_config_update` | Omit agent config update items. |

**Python**:

```python
# Copy everything
full_copy = self.chat_ctx.copy()

# Copy only user/assistant turns, without tool calls
turns_only = self.chat_ctx.copy(exclude_instructions=True, exclude_function_call=True)

```

---

**Node.js**:

```ts
// Copy everything
const fullCopy = this.chatCtx.copy();

// Copy only user/assistant turns, without tool calls
const turnsOnly = this.chatCtx.copy({ excludeInstructions: true, excludeFunctionCall: true });

```

### Truncating a context

`truncate()` reduces a context to the most recent _n_ items. It always preserves system instructions even if they fall outside the item window, and strips any leading function call items to avoid orphaned tool results. This is useful when you want to pass only the tail of a long conversation to the next agent:

**Python**:

```python
recent = self.chat_ctx.copy().truncate(max_items=6)

```

---

**Node.js**:

```ts
const recent = this.chatCtx.copy().truncate(6);

```

### Merging contexts

`merge()` combines items from another context into the current one, deduplicating by item ID and maintaining chronological order. This is useful after parallel tasks when you need to reunify their conversation histories:

**Python**:

```python
primary_ctx.merge(other_ctx)

# Merge without carrying over tool calls
primary_ctx.merge(other_ctx, exclude_function_call=True)

```

---

**Node.js**:

```ts
primaryCtx.merge(otherCtx);

// Merge without carrying over tool calls
primaryCtx.merge(otherCtx, { excludeFunctionCall: true });

```

## Common patterns

These examples show how to use `ChatContext` in typical agent workflows. Each pattern includes both Python and Node.js examples.

### Initialize with user data

Load user-specific context before the session starts and pass it to the agent constructor. This is the recommended approach for personalizing the agent without a round-trip to the LLM:

**Python**:

```python
initial_ctx = ChatContext()
initial_ctx.add_message(role="assistant", content=f"The user's name is {user_name}.")

await session.start(
    room=ctx.room,
    agent=MyAgent(chat_ctx=initial_ctx),
)

```

---

**Node.js**:

```ts
const initialCtx = new llm.ChatContext();
initialCtx.addMessage({ role: 'assistant', content: `The user's name is ${userName}.` });

await session.start({
  room: ctx.room,
  agent: new MyAgent({ chatCtx: initialCtx }),
});

```

For a complete example, see [External data and RAG](https://docs.livekit.io/agents/logic/external-data.md).

### Modifying context during a turn

Override the [`on_user_turn_completed`](https://docs.livekit.io/agents/logic/nodes.md#on_user_turn_completed) node to inject additional context before the LLM generates its reply. Messages added here apply to the current turn only. Call `update_chat_ctx` to persist them:

**Python**:

```python
from livekit.agents import ChatContext, ChatMessage

async def on_user_turn_completed(
    self, turn_ctx: ChatContext, new_message: ChatMessage,
) -> None:
    # your function that retrieves context from a database, API, or other source
    extra = await fetch_relevant_data(new_message.text_content)
    turn_ctx.add_message(role="assistant", content=extra)
    await self.update_chat_ctx(turn_ctx)  # persist beyond this turn

```

---

**Node.js**:

```ts
import { llm } from '@livekit/agents';

async onUserTurnCompleted(
  chatCtx: llm.ChatContext,
  newMessage: llm.ChatMessage,
): Promise<void> {
  // your function that retrieves context from a database, API, or other source
  const extra = await fetchRelevantData(newMessage.textContent);
  chatCtx.addMessage({ role: 'assistant', content: extra });
  await this.updateChatCtx(chatCtx); // persist beyond this turn
}

```

For more details on pipeline nodes, see [Pipeline nodes & hooks](https://docs.livekit.io/agents/logic/nodes.md).

### Passing context during handoffs

Pass the current context to the next agent to preserve conversation history across handoffs. Use `exclude_instructions=True` to avoid forwarding the previous agent's system prompt:

**Python**:

```python
return NextAgent(chat_ctx=self.chat_ctx.copy(exclude_instructions=True))

```

---

**Node.js**:

```ts
return llm.handoff({
  agent: new NextAgent({ chatCtx: this.chatCtx.copy({ excludeInstructions: true }) }),
});

```

For long conversations, summarize the context before passing it to reduce token cost. See [Summarizing context](https://docs.livekit.io/agents/logic/agents-handoffs.md#summarizing-context) for a complete example.

### Adding images and video frames

Message content can include images alongside text. Pass a list of text and `ImageContent` items to `add_message`:

**Python**:

```python
from livekit.agents import ChatContext
from livekit.agents.llm import ImageContent

initial_ctx = ChatContext()
initial_ctx.add_message(
    role="user",
    content=[
        "Here is a picture of me",
        ImageContent(image="https://example.com/image.jpg"),
    ],
)

```

---

**Node.js**:

```ts
import { llm } from '@livekit/agents';

const initialCtx = new llm.ChatContext();
initialCtx.addMessage({
  role: 'user',
  content: [
    'Here is a picture of me',
    llm.createImageContent({ image: 'https://example.com/image.jpg' }),
  ],
});

```

You can also inject live video frames into the context during a conversation turn. For details, see [Images](https://docs.livekit.io/agents/multimodality/vision/images.md) and [Video](https://docs.livekit.io/agents/multimodality/vision/video.md).

### Custom context for `generate_reply()`

Pass a modified `ChatContext` to `generate_reply()` to fully control the context for a single reply. This replaces the agent's session-level context for that reply only, which is useful when you need to exclude certain messages, inject one-off context, or override instructions:

**Python**:

```python
# Copy and modify the current context for this reply only
ctx = session.current_agent.chat_ctx.copy()
# Modify as needed: trim history, inject context, replace instructions, etc.
await session.generate_reply(chat_ctx=ctx)

```

---

**Node.js**:

```ts
// Copy and modify the current context for this reply only
const ctx = session.currentAgent.chatCtx.copy();
// Modify as needed: trim history, inject context, replace instructions, etc.
await session.generateReply({ chatCtx: ctx });

```

For the full list of `generate_reply()` parameters, see [Speech & audio](https://docs.livekit.io/agents/multimodality/audio.md#generate_reply-parameters).

### Standalone LLM usage

`ChatContext` also works outside of agents and sessions. Pass it directly to an LLM's `chat()` method for background tasks, preprocessing, or any workflow that needs LLM output without the voice pipeline.

For more details, see [Standalone LLM usage](https://docs.livekit.io/agents/models/llm.md#standalone-usage).

## Additional resources

- **[Agents & handoffs](https://docs.livekit.io/agents/logic/agents-handoffs.md)**: How to pass and summarize context across agent handoffs.

- **[External data & RAG](https://docs.livekit.io/agents/logic/external-data.md)**: Load external data into the chat context at session start or during turns.

- **[Pipeline nodes & hooks](https://docs.livekit.io/agents/logic/nodes.md)**: Modify the chat context at specific points in the voice pipeline.

- **[Images & video](https://docs.livekit.io/agents/multimodality/vision/images.md)**: Add images and video frames to the chat context.

- **[Speech & audio](https://docs.livekit.io/agents/multimodality/audio.md)**: Use a custom chat context with generate_reply().

- **[LLM overview](https://docs.livekit.io/agents/models/llm.md)**: Use ChatContext with standalone LLM calls outside of agents.

---

This document was rendered at 2026-06-07T11:34:28.292Z.
For the latest version of this document, see [https://docs.livekit.io/agents/logic/chat-context.md](https://docs.livekit.io/agents/logic/chat-context.md).

To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).