English | 한국어
Your AI coding agent can see what your app did.
One-line app instrumentation for your local dev loop — structured telemetry your AI coding agent can query.
Problem · Why · Concepts · Quickstart · Query · Roadmap
import observr
observr.init(service="my-agent")
# Every action this agent takes is now recorded and traceable.- Your AI agent did something unexpected — you don't know which decision caused it
- Three agents are chained together — you can't tell where the failure started
- Claude Code or Cursor made a tool call — you want to review it after the fact
- An agent threw an error — you need to know what prior action led to it
observr answers these questions.
observr is designed for one specific intersection: app-level tracing · local dev loop · one-line setup · output your AI coding agent can query. Each tool below owns its space; here is how they differ:
| App tracing & logs | Instrumentation | Local / no infra | AI coding agent can query | |
|---|---|---|---|---|
| DIY logging | ✓ manual | Manual wiring | ✓ | Build it yourself |
| Datadog | ✓ | SDK + agent config | ✗ Cloud | Via paid MCP server |
| Grafana + OTel | ✓ | OTel SDK config | Self-host required | Via external tooling |
| Netdata | Infra / kernel | Zero-code | ✓ | MCP (infra metrics) |
| Langfuse / Phoenix | LLM behaviour | SDK | ✓ / cloud options | LLM-side traces |
| observr | ✓ App requests, spans, logs | 1 init() call |
✓ SQLite, zero infra | Structured JSON via CLI |
observr does not replace any of the tools above — they solve different problems well. It fills the gap at the intersection of lightweight local dev and AI-consumable structured output.
Is: one-line SDK instrumentation for your app's HTTP requests, spans, and logs — stored locally in SQLite, queryable as structured JSON by your AI coding agent (Claude Code, Cursor).
Is not:
- A tool for observing LLM / AI agent behaviour — that's Langfuse, Phoenix, Helicone, etc. (excellent tools for that problem, opposite direction)
- An infrastructure or kernel-level monitor — that's Netdata, Prometheus, etc.
- A cloud-native operations platform — that's Datadog, Grafana Cloud, etc.
Causal Attribution — trace any outcome back to its root
Every span carries a parent_span_id that links it to the action that triggered it. Use agent_span() / agentSpan() to attach standard observability keys (intent, trigger, model, tool) — nesting automatically threads the causal chain.
# Python — context propagation is automatic when spans are nested
client = observr.get_client()
with client.agent_span("agent.decide", intent="answer user", model="claude-sonnet-4-6") as root:
with client.agent_span("tool.call", trigger=root.span_id, tool="web_search") as child:
results = web_search("relevant context")
child.set_attribute("result_count", len(results))trace_id: 4f2a1b3c
├── agent.decide (a1b2) intent="answer user" model="claude-sonnet-4-6"
│ └── tool.call (c3d4, parent: a1b2) tool="web_search" result_count=12
// Node.js
await client.agentSpan("agent.decide", { intent: "answer user", model: "claude-sonnet-4-6" })
.run(async (root) => {
await client.agentSpan("tool.call", { trigger: root.spanId, tool: "web_search" })
.run(async () => { /* causally linked — parent_span_id set automatically */ });
});Click any trace chip in the dashboard to open the causality tree — a waterfall showing every span, its duration, and agent attributes.
Behavioral Patterns — signal over noise
observr normalizes event messages and groups similar ones by fingerprint.
"Payment failed for user abc123" and "Payment failed for user xyz789" collapse into the same pattern — so you see frequency over time, not noise.
Audit Log — local, queryable, persistent
All events are stored locally in SQLite (WAL mode) with full timestamps, service attribution, structured attributes, and a tamper-evident SHA-256 hash chain.
Queryable by: level · service · trace ID · time range · HTTP path
Verify integrity with GET /verify. The dashboard header shows chain ✓ when the stored chain is intact and chain ✗ when an older event was modified or deleted.
macOS / Linux
curl -sSL https://raw.githubusercontent.com/ydking0911/observr/main/scripts/install.sh | sh
observrd # → http://localhost:7676Homebrew
brew tap ydking0911/observr && brew install observrgo install
go install github.com/ydking0911/observr/server/cmd/observrd@latestpip install observr # Python
npm install @ydking0911/observr # Node.jsPython — FastAPI / Flask / Django (WSGI + ASGI)
import observr
observr.init(service="my-agent") # auto-detects the framework
# Django: X-Trace-Id / X-Span-Id headers are propagated automaticallyNode.js — Express
const { init } = require('@ydking0911/observr')
init({ service: 'my-agent' })Agent spans — causal chain with standard attribute keys:
client = observr.get_client()
with client.agent_span("tool.call", intent="find recent papers", tool="web_search") as span:
results = web_search("observability 2026")
span.set_attribute("result_count", len(results))Logs are captured automatically:
import logging
logger = logging.getLogger(__name__)
logger.error("Payment failed", extra={"user_id": "u_123", "amount": 9900})# Recent errors as JSON
observrd query --level error --last 100 --format json
# All actions from a specific agent
observrd query --service my-agent --last 500 --format json
# Trace a full decision tree
observrd query --trace-id 4f2a1b3c
# Export for review
observrd query --level error --last 10000 --format csv > audit.csv
# Human-readable table
observrd query --format textExample — an AI agent auditing itself:
User: What errors did the agent produce in the last hour?
Claude: Let me pull the audit trail...
$ observrd query --service my-agent --level error --last 200 --format json
→ 3 errors, all traced to span "tool.call" → parent "agent.decide" at 14:32:01
→ Root cause: agent.decide passed malformed input to tool.call
Each inserted event stores its canonical JSON plus SHA256(prev_hash + event_json). Recomputing the chain detects in-place tampering: modifying a past event, or deleting an event from the middle of the log, breaks the next link.
curl http://localhost:7676/verify{ "ok": true, "checked": 1042, "skipped": 0, "broken_at": null }If the chain is broken, broken_at is the first event ID whose stored hash no longer matches:
{ "ok": false, "checked": 204, "skipped": 0, "broken_at": "evt_abc123" }skipped counts leading legacy rows written before this feature existed. Upgrading an existing database in place is safe: those rows have no stored hash, so they are reported as unverifiable (skipped) rather than broken, and verification continues from the first hashed event.
What it protects against, and what it does not. This is a local, unkeyed hash chain — not a blockchain. It is honest about its limits:
- Detects in-place edits and deletion of events from the middle of the log.
- Does not detect tail truncation. Deleting the most recent N events leaves a shorter but internally consistent chain, so
okstaystrue. Closing this gap requires an external head anchor (planned). - Does not resist an attacker with write access to the database file. Because the hash is unkeyed, anyone who can edit a row can recompute every following hash and pass verification. Tamper-evidence holds only against actors who cannot recompute the chain (e.g. accidental corruption, a process without the verification logic, or read-only-then-tampered exports). A keyed (HMAC) or externally anchored variant is future work.
- No distributed consensus. Single-node tamper evidence only.
- Retention interaction. Retention cleanup deletes the oldest rows; once the genesis events are gone, the surviving prefix no longer chains from the empty seed and
/verifyreports broken for the new oldest row.
Fire Slack or Discord notifications when error thresholds are exceeded:
observrd start \
--alert-slack-url https://hooks.slack.com/services/... \
--alert-discord-url https://discord.com/api/webhooks/... \
--alert-level error \
--alert-threshold 5 \
--alert-window 60s \
--alert-cooldown 5m{
"id": "evt_1711234567890",
"trace_id": "4f2a1b3c8e9d0f1a",
"span_id": "a1b2c3d4",
"parent_span_id": "9f8e7d6c",
"service": "my-agent",
"timestamp": "2026-03-24T12:34:56.789Z",
"type": "span",
"level": "error",
"duration_ms": 3241.5,
"message": "tool.call failed",
"attributes": {
"tool": "web_search",
"error": "timeout after 3000ms"
}
}parent_span_id links a span to its causal parent, enabling full decision tree reconstruction.
| Version | Status | Features |
|---|---|---|
| v0.1 | ✅ | Python SDK · Go collector · React dashboard · CLI · CI/CD |
| v0.2 | ✅ | Node.js SDK · PyPI publish · npm publish |
| v0.3 | ✅ | Slack/Discord alerts · Event retention (TTL) · JSON/CSV export |
| v0.4 | ✅ | Causal attribution (parent_span_id) · Behavioral pattern detection · Fastify support |
| v0.5 | ✅ | agent_span() / agentSpan() helper · Dashboard causality tree view · Django support |
| v0.6 | ✅ | Deep behavioral pattern analysis — temporal trends, anomaly detection, agent-attribute grouping (intent/tool/model), causal pattern correlation, dashboard Patterns view |
| v0.7 | 🚧 | Go SDK · Tamper-proof hash-chain audit log · Multi-agent W3C TraceContext propagation |
| v0.8 | 📋 | Native MCP server — AI coding agents (Claude Code, Cursor) query observr directly without CLI (planned, not yet implemented) |
| v1.0 | 📋 | API stability guarantee · CHANGELOG · Cross-platform release artifacts |
make build # build everything (dashboard embedded into binary)
make dev-server # Go server on :7676
make dev-dashboard # Vite dev server on :5173 (proxies to :7676)
make test # Go + Python + Node.js
make test-e2e # full end-to-end test
make lintSee CONTRIBUTING.md to get started.
MIT License · LICENSE
