Skip to main content

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_number and their neighbours) is matched case-insensitively as a substring and replaced before anything leaves the process. Token countsinput_tokens and 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=True opts 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

zevruna-0.2.0.tar.gz (20.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

zevruna-0.2.0-py3-none-any.whl (15.4 kB view details)

Uploaded Python 3

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

Hashes for zevruna-0.2.0.tar.gz
Algorithm Hash digest
SHA256 2a49f6e534b44194b9592c27af279bda24f5e26bbc94940fb030496ab9e0138e
MD5 e2a0bdf498d1042eebe278ef50fa06db
BLAKE2b-256 d4adad2ed61c6d1ecc29c1d196727f50747561bceeee2a93f23a84dac3445bcf

See more details on using hashes here.

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

Hashes for zevruna-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a4e6c557e7ed866844923aa3835d05bb67c925d0af03b3518a9a56a0a2fce10b
MD5 87cd4fc730f74f82ba658808e76bf774
BLAKE2b-256 ec86f75b97db674f7ec482fa26cfc19690e15765e384b6426ad9f3f345bc93eb

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page