How I Taught My Scheduling Agent That Empty Is Not Free
By Ergini, Software & AI Developer
TL;DR
Caldra, the scheduling agent I built, reads the inbox, checks Google and Microsoft calendars and proposes times for a person to approve. When a user changed their password, the calendar's refresh token died, the query came back empty, and the agent read 'no events' as 'completely free', proposing a time on top of an existing meeting. Because it has no tool that writes to a calendar, the damage stopped at a proposal. The fix was one rule: an authentication failure is a hard stop that reaches the user, never an empty result.
A perfect day, as the agent saw it
Put yourself where the agent sits. An email thread arrives asking for a meeting next week. You check the person's calendar with the tool you were given, and the answer comes back: no events. None on Tuesday, none on Wednesday, none at all.
You do exactly what you were built to do. You read the owner's preferences, you rank the open slots, and you propose the best one with a short, confident reason. Every step is correct. The reasoning is clean. And the time you propose sits directly on top of a meeting that was always there.
That is what happened in Caldra, the scheduling agent I built and use for my own calendar. It reads the inbox, checks Google and Microsoft calendars, and proposes times that fit the owner's rules. On that occasion it proposed a slot that was already taken, and nothing in the model was broken.
What was actually on the other end of the tool
A user had changed their password. When that happens, the OAuth refresh token Caldra holds for their calendar dies with the old password. The next calendar query could not authenticate, and by the time the result reached the agent, it had become the most innocent thing a calendar can say: nothing.
The agent read "no events" as "completely free". From inside the conversation, those two look identical. An empty list is an empty list. The model had no way to know that the list was empty because nobody had been allowed to look.
I want to be precise about where the fault was, because it is the whole lesson. The model reasoned correctly from what it was given. The bug lived one layer down, in the plumbing between the calendar provider and the agent, where a failure had been allowed to travel upward disguised as an answer.
Why it stopped at a proposal
This failure was cheap, and that was not luck. It was the one design decision in Caldra I would defend hardest.
The agent gets four tools: find free slots, read preferences, check conflicts, and propose. There is no tool that creates a calendar event. A proposal goes into Caldra's own store, and a person approving it is what performs the write to Google or Microsoft. So the worst the agent could do with a false picture of the calendar was suggest a bad time to someone who could see their own day.
If the agent had been allowed to book, the same bug would have double-booked the owner and sent an invitation for a time they could not make. Instead, it produced a wrong suggestion. Approval costs the user about a second. It bought the whole system the right to be wrong occasionally without anyone paying for it.
One rule: empty is not free
The fix was a rule, and it is the first one I would give any agent that touches real systems. An authentication failure is a hard stop that surfaces to the user. It is never an empty result.
In practice that means a calendar adapter should not be allowed to answer with a bare list. It should answer with a result that says what kind of answer it is. In TypeScript, the shape is simple:
type CalendarRead =
| { status: "ok"; events: CalendarEvent[] } // looked, found these (maybe none)
| { status: "unauthorized"; provider: Provider } // could not look: reconnect needed
| { status: "unavailable"; retryAfterMs?: number } // could not look: try again laterOnly the first case should ever reach the model as calendar data. The second ends the run for that account and tells the owner to reconnect their calendar, which is what Caldra does now instead of guessing. The third backs off and tries again, and if the calendar stays unreachable, says so rather than proposing anything. An empty list then means one thing only: the agent looked, and the day really is free.
None of this is clever. It is the difference between "nothing there" and "could not look", written into a type so that no future change can quietly merge them again. Test it the boring way as well: a revoked token in the test suite should produce a reconnect message, never a proposal.
The same bug wears other clothes
Once you have seen it, you find it everywhere agents touch real systems. A CRM lookup that fails and becomes "new customer", so a long-standing client gets a first-contact email. A stock API that times out and becomes "out of stock", so a sales assistant turns down an order the warehouse could have shipped. An inbox search that returns nothing because a label was renamed, so a follow-up never goes out.
Automations have the same disease without a model involved: a workflow that reports success every run while doing nothing, which is the story behind automation rescue. The model just makes it more dangerous, because it turns the silent failure into a fluent, well-argued answer. My notes on tool calling in production go further into how tool results should be shaped so a model cannot misread them.
Four bugs, and none of them fixed with a prompt
The revoked token was one of four things that broke first when Caldra met real calendars, and looking back, the list is humbling in a useful way. Recurring meetings shifted by an hour after a daylight saving change, because expanded instances were cached across the transition; now the system stores UTC with the original time zone and re-expands. Pulling calendar history at signup hit rate limits at the worst possible moment, so backfill moved into a queue with exponential backoff. And the agent kept proposing 8 a.m. on Mondays, which was valid and unwanted; the fix was scoring slots against the owner's preferences in code, so the model ranks a shortlist instead of an availability grid.
Not one of the four was solved by rewording a prompt. They were a data model, an error contract, a queue and a scoring function. That is the part of agent work nobody puts in a demo, and it is most of the work. The full architecture is in the Caldra case study.
What I carry into every agent now
When I build an agent that reads a company's systems, the first conversation is not about the model. It is about the tools: what each one returns when it works, what it returns when it cannot, and which of those failures must stop the agent and reach a person. The answers go into types and tests before the agent is allowed to reason about anything.
It is a small rule to have learned from a single wrong suggestion. But an agent that is sometimes wrong and always honest about what it could not see is one you can let near real work. That is the kind I build, through AI agent development.
Frequently asked questions
Why do AI agents treat a failed tool call as an empty result?
Because the tool layer often hands the model something that looks like an answer. If an adapter catches an authentication error and returns an empty list, the model has no way to tell 'nothing there' from 'could not look', and it will reason correctly from the wrong premise. The fix belongs in the tool contract, not in the prompt: failed lookups must come back as failures.
How should an AI agent handle an expired or revoked OAuth token?
Stop, and say so. The lookup that hit the dead token should end the agent's run for that account, tell the user to reconnect the account, and log the event. Retrying with the same token will not help, and passing an empty result to the model is the worst option, because it turns a visible outage into a confident, wrong answer.
Why can't Caldra book meetings on its own?
By design, the agent has no tool that writes to a calendar. It can find free slots, read preferences, check conflicts and propose, and the proposal is stored in Caldra's own database. A person approving the proposal is what performs the calendar write, at the cost of about a second of their time, and it removes the most expensive class of mistake.