What causal monitoring is
Traditional tracing answers: what happened, in what order, and how long did it take?
Causal monitoring answers: which agent introduced the fault, and why did the workflow fail?
blamr records a CausalEdge at every agent handoff — not just latency and tokens:
| Signal | Business meaning |
|---|---|
confidence_in / confidence_out | How certain the agent was going in vs going out |
intent_delta | Whether the hop preserved the workflow goal (−1 = total drift, 0 = preserved) |
influence_score | How much downstream behavior this hop can affect |
| I/O previews | Truncated input/output for trace UI, embeddings, and LLM explanations |
Those signals form a directed causal graph per run. When the run completes, workers walk that graph backward and assign fault-weighted blame percentages to each agent.
End-to-end pipeline
Agents ──CausalEdge/hop──► Ingest (:3001)
│
├──► Kafka edges.raw ──► ClickHouse writer ──► causal_edges
│
└──► POST runs/complete ──► runs.completed
│
▼ (settle ~2s)
blame.needed ──► Blame processor
│
├──► Postgres (workflow_runs, blame_reports)
└──► blame.completed ──► Webhooks / UI
Dashboard (:8080) ◄── API (:3000) ◄── Postgres
- Instrument — SDK, MCP proxy, or adapter emits one CausalEdge per hop.
- Ingest — Validates API key, Merkle-hashes edges, publishes to
edges.raw. Returns202immediately. - Edge writer — Batches edges into ClickHouse; optional semantic drift on flush.
- Complete run — Agent calls
POST /v1/runs/:run_id/completewith success or failed. - Run aggregator — Waits
BLAMR_SEMANTIC_SETTLE_MS(default 2s), then publishesblame.needed. - Blame processor — Enriches signals, evaluates confidence gate, computes blame, persists results.
- Dashboard — Renders causal graph, trace, cost, and blame/attribution tabs.
:3001/v1). The dashboard API (:3000) is for operators only.
The unit of observation: CausalEdge
Each edge is one handoff in a multi-agent workflow, ordered by hop_index and linked via prev_hash → edge_hash (SHA-256 audit chain).
Agent A ──edge──► Agent B hop_index: 0 confidence_in: what B received with confidence_out: what B emitted intent_delta: goal preservation on this hop influence_score: downstream causal weight
Agent-side signal computation
The TypeScript SDK (@blamr/sdk) computes hop signals via computeHopSignals():
- Intent delta — from domain alignment or retrieval relevance (aligned ≈ −0.02; weak retrieval −0.15 to −0.35).
- Confidence out — composite of lexical confidence, structured JSON fields, tool scores, alignment ceiling, and upstream cap.
Agents emit honest I/O + scores; the platform derives causal primitives — no manual blame guessing required.
Run lifecycle and status
| Phase | Status | Dashboard |
|---|---|---|
| Hops in flight | no row yet | Edges stream to ClickHouse |
completeRun() called | processing | Poll until blame ready |
| Blame processor finishes | success or failed | Full run detail available |
Business failure vs confidence gate
| Source | Who decides | Example |
|---|---|---|
| Business rule | Agent completeRun({ businessFailed: true }) | Tool threw, validation failed |
| Confidence gate | Workers after enrichment | Final hop 62% < 70% threshold |
If the agent reports success but the gate fails, workers upgrade the run to failed and set error_summary from the gate reason.
Confidence gate modes
| Mode | Pass condition | Use when |
|---|---|---|
final (default) | Last hop confidence_out ≥ accept level | End-to-end output quality matters |
min | Every hop ≥ accept level | No weak link in the chain |
Default accept level: 0.70. Configure in SDK or Settings → Workspace → Workflow profiles.
Post-run enrichment
Semantic drift (BLAMR_SEMANTIC_DRIFT=true)
Embeds I/O previews via local Ollama. Compares tool input vs output and run goal vs downstream output. In telemetry-first mode (default):
intent_delta = min(agent_reported, semantic_value)confidence_out = min(reported, semantic_similarity_ceiling)
ML drift + ranker (BLAMR_ML_ENABLED=true)
| Drift type | Meaning |
|---|---|
domain_mismatch | Output belongs to wrong domain |
retrieval_miss | KB/tool returned irrelevant data |
severity_underrate | Incident severity too low |
confidence_inflation | Overconfident despite downstream fault |
propagation | Echoing an upstream error |
Accuracy score
Each completed run gets accuracy_score (0–1) for workflow health bands and Overview KPIs.
if no edges: success → 0.9 ; failed → 0.4 else: base = last_hop.confidence_out success → clamp(base, 0.65 … 0.99) failed → clamp(base × 0.55, 0.25 … 0.65)
Accuracy reflects final-hop confidence tempered by outcome — failed runs score lower even when last-hop confidence was high.
Blame attribution
Blame answers: of the total fault in this run, what fraction belongs to each agent?
Fault signals per hop
| Signal | Formula | Meaning |
|---|---|---|
| Intent harm | max(0, −intent_delta) | Goal drift on this hop |
| Confidence drop | max(0, confidence_in − confidence_out) | Agent lost certainty |
| Inflation | max(0, confidence_out − confidence_in − 0.15) | Overconfident despite problems |
Blame weights
localFault = intentHarm × 3 + confDrop × 2 + inflation × 2 if run failed: weight = influence_score × (localFault + 0.05) else: weight = influence_score × 0.5 // influence, not fault Normalize per agent → percentages (sum = 100%)
Design principle: blame follows where fault was introduced, not hop order alone or token count.
Failed vs successful runs
| Outcome | Dashboard tab | Meaning |
|---|---|---|
| Failed | Blame | Root cause agent + fault % + reasons |
| Success | Attribution | Influence distribution, not fault assignment |
When ML is enabled on failed runs: fused_pct = (1 − α) × rule_pct + α × ml_pct (default α = 0.55). Optional LLM rewrites reasons citing trace I/O.
Dashboard business logic
Workflow health bands
| Band | Avg accuracy | Action |
|---|---|---|
| Critical | < 60% | Investigate immediately |
| Warning | 60% – 74% | Review recent failures |
| Fair | 75% – 89% | Acceptable, not excellent |
| Healthy | ≥ 90% | Within target |
Agent connection status
| Status | Condition | Meaning |
|---|---|---|
| Live | seen ≤ 15 min ago | Actively emitting edges |
| Idle | 15 min – 7 days | Seen recently, quiet now |
| Offline | > 7 days or never | No recent telemetry |
Run detail tabs
| Tab | Purpose |
|---|---|
| Graph | Causal topology + confidence + influence |
| Trace | Hop-by-hop I/O, latency, tokens |
| Cost | Token and USD breakdown |
| Blame / Attribution | Root cause or influence ranking |
| Timeline | Chronological replay |
Event topics
| Topic | Producer | Consumer |
|---|---|---|
edges.raw | Ingest | ClickHouse writer |
runs.completed | Ingest | Run aggregator |
blame.needed | Run aggregator | Blame processor |
blame.completed | Blame processor | Webhooks |
Configuration map
| Goal | Where |
|---|---|
| Per-workflow confidence threshold | SDK workflowConfig or Settings → Workflow profiles |
| Semantic drift on/off | BLAMR_SEMANTIC_DRIFT (workers) |
| ML blame fusion | BLAMR_ML_ENABLED, BLAMR_ML_FUSION_ALPHA |
| LLM blame narratives | BLAMR_LLM_BLAME_REASON |
| Ingest endpoint | BLAMR_ENDPOINT=http://host:3001/v1 |
Full markdown reference: docs/CAUSAL_MONITORING.md
Mental model for operators
- Instrument every handoff — no edge means invisible to blame.
- Complete every run — without
completeRun(), blame never fires. - Failed ≠ one bad agent — blame % shows contribution, including propagation.
- Success runs still have signal — attribution + inflation flags per hop.
- Health bands lag reality — they aggregate historical accuracy.