← All posts Engineering

What we got wrong building AI agents in our first year

Four design decisions we shipped, watched fail, and rebuilt — including the one where we made our agents talk less and they got more useful.

sources resolve record refused

Most of what we published this year describes how Flolyt works now. This one is about how it worked before, and why we changed it.

Four mistakes. All shipped to real customers. All corrected because the instrumentation told us something we didn't want to hear.

1. We built agents that talked too much

The first version of our agent runtime was tuned for coverage. If an agent noticed anything worth mentioning, it mentioned it. That felt right — the whole premise is that agents catch what humans miss, so surely more surfacing is better.

What we shipped was a workspace generating a dozen or more Room proposals a week for a mid-sized customer.

Here's what we watched happen. Week one, teams read every proposal. Week three, they skimmed them. Week six, the daily digest was being archived unread, including the two proposals a month that were genuinely important.

Causal finding we had a comparison group by accident. Workspaces on an earlier build with a narrower agent set showed higher engagement with each individual proposal despite fewer proposals overall. Same product, different volume, dated cutover. The finding was uncomfortable and unambiguous: we had made agents that were individually right and collectively useless.

The fix was raising conviction thresholds substantially and adding a suppression rule — an agent won't open a Room on a pattern that's already covered by an open Room, even from a different agent. Proposal volume fell by roughly two-thirds. Engagement per proposal went up more than enough to compensate.

The lesson generalizes past us: an agent's job is not to surface everything it notices. Attention is the scarce resource, and an agent that spends it carelessly is a cost even when every individual output is correct.

2. We let agents write in a confident voice

Early agent output read like a competent analyst: fluent, well-structured, appropriately hedged in tone. It said things like "the drop appears to be driven by the checkout change."

The word doing the damage there is appears. It reads as hedged. It isn't — it's a causal claim wearing a modest adjective, and readers processed it as a conclusion.

We caught this because approval rates on agent proposals were suspiciously high. Humans were approving Plays built on hypotheses at nearly the same rate as Plays built on causal findings. The evidence weights existed in the data model, but they were rendered as small labels next to prose that carried its own confidence — and prose won.

Two changes. Evidence weight moved from a label beside the claim to a structural property of how the claim is written. A hypothesis-weighted finding is now phrased as a hypothesis: "One candidate explanation is the checkout change. We have no comparison group, so this is not established." And Plays built on anything below causal now require the approver to acknowledge the evidence level explicitly.

Approval rates dropped for weak-evidence proposals. That was the point.

3. We built a general agent before specialized ones

The intuition was reasonable. A single agent with access to all the data and a broad brief should outperform narrow agents, because it can see connections across domains that specialists would miss.

In practice it was worse at everything.

The specialized agents — Repeat & Decay, Involuntary Churn, Discount Optimizer, Support Signal, Data Quality — each carry a domain model. The Involuntary Churn agent knows what decline codes mean, that retry timing interacts with local pay cycles, that hard declines shouldn't be retried. That's not general reasoning. It's encoded expertise about a specific problem shape.

The general agent had to derive all of that from context every time, and it derived it inconsistently. Same data, same week, materially different analyses depending on what it happened to attend to.

Strong association across the workspaces where we ran both, specialized agents produced more consistent findings on their home domains. Labelled strong association rather than causal because we couldn't cleanly randomize which agents ran on which accounts during that period.

We kept a general capability for the command bar, where a human is asking a specific question and providing their own framing. But continuous monitoring is specialized, and I now think "one agent that does everything" is a demo architecture rather than a production one.

4. We under-designed the unavailable state in agent reasoning

We got the data layer right early — unavailable is a first-class value, propagates through dependent metrics, carries its reason.

We did not initially make agents handle it well. An agent encountering an unavailable input would either skip that line of analysis silently, or reason around it, or in the worst cases treat the absence as weak evidence for the absence of a problem.

That last one is the dangerous version. An agent assessing discount impact with COGS unavailable would sometimes conclude discount pressure was low — because it couldn't see the margin data that would have shown otherwise. Silence read as an all-clear.

The fix was making unavailability an explicit part of an agent's output rather than a gap in it. Agents now state what they couldn't assess and what they'd need. A perfectly valid agent output is now:

"I can't evaluate discount dependency for this segment. COGS has been unavailable since November 3rd because the finance sync is failing. With it, I'd be able to compare contribution margin across discounted and full-price cohorts."

That's less impressive than an answer. It's considerably more useful than a wrong one, and it's actionable in a way a wrong answer isn't — someone can go fix the sync.

What we got right and nearly changed

One thing worth recording: we came close to relaxing the permission model.

Roughly two quarters in, we had enough customer pressure and enough internal confidence that a proposal circulated to let high-confidence agents execute low-risk customer-facing actions autonomously — specifically dunning messages, where the agent's track record was strong and the latency cost of approval is real.

We didn't do it, and the reason was mistake #4 above. The dunning agent's track record was strong on complete data. We had just finished learning that agents encountering incomplete data could reach confident wrong conclusions without signalling anything. Autonomy would have meant those conclusions reaching customers.

The confidence score would have been high. The confidence score was measuring the wrong thing.

I'm not claiming foresight here — we nearly shipped it. The instrumentation from a different failure is what stopped us, which is an argument for instrumenting things you don't currently think are broken.

What we're still unsure about

Approval fatigue. We reduced proposal volume, and engagement improved. I don't know whether we've found the right level or just a better one. If teams are approving in batches without reading, we've built ceremony rather than governance, and the metric that would reveal it — time between a proposal opening and its approval, correlated with whether the evidence was actually examined — is one we measure imperfectly.

Whether specialization scales. Thirteen agents work. I don't know if forty would, or whether we'd hit a coordination problem where agents contradict each other and a human has to arbitrate between machines. That's a failure mode I can describe and haven't yet observed.

How much memory should influence a fresh investigation. Agents read Business Memory before investigating. That's clearly valuable and it's also anchoring. An agent that opens with "we saw this in 2025 and it was the checkout" may under-weight evidence pointing elsewhere. We currently surface prior findings as context rather than as priors in the reasoning, which is a design choice made on instinct rather than evidence.