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

# Events

Events are typed Python objects for communication between your agent and the Cartesia platform. Your agent receives **input events** from the harness and yields **output events** to control the conversation.

<Tip>
  To learn which events trigger your agent and how to customize this behavior (e.g., responding to DTMF, preventing interruptions), see [Controlling the Conversational Loop](/line/sdk/agents#controlling-the-conversational-loop).
</Tip>

## Input Events

Input events are received by your agent from the Cartesia harness. All input events include an optional `history` field containing the complete conversation history. When `history` is `None`, the event is being used within a history list; when `history` contains a list, the event has the full conversation context attached.

### Call Lifecycle

| Event         | Description            |
| ------------- | ---------------------- |
| `CallStarted` | The call has connected |
| `CallEnded`   | The call has ended     |

```python theme={null}
from line.events import CallStarted, CallEnded

async def process(self, env, event):
    if isinstance(event, CallStarted):
        yield AgentSendText(text="Hello! How can I help?")
    elif isinstance(event, CallEnded):
        # Perform cleanup
        pass
```

### User Turn Events

| Event             | Description                                                     |
| ----------------- | --------------------------------------------------------------- |
| `UserTurnStarted` | The user started speaking (triggers interruption by default)    |
| `UserTurnEnded`   | The user finished speaking (triggers new agent turn by default) |
| `UserTextSent`    | User text content (within `UserTurnEnded.content`)              |
| `UserDtmfSent`    | User pressed a DTMF button                                      |

```python theme={null}
from line.events import UserTurnEnded, UserTextSent

if isinstance(event, UserTurnEnded):
    for content in event.content:
        if isinstance(content, UserTextSent):
            user_message = content.content
```

### Agent Turn Events (in history)

| Event              | Description                |
| ------------------ | -------------------------- |
| `AgentTurnStarted` | Agent started its turn     |
| `AgentTurnEnded`   | Agent finished its turn    |
| `AgentTextSent`    | Agent text that was spoken |
| `AgentDtmfSent`    | DTMF tone sent by agent    |

### Handoff Event

| Event            | Description                           |
| ---------------- | ------------------------------------- |
| `AgentHandedOff` | Control transferred to a handoff tool |

### Custom Event

| Event            | Description                                                                                                        |
| ---------------- | ------------------------------------------------------------------------------------------------------------------ |
| `UserCustomSent` | Custom metadata sent from the client via the WebSocket [`custom` event](/line/integrations/calls-api#custom-event) |

Received when your client application sends a `custom` WebSocket event to the call stream. The event carries a `metadata` dict with whatever key-value pairs the client included:

```python theme={null}
from line.events import UserCustomSent

async def process(self, env, event):
    if isinstance(event, UserCustomSent):
        action = event.metadata.get("action")
        # React to client-side triggers (e.g., button clicks, form submissions)
```

***

## Output Events

Output events are yielded by your agent to control the conversation.

### Speech

You can choose to send messages with `AgentSendText`.

```python theme={null}
from line.events import AgentSendText

yield AgentSendText(text="Hello! How can I help you today?")
```

By default, users can interrupt the agent. However, if you have a disclaimer or another important message that you wish be uninterruptible, you can set the `interruptible` flag as false.

```python theme={null}
from line.events import AgentSendText

yield AgentSendText(
    text="Before we continue, I need to share a quick disclaimer.",
    interruptible=False,
)
```

### Call Control

```python theme={null}
from line.events import AgentEndCall, AgentTransferCall, AgentSendDtmf

# End the call
yield AgentEndCall()

# Transfer to another number
yield AgentTransferCall(target_phone_number="+14155551234")

# Send DTMF tone
yield AgentSendDtmf(button="1")
```

### Dynamic Configuration

Update call settings (voice, pronunciation, language) mid-conversation using `AgentUpdateCall`:

```python theme={null}
from line.events import AgentUpdateCall

# Change voice
yield AgentUpdateCall(voice_id="5ee9feff-1265-424a-9d7f-8e4d431a12c7")

# Change pronunciation dictionary
yield AgentUpdateCall(pronunciation_dict_id="dict-123")

# Change language
yield AgentUpdateCall(language="es")

# Update multiple settings at once
yield AgentUpdateCall(
    voice_id="spanish-voice-id",
    pronunciation_dict_id="spanish-dict-id",
    language="es"
)
```

**AgentUpdateCall Parameters:**

| Field                   | Type                     | Description                                                                       |
| ----------------------- | ------------------------ | --------------------------------------------------------------------------------- |
| `type`                  | `Literal["update_call"]` | Event type identifier (automatically set)                                         |
| `voice_id`              | `Optional[str]`          | Updates the agent's voice                                                         |
| `pronunciation_dict_id` | `Optional[str]`          | Updates the pronunciation dictionary                                              |
| `language`              | `Optional[str]`          | Updates the language used on speech-to-text (STT) and text-to-speech (TTS) models |

All fields are optional—only set fields are updated.

### Tool Events

These are emitted by `LlmAgent` to track tool execution:

```python theme={null}
from line.events import AgentToolCalled, AgentToolReturned

# Emitted when LLM calls a tool
yield AgentToolCalled(
    tool_call_id="call_123",
    tool_name="get_weather",
    tool_args={"city": "San Francisco"}
)

# Emitted when tool returns
yield AgentToolReturned(
    tool_call_id="call_123",
    tool_name="get_weather",
    tool_args={"city": "San Francisco"},
    result="72°F and sunny"
)
```

### Logging

```python theme={null}
from line.events import LogMetric, LogMessage

# Log a metric
yield LogMetric(name="response_time_ms", value=150)

# Log a message
yield LogMessage(
    name="order_lookup",
    level="info",
    message="Found order #12345",
    metadata={"order_id": "12345"}
)
```

### Custom Events

Send arbitrary metadata from your agent to the harness:

```python theme={null}
from line.events import AgentSendCustom

yield AgentSendCustom(metadata={"action": "show_form", "form_id": "checkout"})
```

Pair with [`UserCustomSent`](#custom-event) for bidirectional metadata exchange.

### Voice & Language Control

Change voice or speech recognition language mid-call:

```python theme={null}
from line.events import AgentUpdateCall

# Switch to Spanish voice and speech recognition
yield AgentUpdateCall(voice_id="spanish-voice-id", language="es")

# Enable multilingual auto-detect mode
yield AgentUpdateCall(language="multilingual")
```

The `language` field sets the ASR (speech recognition) language. Pass any language code supported by [Ink STT](/build-with-cartesia/stt-models), or `"multilingual"` for automatic language detection.

***

## Event History

All input events include an optional `history` field containing the conversation history. When `history` is `None`, the event is inside a history list; when it contains a list, full conversation context is attached. `LlmAgent` handles this automatically—you only need to understand history if building custom agents.

### Accessing History

```python theme={null}
from line.events import UserTextSent, AgentTextSent

async def process(self, env, event):
    for past_event in event.history:
        if isinstance(past_event, UserTextSent):
            print(f"User said: {past_event.content}")
        elif isinstance(past_event, AgentTextSent):
            print(f"Agent said: {past_event.content}")
```

<Accordion title="Event types in history">
  Events in the history list have `history=None` to avoid redundant nesting. The event types are the same as regular input events:

  | Event Type         | Description               |
  | ------------------ | ------------------------- |
  | `CallStarted`      | Call began                |
  | `UserTurnStarted`  | User started speaking     |
  | `UserTextSent`     | User's transcribed speech |
  | `UserDtmfSent`     | User's DTMF button press  |
  | `UserTurnEnded`    | User finished speaking    |
  | `AgentTurnStarted` | Agent started responding  |
  | `AgentTextSent`    | Agent's spoken text       |
  | `AgentDtmfSent`    | Agent's DTMF tone         |
  | `AgentTurnEnded`   | Agent finished responding |
  | `CallEnded`        | Call ended                |
</Accordion>

<Accordion title="How LlmAgent processes history">
  `LlmAgent` automatically converts the event history to LLM messages:

  * **User messages**: From `UserTextSent` events
  * **Assistant messages**: From `AgentTextSent` events
  * **Tool calls**: From `AgentToolCalled` and `AgentToolReturned` events

  This means the LLM sees full context including previous tool calls and results, enabling it to reference that information without making redundant API calls.
</Accordion>

<Accordion title="Custom agents: Using history">
  If building a custom agent (not using `LlmAgent`), you can use history for context, summarization, or pattern detection:

  ```python theme={null}
  class CustomAgent:
      async def process(self, env, event):
          user_turns = sum(
              1 for e in event.history
              if isinstance(e, UserTurnEnded)
          )

          if user_turns > 5:
              yield AgentSendText(text="We've been chatting for a while! Is there anything else I can help with?")
  ```
</Accordion>
