6. End-to-End Distributed Tracing
Problem: You have a multi-service AI agent application and need to trace requests as they flow across service boundaries to understand performance, errors, and dependencies.
Solution: Use HoneyHive’s distributed tracing with context propagation to create unified traces across multiple services in under 15 minutes.
This tutorial walks you through building a distributed AI agent system using Google ADK, demonstrating how traces flow seamlessly across service boundaries.
6.1. What You’ll Build
A distributed AI agent architecture with mixed invocation patterns:
%%{init: {'theme':'base', 'themeVariables': {'primaryColor': '#4F81BD', 'primaryTextColor': '#ffffff', 'primaryBorderColor': '#333333', 'lineColor': '#333333', 'mainBkg': 'transparent', 'secondBkg': 'transparent', 'tertiaryColor': 'transparent', 'clusterBkg': 'transparent', 'clusterBorder': '#333333', 'edgeLabelBackground': 'transparent', 'background': 'transparent'}, 'flowchart': {'linkColor': '#333333', 'linkWidth': 2}}}%%
graph LR
Client[Client App<br/>Process A]
Principal[Principal Agent<br/>Process A]
RemoteAgent[Research Agent<br/>Process B - Remote]
LocalAgent[Analysis Agent<br/>Process A - Local]
Client -->|user_call| Principal
Principal -->|HTTP + Context| RemoteAgent
Principal -->|Direct Call| LocalAgent
classDef client fill:#7b1fa2,stroke:#333333,stroke-width:2px,color:#ffffff
classDef principal fill:#1565c0,stroke:#333333,stroke-width:2px,color:#ffffff
classDef remote fill:#ef6c00,stroke:#333333,stroke-width:2px,color:#ffffff
classDef local fill:#2e7d32,stroke:#333333,stroke-width:2px,color:#ffffff
class Client client
class Principal principal
class RemoteAgent remote
class LocalAgent local
Architecture:
Client Application: Initiates multi-turn conversation with agents
Principal Agent: Orchestrates calls to research and analysis agents
Research Agent (Remote): Runs in separate process, receives context via HTTP
Analysis Agent (Local): Runs in same process, directly inherits context
Key Learning:
How to propagate trace context to remote services using HTTP headers
How to use
with_distributed_trace_context()for simplified server-side tracingHow to create unified traces spanning both local and distributed agents
How to see complete request flows in HoneyHive across service boundaries
6.2. Prerequisites
Python 3.11+ installed
HoneyHive API key from https://app.honeyhive.ai
Google Gemini API key (get one at https://aistudio.google.com/apikey)
15 minutes of time
6.3. Installation
Install required packages:
pip install honeyhive google-adk openinference-instrumentation-google-adk flask requests
6.4. Step 1: Set Environment Variables
Create a .env file with your API keys:
# Required
HH_API_KEY=your_honeyhive_api_key_here
HH_PROJECT=distributed-tracing-tutorial
GOOGLE_API_KEY=your_google_gemini_api_key_here
# Optional
AGENT_SERVER_URL=http://localhost:5003
Load environment variables:
source .env
6.5. Step 2: Create the Agent Server (Remote Service)
The remote service that runs a Google ADK research agent.
Create agent_server.py:
"""Google ADK Agent Server - Demonstrates with_distributed_trace_context() helper."""
from flask import Flask, request, jsonify
from honeyhive import HoneyHiveTracer
from honeyhive.tracer.processing.context import with_distributed_trace_context
from openinference.instrumentation.google_adk import GoogleADKInstrumentor
from google.adk.agents import LlmAgent
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.genai import types
import os
# Initialize HoneyHive tracer
tracer = HoneyHiveTracer.init(
api_key=os.getenv("HH_API_KEY"),
project=os.getenv("HH_PROJECT", "distributed-tracing-tutorial"),
source="agent-server"
)
# Initialize Google ADK instrumentor
instrumentor = GoogleADKInstrumentor()
instrumentor.instrument(tracer_provider=tracer.provider)
app = Flask(__name__)
session_service = InMemorySessionService()
async def run_agent(user_id: str, query: str, agent_name: str) -> str:
"""Run Google ADK agent - automatically part of distributed trace."""
# Your Agent Code here
return final_response or ""
@app.route("/agent/invoke", methods=["POST"])
async def invoke_agent():
"""Invoke agent with distributed tracing - ONE LINE setup!"""
# 🎯 Single line replaces ~65 lines of context management boilerplate
with with_distributed_trace_context(dict(request.headers), tracer):
# All Google ADK spans created here automatically:
# 1. Link to the client's trace (same trace_id)
# 2. Use the client's session_id, project, source
# 3. Appear as children of the client's call_agent_1 span
try:
data = request.get_json()
result = await run_agent(
data.get("user_id", "default_user"),
data.get("query", ""),
data.get("agent_name", "research_agent")
)
return jsonify({
"response": result,
"agent": data.get("agent_name", "research_agent")
})
except Exception as e:
return jsonify({"error": str(e)}), 500
if __name__ == "__main__":
print("🤖 Agent Server starting on port 5003...")
app.run(port=5003, debug=True, use_reloader=False)
What’s happening:
with_distributed_trace_context()automatically: - Extracts trace context from HTTP headers - Parsessession_id,project,sourcefrom baggage - Attaches context so all spans link to client’s trace - Handles cleanup (even on exceptions)Google ADK instrumentor automatically creates child spans for agent operations
Result: All agent spans appear in the same unified trace as the client
Key benefit: ONE LINE (with_distributed_trace_context) replaces ~65 lines of manual context extraction, baggage parsing, context attachment, and cleanup code.
6.6. Step 3: Create the Client Application
The client orchestrates both remote and local agent calls.
Create client_app.py:
"""Client Application - Orchestrates remote and local agent calls."""
import asyncio
import os
from typing import Any
import requests
from google.adk.sessions import InMemorySessionService
from google.adk.agents import LlmAgent
from google.adk.runners import Runner
from google.genai import types
from honeyhive import HoneyHiveTracer, trace
from openinference.instrumentation.google_adk import GoogleADKInstrumentor
from honeyhive.tracer.processing.context import (
enrich_span_context,
inject_context_into_carrier
)
# Initialize HoneyHive tracer
tracer = HoneyHiveTracer.init(
api_key=os.getenv("HH_API_KEY"),
project=os.getenv("HH_PROJECT", "distributed-tracing-tutorial"),
source="client-app"
)
# Initialize Google ADK instrumentor (for local agent calls)
instrumentor = GoogleADKInstrumentor()
instrumentor.instrument(tracer_provider=tracer.provider)
async def main():
"""Main entry point - demonstrates multi-turn conversation."""
session_service = InMemorySessionService()
app_name = "distributed_agent_demo"
user_id = "demo_user"
# Execute two user calls (multi-turn conversation)
await user_call(session_service, app_name, user_id,
"Explain the benefits of renewable energy")
await user_call(session_service, app_name, user_id,
"What are the main challenges?")
@trace(event_type="chain", event_name="user_call")
async def user_call(
session_service: Any,
app_name: str,
user_id: str,
user_query: str
) -> str:
"""User entry point - initiates the agent workflow."""
result = await call_principal(
session_service,
app_name,
user_id,
user_query,
os.getenv("AGENT_SERVER_URL", "http://localhost:5003")
)
return result
@trace(event_type="chain", event_name="call_principal")
async def call_principal(
session_service: Any,
app_name: str,
user_id: str,
query: str,
agent_server_url: str
) -> str:
"""Principal agent - orchestrates remote and local agents."""
# Agent 1: Research (REMOTE - distributed tracing)
agent_1_result = await call_agent(
session_service, app_name, user_id, query,
use_research_agent=True, agent_server_url=agent_server_url
)
# Agent 2: Analysis (LOCAL - same process)
agent_2_result = await call_agent(
session_service, app_name, user_id, agent_1_result,
use_research_agent=False, agent_server_url=agent_server_url
)
return f"Research: {agent_1_result}\n\nAnalysis: {agent_2_result}"
async def call_agent(
session_service: Any,
app_name: str,
user_id: str,
query: str,
use_research_agent: bool,
agent_server_url: str
) -> str:
"""Call agent - demonstrates mixed invocation patterns."""
if use_research_agent:
# REMOTE invocation: Call agent server via HTTP
with enrich_span_context(
event_name="call_agent_1",
inputs={"query": query}
):
# Inject trace context into HTTP headers
headers = {}
inject_context_into_carrier(headers, tracer)
# HTTP call to remote agent server
response = requests.post(
f"{agent_server_url}/agent/invoke",
json={
"user_id": user_id,
"query": query,
"agent_name": "research_agent"
},
headers=headers, # Trace context propagates here!
timeout=60
)
response.raise_for_status()
result = response.json().get("response", "")
tracer.enrich_span(
outputs={"response": result},
metadata={"mode": "remote"}
)
return result
else:
# LOCAL invocation: Run agent in same process
with enrich_span_context(
event_name="call_agent_2",
inputs={"research": query}
):
# You can run your local analysis agent here
tracer.enrich_span(
outputs={"response": result},
metadata={"mode": "local"}
)
return result
if __name__ == "__main__":
asyncio.run(main())
What’s happening:
Client Side (Context Injection):
@tracedecorators create traced functionsenrich_span_context()creates explicit spans for each agent callinject_context_into_carrier()adds trace context to HTTP headersHeaders are sent with the HTTP request to the agent server
Server Side (Context Extraction):
Agent server uses
with_distributed_trace_context()to extract contextAll Google ADK spans on server inherit the client’s context
Spans from both client and server appear in same unified trace
Mixed Invocation:
Agent 1 (Remote): Calls agent server via HTTP, demonstrating distributed tracing
Agent 2 (Local): Runs in same process, demonstrating local span nesting
6.7. Step 4: Run and Test
Terminal 1 - Start the Agent Server:
source .env
python agent_server.py
You should see:
🤖 Agent Server starting on port 5003...
* Running on http://127.0.0.1:5003
Terminal 2 - Run the Client Application:
source .env
python client_app.py
You should see the client making two user calls (multi-turn conversation):
Research: Renewable energy sources, such as solar, wind, and hydropower...
Analysis: The transition to renewable energy requires addressing...
What’s Happening:
Client makes first
user_callasking about benefits of renewable energycall_principalorchestrates two agents: - Agent 1 (Remote): HTTP call to agent server → research findings - Agent 2 (Local): Runs in same process → analyzes researchClient makes second
user_callasking about challengesSame flow repeats for the second question
All spans from both calls appear in same HoneyHive session
6.8. Step 5: View in HoneyHive
Go to https://app.honeyhive.ai
Navigate to project:
distributed-tracing-tutorialClick “Sessions” in the left sidebar
Find your session - you’ll see:
Unified Trace Hierarchy (First User Call):
📊 user_call [ROOT]
└── 🔗 call_principal
├── 🌐 call_agent_1 (Remote - Process B)
│ └── 🤖 agent_run [research_agent] (on server)
│ └── 💬 gemini_chat_completion (Google ADK instrumentation)
└── 📍 call_agent_2 (Local - Process A)
└── 🤖 agent_run [analysis_agent] (same process)
└── 💬 gemini_chat_completion (Google ADK instrumentation)
Key observations:
Single session across all operations (both user calls in same session)
Parent-child relationships preserved across service boundaries
call_agent_1 (remote) shows HTTP call to agent server
call_agent_2 (local) shows in-process agent execution
Google ADK spans (
agent_run,gemini_chat_completion) automatically capturedSource attribution: -
client-appfor client-side spans -agent-serverfor server-side spansAll metadata enriched: inputs, outputs, mode (remote/local)
6.9. What You Learned
✅ Simplified Distributed Tracing (v1.0+)
Server-side:
with_distributed_trace_context()- ONE LINE replaces ~65 lines of boilerplateClient-side:
inject_context_into_carrier()- Add trace context to HTTP headersAutomatic baggage extraction (
session_id,project,source)Thread-safe context isolation per request
✅ Mixed Invocation Patterns
Remote agents: HTTP calls with context propagation
Local agents: In-process execution with automatic context inheritance
Both patterns unified in same trace
Google ADK instrumentor automatically captures agent operations
✅ Key HoneyHive APIs Used
inject_context_into_carrier(headers, tracer)- Client-side: inject context into HTTP headerswith_distributed_trace_context(headers, tracer)- Server-side: extract and attach context (RECOMMENDED)enrich_span_context(event_name, inputs, outputs)- Create enriched spans with explicit namestracer.enrich_span(outputs, metadata)- Add attributes to current span@tracedecorator - Automatic function tracing (preserves distributed baggage v1.0+)
✅ Practical Skills
Tracing multi-service AI agent systems
Debugging distributed agent workflows
Finding performance bottlenecks across services
Understanding agent interaction patterns end-to-end
6.10. Troubleshooting
Problem: Remote agent spans don’t appear in the trace
Solution: Check that context is being properly injected and extracted:
# Client side: Must inject context into headers
headers = {}
inject_context_into_carrier(headers, tracer)
response = requests.post(url, json=data, headers=headers) # headers required!
# Server side: Use with_distributed_trace_context() helper
with with_distributed_trace_context(dict(request.headers), tracer):
# All spans created here will link to client's trace
result = await run_agent(...)
Problem: Agent server shows “Connection refused”
Solution: Ensure the agent server is running:
# Terminal 1
source .env
python agent_server.py
# Wait for: "🤖 Agent Server starting on port 5003..."
Problem: Missing GOOGLE_API_KEY error
Solution: Set your Google Gemini API key:
# In .env file
GOOGLE_API_KEY=your_google_api_key_here
# Reload
source .env
Problem: Server and client show different projects in HoneyHive
Solution: Both must use the same project name:
# In both agent_server.py and client_app.py
tracer = HoneyHiveTracer.init(
project="distributed-tracing-tutorial", # Must match!
source="agent-server" # Can differ per service
)
6.11. Next Steps
Explore more Google ADK integrations:
Try different Google ADK agents (planning, execution, tool-using agents)
Add more remote services to the distributed trace
Experiment with different agent orchestration patterns
Production considerations:
Add error handling and retry logic for remote calls
Implement timeouts for agent invocations
Add monitoring and health checks for agent servers
Consider using async HTTP clients (
httpx,aiohttp) for better performanceImplement sampling for high-traffic production systems
Key resources:
Utilities Reference - Full API reference for distributed tracing utilities
Custom Span Management - Learn about
enrich_span_context()and span enrichmentGoogle ADK Documentation - Learn more about Google ADK
Key Takeaway: With with_distributed_trace_context(), distributed tracing is now a ONE LINE operation on the server side. You can trace complex multi-agent systems across process boundaries with minimal code. 🎉