Changelog¶
All notable changes to agent-replay are documented here. The format follows
Keep a Changelog, and the project adheres to
semantic versioning.
[1.0.0] — 1.0¶
The stable release. Every subject in the source research and the roadmap is shipped, the causal engine is property-fuzzed, and the project has a docs site and an automated release path.
Added¶
- Action/observation surfaced in the diagnostics. The explanation trace
(
to_text/to_markdown/to_dict) and the HTML report's Explanation panel now show a step's recorded action → observation whenever the two differ (StepTrace.observation), so a split step reads as "actionX→ observationY" instead of a single conflated value. - Docs site (MkDocs Material).
mkdocs.ymlbuilds the README, the design docs, and the changelog into a searchable site with rendered mermaid diagrams; adocsextra and a GitHub Pages workflow (.github/workflows/docs.yml) publish it on every push tomain. - Automated PyPI release (trusted publishing). A tag-gated
publishjob in CI builds and uploads to PyPI via OIDC — no stored token — when av*tag is pushed and lint/test/build are green.
Notes¶
This 1.0 consolidates 0.1→0.11: counterfactual step-ablation attribution with the Point-of-Commitment rule and antithetic Shapley; the full do-calculus intervention algebra (resample / do / remove / mock-observe / swap-model) on an action/observation SCM; branch-safe idempotency-key replay (property-fuzzed); credit mode; drift, faithfulness, and multi-run aggregation; the Multiverse (fork / resume / diff / console); trace import (OTel / JSONL); parallel and adaptive, cache-shared rollouts; validated minimal repair with guard/DPO export; a pytest plugin; universal instrumentation; and a Who&When benchmark. Zero runtime dependencies.
[0.11.0] — Shared rollouts & property-based guards¶
Added¶
- Cross-analysis rollout cache (
RolloutCache,analyze).attribute,driftandfaithfulnesseach re-run the same prefix-hold rollout family on a run; passing a sharedRolloutCache(via the newcache=argument on all three, or the turnkeyanalyze(traj, agent, verifier, ...)wrapper) lets later analyses reuse earlier rollouts, so the combined cost is barely more than the most expensive one alone. Only prefix-hold / factual plans are cached — Shapley coalition values are never cached (each must stay an independent draw to preserve marginal variance), andcoalition_valuebypasses the cache entirely. Cache keys includetrajectory.root_hash, the plan, rollouts,fail_thresholdandbase_seed, so mismatched configs never collide; cached results are byte-identical to uncached ones. - Property-based tests (hypothesis).
tests/test_properties.pyfuzzes the engine's core invariants over random branching agents and plans: factual replay fidelity, no cassette cross-contamination for any prefix or coalition plan (the soundness guarantee for branching agents), and the plan-algebra decision precedence.hypothesisadded to the dev extra.
[0.10.0] — The full intervention algebra¶
Completes the paper's do-calculus intervention set (docs/HANDOFF.md §3.4) via an
additive action/observation split on the step — no existing trajectory,
hash, or call site changes.
Added¶
- Action/observation split (
Step.action). The SCM distinguishes the policy's action from the observation it yields (deck slide 9). A new optionalStep.actionfield records the action when it differs from the observation (Step.output);Step.action_value/Step.observationresolve either case, andStep.action_value_hash()enables "same decision, different observation?" queries. Fully additive:actiondefaults toNone("action == output"), so older trajectories, the Merkle hashes, and every call site are unchanged. - Recording API:
ctx.tool/llm/memory(..., observe=fn)records a distinct action (produce()) and observation (observe(action), served downstream). Asyncobservepolicies are awaited. - Store: nullable
action_hashcolumn (schema v2, idempotent migration); the action blob is only stored when a step actually splits. mock-observeintervention.ReplayPlan(observed={i: value})/ReplayPlan.mock_observe(i, value)replaces a step's observation downstream while keeping its recorded action — distinct fromdo/force, which overrides the action. This is how you test memory/context reliance: mock the observation of the step that produces the context ("edit-context").swap-modelintervention.ReplayPlan(model_override="…")exposesctx.model_hintto the replayed/forked run so a model-parameterized policy can answer "would a different model have succeeded from step i?".fork(..., observe=, model=)wire both new interventions into the Multiverse; children carry themock_observe/swap_modelintervention label (andmodel_overrideinmeta).
[0.9.0] — Systematic blame across many runs¶
Added¶
- Multi-trajectory aggregation (
agent_replay.aggregate).aggregate_runs( trajectories, agent, verifier, ...)attributes each failing run and pools the results by step name (indices shift between runs; names are the stable identity of an operation, and a name may recur within a run). For each named step it reports how often it was the culprit, its mean attribution with a bootstrap interval over runs, and the point-of-commitment rate; the ranking surfaces the agent'ssystematic_culprit— the difference between debugging one failure and finding a design flaw. Passing runs are skipped and counted.aggregate(results)pools already-computedAttributionResults. Reusesattributewholesale (no new estimation), so it inherits the engine's soundness.AggregateResult.to_text()/.to_dict(). - CLI
agent-replay aggregate. Pools attribution over selected sessions (or every session in a store) and prints the systematic-weak-step ranking;--outwrites the aggregate as JSON. stats.bootstrap_mean_interval— public percentile-bootstrap interval for the mean of a sample (used for the pooled over-runs CIs).
[0.8.0] — Prove it & import anything¶
Realizes the "prove it & import anything" milestone (docs/ANALYSIS.md §5): run
attribution on traces recorded elsewhere, and publish a step-accuracy number
against the Who&When LLM-judge baseline.
Added¶
- Trajectory import (
agent_replay.interop). Build a first-classTrajectoryfrom traces this library did not record: interop.from_otel_spans(spans, ...)— map OpenTelemetry GenAI spans (LangSmith / Langfuse / AgentOps / OpenLLMetry exports) onto llm/tool steps, tolerant of missing/non-standard attributes (unknown spans import as opaque tool steps rather than being dropped).interop.from_jsonl(path, ...)— one-step-per-line or a single{"steps": [...]}object.interop.from_steps(step_dicts, ...)— from an in-memory list; Merkle-chains and hashes imported steps identically to the recorder.interop.replayable_agent(traj, resample_fns={...})— the bridge that makes an imported (observation-only) trace attributable: it reconstructs the recorded operations as an executable agent, turning on resamplability for the steps you supply afn(ctx, inputs) -> outputpolicy for. Steps without a policy stay observation-only (served from the cassette), exactly as the non-resamplable contract requires. Pass the agent + trajectory straight toattribute.- Who&When benchmark harness (
benchmarks/whowhen.py). Measures step- localization accuracy against the Who&When task (arXiv:2505.00212), where the strongest LLM-judge attributor reaches ~14.2%. Ships a deterministic synthetic generator with known ground truth (chain agents with a single responsible step, varied length/position) so it runs offline; on the default suite causal attribution localizes 100% of culprits vs 12.5% for max-magnitude blame without the Point-of-Commitment rule and the ~14.2% judge baseline — quantifying what the PoC rule buys.evaluate()accepts imported (real- dataset) trajectories viainterop. Smoke-tested in CI.
Changed¶
- README: mermaid pipeline / replay-decision / multiverse diagrams replace the ASCII architecture art; new "Import from anywhere" and benchmark sections.
- sdist now includes
benchmarks/.
[0.7.0] — Correctness, cost & credibility¶
A hardening release: one genuine correctness fix, the biggest wall-clock lever
(parallel rollouts), a cost cut for the most expensive phase (adaptive Shapley),
and several long-open stragglers from docs/HANDOFF.md / docs/ANALYSIS.md.
Fixed¶
- Credit-mode Shapley sign bug (correctness).
attribute(on_success="credit")previously negated only the contrastive scores, leaving Shapley values failure-signed. Withmethod="both"a passing run then showed credit-signed contrastive scores beside failure-signed Shapley values for the same step, and withmethod="shapley"_select_culpritpicked the step that least secured success as the "save point" — the exact inverse of credit mode._negatenow flips the Shapley value and its CI too, so every signal shares one sign convention. Regression test intests/test_soundness.py.
Added¶
- Parallel rollouts (
AblationEngine(max_workers=N),attribute(max_workers=N)). Rollouts run on aThreadPoolExecutor— each rollout is a pure function of(plan, seed_tag, k)with its ownReplayContext, seed, and thread-bound ambient context, so parallel execution returns byte-identical results to serial (guarded by a test) while collapsing wall-clock for I/O-bound produce fns (real LLM calls). The single biggest practical speedup for real agents. - Adaptive Shapley (
shapley_attribution(adaptive=True),attribute(adaptive=)). Sequential stopping now covers Phase 2, not just contrastive: permutation pairs accrue until every step's bootstrap CI on its marginal mean is narrower thantarget_ci_width(bounded bymin_pairs/max_pairs), cutting the most expensive phase severalfold. The fixed-N path is byte-identical (pairs drawn from one RNG sequence). - Store hardening.
CheckpointStoreopens on-disk databases in WAL mode (synchronous=NORMAL) so readers and parallel writers no longer block the whole file, and stamps aSCHEMA_VERSIONviaPRAGMA user_version(exposed asstore.schema_version) so older databases can be detected and migrated forward. Migrations are idempotent. - Wilson CI on
P(fail | ablated). Contrastive attribution now attaches a Wilson score interval to each step's ablated failure rate (StepAttribution.p_fail_ablated_ci); the HTML report shows it under the point estimate and the JSON report carries it. (Closes a HANDOFF §2.6 leftover: Wilson intervals were computed instats.pybut never surfaced.)
Changed¶
- OpenAI adapter warns on
temperature=0.wrap_openainow warns once when a call usestemperature=0: a deterministic policy makes counterfactual resampling zero-variance, collapsing attribution to ~0 silently. (HANDOFF §2.6.) - Faithfulness baseline is smoothed.
faithfulness(..., kept_rollouts=)(defaultmin(8, rollouts)) averagessuccess_keptover several held-full replays instead of a single one, so a stochastic verifier near the threshold no longer makes the quadrant assignment brittle. A no-op for deterministic verifiers, so existing behaviour is unchanged.
[0.6.0] — Drift & the entropy of autonomy¶
Closes the last open item from the Architecting the Agent Multiverse deck
(docs/MULTIVERSE_GAPS.md): the per-step drift / entropy curve (Gap 6). The
library now covers the deck's full vision.
Added¶
- Per-step drift / entropy curve (Gap 6).
drift(traj, agent, verifier, state_scorer=None)charts a run's health as it unfolds. Always available from the verifier alone: for each step it holds the factual prefix and resamples the remainder to estimateP(success)and its binary entropy — the "entropy of autonomy" that collapses at the empirical point of commitment (cross-checkingattribute). Given an optional intermediate-statestate_scorer(step) -> [0,1]it overlays an alignment-health series and per-step drift, flagsdecayedruns, locates the drift onset, and warns on the deck's silent alignment decay signature (internal health degrading while the outcome still looks recoverable).DriftResult.to_text()/.to_dict()/.to_html()(a self-contained SVG curve). CLIagent-replay drift --state-scorer …. stats.binary_entropy(p)— Bernoulli entropy in bits,[0, 1].
Changed¶
- The package ships zero bundled agents. The demo/reference agent formerly at
agent_replay.mock_agenthas been removed from the installed package and moved into the test tree —agent-replayis now purely a tool you point at your own agent + verifier. README/CLI docs updated to show integrating your own agent.
[0.5.0] — The Multiverse¶
Implements the gaps found against the Architecting the Agent Multiverse deck
(see docs/MULTIVERSE_GAPS.md): first-class forking, deterministic time/entropy,
faithfulness, and a console.
Added¶
- Multiverse forking (Gap 1).
fork(agent, traj, at_step, do=/remove=)records a complete counterfactual child trajectory — held prefix served from the parent cassette, the intervened step, then the live continuation — withmetalinkingparent_session/fork_step/intervention.afork(async),diff(a, b)(first divergence + per-step state diff),CheckpointStore.branches(session), and CLIfork/branches/diff. Shared prefixes dedupe through the CAS blob store. - Durable resume (Gap 5).
resume(agent, traj)fast-forwards the recorded prefix and continues the run live beyond the recorded horizon. - Deterministic virtual time & entropy (Gap 2).
ctx.now()/ctx.uuid()(sync on both contexts) record real values as non-resamplable steps and replay them;instrument.enable_virtual_time()/virtual_time()patchtime.time,datetime.nowanduuid.uuid4so unmodified agents become deterministic. - Step-level faithfulness (Gap 3).
faithfulness(traj, agent, verifier)masks each reasoning step and measures the outcome shift, classifying runs into correct/wrong × faithful/unfaithful and flagging correct-unfaithful (post-hoc rationalization) and wrong-faithful (best debugging signal). CLIfaithfulness. - Multiverse Console (Gap 4).
agent-replay serve— a zero-dependencyhttp.serverUI to browse sessions, per-step frozen state, and the branch graph. - Action/output hashes on the node (Gap 7).
Step.action_hash()/Step.output_hash()expose the deck's Merkle node structure.
Changed¶
CheckpointStore(check_same_thread=...)for the read-only console.
[0.4.0] — Test your agent¶
The "test your agent" milestone: gate CI on agent reliability, cut attribution
cost, close the repair loop, and support async agents. See docs/ANALYSIS.md §4.
Added¶
- Pytest plugin (
agent_replay.pytest_plugin).assert_agent_passes(agent, task, verifier, rollouts=N, p_fail_max=0.05)is a flakiness-aware assertion (agents are stochastic; one green run is not a pass) that, on failure, runs counterfactual attribution and puts the plain-language explanation — which step, why, minimal fix — into theAssertionError, optionally writing the HTML report as a CI artifact. Alsomeasure_failure_rate()(p_fail + Wilson CI), theAgentFlakyError(carries structured results), andagent_replay_session/assert_agentfixtures registered via apytest11entry point. - Adaptive rollouts.
attribute(adaptive=True, target_ci_width=0.2)(andAblationEngine.run_plan_adaptive,contrastive_attribution(adaptive=)) use sequential stopping — rollouts accrue until the failure-rate interval is tight enough. Measured ~2.6× fewer rollouts on the 6-step mock (more on longer trajectories); the verdict is unchanged. CLI:--adaptive/--target-ci-width. - Repair v2 — closed-loop step-wise fixes. A
propose_fn(step, trajectory)hook lets a user-supplied model propose repair candidates (validated causally, core stays dependency-free);attribute(repair_propose_fn=).Repair.to_guard()emits a deploy-time recovery snippet (CLI prints it under[GUARD]), andexport_contrastive_pairs(results, path)writes validated(rejected → chosen)JSONL for DPO/preference fine-tuning.Repairnow carriesstep_name/step_kind/p_fail_before. - Async agents.
async defagents are recorded/replayed/attributed transparently —record/replaydetect coroutine agents and dispatch to the newarecord/areplay(withAsyncAgentContext/AsyncReplayContext), so the whole synchronous attribution pipeline (including Shapley, repair, and branch-safe key matching) works on async agents unchanged.
Changed¶
- The replay matching logic is factored into a shared
_decideused by both the sync and async replay contexts, so they can never drift apart.
[0.3.1] — Instrumentation bug-fix audit¶
Fixed¶
- Non-deterministic idempotency keys for patched instance methods.
instrument's default argument capture usedrepr()for non-JSON objects, which embeds the object's memory address. Any patched instance method (every SDK recipe, whereselfis a captured arg) therefore produced a key that changed across processes, so a cross-processrecord→attribute(e.g. via the CLI) silently resampled every "held" step instead of serving the cassette. Non-JSON values are now reduced to a stable<TypeName>token. - Reserved-keyword collision. Instrumenting a callable whose argument is named
name,produce, orresamplableraisedTypeError(clash with the context op parameters). Such captured keys are now renamed.
Added / Changed¶
Session.record()forwardsstrict_serialization=andpass_context=(so the ergonomic facade also supports auto-instrumented,ctx-free agents).- CLI
attributeon a passing run prints a friendly hint (use--on-success credit) instead of an uncaughtSuccessfulRunErrortraceback. - Documented the concurrency limitation: the ambient context propagates to
asynciotasks but not to raw worker threads; recording is single-threaded.
[0.3.0] — Universal adapters & explainability¶
Added¶
- Universal instrumentation (
agent_replay.instrument) — connect any framework, not just LangChain. Built on an ambientcontextvarscontext so agents need no explicitctx: @instrument.tool/@instrument.llm/@instrument.memorydecorators andinstrument.wrap(fn, kind, name)to record any callable.instrument.patch(dotted_target, kind)/unpatch/install(*frameworks)/installed(...)context manager — monkeypatch unmodified SDK call sites, driven by a data-onlyRECIPESregistry (OpenAI, Anthropic, Cohere, Google GenAI, Mistral, LiteLLM, LangChain, LlamaIndex, CrewAI, AutoGen). Best-effort: absent SDKs are skipped. Adding a framework = one registry entry.instrument.record_agent(agent_fn, task, frameworks=(...))— record an auto-instrumented agent that takes noctx.record/replay/attribute/AblationEnginegainedpass_context=so the ablation engine re-runs auto-instrumented agents identically.- Explainability (
agent_replay.explain) — a traceable, plain-language narrative over an attribution result (methods unchanged; presentation only): explain(result, trajectory)/result.explain(trajectory)→ anExplanationwith what / where / why / fix / confidence plus a per-step causal trace labelling each step decisive, locked-in, contributing, observed-only, or benign — tracing the run from first action to the point of no return, with the numbers behind every claim.Explanation.to_text()(ASCII-safe),.to_markdown(),.to_dict(),.from_dict().- The HTML report gains an Explanation panel with a colour-coded trace; the
JSON report embeds an
explanationsection; the CLI prints the narrative (suppress with--no-explain) and embeds it in generated reports. - Docs:
docs/frameworks.md(three ways to connect any framework),examples/connect_any_framework.py.
Changed¶
- Repository renamed to
Trajectory_Causal_Attribution(importable package remainsagent_replay).
[0.2.0] — Soundness¶
The v0.2 milestone hardens the causal engine so attribution is correct for
branching agents (not just linear ones) and never reports silent noise. See
docs/HANDOFF.md §2 for the reproduced bugs this release fixes and
docs/RESEARCH_NOTES.md for the supporting literature.
Fixed¶
- Idempotency-key cassette matching (critical). The replayer now binds live
replay calls to recorded steps by content key (
Step.op_key= hash of kind + name + inputs), consuming recorded steps in order, instead of by call position. When an upstream ablation changes the control flow, a held step is served from the cassette only when the same operation actually recurs; otherwise the timeline has diverged and the call is resampled live. This eliminates the cross-contamination that made Shapley coalition plans unsound for any agent whose step sequence depends on earlier outputs. Linear agents are unaffected (key-in-order matching == positional there). Legacy behaviour is still available viareplay(..., match="position"). - Passing-run guard.
attribute()on a trajectory that did not fail now raisesSuccessfulRunErrorby default instead of emitting negative "attribution" noise into a failure report. - Non-resamplable steps no longer corrupt rollouts. A step recorded without a
genuine policy (
produce=None, or an observation-only adapter) is markedresamplable=False; on replay it always serves its recorded output rather than returningNone, and the scorer surfaces it as non-attributable.
Added¶
- Credit mode.
attribute(..., on_success="credit")runs the symmetric analysis on a successful run: which step most secured success (the latest step whose re-decision would introduce a significant failure risk). Reports and CLI label this as a "save point". - Strict serialization. Recording validates that every step input/output is
JSON-serialisable and raises
NonSerializableStepErrorat record time (opt out withstrict_serialization=False), instead of silently degrading payloads to strings on store round-trip. resamplableflag onStep/StepAttribution, persisted in the store (with an automatic column migration for older databases) and surfaced as an "observed-only" badge in the HTML report.- CLI:
agent-replay list; andattributeflags--fail-threshold,--base-seed,--on-success {error,credit}. - Ground-truth tests mirroring the Causal Agent Replay validation: AND/OR two-step interaction credit splits, a quantitative Shapley efficiency-axiom check, a pivotal-single-step localisation, and a branching-agent cross-contamination regression.
- New public exceptions:
AgentReplayError,NonSerializableStepError,SuccessfulRunError.
Changed¶
AttributionResultgained amodefield ("failure"|"credit").- Dead code removed (
AblationEngine._seed_salt); theREMOVEDsentinel is now a distinct singleton rather thanNone.
[0.1.0] — Initial release¶
Counterfactual step-ablation failure attribution: recorder, SQLite checkpoint store with content-addressable blobs, deterministic replayer with intervention plans, ablation engine, contrastive + Shapley attribution with the Point-of-Commitment Rule, minimal counterfactual repair, HTML+JSON reports, CLI, LangChain/OpenAI adapters, and a full pytest suite.