Skip to main content
RunTree is the core data structure that represents traces in LangSmith. It organizes runs into a hierarchical tree that mirrors your application’s execution flow.

What is a RunTree?

A RunTree is a tree structure where:
  • Each node is a run representing a single operation
  • The root is the top-level operation (e.g., the entire agent execution)
  • Children are nested operations (e.g., LLM calls, tool invocations)
  • Edges represent parent-child relationships
Agent Execution (root)
├── Format Prompt (child)
├── LLM Call (child)
│   └── Token Usage Calculation (grandchild)
└── Parse Output (child)
    └── Validation (grandchild)

Creating RunTrees

Basic creation

from langsmith.run_trees import RunTree
from datetime import datetime, timezone

# Create a root run
root = RunTree(
    name="my_application",
    run_type="chain",
    inputs={"query": "What is LangSmith?"},
    start_time=datetime.now(timezone.utc),
)

print(f"Run ID: {root.id}")
print(f"Trace ID: {root.trace_id}")  # Same as root.id for root runs
print(f"Dotted Order: {root.dotted_order}")

Creating child runs

# Create a child run
child = root.create_child(
    name="llm_call",
    run_type="llm",
    inputs={"prompt": "What is LangSmith?"},
)

print(f"Child ID: {child.id}")
print(f"Parent ID: {child.parent_run_id}")  # Points to root.id
print(f"Trace ID: {child.trace_id}")  # Same as root.trace_id
print(f"Dotted Order: {child.dotted_order}")  # Includes parent's dotted order

# Create a grandchild
grandchild = child.create_child(
    name="tokenizer",
    run_type="tool",
    inputs={"text": "What is LangSmith?"},
)

RunTree properties

Each RunTree node contains:

Core identifiers

  • id - Unique UUID v7 for this run (time-ordered)
  • trace_id - UUID of the root run (same for all runs in a trace)
  • parent_run_id - UUID of the parent run (None for root)
  • dotted_order - Hierarchical position string

Run metadata

  • name - Human-readable name
  • run_type - Category (llm, chain, tool, etc.)
  • start_time / end_time - Execution timestamps
  • inputs / outputs - Data passed in and out
  • error - Error message if the run failed

Organization

  • tags - List of labels for filtering
  • extra.metadata - Dictionary of additional context
  • project_name - LangSmith project name
  • session_id - Project UUID

Relationships

  • parent_run - Reference to parent RunTree (excluded from serialization)
  • child_runs - List of child RunTrees (excluded from serialization)

Understanding dotted order

The dotted order is a critical feature that maintains the execution sequence of runs.

Format

A dotted order segment combines a timestamp and UUID:
{timestamp}{uuid}
20230914T223155647000Z1b64098b-4ab7-43f6-afee-992304f198d8
For nested runs, segments are joined with dots:
# Root run
root.dotted_order = "20230914T223155647000Z1b64098b-4ab7-43f6-afee-992304f198d8"

# Child run includes parent's dotted order
child.dotted_order = "20230914T223155647000Z1b64098b-4ab7-43f6-afee-992304f198d8.20230914T223155649000Z809ed3a2-0172-4f4d-8a02-a64e9b7a0f8a"

# Grandchild includes both
grandchild.dotted_order = "20230914T223155647000Z1b64098b-4ab7-43f6-afee-992304f198d8.20230914T223155649000Z809ed3a2-0172-4f4d-8a02-a64e9b7a0f8a.20230914T223155651000Zc8d9f4c5-6c5a-4b2d-9b1c-3d9d7a7c5c7c"

Why dotted order matters

Dotted order enables:
  1. Chronological sorting - Traces display in execution order
  2. Parallel run handling - Correctly orders runs that arrive out-of-sequence
  3. Hierarchical relationships - Preserves parent-child structure
  4. Distributed tracing - Maintains order across services
Runs can be submitted to LangSmith in any order. The dotted order ensures they’re displayed correctly regardless of arrival time.

Managing run lifecycle

Ending runs

# End a run with outputs
child.end(
    outputs={"response": "LangSmith is a platform for LLM observability"},
    end_time=datetime.now(timezone.utc),
)

# End a run with an error
try:
    result = risky_operation()
    child.end(outputs={"result": result})
except Exception as e:
    child.end(error=str(e))

# Add metadata when ending
child.end(
    outputs={"response": "..."},
    metadata={"model": "gpt-4", "tokens": 150},
)

Posting to LangSmith

# Post the entire tree (root and all children)
root.post()

# Post just this run (excludes children)
root.post(exclude_child_runs=True)

# Patch/update an existing run
child.patch()

Working with metadata

# Add metadata
root.add_metadata({
    "user_id": "123",
    "session_id": "abc",
    "environment": "production",
})

# Add tags
root.add_tags(["important", "customer-facing"])

# Add more tags
root.add_tags("experiment-v2")

# Update inputs/outputs
root.add_inputs({"additional_context": "some data"})
root.add_outputs({"metadata": {"confidence": 0.95}})

# Use the set method to override default behavior
root.set(
    inputs={"new_input": "value"},
    outputs={"new_output": "value"},
    tags=["tag1", "tag2"],
    metadata={"key": "value"},
)

Advanced patterns

Creating from headers (distributed tracing)

from langsmith.run_trees import RunTree

# In Service A: create headers to pass to Service B
run_a = RunTree(name="service_a", run_type="chain", inputs={"data": "value"})
headers = run_a.to_headers()
# headers = {"langsmith-trace": "...", "baggage": "..."}

# In Service B: continue the trace
parent = RunTree.from_headers(headers)
run_b = parent.create_child(
    name="service_b",
    run_type="tool",
    inputs={"received": "data"},
)

Creating from dotted order

# Create a RunTree from a dotted order string
dotted_order = "20230914T223155647000Z1b64098b-4ab7-43f6-afee-992304f198d8"
run = RunTree.from_dotted_order(
    dotted_order,
    name="continued_run",
    run_type="chain",
)

Run tree with custom client

from langsmith import Client

# Create a custom client
custom_client = Client(
    api_key="your-api-key",
    api_url="https://custom.langsmith.com",
)

# Use it with RunTree
run = RunTree(
    name="custom_run",
    run_type="chain",
    inputs={},
    client=custom_client,
)

RunTree vs @traceable

When should you use each?

Use @traceable when:

  • You want automatic tracing with minimal code
  • Your application follows a function call pattern
  • You’re integrating with LangChain or similar frameworks

Use RunTree directly when:

  • You need fine-grained control over run lifecycle
  • You’re building custom integrations
  • You need to manually manage parent-child relationships
  • You’re implementing distributed tracing
# @traceable - Simple and automatic
@traceable(run_type="chain")
def my_function(input: str):
    result = process(input)
    return result

# RunTree - Full control
def my_function_manual(input: str):
    run = RunTree(
        name="my_function",
        run_type="chain",
        inputs={"input": input},
    )
    try:
        result = process(input)
        run.end(outputs={"result": result})
        return result
    except Exception as e:
        run.end(error=str(e))
        raise
    finally:
        run.post()

Common patterns

Conditional tracing

from langsmith import get_current_run_tree

def my_function(input: str, trace: bool = True):
    if trace:
        run = RunTree(name="my_function", run_type="chain", inputs={"input": input})
    
    try:
        result = process(input)
        if trace:
            run.end(outputs={"result": result})
        return result
    finally:
        if trace:
            run.post()

Best practices

Always end your runsEven if an error occurs, call end() to capture the error and timing information.
Use meaningful namesRun names appear in the UI. Use descriptive names that explain what the operation does.
Leverage dotted orderThe dotted order handles ordering automatically. Don’t try to manage timestamps manually.
Don’t modify dotted_order or trace_id manually. These are managed automatically by the RunTree.

Next steps

  • Learn about tracing patterns and best practices
  • Explore evaluation using run trees
  • Understand datasets for testing with run trees

Build docs developers (and LLMs) love