Tracing
A trace is a structured record of one full RunAsync invocation:
every agent that fired, every LLM call, every tool, with timings,
token counts, retry counts, and cost. AgentRunTrace collects it
for you.
The fluent way
var ctx = new AgentContext().WithTracing(out var trace);
await admin.RunAsync(input);
Console.WriteLine($"Outcome : {trace.Outcome}");
Console.WriteLine($"Duration : {trace.Duration.TotalMilliseconds:0}ms");
Console.WriteLine($"Agent calls : {trace.TotalAgentCalls}");
Console.WriteLine($"LLM calls : {trace.TotalLlmCalls}");
Console.WriteLine($"Tool calls : {trace.TotalToolCalls}");
Console.WriteLine($"Retries : {trace.TotalRetries}");
Console.WriteLine($"Tokens : {trace.TotalTokenUsage.TotalTokens}");
Console.WriteLine($"Cost : ${trace.TotalCost.TotalCostUsd:0.0000}");
Shape
public sealed class AgentRunTrace
{
public AgentRunTrace(); // populates from first RunStartedEvent
public AgentRunTrace(string runId, string adminName, string task, DateTimeOffset startedAt);
public string RunId { get; }
public string AdminName { get; }
public string Task { get; }
public DateTimeOffset StartedAt { get; }
public DateTimeOffset? CompletedAt { get; }
public TimeSpan Duration { get; } // live while running; fixed after
public string Outcome { get; } // "running" | "completed" | "failed"
public string? ErrorMessage { get; }
public IList<AgentSpan> AgentSpans { get; }
public IList<LlmSpan> LlmSpans { get; }
public IList<BudgetEvent> BudgetEvents { get; }
public int TotalLlmCalls { get; }
public int TotalAgentCalls { get; }
public int TotalToolCalls { get; }
public int TotalRetries { get; }
public TokenUsage TotalTokenUsage { get; }
public CostEstimate TotalCost { get; }
public void AttachTo(IAgentEventBus bus); // for advanced use
}
Spans
Each span carries enough detail to reconstruct what the agent or LLM did.
AgentSpan
| Field | What |
|---|---|
AgentName | Name from IAgent.Name. |
Input | The string passed to RunAsync. |
Output | The agent's final response (post-validation). |
StartedAt, CompletedAt, Duration | Wall-clock timing. |
TokensUsed | TokenUsage for this agent's LLM calls. |
Cost | CostEstimate for the agent. |
RetryAttempts | List of (attempt, rawResponse, error). |
ToolCalls | List of ToolCallSpan invocations. |
LlmSpan
| Field | What |
|---|---|
CallerName | Agent name that issued the call. |
Model | Model identifier (llama3.2, gpt-4o-mini, etc.). |
MessageCount | Number of messages sent to the LLM. |
Response | Raw LLM response. |
Usage | TokenUsage. |
StartedAt, Duration | Timing. |
ToolCallSpan
| Field | What |
|---|---|
ToolName | Tool name. |
ArgsJson | Arguments JSON (as the LLM produced them). |
Result | Tool's return value. |
Succeeded | true unless the tool threw. |
Duration | Wall-clock. |
BudgetEvent
| Field | What |
|---|---|
Kind | Warning or Exceeded. |
CurrentCostUsd | Spend at the moment of the event. |
LimitUsd | The configured MaxBudgetUsd. |
PercentUsed | 0..1. |
Inspecting after a run
foreach (var span in trace.AgentSpans)
{
Console.WriteLine(
$"{span.AgentName}: {span.Duration.TotalMilliseconds:0}ms, " +
$"{span.TokensUsed.TotalTokens} tok, " +
$"${span.Cost.TotalCostUsd:0.0000}");
foreach (var tool in span.ToolCalls)
Console.WriteLine(
$" ↪ {tool.ToolName}({tool.ArgsJson}) -> {tool.Result}");
foreach (var retry in span.RetryAttempts)
Console.WriteLine(
$" ⤴ retry {retry.AttemptNumber}: {retry.Error}");
}
foreach (var llm in trace.LlmSpans)
Console.WriteLine(
$"LLM {llm.Model}: {llm.Usage.TotalTokens} tok, " +
$"{llm.Duration.TotalMilliseconds:0}ms");
Run-correlation patterns
Adopt the next run
var trace = new AgentRunTrace();
trace.AttachTo(bus); // populates from the next RunStartedEvent
await admin.RunAsync(input); // trace is fully populated here
This is what .WithTracing(out var trace) does under the hood.
Pre-set the run identity
When you already have an external request ID and want the trace populated up-front (e.g. you record the trace before the run starts):
var trace = new AgentRunTrace(
runId: HttpContext.TraceIdentifier,
adminName: "Editorial",
task: "summarise",
startedAt: DateTimeOffset.UtcNow);
trace.AttachTo(bus);
The trace will only attach to events whose RunId matches.
Shipping traces to your APM
A trace is plain data. Serialise after the run and forward however your infrastructure expects. Example — JSON to OpenTelemetry-style storage:
await admin.RunAsync(input);
var json = JsonSerializer.Serialize(trace, new JsonSerializerOptions
{
WriteIndented = false,
});
await myApm.SendTraceAsync(json);
For per-span shipping, iterate AgentSpans and LlmSpans and emit
each as your APM's span format. The RunId field correlates them.
See also
- Cost & budget — derive spend from
trace.TotalCost, setMaxBudgetUsdto fail fast. - Events — what
AgentRunTraceis subscribing to under the hood.