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

> ## Agent Instructions
> Unmute compiles to exactly three targets. Pipecat and LiveKit are code targets: compile writes a Python project you run. SLNG is a hosted target: compile writes a deployment body and SLNG runs the agent, so it has no `unmute dev`. Those three are the only values `provider` accepts in `targets.yaml`. Deepgram and ElevenLabs appear in these docs as model vendors, which is not the same thing as a target, and `slng` is both.
> The Go structs in `internal/spec` and `internal/ir` are the schema truth. Check a field against them, or run `unmute validate`, rather than against what you remember.

# Langfuse

> Send spans to Langfuse to watch live calls and debug one conversation at a time.

[Langfuse](https://langfuse.com) collects traces from live calls. Use it when you
want to open one conversation and read what happened in it.

## Every key the tracing block takes

```yaml agent.yaml theme={null}
tracing:
  provider: langfuse
```

<ParamField path="provider" type="langfuse | coval" required>
  Which service the spans go to. `langfuse` is this page. Any other name is
  refused, with both accepted names in the message, and leaving the whole
  `tracing:` block out means the agent exports nothing.
</ParamField>

There is no second key. Which Langfuse project a trace lands in is decided by
the three environment names below, read at run time, so the same package can
point at a test project and a production one with no edit.
[Tracing](/tracing/overview) compares the two providers side by side.

## What you need

Three secrets: `LANGFUSE_BASE_URL`, `LANGFUSE_PUBLIC_KEY`, and
`LANGFUSE_SECRET_KEY`. All three are required, and the agent fails at startup if
any is missing. That is deliberate. Langfuse is configured once per deployment,
so a missing key means the deployment is wrong, and failing loudly is better than
running for a week without traces.

## Trace and session names

The trace name is `<entry-agent>-<agent-name>`, where `agent-name` is the
package's `name:` joined to the target it was compiled for, the same value a
deploy uses as the agent's identity. Both targets build it the same way, so a
trace from either one names the same agent the same way.

A local `unmute dev` run and a deployed run get the same trace name. Unlike
Coval tracing, the Langfuse integration adds no `-local` suffix, so the trace
name alone does not tell you which one you are looking at.

Each target picks its own session ID from something it already has:

| Target    | Session ID                                                       |
| --------- | ---------------------------------------------------------------- |
| `livekit` | the LiveKit room name                                            |
| `pipecat` | the Pipecat runner session ID, which is also its conversation ID |

Both go on every observation in the call, not only on the top one. Langfuse v4
answers questions over observations, so a session ID that sat only on the root
would leave you unable to filter the model calls under it or add up what the
session cost.

## One call is one trace

A call is one trace, and it is also one session. Open the trace and the root
observation holds the whole conversation, so you can read what happened without
opening anything. Inside it each exchange is a `turn` span: what the caller
said, what the agent replied, and the model and tool calls that produced it.

That shape is deliberate. A trace is the unit Langfuse aggregates over, so a
call split into one trace per turn leaves nothing to aggregate and leaves the
top of the call empty. Keeping the call whole means you can still deep dive,
by opening a turn, without losing the view of the call.

An observation-level evaluator can read a turn's root and get both sides of that
exchange, because a v4 evaluator cannot read an observation's children.

The two targets differ in how much work this takes. Pipecat already nests `turn`
inside `conversation`, so it is left alone. LiveKit has no span covering one
exchange, since `user_turn` closes when the caller stops speaking and
`agent_turn` is its sibling, so that target adds a `turn` span of its own.

## What the spans look like

| Target    | The tree of one call                                                                                                                      |
| --------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `pipecat` | `conversation`, then a `turn` per exchange, then `stt`, `llm` and `tts` generation observations                                           |
| `livekit` | `agent_session`, then a `turn` per exchange, then `user_turn` and `agent_turn` with `llm_node`, `llm_request`, `stt` and `tts` under them |

Agent lifecycle on LiveKit, the starting, handing over and shutting down, hangs
off `agent_session` beside the turns rather than inside one.

Every tool call is its own observation, typed as a tool and named after the tool that ran, carrying the arguments and, once the call finishes, the result. Both targets read the same way because both set `gen_ai.operation.name` and `gen_ai.tool.name` on the span, which is what Langfuse reads for the type and the name.

A task's structured result is the arguments of its `finish` call, so it is on that call's span rather than on the model request that produced it. That call is named `finish` on `livekit`, and on `pipecat` it carries the step name twice over, once for the entry that runs it, as in `finish_verify_customer_verify_customer`. Every one of them also takes `unserved_request`, which is empty unless the step handed a request back for the agent that owns it to serve.

Pipecat tracing owns the process OpenTelemetry provider and startup fails if another SDK provider is installed first.

In the Langfuse v4 data model a trace is only the observations that share a
trace ID, so there is no separate place for a trace input or output, and this
project writes neither.

## What a trace records

<Warning>
  Traces can contain caller speech, model input and output, and tool arguments and results.
  Use only fake identities and fake customer data for release tests.
</Warning>

## Checking it works

Starting the worker or exporting a synthetic span proves connectivity only.
Complete at least one user turn before you look at the trace, or you will be
reading an empty one and concluding the wrong thing.

## Where to go next

<Columns cols={2}>
  <Card title="Coval" icon="flask" href="/tracing/coval">
    Attach spans to the simulation that produced the call.
  </Card>

  <Card title="Optimizing your agent" icon="gauge" href="/optimization/overview">
    The settings that make a call faster, and where each one goes.
  </Card>
</Columns>
