Every engineer's first reaction to "AI routines as a service" is the correct one: this is a cron job and an API call. You could ship it in an afternoon.
You could. The afternoon version works. What follows is the list of things you then build over the next several months, in roughly the order they become urgent — offered not as an argument that you shouldn't, but so the decision is made with the real list in view.
Week one: the afternoon version
// 0 9 * * 1
const result = await model.generate({ prompt: INSTRUCTIONS });
await slack.post(result.text);
This is genuinely useful. Keep going.
Week two: it ran twice
Your scheduler fired while the previous execution was still going, or two
instances came up during a deploy. Now you need an atomic claim — a row in
Postgres you can SELECT ... FOR UPDATE SKIP LOCKED, a lease with an
expiry so a worker that dies mid-run does not strand the job forever.
The lease expiry is the part that bites. Too short and you double-run slow jobs; too long and a crashed worker blocks the queue for an hour.
Week three: it failed and nobody knew
The run threw. The log line scrolled past. Nobody noticed for eleven days.
So: durable run states. Not a boolean, because "failed" is not one thing. You need at least the difference between the gateway rate limited us (try again), the model hit its step budget (the work is too big), the tool refused (permissions), and we have a bug (page someone). Each one implies a different response, and you will get the taxonomy wrong twice before it settles.
Week five: it cost more than expected
One prompt grew, one input got large, and a weekly job spent a month's budget.
Now you need idle detection, wall-clock deadlines, and token accounting persisted per step so you can see which routine did it. Deadlines are more annoying than they sound: you need one signal that composes cancellation, timeout, and shutdown, and you need every layer to respect it.
Week seven: it needs to touch something
Reading is easy. The moment a routine needs a tool, you are building:
- a client for whatever protocol the tool speaks;
- credential storage that is encrypted at rest and never returned to a client;
- a permission model, because "the agent can use every tool the integration exposes" stops being acceptable the first time it does;
- a per-run record of which tool was called with what, and what came back; and
- redaction on that record, because arguments and results contain secrets.
This is where the afternoon project becomes a quarter. Not because any piece is hard, but because there are a lot of pieces and each one has a security review attached.
Week nine: someone wants to know what happened
"Why did the report say that?"
The answer requires the trace: which model, which steps, which tools, what arguments, what came back, how long, how many tokens. If you did not persist it at the time, the answer is a shrug.
Retrofitting observability onto an agent loop is unpleasant, because the interesting state lives inside a loop you have already optimised.
Week eleven: the model changed
The identifier you hardcoded was deprecated. Or a better one shipped. Or you want to compare two on the same work.
Now you want a directory, validation that a configured model still exists, a notion of which models can call tools at all, and a fallback for when the directory is unreachable — because "we can't list models" should not mean "you cannot edit a routine."
Week thirteen: downstream wants to know
Someone asks for a webhook. Which means signing, replay protection, delivery persistence, bounded retries with jitter, a stable id so consumers can deduplicate, and a rule about redirects and private addresses so your webhook sender is not an SSRF gadget.
Webhook delivery is one of those problems that looks like fetch and is not.
What is actually worth owning
None of this is exotic. That is the point — it is all ordinary, all necessary, and all of it is undifferentiated relative to whatever your product actually does.
The genuinely valuable parts, the ones nobody can build for you:
- The instruction. What the responsibility is, what evidence it must cite, what it must never do.
- The permission decision. Which tools this specific work may use, and which may run unattended.
- The judgement. Reading a trace and deciding it is right.
Everything else is a queue, a state machine, an encryption boundary, and a retry policy. Those are solved problems with known shapes and no upside for solving them again.
The honest counter-argument
If your recurring AI work is one job, on one model, with no tools, whose failure mode is "someone notices on Tuesday" — build the afternoon version. It is the right call and you will not regret it.
The list above is what happens when it becomes six jobs, three of which touch external systems, and someone starts depending on the output. That is the point where the plumbing stops being incidental.
Knowing which side of that line you are on is most of the decision.
How Work on Repeat works covers the objects, the execution path, and where each guarantee comes from.