Hermetic replay
This is the seam that makes “the recorded run is the test” real: the same agent code runs live in production and deterministically offline in CI — no API keys, no cost, no flakiness.
The idea
Wrap each external call in call_tool / call_llm instead of calling it directly:
import tracely_sdk as tracely
def run(user_input: str) -> str:
with tracely.agent("support-agent"):
order = tracely.call_tool("get_order", lambda: get_order(order_id),
args={"order_id": order_id})
answer = tracely.call_llm("gpt-4o", lambda: chat(messages),
input=messages, usage=(812, 96))
return answer- In production (no fixtures active):
call_tool/call_llminvoke yourfn, record its output (and any error) on the span, and return it — a normal traced run. - In CI replay:
tracely replayloads the regression case’s recorded fixture bundle and activates it;call_tool/call_llmthen serve the recorded outputs (in order, or matched byargs) and never call yourfn.
A call that errored in production is reproduced on the replayed span and raised as
tracely.ToolError — so your agent’s own try/except runs exactly as it would live, and the
gate sees the same failure condition. Faithful error-handling replay, for free.
No rewrite needed: @observe tools and auto-instrumented LLM calls replay too
The call_tool/call_llm seam is the fully-portable path, but most agents don’t need a rewrite:
- Tools — a function decorated
@tracely.observe(as_type="tool")is transparently hermetic: under fixtures it serves the recorded output (or re-raises the recorded failure astracely.ToolError) instead of running. Decorate your tools and the tool side replays as-is. - LLM calls — inside a
fixtures()block Tracely class-patches OpenAIchat.completionsand Anthropicmessages, so code that calls the SDK directly (underinstrument="auto", or via thewrap_openai/wrap_anthropicdrop-ins) is served the recorded completion — reconstructed into a provider-shaped response object — and never hits the network. Other providers’ direct calls fall back to live in replay; usecall_llmthere.
Activating fixtures manually
tracely replay does this for you, but the primitive is public:
with tracely.fixtures(bundle): # bundle = the case's recorded tool/LLM outputs
run(case_input) # call_tool/call_llm now serve recorded values
# outside the block, calls are live againfixture(kind, name) peeks the next recorded output for a tool/model without consuming it.
Why this matters
- Deterministic — the agent sees the exact tool/LLM outputs the production trace saw.
- Offline & free — no live keys or spend in CI;
lambda: …is never called under fixtures. - Faithful — recorded errors replay as
ToolError, so error-handling logic is gated, not bypassed.
The reference agent in
sdk/examples/weather_agent.py
wires this up (run, run_broken, run_handles, …) and is what tracely replay --entrypoint weather_agent:run executes.
Replay is single-turn and hermetic: it re-runs promoted regression cases through your own code against recorded fixtures. To gate multi-turn behaviour against a live service — in any language, with no agent code in CI — see Scenarios. Most teams run both.
Next: the CI gate CLI.
Comparing tools? Langfuse alternatives, compared honestly covers how hermetic replay differs from dataset-and-live-calls CI, including where the other approach wins.
Comparing tools? Langfuse alternatives, compared honestly covers how hermetic replay differs from dataset-and-live-calls CI, including where the other approach wins.