We Built Three Feedback Loops. None Collected Anything.

Oct 16, 2026 · 6 min read

We Built Three Feedback Loops. None Collected Anything.
_ □ ×

In August we built three ways for our company brain to learn from its own work: an estimate calibration engine, a log of which skills get used, and a hook that asks “is this worth building?” before we build something. Two months later the calibration store has zero rows, the usage log has zero lines, and the hook never fired once on the day it went live. Meanwhile, the one loop our planning doc described as fully closed turned out never to have run at all.

I’m writing this up because “the AI learns from you and improves over time” is in a lot of pitches right now, and I’ve spent most of a year building a system that’s supposed to do that. Most of the time it doesn’t, and the reasons turned out to be more mundane than I expected.

The table that admitted it

Back in May we wrote a vision doc for how the company should run. Section 5a split every automated loop into three parts. Capture means a sensor reads what happened. Route means the output reaches the right person. Improve means the system itself changes because of what it captured.

Then it listed a dozen of our loops in a table with a column for each. Capture was “YES” all the way down. Route was nearly all “YES.” The Improve column was almost entirely “NO,” with one “PARTIAL” and a few loops marked as closed only within their own little function. The doc’s own test for health was blunt: if improvements only happen when one of the founders notices something, the loop is still open.

One row had “YES” in all three columns: the marketing loop. Competitor data in, a three-agent analysis, a weekly plan, posts published, performance tracked, next week informed. The doc called it the working template that every other loop should copy.

In September we audited our scheduled jobs. Cloud Scheduler had 11 live jobs. Our docs described 20 endpoints. Eight of the documented jobs had a documented schedule, a monitoring entry or a README section, and in three cases a line in our operating-rhythm doc calling them running automation. Cloud Run request logs showed that none of the eight had ever received a single request.

The marketing feedback loop was one of them.

Two bugs made sure they couldn’t have worked even if someone had scheduled them. Three depended on a CLI that wasn’t installed in the container. Six had their route prefix applied twice, so the documented URL would have returned a 404. And our monitoring tool creates a monitor the first time a job checks in, which means a job that never runs never gets a monitor, and nothing ever alerts. We retired all eight and wrote a RETIRED.md explaining what each did and what a rebuild has to fix first.

So the template loop was a design that had been described as running for months.

Loop one: estimate calibration

This one is the most carefully designed and the most empty. The idea: every time an engagement closes, record what we estimated and what it actually took. Regenerate multipliers per category and size band. Only let a multiplier override the default contingency once there are at least three data points for that band. Keep a “golden set” of five to ten closed engagements and replay every proposed calibration against them. A change that makes the replay less accurate doesn’t merge.

I still think that design is right. The golden set file has one row, and the engagement column says “TBD.” The actuals log has zero lines.

The engine depends on two things happening. Someone runs a backfill command against past engagements, and every closed engagement goes through a retrospective step that writes its actuals. Neither of those is part of anyone’s week. The engine was built, tested, and wired into the weekly review, which now honestly prints “no data” for calibration drift. It’s been printing that since August.

Loop two: skill-usage telemetry

We have dozens of skills and commands. I wanted to know which ones get used and which ones nobody touches, so a hook appends a line every time a skill runs, and the weekly review shows the top five and the never-used list.

It’s been live since August 20. The log has zero lines.

The hook’s own header was honest about its limits from the start: it only sees skills invoked through the skill tool, not slash commands someone types, and it records invocation, not outcome. What it didn’t anticipate is that it may not see anything. It reads its input from an environment variable only, with no fallback to stdin. That’s exactly the first bug we found in loop three.

Loop three: the worth-it hook

The worth-it check asks whether something should exist before we spend time automating it (more on that in Before Automating Something, Ask Whether It Should Exist). The hook’s job was to notice when a session starts building new capability and suggest running the check.

It never fired in the day it was live. The rewrite’s docstring lists three independent reasons:

  1. It only read hook input from an environment variable. Claude Code delivers it on stdin. So it exited early on every single tool call.
  2. It only watched the Write and Edit tools. A session that wrote files through a shell heredoc was invisible to it.
  3. It didn’t treat the plans/ folder as a sign of new capability. So even with the first two bugs fixed, it would only fire once code was being written, after the decision to build had already been made.

The third one is the bug I find most instructive. Even working perfectly, the hook was aimed at the wrong moment.

The Python rewrite fixes all three, and it ships muted. It records what it would have fired on, so we can read a week of would-have-fired lines before it interrupts anyone. A hook that fires every session becomes wallpaper, and that’s the exact failure the worth-it check exists to prevent.

Silence looks like a quiet week

Every one of these failures had the same shape. An empty log looks identical to a week where nothing happened. A monitor that never checks in looks identical to a job that never fails.

We kept hitting versions of this outside the feedback loops too. An integration shipped with 153 passing tests while its main feature returned zero results in production every time, because the test fixture invented two response fields the real API never sends. A code review workflow we built “refuted” 10 out of 10 findings, and hand-checking showed at least three were real bugs. The refuter had been told to default to refuted, and it could always retreat to “that’s cosmetic.” After a hosting move, a scheduled keepalive returned a 405 every day because the new scheduler sends GET and the route only accepted POST. A manual test with POST had masked it. (More of these in Eight Small Ways Our Tools Lied to the Brain.)

What we changed is unglamorous. A scheduled job only counts as working once the scheduler’s own logs show it running, never because a manual call succeeded. Tests for an integration get mutation-tested: put the defect back and confirm the suite actually fails. The weekly review prints “no data” or “not measurable” instead of leaving a line out, because a missing line reads as zero. And I now treat an empty store a couple of weeks after launch as a bug report.

The loops that do fill up

Two stores are growing: a decisions log with 42 rulings and a learnings file with 42 entries. Day-end alone has captured 33 learnings since late August.

The difference is boring. Both are written as a side effect of something we already do every day. Day-end runs whether or not anyone cares about learnings, and capturing one is a single extra step at a gate a human is already looking at. Decisions get logged when someone makes a ruling in a conversation that was happening anyway.

The empty loops all needed a new habit. Someone has to run the backfill, someone has to remember the retrospective, a hook has to fire at the right moment in a session. None of those had an owner, and the system had no way to notice they weren’t happening.

Even the loops that fill up don’t improve anything by themselves yet. A learning becomes a rule only after three confirmations, and skill changes arrive as proposed diffs a person merges. By the doc’s own test, most of our loops are still open. I’m fine with that. I’d rather have a system that’s honest about which half of “self-improving” it does.

If you’re evaluating anything sold as self-improving, ask to see the log it learns from and how many rows are in it. If you want a company brain built with that kind of honesty about what’s working, tell me about your team.

More Thoughts

Before Automating Something, Ask Whether It Should Exist

Oct 16, 2026 · 6 min read

The first verdict our build gate ever gave was DON'T, for a vacation reminder job. It got the right answer by asking the …

Read more

Eight Small Ways Our Tools Lied to the Brain

Oct 16, 2026 · 5 min read

The tools feeding our company brain broke it more often than the model did: transcription, email, notes and auth kept …

Read more

Our AI Created 1,042 Tasks in Four Months

Oct 16, 2026 · 6 min read

Between June and September our company brain created 1,042 tasks from our email and meetings. Most of them were noise. …

Read more

Our AI Drafts Every Email and Sends None of Them

Oct 16, 2026 · 5 min read

In four months our company brain surfaced 626 email threads where we owed a reply, and it can draft any of them. It …

Read more