Build real AI agents
Trace and debug trajectories
Replay a failed run from JSONL
- Agents and the ReAct loop (browse)
- LangGraph and LangChain patterns (browse)
- OpenTelemetry for LLMs (browse)
- Evals fundamentals (browse)
- Agents in production: ReAct loops, timeouts, and human-in-the-loop (example)
- Building a ChatGPT-like product: streaming, tools, and memory (example)
Learning objectives
- Replay a failed run from JSONL
- Locate the bad tool call or hallucinated arg
- Add a regression fixture from the failure
If you cannot replay it, you cannot fix it
Agent bugs hide in trajectories — sequences of model decisions, tool args, and observations. When a run fails (wrong answer, bad tool args, max_steps, denied HITL), you need to replay from logged JSONL, pinpoint the offending step, patch tool validation or prompt, and add a regression fixture so the bug never returns silently.
This lesson treats trajectories as test artifacts, bridging hand-rolled loops to production tracing (OpenTelemetry spans in later modules).
Callout — log raw model payloads: Summaries help humans; raw tool_call JSON helps diff across prompt versions.
Trajectory format recap
Standardize runs/<run_id>.jsonl — one JSON object per line:
{"type": "user", "content": "Refund order 8812"}
{"type": "assistant", "tool_calls": [{"name": "get_order", "args": {"id": "8812"}}]}
{"type": "tool", "call_id": "tc1", "result": {"status": "shipped"}}
{"type": "assistant", "tool_calls": [{"name": "issue_refund", "args": {"order_id": 8812}}]}
{"type": "error", "message": "validation: order_id must be string"}
{"type": "final", "content": "Could not complete refund."}Include metadata file runs/<run_id>.meta.json:
- model, prompt_version, started_at, outcome, total_steps
Replay tooling
replay.py modes:
python replay.py runs/failed_042.jsonl --summary # human timeline
python replay.py runs/failed_042.jsonl --step 3 # zoom one step
python replay.py runs/failed_042.jsonl --reexecute 3 # rerun tool with recorded argsSummary view prints:
Step 2: get_order(id="8812") → ok
Step 3: issue_refund(order_id=8812) → VALIDATION FAIL (expected string id)Reexecute calls real tool with logged args — verifies whether bug was transient data vs systematic schema mismatch (int vs string).
Do not replay mutating tools against prod without dry-run flag.
Debugging workflow
- Classify failure — validation, wrong tool choice, bad observation, policy deny, max_steps.
- Isolate step — first step where gold path diverges (compare to successful run on similar input).
- Hypothesis — e.g. "model passes integer because schema said number not string."
- Fix — tighten schema, add few-shot, pre-validate with coercion, or block tool until arg type match.
- Regression — add fixture from this run's user input + expected tool args or final outcome.
Callout — compare to golden trajectory: Store
fixtures/golden/run_refund_ok.jsonlfor diff tools.
Regression fixtures
fixtures/regression/issue_refund_type.json:
{
"id": "reg_042",
"source_run": "failed_042",
"user_input": "Refund order 8812",
"assertions": [
{"step_tool": "issue_refund", "args_schema_valid": true},
{"final_contains": "refund"}
]
}Test runner simulates tool responses from recorded trajectory (deterministic) or mocks backend — assert agent behavior after fix without live LLM in CI optional; live nightly if affordable.
Minimum: fixture file + manual rerun note in README.
Common failure signatures
| Signature | Likely fix |
|---|---|
| Tool never called | Description unclear; add nudge or required plan step |
| Wrong tool arg type | JSON Schema types + Pydantic coercion policy |
| Infinite search loop | max_steps + dedupe search queries |
| Ignored tool error | Prompt: read error and adapt |
| HITL bypass | Driver bug — gate before execute |
Diff two trajectories
Compare successful vs failed run on same input:
python replay.py --diff runs/ok_12.jsonl runs/failed_042.jsonlHighlight first divergent step — faster than manual scan on long runs. Store diff output in POSTMORTEM appendix.
Team workflow
When multiple engineers debug agents:
- Name runs with git branch + author:
failed_042_ak_react - Link run id to Linear/Jira ticket
- Attach minimal repro user input in fixture, not entire session history
Regression fixtures become shared contract — failing CI blocks merge on agent prompt changes.
Building a minimal replay TUI
Optional terminal UI: arrow keys step through trajectory, t reexecute tool, q quit. Even a 50-line curses or rich panel accelerates debugging vs raw JSON — portfolio stretch goal.
Export subset to share in bug reports: replay.py --export-step 3 failed_042.jsonl.
Trajectory schema versioning
Add schema_version: 1 to meta.json when trajectory format evolves. Replay tool migrates v0 → v1 or fails loudly — silent schema drift breaks regression fixtures stored in git history.
Document breaking changes in m7/replay/CHANGELOG.md.
Linking replay to prompt version
Include prompt_version and model in meta.json — when replay shows step 2 tool arg failure, bump prompt version and re-run same fixture to verify fix without re-discovering input manually.
Golden vs failed run library
Organize trajectories:
m7/replay/library/
golden/
failed/
regression_fixed/Move run from failed/ to regression_fixed/ after patch — visual progress for portfolio reviewers.
Automated replay in CI
Nightly job replays library/regression_fixed/ with mocked tools — ensures replay.py never rots when trajectory schema bumps. Fast unit test replays one golden file on every PR.
From JSONL to spans (preview)
Production adds trace ids linking LLM spans, tool spans, retrieval spans. Your JSONL is a minimal compatible artifact — fields map 1:1 to span events later.
Engineering problem (staff framing)
Without trajectories you cannot debug agents. Traces are the stack dumps of LLM systems.
Diagram — Trace spans
flowchart TD
Req[Request] --> Spans[LLM/tool spans]
Spans --> Store[Trace store]
Store --> UI[Debug UI]
Store --> Eval[Offline replay]
Precise definitions & mental model
Span attributes, redaction, replay, failure taxonomies.
Tradeoffs — when to use what
Full prompt logging vs privacy.
Failure modes (interview + on-call)
No correlation IDs; logging secrets; unreproducible tool side effects.
Production & OSS practices
OpenTelemetry-style traces; sampled full prompts; replay harness.
Deep dive (FAANG / OSS bar)
Push «trace-debug-trajectories» past tutorial depth: write the interface contract (inputs/outputs/invariants), list three measurable metrics, and name two degrade modes if the happy path fails. Add a short threat note: what an attacker or noisy tool result could do, and which layer catches it (schema, policy, HITL, or eval gate).
flowchart LR
Contract[Interface contract] --> Metrics
Metrics --> Degrade[Degrade modes]
Degrade --> Threat[Threat + control]
Micro-project: Replay failed run
In m7/replay/:
replay.pywith summary + step zoom (+ optional reexecute).- Take one failed run from prior lessons; document root cause in
POSTMORTEM.md. - Add regression fixture derived from failure.
- After fix, link passing rerun id in POSTMORTEM.
Checklist
- Replay tool reads standard JSONL format
- Bad step identified with evidence
- Regression fixture committed
- POSTMORTEM explains fix
ShipAI delivery model is: