Skip to main content

Dunetrace SDK

Runtime observability for AI agents. Detects tool loops, context bloat, prompt injection, and 20 other failure patterns in real-time — with a Slack alert while the run is still live.

Zero external dependencies.

Install

pip install dunetrace                    # core SDK
pip install 'dunetrace[langchain]'       # + LangChain / LangGraph
pip install 'dunetrace[otel]'            # + OpenTelemetry exporter

Quickstart

LangChain / LangGraph

from dunetrace import Dunetrace
from dunetrace.integrations.langchain import DunetraceCallbackHandler

dt = Dunetrace()
callback = DunetraceCallbackHandler(dt, agent_id="my-agent")

result = agent.invoke(input, config={"callbacks": [callback]})
dt.shutdown()

Pure Python / custom agent — decorator style

from dunetrace import Dunetrace

dt = Dunetrace()

@dt.tool                                  # auto-emits tool.called / tool.responded
def web_search(query: str) -> list: ...   # args are redacted + capped, then transmitted

@dt.trace                                 # agent_id defaults to "my_agent"
def my_agent(question: str) -> str:
    return web_search(question)[0]        # zero SDK calls needed inside function bodies

@dt.trace supports bare usage (@dt.trace with no parens), explicit agent ID (@dt.trace("research-agent")), and keyword args (@dt.trace(model="gpt-4o")). @dt.tool works on both sync and async functions and is a no-op when called outside a run context.

Or with @dt.agent + auto-instrumentation:

dt.init(agent_id="my-agent")   # patches openai, anthropic, mistral, httpx, requests, langchain, crewai

@dt.agent(model="gpt-4o")      # agent_id inherited from init()
def run_agent(query: str) -> str:
    return openai_client.chat.completions.create(...).choices[0].message.content

LangChain/LangGraph and CrewAI agents need zero manual callback wiring — see docs/integrations/auto-instrumentation.md for how agent attribution is resolved.

FastAPI / Flask — one line each, see docs/integrate-custom-python-agent.md.

What it detects

34 detectors run on every completed run — no configuration, no LLM. A few of the main ones:

Detector What it catches Severity
TOOL_LOOP Same tool called 3+ times in a 5-call window HIGH
RETRY_STORM Same tool fails 3+ times in a row HIGH
PROMPT_INJECTION_SIGNAL Input matches known injection / jailbreak patterns CRITICAL
COST_SPIKE Total tokens 3× above per-agent P75 baseline MEDIUM
PREMATURE_TERMINATION Agent claims success right after a tool call it made actually failed HIGH/CRITICAL
RUNAWAY_ITERATION Step or cost ceiling crossed with no completion signal HIGH/CRITICAL

docs/detectors.md for the full list of 34 detectors

Output modes

Mode How to enable Destination
HTTP ingest (default) endpoint="http://…" Dunetrace backend → detection, alerts, dashboard
Loki NDJSON emit_as_json=True stdout → Promtail / Grafana Alloy
OpenTelemetry otel_exporter=DunetraceOTelExporter(provider) Tempo, Honeycomb, Datadog, Jaeger

What leaves the process

Every free-text field the SDK ships — tool args and output, LLM output, retrieval query/content, memory values, input_text and system_prompt — is capped at max_field_chars (default 8192, the same limit the OTLP ingest path enforces). A capped field carries <field>_truncated: true and <field>_original_length: N beside it; an uncapped field carries neither, so ordinary payloads are unchanged. Length fields such as output_length always report the real size, and ToolCall.args_length keeps the real length so OVERSIZED_TOOL_ARGUMENTS still fires in-path. In-path detectors read the capped text — the same text the server sees.

Structured tool args (tool_called and approval requests) are also redacted by key before they are serialised: any value under authorization, api_key, apikey, token, secret, password, cookie or set-cookie becomes "[REDACTED]". Matching is case-insensitive after normalising - to _, and a key matches when it equals an entry or ends with _<entry> — so Authorization, X-Api-Key, access_token, client_secret and db_password are all caught. Nested dicts and lists of dicts are walked; keys are kept, only values are replaced. Plain-text fields are capped, not redacted.

dt = Dunetrace(
    max_field_chars=4096,                 # env: DUNETRACE_MAX_FIELD_CHARS; 0 disables the cap
    redact_keys=["x-session-id", "ssn"],  # extends the built-in denylist
    redact=lambda args: {**args, "account": "***"},  # runs first, then the denylist
)

redact receives a shallow copy of the args dict and must return a new dict. If it raises or returns a non-dict, the SDK logs a WARNING once per process, drops its output and continues with the built-in denylist alone — the agent is never blocked by its own redaction code. Policy evaluation still sees the raw args (a policy gating on args.amount needs the real value); only what is shipped and what in-path detectors read is redacted.

Backend

git clone https://github.com/dunetrace/dunetrace
cd dunetrace && cp .env.example .env && docker compose up -d

Dashboard → http://localhost:3000 · Ingest → http://localhost:8001

Deploy markers

Annotate the detector timeline with release boundaries so you can correlate failure spikes with deploys:

# Call from your deploy script, CI/CD pipeline, or app startup
dt.mark_deploy("my-agent", version="v1.4.2", commit="abc1234", env="production")

The dashboard renders blue dashed vertical lines at each deploy timestamp on the 30-day detector rate chart. Fire-and-forget — runs on a background thread, never blocks the caller.

Additional keyword arguments are stored as meta and shown on hover.

Policies

Runtime guardrails that fire mid-run — before a failure propagates. Define conditions with any supported trigger and attach a stop, switch_model, inject_prompt, or log action.

from dunetrace import Dunetrace

dt = Dunetrace()

# Stop the run if tool call count exceeds 5
dt.add_policy(
    name="cap tool calls",
    condition={"trigger": "tool_call_count", "operator": "gt", "value": 5},
    action={"type": "stop"},
)

# Downgrade model when cost exceeds $0.50
dt.add_policy(
    name="cost cap",
    condition={"trigger": "cost_usd", "operator": "gt", "value": 0.50},
    action={"type": "switch_model", "params": {"model": "gpt-4o-mini"}},
)

# Inject a corrective prompt when a loop is detected
dt.add_policy(
    name="loop fix",
    condition={"trigger": "signal", "operator": "contains", "value": "TOOL_LOOP"},
    action={"type": "inject_prompt", "params": {"prompt": "Stop repeating tool calls. Summarise what you know and answer."}},
)

with dt.run("my-agent", user_input=query, tools=["search"]) as run:
    ...
    # After a stop policy fires, PolicyViolation is raised
    # After switch_model fires, check run.model_override
    # After inject_prompt fires, check run.pop_prompt_addition()

Policies can also be defined in the dashboard and fetched automatically at run start (60-second TTL cache per agent). See docs/policies.md for the full reference.

MCP server

Query agent signals directly from Claude Code, Cursor, or any MCP-compatible editor — no context switch to the dashboard required.

pip install dunetrace-mcp

Ten tools: list_agents, get_agent_signals, get_agent_health, get_run_detail, get_agent_runs, search_signals, get_signal_detail, get_agent_patterns, summarize_agent, get_instrumentation_guide.

Claude Code — add to ~/.claude.json:

{
  "mcpServers": {
    "dunetrace": {
      "command": "dunetrace-mcp",
      "env": {
        "DUNETRACE_API_URL": "http://localhost:8002",
        "DUNETRACE_API_KEY": "dt_dev_test"
      }
    }
  }
}

Cursor — add to .cursor/mcp.json in your project root (same shape as above).

Once connected, ask your editor things like:

  • "Is my agent healthy?"
  • "What failed in the last 24 hours?"
  • "Show me signal #42 with its fix."
  • "Is this failure systemic or a one-off?"

docs/mcp-server.md

Tests

python -m unittest discover -s tests -v          # SDK tests (no network required)
cd ../mcp-server && python -m pytest tests/ -v   # MCP server tests (no network required)

SDK: 620 tests · MCP server: 154 tests — both run fully offline.

Release files for dunetrace 0.5.6

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for dunetrace 0.5.6
File Size Uploaded
dunetrace-0.5.6.tar.gz 435.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for dunetrace 0.5.6
File Interpreter ABI Platform
dunetrace-0.5.6-py3-none-any.whl Python 3 none any Details

Total release size: 716.0 kB

Release files / dunetrace-0.5.6.tar.gz

Download URL dunetrace-0.5.6.tar.gz
Size 435.9 kB
Tags Source
SHA-256 checksum
How to use checksums
6ab0be6226cafd8865277d825cb3949f6bedc508c8286c1eda5c4ccda15127cb
BLAKE2b-256 checksum
How to use checksums
f3827b8e8deb8830097095a8a5d61c6184a0396102e8997a3c3d42f35e047fee
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.

Transparency log

Release files / dunetrace-0.5.6-py3-none-any.whl

Download URL dunetrace-0.5.6-py3-none-any.whl
Size 280.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2d3c739acdabd695c573fd1475f00b6aeb36d3d674f0568e7a73e14eab1e92c7
BLAKE2b-256 checksum
How to use checksums
f8cb2e51b2d4cfb532765db96eb82862862a78bc9d1c3e6d3d0aea2ea3218658
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.

Transparency log

Release history Release notifications | RSS feed

0.5.7

2 release files

This release

0.5.6 This release

2 release files

0.5.4

2 release files

0.5.3

2 release files

0.5.2

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.21

2 release files

0.3.20

2 release files

0.3.19

2 release files

0.3.16

2 release files

0.3.15

2 release files

0.3.14

2 release files

0.3.13

2 release files

0.3.9

2 release files

0.3.8

2 release files

0.3.7

2 release files

0.3.6

2 release files

0.3.5

2 release files

0.3.4

2 release files

0.3.3

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page