July 28, 2026
How to Instrument AI Test Agents With OpenTelemetry Spans, Structured Logs, and Replayable Artifacts
A practical tutorial for instrumenting AI test agents with OpenTelemetry spans, structured logs, and replayable artifacts so teams can trace decisions, debug failures, and replay test runs.
When an AI test agent fails, the obvious question is not just what broke, but why the agent chose the path it did. Traditional test logs often answer part of that question, yet they usually stop at browser actions, HTTP calls, or assertion failures. Agentic QA needs a richer trail: the model prompt that shaped the decision, the span that shows which branch was taken, the structured log event that explains the confidence or fallback, and the artifact that lets you replay the run later.
That is where the combination of OpenTelemetry spans, structured logs, and replayable artifacts becomes useful. If you instrument AI test agents with OpenTelemetry, you can trace autonomous test workflows end to end, correlate model calls with browser actions, and preserve enough evidence to debug failures without guessing.
This article focuses on a reproducible setup you can adapt in your own stack. The goal is not to create more telemetry for its own sake. The goal is to make agent behavior inspectable, searchable, and replayable.
What you should be able to answer after instrumentation
A good observability layer for agentic testing should let you answer these questions quickly:
- Which step in the test plan led to the failure?
- Did the agent retry because the locator was missing, the assertion was ambiguous, or the model output was malformed?
- Which prompt and tool inputs produced the action that changed state?
- Was the browser page in the expected state when the agent made its decision?
- Can we replay the same run against the same app build and see whether the failure is deterministic?
If you cannot reconstruct the agent’s decision path, you are debugging a black box with a screenshot attached.
The observability model for AI test agents
For agentic QA systems, I recommend thinking in three layers.
1) Spans for causal structure
Spans are the skeleton of the run. They should answer:
- What run is this?
- Which test scenario is being executed?
- Which plan step, model call, browser interaction, or assertion belongs to which parent decision?
OpenTelemetry’s trace model is a good fit because it already captures parent-child relationships and cross-process context propagation. The OpenTelemetry specification gives you the shape, and your backend can render the waterfall.
2) Structured logs for decision detail
Logs should carry the details that do not fit neatly into span names or attributes, such as:
- prompt version
- model name
- tool arguments
- retry reason
- parsed output shape
- validation errors
- warning conditions
Use JSON logs, not free-form strings. Free-form logs become difficult to query once you need to filter by run ID, scenario ID, tool name, or error type.
3) Replayable artifacts for reproducibility
Artifacts are the evidence bundle, usually including:
- the final prompt and all tool messages
- model responses, including rejected outputs
- browser console logs
- network HAR files where appropriate
- screenshots on each major decision point
- DOM snapshots or HTML captures
- the exact test plan or scenario definition
- environment metadata, such as app version, feature flags, and git SHA
A replayable artifact is not only for postmortems. It is also a review object for pull requests and a training signal for improving the agent later.
A practical span hierarchy for autonomous test workflows
A common mistake is to create one span per browser action and call it done. That gives you a thin activity log, but not a decision trace.
A better trace structure looks like this:
test.runagent.planagent.reasonllm.chatagent.select_tool
browser.navigatebrowser.wait_for_statebrowser.clickassert.evaluateartifact.capture
This structure gives you causal nesting. For example, if the agent decides to refresh the page before clicking, that decision should have a child span or sibling span that records the reason, not just the click itself.
Suggested span names and attributes
Use stable names. Do not encode variable data in the span name if you can avoid it.
| Span name | Purpose | Useful attributes |
|---|---|---|
test.run |
One end-to-end scenario execution | test.id, test.name, run.id, repo.sha, env.name |
agent.plan |
Scenario decomposition or step planning | agent.name, plan.version, scenario.id |
llm.chat |
One model invocation | model.name, model.provider, prompt.version, token.input, token.output |
agent.select_tool |
Choosing the next tool or action | tool.name, decision.reason, confidence.bucket |
browser.navigate |
Navigation event | url.host, url.path, navigation.type |
browser.wait_for_state |
Explicit wait | wait.kind, wait.timeout_ms, state.expected |
assert.evaluate |
Assertion logic | assertion.type, assertion.outcome, assertion.target |
artifact.capture |
Persisting run evidence | artifact.kind, artifact.path, artifact.size_bytes |
A useful discipline is to avoid putting sensitive prompt text in span attributes unless you have already thought through privacy and retention. Use a hash or prompt version identifier if the full content is stored in artifacts.
Structured logs for AI agents, not just for browsers
Structured logs are where the agent explains itself. Here is a simple event shape that works well in practice:
{ “ts”: “2026-07-28T12:00:00.123Z”, “level”: “info”, “run_id”: “run_01HXYZ”, “scenario_id”: “checkout-happy-path”, “step_id”: “step_03”, “event”: “agent.decision”, “agent”: “checkout-agent”, “model”: “gpt-4.1-mini”, “tool”: “browser.click”, “reason”: “Proceeding after product page loaded and primary CTA became enabled”, “confidence”: “medium”, “locator”: “button[data-testid=’add-to-cart’]”, “attempt”: 1 }
This shape supports three important queries:
- Show me every decision for a single run.
- Show me all failures where the agent retried a locator more than twice.
- Show me all runs where the model chose a fallback path after an assertion warning.
Fields worth standardizing early
If teams invent different names for the same concept, telemetry becomes painful to query. Standardize these fields early:
run_id, a unique ID per executionscenario_id, a stable ID for the test intentstep_id, a stable ID for the agent step or human-authored steptrace_id, propagated by OpenTelemetrymodel, the model name or deployment aliastool, the exact tool or browser operationdecision, the action type or branch takenreason, a short natural-language explanationconfidence, a coarse bucket likelow,medium,highartifact_ref, a pointer to stored evidence
A log line should be easy to grep, but more importantly, it should be easy to aggregate in your log backend.
A minimal instrumentation pattern in TypeScript
The following example shows a browser-driven agent that emits a span per decision and structured logs for each step. It is intentionally small so the shape is easy to copy.
import { trace } from '@opentelemetry/api';
const tracer = trace.getTracer(‘ai-test-agent’);
async function runScenario(scenarioId: string) { return tracer.startActiveSpan(‘test.run’, async (runSpan) => { runSpan.setAttribute(‘scenario.id’, scenarioId); runSpan.setAttribute(‘run.id’, crypto.randomUUID());
try {
await tracer.startActiveSpan('agent.plan', async (planSpan) => {
planSpan.setAttribute('plan.version', 'v3');
logEvent('agent.plan.started', { scenarioId });
planSpan.end();
});
await tracer.startActiveSpan('browser.navigate', async (navSpan) => {
navSpan.setAttribute('url.path', '/checkout');
// browser.goto('https://app.example.com/checkout')
navSpan.end();
});
logEvent('agent.decision', {
scenarioId,
tool: 'browser.click',
reason: 'Primary CTA enabled',
confidence: 'high'
});
} catch (err) {
runSpan.recordException(err as Error);
runSpan.setStatus({ code: 2, message: 'scenario failed' });
throw err;
} finally {
runSpan.end();
} }); }
function logEvent(event: string, fields: Record<string, unknown>) { console.log(JSON.stringify({ ts: new Date().toISOString(), level: ‘info’, event, …fields })); }
The example omits exporter wiring, because the key idea is the structure. In a real system, you would attach a trace exporter and a JSON logger that emit to your collector and log backend.
Capture artifacts at decision boundaries, not only at failure
One of the most useful habits is to save artifacts at points where the agent changes state or makes a meaningful decision. If you only capture artifacts on failure, you often lose the context needed to understand why the failure happened.
Good capture points include:
- before and after navigation
- before a destructive action
- after a model-generated plan is accepted
- after each recovery attempt
- before assertion evaluation
What to store in a replay bundle
A replay bundle can be a directory or object storage prefix containing:
trace.json, exported trace data or a pointer to itlogs.jsonl, structured logs for the runprompt.txt, the exact prompt or prompt template expansionresponses.jsonl, model outputs, including rejected outputsdom.html, page source or a DOM snapshotscreenshot.png, a visual snapshot for each significant statenetwork.har, when the flow involves backend behavior that mattersmetadata.json, env, git SHA, browser version, feature flags
A practical naming convention is to prefix everything with the run ID and scenario ID. That makes artifact collection and retention policies much easier to automate.
Replayability depends on environmental control
Replayable artifacts are only valuable if the environment is controlled enough to make a replay meaningful. That does not mean every run must be perfectly deterministic, because UI systems and LLMs are not deterministic by nature. It does mean you need a record of the variables that can change the outcome.
Capture at least:
- application build or deployment version
- commit SHA of the test agent
- browser version
- viewport size
- locale and time zone
- feature flags
- model version or endpoint alias
- seed values if your orchestration uses them
A replay without environment metadata is often just a second failure with better paperwork.
Failure modes you should design for
1) Span explosion
If you create a span for every keystroke or micro-action, tracing becomes noisy and expensive. Keep spans at the level of decisions, user-visible actions, and important waits.
2) Missing causal links
If logs and spans do not share a stable run_id or trace_id, you will waste time joining data manually. Propagate IDs explicitly into every log event.
3) Prompt drift
If prompts change without versioning, you lose the ability to compare runs. Treat prompt templates like code, version them, and emit the prompt version in both logs and spans.
4) Artifact bloat
Storing every screenshot, DOM snapshot, and HAR file forever is a storage problem. Use retention rules, compress where appropriate, and keep the highest-fidelity artifacts for failing or sampled runs.
5) Sensitive data leakage
Prompts and browser snapshots can include secrets, emails, or personal data. Redact at capture time when possible, and define retention and access controls before broad rollout.
A CI-friendly capture workflow
In continuous integration, the observability workflow should be predictable. A simple pattern is:
- start a trace at the beginning of the job
- export logs and artifacts after each test run
- upload them as job artifacts
- attach a trace link or run ID to the CI summary
A GitHub Actions example can help standardize the shape:
name: agent-tests
on: [push, pull_request]
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm test
- uses: actions/upload-artifact@v4
with:
name: agent-test-artifacts
path: artifacts/
The important part is not the CI platform. The important part is that every run leaves behind the same evidence bundle, with the trace ID embedded in filenames or metadata.
How to evaluate whether the instrumentation is good enough
A useful evaluation guide is to pick one failing run and ask whether an engineer can reconstruct the sequence without reading source code.
You want to see whether the following are obvious:
- the initial scenario intent
- the plan produced by the agent
- the browser state before the failure
- the exact tool call that changed state
- the final assertion or guard that failed
- the artifacts needed to replay the issue
If the answer is no, the instrumentation is still too shallow.
Practical tradeoffs
There is always a balance between observability depth and operational overhead.
- More spans improve causal visibility, but too many spans make traces harder to read.
- More logs improve debugging, but too many verbose events make noise and storage costs grow.
- More artifacts improve reproducibility, but more captures increase runtime and retention burden.
The right balance is usually to instrument around decisions, retries, state changes, and assertions. That gives you enough context to debug most failures without drowning the system in telemetry.
A field-tested mental model for teams
If your team is building autonomous test workflows, treat observability as part of the test contract. A passing test that leaves no useful trace is only partially done. The test should produce:
- a trace that shows what happened
- logs that explain why the agent acted
- artifacts that let someone replay the run later
This is especially important when the agent is allowed to self-correct. Self-correction is useful, but it can also hide instability unless every branch is observable.
Final checklist for implementation
Before you roll this out broadly, verify these points:
- Every run gets a unique
run_id run_idandtrace_idappear in spans and logs- Model calls, browser actions, and assertions are separated into meaningful spans
- Structured logs use stable keys and JSON output
- Prompt versioning is explicit
- Replay bundles include prompts, responses, DOM state, screenshots, and environment metadata
- Redaction and retention rules are defined
- CI artifacts are linked back to the trace
Closing thought
Instrumenting AI test agents is less about adding a telemetry stack and more about making agent behavior legible. OpenTelemetry gives you the trace backbone, structured logs for AI agents give you decision detail, and replayable artifacts give you the evidence to reproduce what happened. Put those three together, and autonomous test workflows become much easier to trust, debug, and evolve.
For background on the surrounding disciplines, see software testing, test automation, and continuous integration.