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

# Google ADK

> Trace Google Agent Development Kit (ADK) agents and multi-agent systems using instrumentADK, wrapADKRunner, and wrapADKAgent in TypeScript and Python.

<Note>
  Available for: **Python** and **TypeScript**.
</Note>

Zespan provides first-class tracing for Google's Agent Development Kit (ADK). Every model call, tool invocation, and agent-to-agent delegation in your ADK workflow is captured as a linked span — without modifying your agent definitions.

## TypeScript

### Installation

```bash theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
npm install @zespan/sdk @google/adk
```

### Four integration patterns

Choose the integration pattern that fits your setup:

| Pattern                    | Use when                                           |
| -------------------------- | -------------------------------------------------- |
| `instrumentADK`            | Production apps — wraps agent + runner in one call |
| `wrapADKRunner`            | You manage the runner lifecycle separately         |
| `ZespanADKCallbackHandler` | Native ADK callbacks — wire into `LlmAgent` config |
| `wrapADKAgent`             | Tests and scripts — direct agent wrapping          |

***

### `instrumentADK` — recommended for production

`instrumentADK` wraps both your coordinator agent and its runner in a single call. Use this when building production ADK applications.

```typescript theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
import { zespan, instrumentADK } from "@zespan/sdk";
import { Agent, InMemoryRunner } from "@google/adk";

zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });

// Your normal ADK setup
const policyAgent = new Agent({
  model: "gemini-2.5-flash",
  name: "PolicyAgent",
  instructions: "Check refund policies and eligibility.",
});

const orderAgent = new Agent({
  model: "gemini-2.5-flash",
  name: "OrderAgent",
  instructions: "Look up order details by order ID.",
});

const coordinator = new Agent({
  model: "gemini-2.5-pro",
  name: "SupportCoordinator",
  subAgents: [policyAgent, orderAgent],
  instructions: "Route customer requests to the right specialist.",
});

const runner = new InMemoryRunner({ agent: coordinator });

// Wrap both at once — returns instrumented versions
const { coordinator: tracedCoordinator, runner: tracedRunner } = instrumentADK({
  coordinator,
  runner,
  agentName: "SupportCoordinator",
  agentRole: "coordinator",
  sessionId: req.sessionId,
});

// Use tracedRunner exactly as you would the original runner
for await (const event of tracedRunner.runEphemeral({ userId: "u1", newMessage })) {
  // handle ADK events
}
```

***

### `wrapADKRunner` — runner-level wrapping

Wraps `InMemoryRunner.runEphemeral()` and `Runner.run()` by intercepting the ADK event stream. Emits one span per agent author turn, one span per tool call, and handoff spans on delegation.

```typescript theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
import { zespan, wrapADKRunner } from "@zespan/sdk";
import { InMemoryRunner } from "@google/adk";

zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });

const runner = wrapADKRunner(
  new InMemoryRunner({ agent: coordinator }),
  {
    agentName: "SupportCoordinator",
    agentRole: "coordinator",
    model: "gemini-2.5-pro",
    sessionId: req.session.id,
  }
);

for await (const event of runner.runEphemeral({ userId: "u1", newMessage })) {
  if (event.content) {
    console.log(event.content.parts[0].text);
  }
}
```

**`wrapADKRunner` options:**

<ParamField body="agentName" type="string" required>
  Display name for the root agent span in Zespan.
</ParamField>

<ParamField body="agentRole" type="string" default="coordinator">
  Role hint for the agent registry. Use `"coordinator"` for orchestrators and `"specialist"` for sub-agents.
</ParamField>

<ParamField body="model" type="string">
  The model name used by the root agent. Shown in the trace detail panel.
</ParamField>

<ParamField body="sessionId" type="string">
  Associates all spans from this run with a session in the Sessions view.
</ParamField>

***

### `ZespanADKCallbackHandler` — native ADK callbacks

Uses ADK's built-in callback system (`beforeAgentCallback`, `afterModelCallback`, etc.). Create one handler instance and spread `.callbacks` into your `LlmAgent` config. Captures agent spans, LLM spans with full token counts, and tool call spans.

```typescript theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
import { zespan, ZespanADKCallbackHandler } from "@zespan/sdk";
import { LlmAgent, InMemoryRunner, RunConfig } from "@google/adk";

zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });

const tracer = new ZespanADKCallbackHandler();

const agent = new LlmAgent({
  name: "SupportAgent",
  model: "gemini-2.5-flash",
  instruction: "Help customers with order questions.",
  tools: [lookupOrder],
  ...tracer.callbacks, // wires all 6 callbacks
});

const runner = new InMemoryRunner({ agent });

for await (const event of runner.runEphemeral({ userId: "u1", newMessage })) {
  // handle events
}
```

For multi-agent systems, use the **same handler instance** across all agents so spans share the same trace:

```typescript theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
const tracer = new ZespanADKCallbackHandler();

const policyAgent = new LlmAgent({
  name: "PolicyAgent",
  model: "gemini-2.5-flash",
  ...tracer.callbacks,
});

const coordinator = new LlmAgent({
  name: "Coordinator",
  model: "gemini-2.5-pro",
  subAgents: [policyAgent],
  ...tracer.callbacks,
});
```

`ZespanADKCallbackHandler` is also exported as `ADKCallbackHandler` for shorter imports:

```typescript theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
import { ADKCallbackHandler } from "@zespan/sdk";
```

***

### `wrapADKAgent` — agent-level wrapping

Wraps `agent.run()` directly. Best for unit tests and simple scripts. Recursively wraps `subAgents` by default.

```typescript theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
import { zespan, wrapADKAgent } from "@zespan/sdk";

zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });

const tracedAgent = wrapADKAgent(coordinator, {
  agentName: "SupportCoordinator",
  agentRole: "coordinator",
  traceSubAgents: true, // wrap all subAgents recursively
});

const result = await tracedAgent.run("I need a refund for order #123");
```

***

### What gets captured

All three wrappers capture the same data from the ADK event stream:

| ADK event            | Zespan span                                                       |
| -------------------- | ----------------------------------------------------------------- |
| Agent turn start/end | `span_kind: "agent"` with latency, model, token counts            |
| Tool call            | `span_kind: "tool"` with tool name, args, result                  |
| Sub-agent delegation | `span_kind: "handoff"` with target agent name                     |
| Model response       | Token counts (including cached + reasoning tokens), finish reason |

***

## Python

### Installation

```bash theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
pip install zespan google-adk
```

### Three integration patterns

| Pattern                    | Use when                                                |
| -------------------------- | ------------------------------------------------------- |
| `ZespanADKCallbackHandler` | Native ADK callbacks — wire into `LlmAgent` constructor |
| `ZespanADKTracer`          | Class-based — attach to an existing agent instance      |
| `wrap_adk_agent`           | Functional — wrap and return the agent in one call      |

***

### `ZespanADKCallbackHandler` — native ADK callbacks

Uses ADK's built-in callback system. Create one handler and spread `.callbacks` into each `LlmAgent` constructor. Captures agent spans, LLM spans with full token counts, and tool spans.

ADK Python is async — run agents with `InMemoryRunner` inside `asyncio.run()`.

**Single agent:**

```python theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
import asyncio
import zespan
from google.adk.agents import LlmAgent
from google.adk.runners import InMemoryRunner
from google.genai.types import Content, Part
from zespan import ZespanADKCallbackHandler

zespan.init(api_key="zsp_your_api_key_here")

def lookup_order(order_id: str) -> dict:
    """Look up an order by ID to get status and delivery details."""
    return {"orderId": order_id, "status": "delivered", "total": 49.99}

handler = ZespanADKCallbackHandler()

agent = LlmAgent(
    name="SupportAgent",
    model="gemini-2.5-flash",
    instruction="Help customers with order questions.",
    tools=[lookup_order],
    **handler.callbacks,  # wires all 6 callbacks
)

async def run(message: str) -> str:
    runner = InMemoryRunner(agent=agent, app_name="my-app")
    session = await runner.session_service.create_session(
        app_name="my-app", user_id="user-123",
    )
    new_message = Content(role="user", parts=[Part(text=message)])
    response = ""
    async for event in runner.run_async(
        user_id="user-123",
        session_id=session.id,
        new_message=new_message,
    ):
        if event.is_final_response() and event.content:
            response = "".join(p.text for p in event.content.parts if getattr(p, "text", ""))
    return response

result = asyncio.run(run("What is the status of order #ORD-123?"))
print(result)
```

**Multi-agent system:**

Use the **same handler instance** across all agents — ADK shares the same `invocation_id` across coordinator and sub-agents, so all spans are automatically linked under one trace.

```python theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
import asyncio
import zespan
from google.adk.agents import LlmAgent
from google.adk.runners import InMemoryRunner
from google.genai.types import Content, Part
from zespan import ZespanADKCallbackHandler

zespan.init(api_key="zsp_your_api_key_here")

def lookup_order(order_id: str) -> dict:
    """Look up an order by ID."""
    return {"orderId": order_id, "status": "delivered", "total": 49.99}

def process_refund(order_id: str, reason: str) -> dict:
    """Process a refund for an order."""
    return {"success": True, "refundId": f"ref_{order_id}", "eta": "3-5 business days"}

# One handler for the entire agent system
handler = ZespanADKCallbackHandler()

order_agent = LlmAgent(
    name="OrderAgent",
    model="gemini-2.5-flash",
    description="Handles order lookups.",
    instruction="Look up order details and answer questions about order status.",
    tools=[lookup_order],
    **handler.callbacks,
)

refund_agent = LlmAgent(
    name="RefundAgent",
    model="gemini-2.5-flash",
    description="Handles refund requests.",
    instruction="Process refund requests. Always look up the order first.",
    tools=[lookup_order, process_refund],
    **handler.callbacks,
)

coordinator = LlmAgent(
    name="SupportCoordinator",
    model="gemini-2.5-flash",
    description="Routes customer support requests.",
    instruction=(
        "Route requests to the correct specialist:\n"
        "- OrderAgent: order status, tracking\n"
        "- RefundAgent: refunds, returns\n"
        "Always delegate — do not answer directly."
    ),
    sub_agents=[order_agent, refund_agent],
    **handler.callbacks,
)

async def run(message: str, user_id: str) -> str:
    runner = InMemoryRunner(agent=coordinator, app_name="support")
    session = await runner.session_service.create_session(
        app_name="support", user_id=user_id,
    )
    new_message = Content(role="user", parts=[Part(text=message)])
    response = ""
    async for event in runner.run_async(
        user_id=user_id,
        session_id=session.id,
        new_message=new_message,
    ):
        if event.is_final_response() and event.content:
            response = "".join(p.text for p in event.content.parts if getattr(p, "text", ""))
    return response

result = asyncio.run(run("I need a refund for order #ORD-456", user_id="user-123"))
print(result)
```

**Tool functions** are plain Python functions with type hints and docstrings — ADK auto-generates the function declarations. Return `dict` or any JSON-serializable value.

***

### `ZespanADKTracer` — attach to an existing agent

Pass your ADK agent to `ZespanADKTracer` after creating it. The tracer intercepts `agent.run` to capture every model call, tool invocation, and agent session as hierarchical spans.

```python theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
import zespan
from google.adk import Agent
from zespan import ZespanADKTracer

zespan.init(api_key="zsp_your_api_key_here")

def lookup_order(order_id: str) -> dict:
    """Look up an order by ID."""
    return {"id": order_id, "status": "delivered", "total": 49.99}

agent = Agent(
    model="gemini-2.5-pro",
    tools=[lookup_order],
    instructions="Help customers with order questions.",
)

# Attach the tracer — all subsequent runs are instrumented
ZespanADKTracer(agent)

response = await agent.run("What is the status of order #ORD-123?")
print(response.text)
```

***

### `wrap_adk_agent` — callback-handler style

`wrap_adk_agent` is the functional equivalent: it attaches tracing and returns the same agent. Use this when you want a one-liner or are composing agents inline.

```python theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
import zespan
from google.adk import Agent
from zespan import wrap_adk_agent

zespan.init(api_key="zsp_your_api_key_here")

agent = wrap_adk_agent(
    Agent(
        model="gemini-2.5-pro",
        tools=[lookup_order],
        instructions="Help customers with order questions.",
    )
)

response = await agent.run("What is the status of order #ORD-123?")
```

Both `ZespanADKTracer` and `wrap_adk_agent` accept an optional `guardrails` argument:

```python theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
from zespan import wrap_adk_agent

agent = wrap_adk_agent(agent, guardrails={"pre": True, "post": True})
```

***

### Multi-agent systems

```python theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
from google.adk import Agent
from zespan import ZespanADKTracer

# Sub-agents
policy_agent = Agent(model="gemini-2.5-flash", name="PolicyAgent")
order_agent = Agent(model="gemini-2.5-flash", name="OrderAgent")

# Coordinator
coordinator = Agent(
    model="gemini-2.5-pro",
    name="SupportCoordinator",
    sub_agents=[policy_agent, order_agent],
)

# Attach tracer to the coordinator — sub-agents are traced automatically
ZespanADKTracer(coordinator)

response = await coordinator.run("I want to return my order from last week.")
```

<Note>
  Attach `ZespanADKTracer` (or call `wrap_adk_agent`) after the agent is fully configured. Attaching before `sub_agents` are set means sub-agent spans won't be linked correctly.
</Note>

***

## How multi-agent traces look in the dashboard

A coordinator + specialist ADK run produces a trace like this:

```
SupportCoordinator (agent)        ──────────────────────────────── 3.2s
  ├── gemini-2.5-pro (llm)        ──────────── 1.1s
  │     → tool_calls: [OrderAgent.run]
  ├── OrderAgent → handoff        ─ 4ms
  ├── OrderAgent (agent)          ────────────────── 1.8s
  │   ├── gemini-2.5-flash (llm)  ─────── 820ms
  │   └── lookup_order (tool)     ── 12ms
  └── gemini-2.5-pro (llm)        ──── 280ms  finish_reason: stop
```

Each agent's total token cost is shown separately in the agent registry view, which also maps the coordinator-specialist hierarchy visually.

<Frame>
  <img src="https://mintcdn.com/zespancom/OVq7q4R1vLkWzInd/images/agent-visualization.png?fit=max&auto=format&n=OVq7q4R1vLkWzInd&q=85&s=9c8c9b0684eaa5de723aa2fd0cf0a817" alt="Multi-agent trace visualization in Zespan showing coordinator and specialist agent spans" width="2949" height="1383" data-path="images/agent-visualization.png" />
</Frame>

***

## Session and user context

To associate ADK traces with a user session for the Sessions dashboard view, set `sessionId` and `userId` in the wrapper options (TypeScript) or via `with_zespan_context` (Python):

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
    const { runner: tracedRunner } = instrumentADK({
      coordinator,
      runner,
      agentName: "SupportCoordinator",
      sessionId: req.session.id, // links to Sessions view
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
    from zespan import with_zespan_context

    async def handle_request(user_id: str, session_id: str, message: str):
        with with_zespan_context(user_id=user_id, session_id=session_id):
            return await agent.run(message)
    ```
  </Tab>
</Tabs>
