Problem
When manually creating RunTree objects for async/distributed workflows, client.read_run(run_id, load_child_runs=True) only returns one child run instead of all children, even though the UI displays the complete trace correctly.
Root Cause
Creating a RunTree without preserving dotted_order generates a new ordering value, breaking the trace hierarchy.
Solution
Always preserve dotted_order, trace_id, and parent_run_id when reconstructing RunTree objects:
# WRONG - Breaks hierarchy
def get_run_tree(run_id: str) -> RunTree:
return RunTree(id=run_id, client=client)
# CORRECT - Preserves hierarchy
def get_run_tree(run_id: str) -> RunTree:
runs = list(client.list_runs(run_ids=[run_id], limit=1))
run = runs[0]
return RunTree(
id=run_id,
dotted_order=run.dotted_order, # Critical
trace_id=run.trace_id,
parent_run_id=run.parent_run_id,
client=client,
)Better Alternative: Distributed Tracing
Use to_headers() and from_headers() to avoid manual field management:
# Service A - Serialize context
run_tree = RunTree(name="Parent", project_name="my-project")
trace_headers = run_tree.to_headers()
redis.set(f"trace:{job_id}", json.dumps(trace_headers))
# Service B - Deserialize and continue
headers = json.loads(redis.get(f"trace:{job_id}"))
with tracing_context(parent=RunTree.from_headers(headers)):
await process_job() # Automatically preserves hierarchyRequirements
Minimum SDK version:
langsmith >= 0.4.56Earlier versions have issues with malformed
dotted_order