A trace ID strategy is the set of rules that decides what gets identified, who creates the identifier, how it travels between components, and how long it survives. Get those rules right and debugging an AI workflow becomes a query; get them wrong and every investigation starts with grepping logs across services that never agreed on a common key.
This guide is for practitioners building agent or LLM-backed workflows — including local model setups and WordPress-driven automations — who want traces that can answer “what happened in this run?” without guesswork. It covers the identifier hierarchy, ID format choices, propagation across asynchronous boundaries, the AI-specific signals worth recording, and the checks that reveal a broken strategy before an incident does.
What a trace ID strategy actually decides
Tracing records what your agent did. Each useful step becomes a span, the spans from one uninterrupted run form a trace, and related traces form a session. That three-level structure is the model used by Judgment’s tracing documentation, and it is a good working definition regardless of which backend you use.
Applied to AI agents, tracing is distributed tracing extended with signals unique to LLM-powered systems: which tools the model considered versus selected, how many tokens each reasoning step consumed, whether retrieved context was relevant, and why an orchestrator routed to a specific agent. A practical architecture has three parts — trace propagation, span recording, and a query layer for debugging — as described in Thorium’s overview of agent workflow tracing.
Your strategy therefore answers four questions: what is the unit of identity, who mints the ID, how does it cross process and queue boundaries, and what does it outlive. Everything else is implementation detail.
The identifiers you need — and the ones you don’t
Most confusion in AI tracing comes from using one identifier for several jobs. Keep them separate:
| Identifier | Scope | What it answers | Common mistake |
|---|---|---|---|
| Trace ID | One stretch of autonomous work, from root span through every child | “What happened in this run, end to end?” | Reusing an HTTP request ID that stops at the first service boundary |
| Span ID | One step: model call, tool call, retrieval, delegation | “What did this one step receive and return?” | Generating a new span ID without recording its parent |
| Parent span ID | The link back up the tree | “Which step caused this step?” | Leaving it empty for child spans, producing orphan rows |
| Session ID | Related traces from one conversation, job, or workflow | “What else belongs to this interaction?” | Minting a new session on every retry, so retries look unrelated |
The distinction between a trace and a session matters most when work resumes. Per the Judgment documentation, if a user asks an assistant to summarize a meeting and draft a follow-up, that is one trace; when the user later says “send it,” that new request starts another trace, and both traces share a session because they belong to the same continuing interaction. Sessions are also the right grouping when work resumes after a queue delivery, a retry, or a human approval.
Step 1: Choose an ID format and a generation rule
If you already run OpenTelemetry-compatible collectors, adopt the W3C Trace Context convention rather than inventing a scheme: a 32-character hexadecimal trace ID and a 16-character hexadecimal span ID. Standard formats let existing tooling ingest your spans without a translation layer, and they make it obvious when a component failed to propagate context — a missing or all-zero ID is a visible defect rather than a plausible-looking custom string.
Generate trace IDs randomly (128 bits of entropy is the conventional target) and keep them opaque. Do not encode user IDs, tenant names, timestamps, or prompt hashes into the ID itself. Anything embedded in an identifier becomes a privacy liability in every log line it touches, and it tempts downstream code to parse a field that should be treated as a black box.
Deterministic IDs — for example, a hash of the workflow inputs — are occasionally useful for replayable pipelines, but they have a specific cost: a retry of the same inputs produces the same trace ID, so two genuinely separate attempts collapse into one record. If you need that property, treat the hash as a separate run_key attribute and keep the trace ID random.
Step 2: Decide where the trace begins and when it ends
The trace begins at the point of user intent, not at the first function that happens to call a model. For a chat assistant that is the inbound message; for a WordPress automation it is the hook, cron event, or webhook that starts the job; for a batch pipeline it is the job submission.
The root span must stay open until its child work finishes. The Judgment documentation is explicit that closing it before the searches, model calls, or final response produces an incomplete trace and leaves judges without the evidence needed to evaluate the run reliably. In practice this means the root span is closed in the same scope that created it, after all awaited work resolves — not inside a callback that fires early.
Step 3: Propagate context across every boundary
In a synchronous HTTP-based system, trace context travels in headers. In an asynchronous system with queues, it must be serialized into the message body, because there is no ambient request to inherit from. Thorium’s write-up gives a compact envelope for this:
interface AgentMessage {
type: "new_task" | "completion";
traceId: string;
parentSpanId: string;
payload: unknown;
}When an orchestrator dispatches a subagent, the message carries the trace ID and the orchestrator’s own span ID as the parent. The subagent creates its own span linked to that parent, and the chain continues through every tier — subagent to skill to tool — preserving the causal relationship between operations.
Two environments deserve extra care. First, scheduled work: if your AI workflow runs from WordPress via WP-Cron or a job scheduler, the trace context belongs in the job payload or a persisted job record, not in a PHP request global that the next request will overwrite. Second, local model servers: runtimes such as llama.cpp or Ollama do not know about your trace tree, so the wrapper that issues the inference call must attach the current span context and record the response as a child span. Neither case is exotic; both are ordinary async boundaries that silently drop context if you assume inheritance.
Step 4: Record AI-specific attributes on spans
Generic HTTP attributes are not enough to debug an agent. At minimum, capture model selection and version, prompt template identifier, sampling settings, token usage for input and output, tool name and arguments (with secrets masked), retrieval document identifiers and relevance scores, and the orchestrator’s routing decision. Thorium’s list of AI-specific signals also includes which tools the model considered versus actually selected — that distinction is often the fastest way to explain a wrong answer.
Store prompts and completions as span attributes only if your retention policy allows it. They are the highest-value debugging data and the highest-risk personal data in the same field.
Step 5: Group related traces into sessions
A session ID should be created once per conversation, job, or workflow and reused by every trace that belongs to it. Retries are the classic test: a retry should produce a new trace inside the same session, with an attempt counter attribute, so you can see the failure and the recovery side by side instead of treating them as two unrelated incidents.
An illustrative span envelope
The following is a design sketch, not output from a tested system, but it shows the minimum fields a span needs to be queryable:
{
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"span_id": "00f067aa0ba902b7",
"parent_span_id": "a3ce929d0e0e4736",
"session_id": "sess_acme_followup_01",
"name": "tool.search_meetings",
"status": "ok",
"attributes": {
"model": "local-instruct",
"tool.name": "search_meetings",
"retrieval.doc_ids": ["mtg_8841"],
"tokens.input": 1180,
"tokens.output": 96
}
}A debugging checklist that uses the trace
Once spans land in a query layer, run these checks in order, from cheapest to most involved:
- Orphan scan. Count spans whose parent span ID does not exist in the same trace. A nonzero count almost always means context was dropped at a queue or callback boundary.
- Root closure check. Find traces with no span marked as root, or roots that end before their children. The latter indicates a root span closed too early.
- Session continuity. For a known multi-turn interaction, confirm that follow-up traces share the session ID and that retries appear as separate traces within it.
- Attribute completeness. Sample ten spans and verify model version, token counts, and tool names are present. Missing attributes usually mean an instrumentation wrapper was bypassed.
- Clock sanity. Compare span start and end times across hosts; skewed clocks make causality look impossible and waste investigation time.
Failure modes and tradeoffs
Sampling. Full-fidelity capture of prompts and completions is expensive at volume. Head sampling decides at trace start and is simple but may discard the interesting failures; tail sampling decides after the trace completes and can retain errors, at the cost of buffering. If you sample, sample whole traces, never individual spans — a partial tree is worse than no tree.
High cardinality. Putting raw user input, full prompts, or unbounded document IDs into indexed attributes will bloat your query layer. Keep identifiers in attributes and payloads in object storage, linked by the span ID.
Retry storms. A failing tool called in a loop can generate hundreds of traces. The session grouping plus an attempt attribute is what keeps that readable.
Multi-agent handoffs. Every delegation must carry the parent span ID forward. If a subagent starts a fresh root, the causal chain breaks exactly where debugging is hardest.
Privacy. Trace IDs themselves should carry no personal data, and prompt attributes should be subject to the same retention rules as the conversation they came from.
What to do next
Write the contract down in one page: the ID formats, the component that mints each identifier, the propagation rule for synchronous and asynchronous hops, and the attributes every span must carry. Then add one automated check — the orphan scan is the highest-value place to start — and run it against a real workflow before you need it during an incident.
If you are working through this on a local model stack or a WordPress automation, share your environment, the boundary where context was lost, and the check that caught it in the Ai-Forum.org community so others can compare notes.
FAQ
Can I reuse my existing HTTP request ID as the trace ID?
Only if that identifier is propagated through every hop of the agent run, including queue messages and tool calls. A typical request ID stops at the first service boundary, which leaves downstream spans orphaned and the trace incomplete.
Should retries create a new trace or extend the existing one?
Create a new trace for each attempt and keep them in the same session, with an attempt attribute on each. That preserves the causal chain of the original run while keeping the failed and successful attempts distinguishable.
How do I trace inference calls to a local model server?
Local runtimes do not propagate your trace context on their own. The wrapper that issues the inference call must attach the current span context, send it as metadata if the server supports it, and record the response as a child span.
Is it safe to store prompts and completions as span attributes?
It is the most useful debugging data and often the most sensitive. Apply the same retention, access, and redaction rules you apply to the underlying conversation, or store the payload separately and reference it by span ID.