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

# Capturing conversations

> Wrap each exchange in a turn — transcript recorded, traces nested, sessions grouped.

Conversations are captured with the `turn()` API: plain function calls, so it drops into **any** server framework and is independent of your wire protocol — you hand Eldros the role and content directly.

## The turn() API

```python theme={null}
async def ws_handler(sock):
    while True:
        msg = await receive_user_message(sock)
        with eldros_sdk.turn(msg, session_id=session_id) as t:
            reply = await respond(msg)      # LLM span nests under the turn
            await send_reply(sock, reply)
            t.reply(reply)
```

Each `turn()` block records the user message on entry and the assistant reply via `t.reply()` — and because the turn is the **active span** while your handler runs, any [auto-instrumented LLM call](/docs/tracing) inside it nests underneath automatically. Transcript and trace land in one tree, with no ids to manage.

<Note>
  If the session id isn't in scope where turns happen, call `eldros_sdk.set_session(session_id)`
  once at connect instead and omit the parameter.
</Note>

## Extra roles

Tool calls and other roles go through the same handle, or the low-level primitive:

```python theme={null}
with eldros_sdk.turn(msg, session_id=sid) as t:
    t.record("tool_call", "search_flights(origin='SFO')")
    t.record("tool_result", "3 flights found")
    t.reply("I found three options for you.")
```

## Simulation traffic: continue the platform's context

When Eldros simulates your agent, the connection it opens carries standard W3C
`traceparent` + `baggage` headers with the test run's identity. Continue them
with `trace_context` — same handler, one extra line around it:

```python theme={null}
async def ws_handler(sock):
    with eldros_sdk.trace_context(sock.headers):   # handshake headers
        while True:
            msg = await receive_user_message(sock)
            with eldros_sdk.turn(msg, session_id=session_id) as t:
                reply = await respond(msg)
                await send_reply(sock, reply)
                t.reply(reply)
```

The same pattern works for any HTTP or WebSocket based agent — REST, A2A, JSON-RPC, or anything else. The protocol doesn't matter; `trace_context` accepts any header mapping.

**HTTP / REST / A2A (per-request):**

```python theme={null}
# FastAPI, Starlette, Flask, A2A executor — any HTTP handler
async def handle(request):
    with eldros_sdk.trace_context(request.headers):
        with eldros_sdk.turn(user_msg, session_id=session_id) as t:
            reply = await agent.respond(user_msg)
            t.reply(reply)
```

**WebSocket (once at handshake, covers the whole connection):**

```python theme={null}
async def ws_handler(sock):
    with eldros_sdk.trace_context(sock.headers):   # upgrade request headers
        while True:
            msg = await receive_user_message(sock)
            with eldros_sdk.turn(msg, session_id=session_id) as t:
                reply = await respond(msg)
                await send_reply(sock, reply)
                t.reply(reply)
```

Everything inside the block is stamped with the run's `episode.id` and
`traffic_type="simulation"`, so test traffic stays out of your production
views. On production connections the headers simply aren't there and the line
does nothing — keep one handler for both. Full model:
[Simulation & episodes](/docs/concepts/simulation).

## Safe to leave in

* **No-op until `init()` runs** — and `init(enabled=False)` hard-disables everything.
* Exceptions inside a turn are recorded on the span and re-raised — capture never swallows your errors.
