AI 7 min read

What I Actually Learned Building a Data Agent

This is the first post in a series about building data agents — systems that let people ask questions about business metrics in natural language. I've built more than one of these now, and these are the lessons I wish I'd internalized sooner. Not because I've figured it all out, but because the mistakes repeat in recognizable ways.


I thought most of the work would be about prompts: better instructions, more examples, a tighter system message. That turned out to be a small part of the job.

The harder and more useful work was building the harness around the model — the tools, schemas, routing rules, evals, trace logs, safety checks, and response contracts. That harness is what makes an agent reliable or not. The model is just one component.

The model is not the product

It's tempting to think of an AI agent as "an LLM with tools." Technically true, but not a useful mental model for building one.

A data agent is closer to a small operating system for answering questions. Around the model sit data contracts, tool interfaces, permissions, entity resolution, query safety, response formatting, observability, and evaluation.

When the agent failed, the failure was rarely because the model was "bad." It was because the harness gave it an ambiguous choice, missing context, too much freedom, or a misleading tool result.

For example, when the agent picked the wrong query path, the fix was not always a stronger prompt. Sometimes the real fix was making tools less overlapping. Sometimes it was returning better error messages. Sometimes it was removing a subagent entirely because it didn't have enough context to do the job.

That changed how I debug. I stopped asking, "Why didn't the model follow the instruction?" and started asking, "What did the harness make easy, hard, or invisible?"

Tool design is UX — for the model

This was one of the biggest shifts in thinking. Tools are not just backend functions. They're the interface the model uses to understand and act on the world.

Humans struggle with bad UI. Models struggle with bad tool interfaces. A vague tool name, overlapping responsibilities, free-text arguments, or inconsistent error shapes cause the model to do strange things — not randomly, but because the available actions are poorly shaped.

The tools I ended up trusting shared a few properties:

Clear jobs. One tool answered single-entity metric questions. Another handled comparisons. Another handled record-level drilldowns. Another generated SQL only when curated tools couldn't answer.

Controlled inputs. Enums, IDs, limits, and structured filters are far more reliable than vague strings.

Self-contained results. If a tool returns an ID without the name, or a count without the scope, the model has to infer too much. A good tool response gives the model enough to answer without unnecessary follow-up calls.

Useful errors. "Not found" is less helpful than a structured error that tells the agent whether to retry, narrow scope, ask the user, or stop.

I started thinking of tool schemas the way I'd think about any API a human developer consumes. A clean tool interface does more for reliability than another paragraph in the prompt.

Prompting is a scarce resource

Prompts matter. The agent needs to know the domain, security rules, preferred routing, response style, and when to ask for clarification.

But prompt space is not free. Long prompts create maintenance cost and internal contradiction: one section says "be concise," another says "include a table," another says "always explain your assumptions," and soon the model is reconciling a policy document instead of answering the user.

A pattern I had to learn: when adding a rule, delete or merge another rule.

The first instinct after any mistake is to add a new instruction. That works for a while, but it becomes a prompt landfill. Every failure leaves behind a sentence. Over time, the prompt becomes harder to reason about, harder to test, and easier to contradict.

Some of the best fixes were removals. Removing a confused subagent. Deleting duplicate guidance. Moving logic out of prose and into deterministic code. Replacing five reminders with one better tool contract.

The prompt should contain durable principles and high-signal domain knowledge. It should not become the place where every bug goes to hide.

Evals should come before confidence

I learned this one the uncomfortable way.

When an agent gives a bad answer — especially one that feels obvious — it's easy to patch the prompt immediately. The fix feels self-evident. But without an eval, you often don't know whether you fixed behavior or just changed wording.

A trace can show a failure. It doesn't automatically prove the general fix. For that, you need at least a small repeatable test: the triggering case, a nearby variant, and a regression case that should not change.

The more I worked on the harness, the more I valued a simple rule: no behavioral patch without a trace, and no shipped confidence without an eval.

That doesn't mean every change needs a massive benchmark. Small targeted evals are enough for many issues. But there should be some reproducible evidence. Otherwise, the changelog becomes a diary of intentions instead of a record of behavior.

The trace is the source of truth

Agent bugs are hard to review from memory. A user says something, the model thinks, it calls a tool, the tool returns a payload, the model interprets it, another tool call happens. Somewhere in that chain, the answer goes wrong. If you only look at the final message, you miss the system.

A surprising number of failures that looked like "hallucination" were really trace-level issues:

  • The model had the right tool but used it with the wrong entity.
  • The tool returned a confusing but technically valid result.
  • The model saw two numbers that seemed inconsistent and invented an explanation instead of admitting the data looked inconsistent.
  • A metric looked wrong, but the underlying environment had different data than my test fixture.

Reading traces changed the way I define bugs. A report shouldn't just say "the agent gave a bad answer." It should say where the failure entered the system. Was it routing? Entity resolution? Data freshness? Tool contract? SQL generation? Response formatting? Ground truth?

Until I can place the failure in the stack, I probably don't understand it.

Data agents sound authoritative — and that's the danger

A data agent has a special problem: it sounds like a database.

If it gives a small active-user count for a reporting period, the reader treats that as a data fact, not as model prose. That means the agent has to be careful about the difference between retrieving, computing, interpreting, and guessing.

The model should not be inventing metric semantics. It should receive them from the semantic layer, metric catalog, or tool response. If a field says active_users, the agent needs to know what "active" means in that context. If a percentage is returned as 0.3, the agent needs to know whether that's 0.3% or 30%. If two counts appear contradictory, the agent needs a way to inspect definitions or admit uncertainty.

One painful lesson: an answer can be fluent and still be the wrong kind of answer. When the data looked inconsistent, the agent sometimes fabricated a plausible business explanation. The better behavior is: "These numbers appear inconsistent based on the fields I retrieved. I wouldn't treat this as conclusive without checking the metric definition or source data."

For data agents, honesty is a feature. "I don't know yet" is almost always better than a confident explanation.

Give the agent fewer ways to be clever

I underestimated how much damage comes from giving the agent multiple plausible paths.

Should it use the KPI tool or SQL? Should it search entities first or inspect schema? Should it ask the user or infer from context? Every extra path creates routing ambiguity.

The agent became more reliable when the harness made the preferred path obvious: for standard metric questions, use the fast KPI tool first. For entity names, resolve before querying. For broad ambiguous questions, clarify scope. For ad-hoc analysis, fall back to SQL with safety checks. For oversized results, return a preview instead of flooding the model context.

The goal isn't to make the model powerless. It's to reduce unnecessary degrees of freedom so the model spends its reasoning on the user's question, not on navigating a messy internal API.

The same logic applies to subagents. A subagent sounds like an obvious upgrade — give the task to a specialist. But a subagent is only useful if it has the right context and a clean contract with the parent. Otherwise, it becomes an expensive place to lose information. In one case, removing a subagent and letting the parent use a clearer tool directly made the whole system more reliable.

Simpler architectures are easier to debug, easier to evaluate, and often just better.

Security can't live in the prompt alone

For a data agent, security cannot be a prompt-only concern.

The model should be told the rules, but the system should enforce them anyway. Tenant scoping, read-only access, SQL validation, and column restrictions need to happen outside the model. The prompt can say "never query another tenant's data." The tool layer still needs to inject the scope. The database layer still needs to validate it. The logs still need to show what happened.

The safest pattern is defense in depth: the user request carries the scope, the backend validates it, every tool reads scope from trusted runtime context (not from the model), queries are parameterized with that scope, and returned records are checked before being surfaced.

Less glamorous than prompt engineering, but it's the difference between a demo and a system people can trust.

What I'd do earlier next time

If I were starting a third agent, I'd invest earlier in four things:

Design the tool interface slowly. Names, parameters, result shapes, and error contracts are foundational. Changing them later is expensive.

Build evals alongside the first demo. Not a giant benchmark — just small trace-driven cases covering routing, entity resolution, security, and common metric questions.

Make traces and changelogs part of the development loop from day one. If a change can't be tied to a trace or eval, it's a hypothesis, not a fix.

Keep a strict prompt budget. Every instruction should earn its place. Domain knowledge belongs in the prompt only if it's durable, frequently needed, and hard to enforce elsewhere.


The main lesson

Building data agents taught me that AI engineering is less about making the model smarter and more about making the system around it clearer.

Clear tools. Clear data contracts. Clear security boundaries. Clear traces. Clear evals. Clear response expectations.

The model can do impressive reasoning, but it needs a well-shaped environment. When that environment is vague, the model fills gaps with fluency. When the environment is clear, the model becomes genuinely useful.

Reliability is not one big prompt. It's many small contracts, enforced at the right layers, improved one trace at a time.

Next in the series: how I structure tool schemas and why the error contract matters more than the happy path.

Details, names, and numbers have been altered; the lessons are real.