Skip to slide 1
01 / 42
VCN · Glass Box · trace agents AND the conversations between them · Frontier Tower
traces live

GLASS BOX.

trace agents with opentelemetry / from a black box to a glass house

your agents are a black box. tonight we make it glass.

Rayyan Zahid · w/ Michalis Vasileiadis · Eric Mockler · Devinder Sodhi
> system.run() started
> plannerresearchercoder
> emitting spans · propagating context
● one trace · every agent visible
cold open · the black box

THE ANSWER WAS WRONG. WHICH AGENT BROKE?

Your planner called a researcher called a coder, and the final answer came back wrong. The logs show a few scattered lines. You cannot tell where in the trajectory it stopped making progress.

Agents fail mid-run, and the failure point is invisible to anything that only looks at the final answer.

[F1]

cold open · tonight

BLACK BOX. GLASS BOX. GLASS HOUSE.

Logs tell you what happened. A trace tells you where, and how fast. We turn the box to glass, one span at a time.

where we start
Black box
an agent runs, it misbehaves, you see the output and nothing else.
Glass box
act 2-3
instrument one agent with OpenTelemetry; read its span waterfall.
Glass house
act 4-5
trace the whole society of agents; every conversation between them observable.

[F2] [BOARD]

THE CONCEPT / FROM OPAQUE TO OBSERVABLE ? BLACK BOX it runs. it breaks. you cannot see why. GLASS BOX one agent, instrumented: every span visible GLASS HOUSE A B C a society of agents: every conversation is one trace tracing one agent is table stakes; the real problem is seeing the whole house talk
why · logs answer what, a trace answers where

"IT TOOK 8 SECONDS" IS NOT THE SAME AS "WHERE".

logs
Forensics
A scatter of lines after the fact. They tell you the request was slow. They do not tell you which of the twelve steps burned the time.
a trace
Localization
The same per-step timeline distributed tracing gave microservices, now pointed at an agent: which step, how long, what it cost.

[F2]

THREE PILLARS / WHAT EACH ANSWERS TRACES start here for agents what happened in this one request, end to end causal path + latency of every span in the run LOGS discrete events 12:04:01 INFO request received 12:04:01 DEBUG calling tool 12:04:02 ERROR db timeout 12:04:02 INFO retry scheduled what happened at this point in time rich detail per event, but not connected across a run METRICS aggregates over time how much, how often p95 latency, tokens/min, error rate across all runs for a non-deterministic agent, the trace is the one view that shows the whole causal path
why · the final answer hides the failure

AGENTS FAIL MID-RUN, NOT AT THE END.

An agent spends compute across tokens, tool calls, retries, and code. When a run fails, the final answer shows the endpoint, not the point where the trajectory stopped making recoverable progress. The failure modes map onto signals that live in the spans.

tool reliability did the call return
execution recovery did a retry help
orchestration loops are agents cycling
budget pressure tokens vs progress

[F1]

why · the killer

ONE AGENT IS HARD. A SOCIETY IS THE BLIND SPOT.

Multi-agent systems fail in systematic, nameable ways.

A single agent you can babysit. But when a planner delegates to a researcher that delegates to a coder, the failure hides in the handoffs, the place your logs go quiet. The blind spot is not inside an agent. It is between them. [F3]

why · the few decisive events are buried

THE BUG IS IN THE LOGS. SO ARE TEN THOUSAND OTHER LINES.

The handful of causally-decisive events, the route taken, the memory written, the tool that failed, are buried in unstructured log spew. The win comes from compiling the run into a structured trace.

OTel's span model is that structure, off the shelf.

Parent and child nesting, typed attributes, an explicit status: the run stops being a wall of text and becomes a tree you can read. [F4]

why · this is the direction, not a bet

YOU ARE NOT EARLY. YOU ARE ON STANDARD.

The 2026 agent-framework landscape already runs on traces:

The OpenAI Agents SDK is OpenTelemetry-native.
Trace debugging is a product differentiator (LangSmith leans on time-travel trace replay).
SDKs without built-in observability push the instrumentation burden onto you.

Standardize on OTel and the gen_ai conventions: your traces stay portable across backends instead of locked to one vendor's UI. [F5]

otel primer · the two nouns

A TRACE IS THE PATH. A SPAN IS ONE STEP.

trace
The path
The path of a request through your system: a collection of causally-related spans. One trace = one run.
span
One unit of work
A single operation with a start, an end, and metadata. Nest spans and you get the waterfall.

[OTEL-TRACES]

otel primer · anatomy of a span

NINE FIELDS, AND THEY ALL EARN THEIR PLACE.

Name
the operation
Context
trace id, span id, flags, state (immutable)
Parent id
empty for the root span
Start / end
the duration
Kind
Client, Server, Internal, Producer, Consumer
Attributes
key/value metadata
Events
a point-in-time annotation
Links
relate this span to others
Status
Unset, Error, or Ok

[OTEL-TRACES]

otel primer · context is what connects the steps

CONTEXT IS HOW ONE TRACE SPANS MANY STEPS.

A span is not an island. The thing that links a tool call to the model call that triggered it is the propagated context: the same trace id carried from parent to child.

every span in a trace shares one trace id
a child's parent_id equals its parent's span_id
that nesting IS the waterfall

Hold onto this. When two agents talk, the only thing that keeps it one trace is carrying that context across the boundary. That is the whole game in Act 4. [OTEL-TRACES]

otel primer · from code to a backend

SPANS HAVE TO GO SOMEWHERE.

You do not read spans in your code. A small pipeline ships them out to a place you can query.

Tracer Provider (one per app) → Tracer → creates spansExporter
    → stdout  |  the OpenTelemetry Collector  |  a backend

[OTEL-TRACES]

OPENTELEMETRY PIPELINE / SPAN to BACKEND YOUR AGENT instrumented app LLM + tool calls OTel SDK creates spans GenAI semantic conv. gen_ai.* attributes OTLP COLLECTOR receive . process batch . export Jaeger Tempo Grafana instrument once, export anywhere: the collector decouples your agent from the backend
otel primer · describe an LLM call the same way

THE GEN_AI CONVENTIONS. experimental

Standard attribute keys so every tool emits comparable traces. Stability is "Development": cite them, but treat them as a moving target, not a contract.

gen_ai.provider.name gen_ai.operation.name gen_ai.request.model gen_ai.request.temperature gen_ai.request.max_tokens gen_ai.request.top_p gen_ai.response.model gen_ai.response.id gen_ai.response.finish_reasons gen_ai.usage.input_tokens gen_ai.usage.output_tokens // superseded: gen_ai.system, // prompt_tokens, completion_tokens

[OTEL-GENAI]

otel primer · instrument one call

FOUR MOVES AND THE CALL IS VISIBLE.

1. Start a span around each step: the LLM call, each tool call.
2. Set the gen_ai.* attributes on the model span.
3. Set Status = Error on the span when the step fails.
4. Export the spans: stdout, the Collector, or a backend.

The runnable code lives in the lab, not on this slide. How the prompt and completion text itself is captured is still evolving in the conventions, so we instrument the metadata above and label any captured content as illustrative. [OTEL-TRACES] [OTEL-GENAI] · FACTS Open-items #1

otel primer · where that leaves us

YOU CAN NOW DESCRIBE ANY SINGLE CALL.

A trace, a span, the fields, the context that links them, the gen_ai keys, and four moves to instrument a call. That is the whole primer.

Next: point it at a real agent and read the waterfall. Then the hard part: what happens when the agent is not alone. [OTEL-TRACES]

single-agent trace · one run, top to bottom

A REAL AGENT RUN, AS A WATERFALL.

One root span for the agent turn, a child span for each step: prompt assembly, the tool call, the LLM call, the response. The nesting is parent_id; the bar length is start to end. [OTEL-TRACES]

TRACE agent.run trace_id 4bf92f3577b34da6a3ce929d0e0e4736 0ms 300 600 900 1200 agent.run 1240ms llm.chat (plan) 480ms 2,310 tok tool.web_search 540ms http.GET 500ms tool.db_query ERROR status=ERROR timeout llm.chat (synth) 1,180 tok the bottleneck: 44% of total latency one slow tool call you could not see before ok slow error span (bar length = duration) one trace shows latency, cost, and errors that logs alone never connect
read 01 · latency

THE LONGEST BAR IS YOUR LATENCY.

"Why is this agent slow?" stops being a guess.

start and end on every span show exactly which step ate the time, usually a tool call or the model. You optimize the span that actually hurts, not the one you assumed. [OTEL-TRACES]

read 02 · cost

EVERY MODEL SPAN CARRIES ITS BILL.

The token cost has a cause you can point at.

gen_ai.usage.input_tokens and gen_ai.usage.output_tokens per model span turn a monthly surprise into a per-step number. The runaway prompt is right there on the trace. [OTEL-GENAI]

read 03 · errors

STATUS = ERROR LANDS YOU ON THE FAILING STEP.

Not "the run failed." This span failed.

Span Status is Unset, Ok, or Error. The errored span is flagged in the waterfall, so a flaky tool or a bad parse surfaces as one red bar instead of a mystery. [OTEL-TRACES]

read 04 · non-determinism

WHY DID THIS RUN DIFFER FROM THE LAST?

The trace remembers what the model forgot.

gen_ai.request.model, the request params, and gen_ai.response.finish_reasons are captured on the span, so two diverging runs become a diff you can read instead of a shrug. [OTEL-GENAI]

Four reads, one agent. But your agents are not alone. What happens when one agent calls another? [OTEL-TRACES]

the a2a expansion · the real problem

ONE AGENT WAS THE EASY PART.

Real systems are not one agent. They are a planner that calls a researcher that calls a coder. The interesting failures live in the handoffs, in the space between agents.

Black box to glass box was one agent. Now the glass HOUSE: a whole society of agents, every conversation observable.

[A2A]

the a2a expansion · what agent-to-agent means

A2A: AN OPEN STANDARD FOR AGENTS THAT CALL AGENTS.

Not an agent calling a tool. One independent agent system calling another, over a real protocol. [A2A] v1.0.0

discovery
The Agent Card
A JSON document an agent publishes: its identity, capabilities, skills, endpoint, and auth. Discovery starts here.
roles
Client and Server
An A2A Client initiates the request; an A2A Server (the remote agent) exposes the endpoint.
transport
JSON-RPC over HTTP
JSON-RPC 2.0 over HTTP, gRPC, or REST; streaming via Server-Sent Events.

[A2A]

the a2a expansion · one conversation, four moves

THE A2A HANDSHAKE.

01
Discover the client fetches the remote Agent Card (capabilities, endpoint, security).
02
Send the client sends a SendMessageRequest carrying a Message (role + Parts).
03
Task the remote agent returns a stateful Task (or a direct Message for a simple turn).
04
Stream the client subscribes for status + artifact updates until a terminal state: completed, failed, canceled, or rejected.

[A2A]

the a2a expansion · the conversation becomes spans

EVERY TURN OF THE HANDSHAKE IS A SPAN.

An A2A call is, under the hood, an HTTP request and a response. Wrap each turn in a span and the conversation becomes readable.

discover Agent Cardspan: a2a.discover
send Messagespan: a2a.send
remote Task runsspan: a2a.task (on the remote)
stream updatesspan events: status / artifact

[A2A] [OTEL-TRACES]

A2A HANDSHAKE AS A TRACE / ONE CALL, TWO AGENTS AGENT A (caller) AGENT B (callee) agent boundary -- traceparent crosses here agent.run (A) llm.chat plan a2a.call -> AgentB waits for B header traceparent: 00-4bf9..-a3c..-01 a2a.handle (B) llm.chat respond ok B's span is a CHILD of A's call, not a new trace because the traceparent rode the request across the handshake is not two logs in two services; it is one trace that spans both agents
the a2a expansion · the hard part, the heart of it

HOW DOES ONE TRACE CROSS A NETWORK CALL?

In Act 2 context linked spans inside one process. Across agents, the same idea rides the wire: the carrier is the W3C Trace Context standard, two HTTP headers, traceparent and tracestate.

If the context crosses the boundary, two agents are one trace. If it does not, you have two black boxes again.

[W3C-TRACECTX]

the a2a expansion · one header carries the whole thread

READ A TRACEPARENT.

00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01

version00
trace-idthe whole distributed trace
parent-idthe caller's span id
flagslow bit = sampled

[W3C-TRACECTX] · tracestate carries vendor pairs alongside

the a2a expansion · inject, then extract

TWO AGENTS, ONE TRACE.

Agent A
injects its current traceparent into the outgoing A2A request
traceparent
Agent B
extracts it; starts its spans as children of A's span

Same trace-id. B's spans point at A's parent-id. The two agents' work joins into one waterfall. [W3C-TRACECTX] [OTEL-TRACES]

CONTEXT PROPAGATION / THE TRACEPARENT IS THE BATON WITH PROPAGATION = ONE TRACE AGENT A inject(traceparent) traceparent 00-4bf9..-a3c..-01 boundary AGENT B extract() -> same trace one connected waterfall A and B in the same trace_id HEADER DROPPED = ORPHAN TRACES AGENT A forgets to inject no traceparent AGENT B starts a NEW trace two traces, no link the handoff is invisible propagate the W3C traceparent on every A2A call or your multi-agent trace silently splits in two most A2A frameworks inject + extract for you; verify it, do not assume it
the a2a expansion · one trace, many agents

THE MULTI-AGENT WATERFALL.

One trace whose root is the originating turn, with nested spans crossing into each downstream agent: A calls B calls C, every agent's own LLM and tool spans beneath it. Propagation working end to end. [W3C-TRACECTX] [OTEL-TRACES]

MULTI-AGENT TRACE one trace_id, three agents, end to end trace_id 7d1a9c.. total 1860ms Agent A Agent B Agent C 0 900ms 1860 A agent.run 1860ms A llm.chat plan A a2a.call -> B B a2a.handle B tool.retrieve B a2a.call -> C slow 940ms C a2a.handle C llm.chat 4,120 tok C tool.db_write ERROR status=ERROR permission denied A llm.chat synth A->B boundary B->C boundary the slow leg lives two agents deep (B -> C) no single-agent view would ever show you this one trace follows the work across A -> B -> C: the bottleneck and the error are both in plain sight
the a2a expansion · where multi-agent bugs hide

FOUR WAYS THE HOUSE GOES DARK.

Lost context
a downstream agent never extracted traceparent, so its work starts a new trace and the trail ends at the handoff.
Orphan spans
spans whose parent-id points at a parent that never arrived, floating unattached.
Silent handoff
A handed off to B but B errored or never ran; without a trace you only see A's optimistic output.
Runaway loop
agents calling each other in a cycle, burning tokens; a deep repeating span chain.

[W3C-TRACECTX] [OTEL-TRACES] [OTEL-GENAI] · read one for real in Act 5

the a2a expansion · the concept lands

A WHOLE SOCIETY OF AGENTS, AND YOU CAN SEE INSIDE IT.

One agent was the glass box. Every conversation between them is the glass house.

The mechanic is not magic: shared trace-id, propagated context, spans that nest across the network. Next we read a real one and find the broken handoff. [W3C-TRACECTX]

read a multi-agent trace · the whole conversation

ONE TRACE, THREE AGENTS, NO SECRETS.

A planner calls a researcher calls a coder. One trace, the planner's turn at the root, each downstream agent's spans nested beneath the call that spawned them. Now we read it and find the bug. [W3C-TRACECTX] [OTEL-TRACES]

MULTI-AGENT TRACE one trace_id, three agents, end to end trace_id 7d1a9c.. total 1860ms Agent A Agent B Agent C 0 900ms 1860 A agent.run 1860ms A llm.chat plan A a2a.call -> B B a2a.handle B tool.retrieve B a2a.call -> C slow 940ms C a2a.handle C llm.chat 4,120 tok C tool.db_write ERROR status=ERROR permission denied A llm.chat synth A->B boundary B->C boundary the slow leg lives two agents deep (B -> C) no single-agent view would ever show you this one trace follows the work across A -> B -> C: the bottleneck and the error are both in plain sight
read a multi-agent trace · the silent handoff

THE BROKEN HANDOFF.

A says it finished. B never actually ran.

Agent A handed off to Agent B, but B errored or never started. Without the trace you only see A's optimistic output and a wrong final answer with no obvious cause. [OTEL-TRACES]

the tell: a missing child span at the boundary, or one with Status = Error right where the handoff should be

read a multi-agent trace · the cycle

THE RUNAWAY LOOP.

Two agents, each waiting for the other to be done.

Agents calling each other in a cycle burn tokens and wall-clock with nothing to show. In the trace it is unmistakable: a deep, repeating span chain that does not terminate. [F1] [OTEL-TRACES]

plannerresearcherplannerresearcher → ...

read a multi-agent trace · who spent the budget

THE COST BLOWUP.

The bill is a sum across agents you could not see.

Read gen_ai.usage.input_tokens and gen_ai.usage.output_tokens on every model span and roll it up per agent. Suddenly the researcher that quietly re-queries five times is the line item, not a mystery on the invoice. [OTEL-GENAI]

read a multi-agent trace · the shape tells you

TWO MORE TELLS, IN THE SHAPE.

Lost context
the trace simply ends at a handoff: a downstream agent never extracted traceparent, so its work started a new trace.
Orphan spans
spans whose parent-id points at a parent that never exported, floating unattached.

The method: start at the root, follow the nesting, and look for the span that is missing, errored, repeating, or expensive. [W3C-TRACECTX] [OTEL-TRACES]

the workshop · build your own glass box

NOW YOU MAKE THE BOX GLASS.

The hands-on hour. Three labs, copy-paste runnable, that walk from one agent to a whole conversation. Pair up and start.

lab 01
Trace one agent
lab 02
Two agents talking
lab 03
Explore the trace

[BOARD]

lab 01 · start here

INSTRUMENT A TINY AGENT. SEE THE WATERFALL.

A self-contained program: a one-agent run, OpenTelemetry added, a real span tree printed to your console. Read it in two minutes, then make it yours.

runnable lab
/lab/01-trace

[BOARD]

lab 02 · the a2a lab

WATCH A CONVERSATION BECOME A TRACE.

Agent A calls Agent B. The traceparent crosses the boundary, B's spans nest under A's, and the two agents resolve into one waterfall in front of you.

runnable lab
/lab/02-a2a

[BOARD] [W3C-TRACECTX]

lab 03 · the explorer

CLICK A SPAN. READ ITS ATTRIBUTES.

An interactive trace explorer. Click into any span to see its name, timing, status, and the gen_ai attributes on it. This is what a backend gives you, in miniature, in your browser.

interactive lab
/lab/03-explorer

[BOARD] [OTEL-TRACES]

the workshop · do it to your own agents

FIVE MOVES AND YOUR STACK IS A GLASS HOUSE.

1. Make a Tracer; start a span around each step.
2. Set the gen_ai.* attributes on each model span.
3. Set Status = Error on failure.
4. On every A2A call, inject your traceparent; on the receiving side, extract it. This is the move that keeps the house one trace.
5. Export to a backend and watch the multi-agent waterfall appear.

[OTEL-TRACES] [OTEL-GENAI] [W3C-TRACECTX]

the workshop · the ask

BRING A SYSTEM YOU CANNOT FULLY EXPLAIN.

The best thing to instrument is the agent stack that already surprises you.

Bring: a laptop, an agent or multi-agent system you ship (or want to), and one run where you could not tell which step or which agent went wrong. You leave able to see it.

[BOARD]

the workshop · take it home

ALL THREE LABS, ONE HUB.

The labs do not expire when the night ends. The hub stays up: run them again, share them with your team, hand them to the agent that ships on Monday.

the workshop hub
/lab
Published as the Glass Box workshop on Immersive Commons.

[BOARD]

patterns · what good teams do

THREE HABITS OF AN OBSERVABLE HOUSE.

The mechanics are settled. The discipline is what separates a glass house from a pile of disconnected traces.

Propagate everywhere
Instrument for the failures
Read run + aggregate
pattern 01 · the one rule

IF IT CROSSES A BOUNDARY, IT CARRIES CONTEXT.

One broken handoff and the trace goes dark.

Every A2A call, every queue, every async hop injects and extracts traceparent. The discipline is uniform: no agent is allowed to start a new trace just because it forgot to look for the old one. [W3C-TRACECTX]

pattern 02 · instrument for what breaks

EVERY KNOWN FAILURE HAS A SPAN SIGNAL.

Status on every span
so a silent handoff shows up as Status = Error, not a wrong answer.
Cost per agent
roll up gen_ai.usage.* so budget pressure has a name.
Watch the shape
a deep repeating chain is a runaway loop; a trace that ends mid-flow is lost context.
Retries as spans
so execution-recovery shows whether the second attempt actually helped.

[OTEL-TRACES] [OTEL-GENAI] [F1]

patterns · what good looks like

ONE RUN TO DEBUG. THOUSANDS TO STEER.

per run
The waterfall
open any single conversation and read it end to end: which agent, which step, how long, what it cost, where it failed.
in aggregate
The dashboard
p95 latency per agent, token cost per goal, error rate per handoff, across thousands of runs.

[OTEL-TRACES] [OTEL-GENAI]

close

THE WHOLE HOUSE, MADE OF GLASS.

By the end of the night your agents emit traces, and you can see inside every conversation between them.

Black box to glass box was one agent. The glass house is the rest: shared trace-id, propagated context, one waterfall across the whole society. You leave with the labs and the playbook. [BOARD]

close · take it home

RUN THE LABS. HARDEN YOUR HOUSE.

labs · /lab
where · Frontier Tower, San Francisco
when · date via Luma (TBD)
[BOARD] · date/venue-time/Luma not yet set (FACTS Open-items #4)
hosted by vibe coding nights
Rayyan Zahid · Immersive Commons · Facilitator
Michalis Vasileiadis
Eric Mockler · F11 Health & Longevity
Devinder Sodhi · Frontier Tower
[DIR] · speaker TBD (Ray to name); host orgs per B-2