EASY: Getting an LLM to produce something useful
HARD: Making that output trustworthy enough to ship
Think of AI systems on a leash of increasing length:
| Project | Autonomy Level | What It Does |
|---|---|---|
| Gage's Check Suggester | Lowest | Proposes only, opens draft PR |
| Sharif's Dashboard Agent | Medium | Authors artifact directly |
| Shreeyash's RCA Pipeline | Highest | Runs investigations on schedule, no human until review |
Key Insight: Each step further out on the leash required its own unique safeguards. Autonomy and safety engineering scale together.
Silent data corruption path:
Bad data lands → Models train on it → Dashboards report it
→ Problem surfaces DAYS LATER when someone notices a wrong number
Data quality checks are assertions that run after each ingestion:
Problem: Engineers write them by hand, so most datasets have few or none.
A rules engine handles easy, mechanical cases:
An LLM handles cases that depend on what columns mean:
Example: Two tenant ID columns on a payment table
- One written by upstream system
- One written by DoorDash's system
- Both always populated, always agree
LLM proposes: Flag any row where the two DISAGREE
LLM reasoning: IDs come from different systems and should reconcile
A mismatch = routing error = could tag production events as test data
A rules engine would find that match plus dozens of accidental matches and could not distinguish a meaningful rule from a coincidence.
The Long Tail Problem:
Rules engine approach:
- Maintain lists of placeholder values worth flagging
- Maintain name patterns marking cross-system pairs
- Maintain dates that count as implausible
- Someone extends these lists EVERY TIME a new pattern appears
LLM approach:
- Avoids encoding most of that long tail up front
The worst bug in the project:
Profiler couldn't compute column statistics
↓
Left the field BLANK
↓
Blank stored same as measured ZERO
↓
Model read "zero non-null values" as "100% null"
↓
Proposed completeness check that would FAIL ON EVERY ROW
The model reasoned correctly over wrong input.
The fix:
BEFORE: Empty field = "0 non-null values" ← ambiguous
AFTER: Empty field = "not measured" ← explicit
Core Principle: A lot of the trust work in an LLM system is in how you represent the data you feed the model, not in the model itself.
Layer 1: TIERED PROFILING
├── Cheap row count first
├── Sampling strategy matched to table size
└── Full scan (small) → bounded sample (large)
Layer 2: DETERMINISTIC POST-PROCESSING
├── Runs AFTER the model
├── Drops suggestions breaking known rules:
│ ├── Completeness check on nullable column → DROPPED
│ └── Check that fires on every row → DROPPED
└── Enforced in CODE, not in the prompt
(A model won't apply the same rule the same way on every run)
Layer 3: HUMAN REVIEW
├── Every suggestion shown to engineer
├── Statistics behind each suggestion visible
├── Model reasoning available on request
├── Refine command accepts context ("this column is nullable by design")
└── Only accepted checks reach the draft PR
Layer 4: LLM-AS-A-JUDGE EVALUATION
Scores each suggestion on four axes:
├── Relevance to the column
├── Whether reasoning holds up
├── Whether assertion is specific enough to implement
└── Likelihood of false positives
The judge found a recurring problem: the tool picked the right column and explained why it mattered, then left the threshold undefined.
Wrong approach: Hope the model fills in the threshold
Right approach: Make threshold a REQUIRED OUTPUT FIELD in the schema
Principle: If you depend on a rule, enforce it in the output schema, not in the prompt.
ML observability data scattered across:
- Dozen internal dashboards
- Third-party tools
- Multiple data sources
Result:
- Hard for engineers to correlate
- Effectively unusable for agents
- No reasoning layer explaining what's happening and why
The challenge: How do you teach an LLM to write a dashboard? It can't see.
The insight: LLMs are very good at text.
Traditional approach:
Dashboard = Visual elements (LLM cannot verify layout or intent)
New approach:
Dashboard = YAML document (panels, queries, transformations, layout)
The three-phase parser:
Phase 1: SYNTAX CHECK
└── Reports line and column errors
Phase 2: SHAPE VALIDATION
└── Per-issue errors pointing at exact paths in document
Phase 3: STRUCTURAL WALK
└── Catches what schema can't:
├── Duplicate panel IDs
└── References to undeclared sources
Every failure → uniform error shape
Agent gets same structured feedback as human editor
Agent can iterate on its own output
Inspired by observability platform data models, all data presented as DataFrames:
DataFrame = list of named, typed fields
+ row-count metadata
+ per-field labels and config
Source plugin = async function that returns DataFrames
Three properties this buys:
Property 1: PANEL INDEPENDENCE
└── Timeseries panel renders ANY DataFrame with time field + number fields
Same panel, any backend
Property 2: UNIVERSAL TRANSFORMATION COMPOSITION
└── Each transformation = pure function from frames to frames
Runs in order after fetch
None know which source produced the frames
Property 3: SINGLE LEARNING TARGET
└── The LLM only has to learn ONE data model
The challenge: Encoding all panel configurations exhausts the context window.
Panel configurations include:
- Field options
- Units
- Thresholds
- Mappings
- And more...
Solution: "Ask only for what you need"
Initial agent context:
└── Concise descriptions of panels and data sources only
When more detail needed:
└── Agent requests it through tool calls
Result:
├── Reduced context usage
└── Improved dashboard generation and debugging
Enabled by registry design:
Registry contains:
├── Machine-readable schema (for validation and codegen)
└── Human-readable documentation (for the agent)
Adding new panel = single file that registers itself
Agent gains ability to use it immediately
No prompt changes, no retraining
Traditional dashboard: Shows data
Smart ML Dashboard:
├── Shows data
├── Enables AI debugging within panels
├── Declares what the user CARES ABOUT
└── Enables proactive agentic reports before user asks
Overview tab:
└── Agent's analysis without requiring user to read the data at all
Current state:
Detection of failures → SOLVED (per-axis judges, trend monitoring)
Diagnosis of failures → NOT SOLVED
Diagnosis still means:
Engineer reads transcript → Forms hypothesis → Confirms against logs and commits
Scale problem:
Thousands of transcripts/week
2% flag rate = substantial manual review burden
Review cost scales with usage
Team size does NOT scale with usage
Three properties making each investigation expensive:
Property 1: FAULT ORIGIN DECOUPLED FROM SYMPTOMS
└── Failure in final turn can originate several agents upstream
Reading the trace alone does not localize it
Property 2: DENSE PRODUCTION GUARDRAILS
└── Policy enforced across:
├── System prompts
├── Policy engines
└── Tool-layer checks
Two rules can silently conflict
Hard to tell if rule was missing, wrong, overridden, or not followed
Property 3: HETEROGENEOUS FAILURE MODES
└── Execution faults: truncation, premature termination, orphaned tool calls
Semantic faults: contradictory context, malformed handoff, sampling variance
These share almost nothing diagnostically
Stochastic execution means failures often can't be reproduced
OBSERVABILITY → What to investigate
└── Full-fidelity capture of input, tool calls, steps, outcome
GRADING → Why to investigate
└── Per-axis metrics and judges tied to product specification
Multi-agent failures don't lie on single axis
One aggregate score rarely captures the failure
MONITORING → When to investigate
└── Trend monitoring over metrics
Detection triggered by anomaly, not on demand
MINING STAGE TUNING STAGE
(covered in detail) (proposed fix + regression check)
↓
Three sequential phases:
PLANNER
├── Reads: transcript, judge verdicts, execution signals
├── Does NOT conclude
└── Emits: hypotheses + questions to confirm/refute them
↓
WORKERS (fan-out)
├── Each inspects: source code, git logs, OR one implicated prompt
├── Returns: verbatim evidence for assigned questions ONLY
└── May NOT conclude (prevents self-confirming pipeline)
↓
MANAGER
├── Adjudicates evidence
└── Produces per-session diagnosis:
├── Originating fault
├── Propagation path
├── Responsible agent
├── Plausible fix location
└── Implicated prompt span quoted verbatim
Why split the work? Supplying all evidence in one context window yields a confident but unfalsifiable summary. Splitting forces each agent to stay in its lane.
Fixed schema contains:
├── Causal chains
├── Verbatim evidence
└── Categorization fields from published taxonomy
Schema serves TWO purposes:
1. Constrains what the Manager MUST justify
2. Makes findings comparable and auditable across sessions
Comparable findings → can be embedded and clustered
Clustered findings → proposed fix targets a FAMILY of sessions, not one trace
Four conditions tested:
Condition 1: Free agent, no schema
Condition 2: Free agent + schema constraint
Condition 3: Multi-agent, no schema
Condition 4: Custom pipeline (full structure)
Key findings:
Window 1 Results:
├── Custom pipeline: led on output quality and groundedness
├── Simple free agent: led on equal-weighted 10-axis score
└── Two intermediate conditions: fixated on headline anomaly
(provider-side latency noise no code change could address)
Lesson: Splitting work across agents ≠ deeper investigations
Giving each agent a TIGHTLY SCOPED TASK = deeper investigations
Window 2 Results (after improvements):
├── Custom RCA + semantic clustering: best overall
│ ├── Output quality ✓
│ ├── Groundedness ✓
│ ├── Sampling robustness ✓
│ └── Reproducibility ✓
└── Free agent: kept novelty, portability, lower cost per run
Conclusion 1:
Off-the-shelf agent harnesses CAN already root-cause agentic failures today
Conclusion 2:
Customized structure adds provenance, specificity, thematic stability
at the cost of portability
Conclusion 3:
Schema helps at REPORTING step
Schema HURTS at EXPLORATION step
(intermediate conditions showed this clearly)
All three projects independently arrived at the same four safeguards:
Safeguard 1: DETERMINISTIC GUARDRAILS OUTSIDE THE MODEL
└── Rules enforced in code, not in prompts
A model won't apply the same rule the same way on every run
Safeguard 2: DATA REPRESENTATION THE MODEL CAN'T MISREAD
└── Explicit nulls instead of magic numbers
"not measured" instead of "0 non-null values"
One data shape for every source
Safeguard 3: OUTPUT SCHEMA CONSTRAINING WHAT IT MUST JUSTIFY
└── Required fields instead of hoped-for fields
Fixed handoff contracts between agents
Structured findings with verbatim evidence
Safeguard 4: EVALUATION HARNESS MAKING "DID THIS HELP" A NUMBER
└── LLM-as-a-judge scoring on defined axes
Before/after comparison on prompt or model changes
Multi-window, multi-replicate evaluation
| Concept | Gage (Checks) | Sharif (Dashboards) | Shreeyash (RCA) |
|---|---|---|---|
| Autonomy | Draft PR only | Writes artifact | Runs unattended |
| Data representation fix | Null vs. zero | DataFrame abstraction | Execution signal layer |
| Deterministic guardrail | Post-model filter | Three-phase parser | Pre-model signal reconciliation |
| Output schema | Required threshold field | YAML with validation | Fixed finding schema |
| Evaluation | LLM-as-a-judge, 4 axes | Registry-driven testing | 10-axis judge, 2 windows, 4 replicates |
| Human in loop | Accept/reject + PR review | Reads result | Reviews findings |
- Generating output is easy. Making it trustworthy is the engineering work.
- The representation of data fed to the model matters as much as the model itself.
- Any rule you depend on must be enforced in code, outside the model.
- Schema constrains what the model must justify — use it at reporting, not exploration.
- "Did this help" must be a number, not a feeling.
- Autonomy and safety engineering must scale together.