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

# Span Kinds

> Tell the engine what each step is - an LLM call, a tool call, a retrieval - instead of letting it guess

A trace is a tree of steps, and every surface that renders or scores it needs to know what kind
of step each span is: the Execution Timeline colors and filters by it, Code scorers branch on
it, the dashboard's span buckets count by it, and the RAG judges pull their `{context}` from it.

Spans carry that answer as a **stated kind**. Whoever produces the span says what it is, the
engine resolves it once at ingest, and every reader gets the same answer back on the wire as
`spanKind`. This is the same design as LangSmith's `run_type`, Langfuse's observation type, and
OpenInference's `openinference.span.kind`.

## The vocabulary

| Kind        | What it is                                                                                                                                                                    |
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `agent`     | An agent turn: the step that owns the whole interaction's input and output                                                                                                    |
| `llm`       | One call to a language model                                                                                                                                                  |
| `tool`      | One tool or function execution                                                                                                                                                |
| `retrieval` | A data-retrieval step: vector store, knowledge base, database lookup                                                                                                          |
| `chain`     | Glue between steps: formatting a prompt, routing, parsing                                                                                                                     |
| `embedding` | An embeddings call                                                                                                                                                            |
| `reranker`  | Reordering retrieved candidates                                                                                                                                               |
| `guardrail` | A safety or policy check                                                                                                                                                      |
| `evaluator` | A step that grades another step's output                                                                                                                                      |
| `prompt`    | Prompt construction from a template                                                                                                                                           |
| `memory`    | A long-term-memory operation: recalling or storing user/agent state (Mem0, Zep, Letta, or hand-rolled). Reads and writes share the kind - the span's name/metadata says which |

Kinds are mostly labels - with a few that carry real semantics:

* **`retrieval` has a behavioral consequence.** The output of every retrieval span in an
  interaction is what the [RAG judges](/evaluation/rag) grade against as `{context}` - a
  Faithfulness or Context Relevancy scorer reads exactly those chunks.
* **`memory` is deliberately not `retrieval`**, even though a memory read looks like a lookup.
  `{context}` means *knowledge the answer should be grounded in*; a recalled user preference is
  *state*, not grounding - feeding it to a groundedness judge would penalize answers for not
  citing it. Keeping the kinds apart lets the timeline and scorers treat "consulted the
  knowledge base" and "remembered the user" as the different acts they are.
* **The bare kind string `recall` is deliberately NOT an alias**: eval harnesses (Ragas, DeepEval) emit per-metric spans kinded `recall`/`precision`/`faithfulness` - evaluator spans about a retrieval metric, not memory ops. Use `memory_recall` (or state `memory`) for a recall operation.

## How a span gets its kind

Three paths, checked in this order - an earlier one always wins.

**1. Stated explicitly.** Both the root span and child spans take `span_kind`:

```python theme={null}
with client.tracer.trace("rag-agent", span_kind="agent", sync=True) as span:
    span.child_span("jailbreak_check", span_kind="guardrail",
                    input=query, output={"flagged": False})
    span.child_span("rerank_chunks", span_kind="reranker",
                    input={"candidates": 8}, output={"kept": 3})
```

**2. Stamped by the SDK.** The helpers that already know what they are say so - you never pass
`span_kind` to these:

```python theme={null}
client.tracer.record_retrieval("kb_search", query=q, output=chunks)   # -> retrieval

with client.tracer.trace_memory("user prefs", operation="read", query=user_id) as m:
    m.output = memory.search(user_id, question)                       # -> memory

client.tracer.record_tool_call("charge_card", input=..., output=...)  # -> tool
```

One behavioral difference between them: `record_memory`/`trace_memory` drop (warned once per process, then at debug) when no
span is active, unlike `record_tool_call`/`record_retrieval`, which queue onto the next trace.

Framework integrations do the same: merged LLM steps arrive as `llm`, LangChain graph nodes as
`chain`, tool executions as `tool`, retriever runs as `retrieval`.

**3. Inferred by the engine, as a fallback.** A span that never stated a kind - a trace
recorded before span kinds existed, or a producer that just doesn't say - still classifies,
by the engine's inference ladder: a span with a `model` is `llm`, one carrying tool calls is
`tool`, the SDK's auto-generated names (`LLM Call N`, `Retrieval N`, `Memory ...`) are
recognized, and everything else is `chain`. One nuance with a real consequence: a
retrieval-named span whose name contains the word *memory*/*memories* (`retrieve_memories`)
classifies as `memory`, so recalled user state never rides into the RAG judges' `{context}` -
while `Retrieval: memoranda index` stays a `retrieval`. Old traces classify exactly as they
always did; inference is a best guess, never a fact.

Alias spellings from the other products' vocabularies (OpenInference's `retriever`,
Langfuse's `generation`, every `memory_*` variant, ...) fold onto the kinds above - the
[table below](#other-products-vocabularies-just-work) is the reference, generated from the same
alias map the engine runs. Two deliberate absences: `UNKNOWN` (OpenInference / MLflow's
default) is not an alias - it means "nothing was stated" and falls through to the ladder -
and bare `recall` is not one either, because eval harnesses emit per-metric spans literally
kinded "recall" (the retrieval metric), which must never classify as memory
(`memory_recall` works).

<Note>
  A **stated kind always beats the ladder**. A guardrail implemented as an LLM
  call carries a model, which the ladder would read as `llm` - stating
  `span_kind="guardrail"` is what keeps it a guardrail. One thing the engine
  will not infer: that a root span is an `agent` turn. A flat trace's root
  **is** the LLM call, so root-ness proves nothing - a root that really is an
  agent turn should say so.
</Note>

## Example: memory steps in a trace

One trace whose steps mix the three step families - a memory read (recalled user state), a
knowledge retrieval, and a memory write:

```python theme={null}
from agentx import AgentX

client = AgentX(api_key="...", base_url="http://localhost:4700/api/v1")
tracer = client.tracer
USER_ID = "demo-user-7"

with tracer.trace(
    "travel-concierge",
    input={"query": "Book my usual kind of flight to Denver next month."},
    sync=True,
) as span:
    # Memory READ - recalled user state, deliberately NOT a retrieval.
    with tracer.trace_memory("user prefs", operation="read", query=USER_ID) as m:
        m.output = ["prefers window seats", "vegetarian meals", "flies out of Oakland"]

    # A knowledge lookup for contrast - this one IS a retrieval, in its own lane.
    with tracer.trace_retrieval("route_search", query="Oakland to Denver flights") as r:
        r.doc_count = 2
        r.output = ["OAK->DEN nonstop daily 7:40", "OAK->DEN nonstop daily 18:05"]

    # Memory WRITE - the agent learned something new this turn and stored it.
    with tracer.trace_memory("user prefs", operation="write", query=USER_ID) as m:
        m.output = "stored: planning a Denver trip next month"

    span.output = "Booked the 7:40 nonstop - window seat, vegetarian meal, as usual."
```

See it from the dashboard:

<Frame caption="The Execution Timeline: memory read and write steps in their own lane, next to the retrieval step.">
  <img src="https://mintcdn.com/agentx-ffe8d995/V1hBV5Nj2zNcSTYd/images/memory-timeline.png?fit=max&auto=format&n=V1hBV5Nj2zNcSTYd&q=85&s=caa135bd24308dccef0ea01b6ff4a637" alt="screenshot of the trace Execution Timeline for the travel-concierge trace - a 'user prefs' Memory step (read), a 'route_search' Retrieval step, and a second 'user prefs' Memory step (write), each with its own colored dot, and the Memory chip active in the kind filter row." width="3148" height="1694" data-path="images/memory-timeline.png" />
</Frame>

## Other products' vocabularies just work

The engine folds every convention it knows onto its own vocabulary, so a span instrumented for
another product classifies on arrival with no change by the producer:

| You send                                                                                                                                                         | Stored as   | Convention                                                                                           |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------- |
| `retriever`                                                                                                                                                      | `retrieval` | OpenInference                                                                                        |
| `retrieve`                                                                                                                                                       | `retrieval` | OTel GenAI semconv (newer spelling of the operation)                                                 |
| `generation`                                                                                                                                                     | `llm`       | Langfuse                                                                                             |
| `chat`, `text_completion`, `completion`                                                                                                                          | `llm`       | OTel GenAI semconv                                                                                   |
| `chat_model`, `generate_content`                                                                                                                                 | `llm`       | MLflow / OTel GenAI (Gemini, Vertex)                                                                 |
| `execute_tool`, `function`                                                                                                                                       | `tool`      | OTel GenAI semconv                                                                                   |
| `invoke_agent`, `create_agent`                                                                                                                                   | `agent`     | OTel GenAI semconv                                                                                   |
| `embeddings`                                                                                                                                                     | `embedding` | OTel GenAI semconv                                                                                   |
| `parser`                                                                                                                                                         | `chain`     | MLflow / LangSmith "some step in the middle"                                                         |
| `TOOL`, `AGENT`, `CHAIN`, ...                                                                                                                                    | lowercased  | MLflow `spanType`                                                                                    |
| `memory_read`, `memory_write`, `memory_search`, `memory_store`, `memory_update`, `memory_recall`, `memory_add`, `memory_delete`, `add_memory`, `get_memory`, ... | `memory`    | The memory SDKs' spellings (Mem0, Zep, Letta) - reads and writes all fold onto the one `memory` kind |

On the [OpenTelemetry ingest path](/trace/opentelemetry) the kind is read from the span's own
attributes, in this order: `openinference.span.kind`, `gen_ai.operation.name`,
`mlflow.spanType`, `langfuse.observation.type`, then `gen_ai.tool.name` (a span carrying one IS
the tool call) and finally the model attribute. Anything already instrumented with
OpenInference or the GenAI semconv arrives fully classified.

A word the engine does not recognize is **ignored, not stored**: the span falls back to the
inference ladder rather than carrying a "kind" nobody defined.

## Kind vs. `tool_calls`

These answer different questions. `span_kind="tool"` says *this span is one tool execution*.
The root trace's flat `tool_calls[]` list says *this interaction made these calls* - it is what
the built-in Tool failure check, trajectory matching, and the Tool quality column read. The SDK
maintains both for you (`trace_tool_call` writes the child span with its kind **and** appends to
the active span's `tool_calls` list); if you build spans by hand, keep doing both.
