zevruna (Python)
Execution traces for AI agents — models, tools, APIs, databases and MCP, on one timeline.
One run, one trace: what the agent did, how long each step took, and whether the run actually
achieved anything. Including the runs that failed while every call returned 200.
This is the Python half of the Zevruna protocol. It emits the
same versioned event schema as @zevruna/observe and behaves identically down to retry backoff,
batch size, buffer eviction and redaction. That is enforced, not asserted: both SDKs replay the same
ten fixtures in CI and the build fails if they diverge.
Status: beta. The protocol is stable; the ergonomics may still move.
Install
pip install zevruna
No runtime dependencies, deliberately. An observability SDK that drags a dependency tree into your process has made your build a place your monitoring can break.
Python 3.8+.
Usage
One boundary, wrapped. Instrumenting the MCP client covers every tool call it makes, so these two lines are usually the whole integration.
from zevruna import observe_agent, instrument_mcp_client
client = instrument_mcp_client(mcp_client, "acme-crm")
async with observe_agent("support-agent"):
await run_support_agent(user_input)
Every entry point works in sync code too — with instead of async with. Context propagation uses
contextvars, so a span opened inside asyncio.gather finds the span that was open when it
started, not whichever sibling happened to finish last.
observe_agent(name, run_id=None, attributes=None)
One complete agent execution. Everything instrumented inside, including MCP calls through a wrapped client, attaches to this run automatically. An exception is recorded as a failed run and re-raised unchanged — your error handling is never degraded.
instrument_mcp_client(client, server_name)
Wraps call_tool so every call becomes a step: tool name, timing, outcome. Call sites do not
change. A 200 carrying isError is recorded as a failure, because it is one, and raised as
zevruna.McpToolError.
observe_step(kind, name, attempt=1, attributes=None)
Any other boundary worth timing. kind is model, tool, mcp, http, db, agent or
approval. Nested calls become child steps automatically. Outside an observe_agent there is no
run to attach to, so the step runs untraced rather than starting one.
async with observe_step("model", "plan", attributes={"model": "claude-opus-5", "input_tokens": 812}):
await call_model(prompt)
mark_success(outcome=None) / mark_failure(reason, outcome=None)
The business outcome, which is not the HTTP status. A run where every request succeeded and the ticket never got resolved is a failed run, and only your code knows that.
milestone(name, attributes=None)
A zero-duration marker on the timeline — hand-off, human approval, escalation.
flush() / shutdown()
Runs are batched and flushed on a daemon thread. On serverless or any short-lived process, call
shutdown() before exit or the buffered runs are lost. flush() returns a FlushResult with
sent, queued, events, rejected, dropped, error and error_kind, and never raises.
await aflush() and await ashutdown() run the same work in a thread — urllib is blocking, and
an SDK that stalls your event loop to deliver telemetry has broken the one promise it makes.
A 202 is not proof the runs were kept: over your plan's monthly quota the API accepts the request
and drops the batch. flush() reads the response, so that comes back as sent=0 with the reason in
error and error_kind="quota", and warns once on stderr. It is not requeued — the quota will still
be there on the next flush, and a growing buffer would evict live runs.
Configuration
ZEVRUNA_TOKEN=zv_live_…
ZEVRUNA_ENDPOINT=https://zevruna.com/api
ZEVRUNA_ENVIRONMENT=production
ZEVRUNA_DISABLED=1
ZEVRUNA_SAMPLE_RATE=0.25
ZEVRUNA_BATCH_SIZE=50
ZEVRUNA_FLUSH_INTERVAL_MS=5000
ZEVRUNA_TIMEOUT_MS=10000
ZEVRUNA_MAX_RETRIES=2
ZEVRUNA_MAX_BUFFER=500
ZEVRUNA_CAPTURE_ERROR_TEXT=1
ZEVRUNA_PROTOCOL=legacy
Or pass them to init(...). init is optional; the first wrapper call configures from the
environment. Every name, default and clamp is identical in the Node SDK.
What is collected
Step names, kinds, timings, attempt counts, error classes, and the attributes you pass. Never
tool arguments, never tool results, never payloads.
- A built-in key denylist (
password,secret,token,api_key,authorization,credential,cookie,ssn,card_numberand their neighbours) is matched case-insensitively as a substring and replaced before anything leaves the process. Token counts —input_tokensand friends — are explicitly exempt. - Your
redact(key, value)hook runs after it. - Error text written by an MCP server can quote the arguments it rejected, so it is not
transmitted by default.
capture_error_text=Trueopts in. Either way your own code always receives the server's full message.
Details: what we collect.
Tests
python -m unittest discover -s tests -t . # this SDK
node ../../protocol/contract-tests/run.mjs # this SDK against every other one
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file zevruna-0.2.0.tar.gz.
File metadata
- Download URL: zevruna-0.2.0.tar.gz
- Upload date:
- Size: 20.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2a49f6e534b44194b9592c27af279bda24f5e26bbc94940fb030496ab9e0138e
|
|
| MD5 |
e2a0bdf498d1042eebe278ef50fa06db
|
|
| BLAKE2b-256 |
d4adad2ed61c6d1ecc29c1d196727f50747561bceeee2a93f23a84dac3445bcf
|
File details
Details for the file zevruna-0.2.0-py3-none-any.whl.
File metadata
- Download URL: zevruna-0.2.0-py3-none-any.whl
- Upload date:
- Size: 15.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a4e6c557e7ed866844923aa3835d05bb67c925d0af03b3518a9a56a0a2fce10b
|
|
| MD5 |
87cd4fc730f74f82ba658808e76bf774
|
|
| BLAKE2b-256 |
ec86f75b97db674f7ec482fa26cfc19690e15765e384b6426ad9f3f345bc93eb
|