$ cat
I Paid a Model to Notice Nothing Had Happened
The first scheduled agent I built did exactly what the documentation said it would. It woke on a timer. It loaded its context. It looked around, decided there was nothing worth doing, and went back to sleep.
It was right. There was nothing worth doing. Most days there never is.
I ran it for weeks like that, and the thing I remember is not a crash or a bad output. It’s the absence of one. Nothing went wrong. The agent behaved impeccably, and credit drained into days where the correct answer was silence. Tokens disappearing without anything happening is a particular kind of failure, because the system never signals it. You have to go looking.
The bill is what made me look.
The wake-up was the model call
What I found when I read my own code was that I had fused two things.
Think about the person you’d otherwise hire for this. They open a dashboard. They scan a list. They compare it against roughly what normal looks like. They conclude nothing has changed and close the tab. Nine times out of ten (more, if the job is any good) that is the entire job for that hour.
That part is noticing. It’s constant, mechanical, and for most jobs worth automating, it is a database query.
Then occasionally something has changed, and the work becomes a different kind of work: reading the situation, weighing it against last month, deciding whether it’s worth interrupting a human being. That part is judgment. It’s rare, it’s expensive, and it’s the part worth paying a model for.
My agent did both inside one model call. The question “is there anything to do here?” was being answered by the model, after it had already loaded my context to answer it. The wake-up was the thought. Every quiet Tuesday, I was buying a considered opinion that nothing had happened.
Perception is constant and cheap. Cognition is rare and expensive. I had wired them into the same event.
The rebuild
Here is the machine I replaced it with, in plain terms. A script wakes on the schedule and runs a query. No model anywhere yet. A few lines of ordinary code look at the result and decide whether anything happened: that check is the gate, and when something has happened, the gate trips. Only on a tripped gate does the model run: once, as a judge, reading what the gate caught and deciding whether it deserves a human’s attention; if it does, a message goes out, which is the delivery. On a normal day the gate doesn’t trip, no model runs, nothing is delivered, and the whole run costs one line in a journal.
In code that’s five steps, and every job I’ve written since has the same shape:
GATHER deterministic. SQL, an API call, a shell
command. No model.
GATE plain Python. Did anything happen?
If not, stop here. Silently.
JUDGE one model call. Tools off. Once.
DELIVER gated. Silence is the right answer
most days.
RECORD journal, archive.
Read that with an eye on where the model appears: once, in the middle, on data it did not fetch, producing text that does not act.
The gate is the whole trick and it isn’t clever:
stale = [r for r in rows
if r["age_hours"] >= 24 and r["size"] >= FLOOR]
note = "%d/%d stale" % (len(stale), len(rows))
mode = gate(EMP, TASK, tripped=len(stale) >= 2,
summary=note)
If it doesn’t trip, the job writes one line to a journal and exits. Here’s what that means across two runs of the same job:
Same code, same schedule, same query. Which day you get is decided before any model is involved.
The word carried an architecture
I went looking for prior art expecting to find none, and walked straight into thirty years of it. What I’d built was an alert rule. A cheap check runs constantly, and only when it trips does anything expensive wake up. It’s most of what Prometheus is for, and every monitoring system ever built works this way.
So the interesting question isn’t why the pattern works. It’s why the people building agents never reached for it.
I think it’s the word. An agent is something that wakes up, looks around and decides. That’s what the noun means, and it’s a fair description of what a model does during a task. But a noun isn’t an architecture, and this one arrived carrying one. If the agent is a mind, the mind has to be the thing on the timer. Nobody drew that on a whiteboard. It came free with the metaphor, and a generation of tooling inherited it without anyone choosing it.
Then the gate got loud
The rebuild fixed the money. It did not fix the next thing.
One of my jobs watches search traffic and tells me when a page starts losing visitors. The rule I gave it was simple: compare the last few days against the trailing average, and speak up if clicks have dropped by more than a quarter.
That rule is sensible for a page getting a thousand clicks a day. It is nonsense for a page getting nine.
A page on nine clicks a day that drops to six has lost a third of its traffic. Nothing happened (three people didn’t turn up), but the arithmetic says catastrophe. And a site has hundreds of pages like that, wobbling by two or three clicks a day, forever.
So it told me something was wrong eight days running. Every single time, the same cause: one to three tiny pages moving by about three clicks. The gate was doing precisely what I’d told it to.
Eight days is roughly five days past the point where anyone still reads the messages. And once nobody reads them, the quality of the judgment inside is irrelevant: judgment nobody reads is not judgment. The job was worth zero while running perfectly.
My instinct was to rewrite the prompt. Tell it to be more conservative. Tell it to consider normal variance, to only alert on serious issues. I tried that first, and it half-worked in the way prompt patches half-work: better for a few days, then not. You cannot ask the judge to compensate for a gate that feeds it garbage.
The fix was three constants:
# below ~20 clicks/day a 3-click wiggle reads as -30%
PAGE_BASE_MIN = 20
# one page's daily variance isn't sitewide news...
MIN_PAGES = 2
# ...unless it sheds 25+ clicks/day outright
SEVERE_ABS = 25
No prompt changes. It hasn’t false-fired in the month since.
Two patterns did the actual work there and I now reach for them by reflex. Require a floor before a ratio: percentages on small numbers are noise generators, so “twenty percent down” means nothing until you also say “and it was at least N to begin with.” And require breadth or severity: either two things moved, or one moved a lot.
One more thing false alarms teach you: gate rules accumulate. Every false alarm wants to add a condition, and if you’re careless you end up maintaining the rules engine you were trying to avoid. Few thresholds, named at the top of the file, each commented with the specific false alarm that created it, so a year from now someone can tell which ones have outlived their reason.
When the agent is noisy, tune the SQL. Not the prompt.
The failure I couldn’t see
The gate was quiet. That was the point. It was also the problem.
A gate that doesn’t trip produces silence. And the cases it wrongly skipped are invisible: the model never saw them, so nothing exists that could tell me what I missed. The delivery step is silent by default too. Two layers of silence, stacked. I had built a system whose most likely failure leaves no trace.
I can’t fix that. So I pay a small tax on it instead, in two parts.
The first is a log. Every gate decision gets written down, tripped or not, with the numbers that produced it. Months later I can still go back and see what got skipped, and why.
The second is a sample. Two percent of the runs that don’t trip go to the model anyway, and I never deliver those. They’re an audit. If the model keeps saying “this is real” about runs the gate called quiet, my thresholds are too high and I’m missing things. It’s the only way to catch a miss. And it costs two percent of what asking every time would.
Where the gate stops holding
It stops holding when noticing itself requires judgment. Everything above assumed I could name the metric (rows past an age, a status that didn’t change, a number below a floor), and most scheduled monitoring is like that. But one of my own jobs sits directly on the line where it isn’t. It reads message threads and flags the ones that look genuinely stuck. The deterministic half is easy: nobody replied in four hours. The half that decides whether a thread is actually stuck (rather than resolved somewhere else, or never really a question) is judgment, and I pay for it. There is no SQL for that.
At that point there are two bad answers, one at each end. Write the query anyway and you get a gate that’s deterministic and blind. Ask the model every time instead and you’ve rebuilt the agent from the top of this post: paying a considered opinion for the news that nothing happened, the exact sin the gate exists to prevent.
Between them sits a middle option, and it has a name: cascading. You put a
small, cheap model where the if statement would go, and keep the expensive
one for the cases it escalates. Researchers have measured this for years:
FrugalGPT and RouteLLM are the two papers to read. It costs more than an if
and much less than asking every time, and it’s the right answer when what
you’re watching for is a change in language rather than a change in a number.
What I’d resist is jumping straight there out of habit. Most jobs people want automated are metric-shaped. For those a threshold isn’t a compromise; it’s the right tool that everyone forgot they had.
So here is the line: the cheapest thing that can do the noticing stands watch (SQL when the change is a number, a small model when the change is language), and the expensive model only ever judges what the watch brings in.
What I run now
One of these jobs reads a production system and opens pull requests I review
before anything ships. None of them can send, merge, or deploy on their own.
Nearly half contain no model call at all: not “the model wasn’t invoked
today,” there is no model call in the code. Those are the jobs whose output
needs no judgment (a reconciliation pass, a health check), so a judge step
never got written; they are SQL and Python end to end. The rest call a model
exactly once: one turn, tools disabled, and only after a plain if statement
decided the day was worth thinking about.
I chased this for the money, and the money is the part that stopped mattering. Partway along, my jobs moved onto a Claude Code subscription rather than metered API calls, so a wasted wake-up no longer costs anything extra. The economics that put me on this trail walked off it. And the design paid anyway. What the gate actually bought was three things I care about more.
One answer, every time. “Did anything change?” is decided in code I can read. When a job misbehaves I go and read the SQL. There’s no transcript to reconstruct.
Fewer dice. Every model call is a probability, and probabilities multiply. Ten of them at ninety percent each gets you thirty-five percent. My jobs make one call, so there’s nothing to multiply.
Somewhere to put the fix. When the job is wrong, it’s wrong in three constants at the top of a file, not somewhere inside a paragraph of English.
Hiring one
I packaged the harness (everything around the model call: the schedule, the gate, the audit sampling, the guardrails) as Beadle. MIT, four working examples, no framework and no platform.
Beadle’s unit isn’t an agent, it’s an employee: a job hired to do one narrow thing on a schedule and stay silent unless something needs you. Which is why the install line reads the way it does:
pip install beadle
beadle init my-employees
You don’t have to write the job yourself. The repo ships a slash command for Claude Code, so from inside the project you type:
/hire a job that tells me when an invoice
goes unpaid past 30 days
and it writes the whole thing: the job description, the gather step, the gate, the schedule.
But it won’t start until you’ve answered four questions, and those are the ones worth sitting with. What number moves if this works? Who receives the output, and what will they do about it? What has someone already suggested that you’ve decided against, and why? Because without your rejected ideas a scheduled job will re-propose the same one every week forever. And what’s the data source, and is that credential read-only?
You need to know what you want done. You don’t need to write the code.
A harder problem waits behind this one, and I’ll write it separately: a job that produces plausible output on schedule looks identical, from the outside, to one that’s useful. Getting the gate right makes it cheap and quiet. Whether it’s worth having takes going back a day later and checking whether anything downstream actually moved.
But that comes after this. First, stop paying a mind to notice what a query can see. The word agent describes what the model does when it thinks. It was never a licence to make it think about nothing.