Observed since 20 Oct, 23:27 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Snapshot tests for AI agents. Record an agent's tool-call trace, diff against a baseline, fail CI on regressions. Python port of @mukundakatta/agentsnap.
Snapshot tests for AI agents. Record an agent's tool-call trace, diff it against a baseline, fail CI on regressions. Zero runtime dependencies. Drops into pytest or any test runner.
Python port of @mukundakatta/agentsnap.
pip install agentsnap-py
from agentsnap import record, trace_tool, expect_snapshot
search = trace_tool("search", lambda q: fetch_results(q))
summarize = trace_tool("summarize", lambda docs: llm_summarize(docs))
def agent(question):
docs = search(question)
return summarize(docs)
def test_research_agent_stays_on_rails():
trace = record(lambda: agent("What is RLHF?"))
expect_snapshot(trace, "tests/__snapshots__/research.snap.json")
First run writes the snapshot. Every run after that diffs against it. If the agent calls a different tool, calls them in a different order, or starts erroring, the test fails with a readable diff. Regenerate with AGENTSNAP_UPDATE=1.
import asyncio
from agentsnap import arecord, trace_tool, expect_snapshot
asearch = trace_tool("search", async_fetch)
async def agent(q):
return await asearch(q)
def test_async_agent():
trace = asyncio.run(arecord(lambda: agent("hello")))
expect_snapshot(trace, "tests/__snapshots__/async.snap.json")
| Status | When | Default action |
|---|---|---|
PASSED | Bytewise match | green |
OUTPUT_DRIFT | Tools + args identical, only output text or external result hashes differ | warn (non-failing) |
TOOLS_REORDERED | Same tool names, different order | fail |
TOOLS_CHANGED | Different tool names called, or different args | fail |
REGRESSION | New error in the trace, or a tool that used to work now throws | fail |
Override per snapshot via expect_snapshot(trace, path, fail_on=[...]).
record(fn, *, input=None, model=None, capture_results=False) -> TraceRun fn (sync) and capture every trace_tool-wrapped call inside it. Returns a JSON-serializable dict.
arecord(fn, ...) -> TraceAsync variant for async def agents. Use with asyncio.run(arecord(lambda: agent())) or inside an async test.
trace_tool(name, fn) -> wrapped_fnWraps a tool. Inside record, calls go into the trace; outside, transparent pass-through. Works with sync and async tools (returns the same shape).
expect_snapshot(trace, path, *, update=False, fail_on=None) -> dictCompare against an on-disk JSON baseline. Writes if missing, regenerates if AGENTSNAP_UPDATE=1 (or update=True), otherwise diffs and raises AgentSnapshotMismatch on a failing status.
diff(baseline, current) -> DiffResultLow-level diff engine. Returns a DiffResult(status=..., changes=[Change(...)]).
format_diff(result, path=None) -> strRender a colored terminal block for the diff (used in the failure message).
Installing this package registers a pytest plugin that exposes the same API as fixtures:
def test_my_agent(agentsnap_record, trace_tool, expect_snapshot):
fn = trace_tool("hello", lambda: "world")
trace = agentsnap_record(lambda: fn())
expect_snapshot(trace, "tests/__snapshots__/hello.snap.json")
contextvars (Python's AsyncLocalStorage equivalent) instead of node:async_hooks.record(); async agents use arecord() -- Python doesn't have JS's "async by default" assumption.Trace is a dict (not a class) so it serializes / inspects naturally.Change.to_dict() produces the JS-style {"path": ..., "from": ..., "to": ...} -- the dataclass uses from_ because from is a Python keyword.pyproject.toml pytest11 entry point).See the JS sibling's README for the full design notes.
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.1.0 | 27 Apr 2026 | 1 |
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 23:27 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:efcab0b943033fbf6ef4bd39bd0f0decf6e4a6afe20308b5d7ef2853e6d83a22