Real-time monitoring for AI agents
Project description
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 transmitted as-is
@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, 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
23 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 23 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 |
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": "eq", "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?"
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.
Links
Project details
Release history Release notifications | RSS feed
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 dunetrace-0.5.0.tar.gz.
File metadata
- Download URL: dunetrace-0.5.0.tar.gz
- Upload date:
- Size: 193.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3328854b814d6886afd5821b07c8cc42815b898da0cc060667f738fae592e773
|
|
| MD5 |
5c66b9eafc7c3142fd24c0abd0584ff2
|
|
| BLAKE2b-256 |
287332ccc9ed48c1e4b01b2213528322a05b7cc56b150df82cdc9c62890b2d51
|
Provenance
The following attestation bundles were made for dunetrace-0.5.0.tar.gz:
Publisher:
publish.yml on dunetrace/dunetrace
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dunetrace-0.5.0.tar.gz -
Subject digest:
3328854b814d6886afd5821b07c8cc42815b898da0cc060667f738fae592e773 - Sigstore transparency entry: 2165090792
- Sigstore integration time:
-
Permalink:
dunetrace/dunetrace@d33435ae95c4535a956dd338f1f1b0ef50d0b702 -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/dunetrace
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d33435ae95c4535a956dd338f1f1b0ef50d0b702 -
Trigger Event:
push
-
Statement type:
File details
Details for the file dunetrace-0.5.0-py3-none-any.whl.
File metadata
- Download URL: dunetrace-0.5.0-py3-none-any.whl
- Upload date:
- Size: 144.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3dd7f9f36fe24c82f7f84de8ce28f62f626349d06b954b7f57ab3bbb21992243
|
|
| MD5 |
53e60ae2c4a214389209cbfedbd49922
|
|
| BLAKE2b-256 |
e47deefedc15d0a552f4874bc7dd89453292e0006da093d2ee9dd5f0cd1050f1
|
Provenance
The following attestation bundles were made for dunetrace-0.5.0-py3-none-any.whl:
Publisher:
publish.yml on dunetrace/dunetrace
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dunetrace-0.5.0-py3-none-any.whl -
Subject digest:
3dd7f9f36fe24c82f7f84de8ce28f62f626349d06b954b7f57ab3bbb21992243 - Sigstore transparency entry: 2165090795
- Sigstore integration time:
-
Permalink:
dunetrace/dunetrace@d33435ae95c4535a956dd338f1f1b0ef50d0b702 -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/dunetrace
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d33435ae95c4535a956dd338f1f1b0ef50d0b702 -
Trigger Event:
push
-
Statement type: