AI Tooling – Making The Scheduler

It’s Reimplemented Cron!

My WhatsApp Game bot needs scheduled tasks. At first it’s database cleanup, to clear down expired mementos and conversation state. I’ll add game completion and tidying when those features are implement. The task for the AI was “Implement Scheduling”.

It’s written a pretty comprehensive Cron implementation which is database backed.

Would I have done this? 20 years ago working on AIX systems I’d have used Cron. Today, professionally, working with Kubernetes I’d look at Kubernete’s scheduler. It could be a quick and dirty thing to add a cron-job – every 15 minutes

DELETE FROM mementos WHERE expires_at < NOW()

I challenged Claude on this, and it did have a valid point. Having one binary gives me easy deployment in my early dev days. I can split out scheduling later when I need to, but for now I can push a container image to my dev server and everything will update. Management is easier.

The mementos will likely move to REDIS, maybe something I should have already done (I was concerned with the cost of REDIS/Elasticache if I used AWS for this, but have a local dev server for now). Tasks like game ending are not so easy to do in Cron without producing a purpose made binary from the Go code.

It did it in 15 minutes

The scheduler took about 15 minutes to write. Interestingly it’s used a Store Pattern rather than bring GORM into the mid-tier (see my earlier post on domain layer design). This could be because it had a previous attempt as reference. This is quite fast.

I had to ask for concurrency (and other things)

The first version had single shot scheduled tasks which would reschedule themselves. This seemed flaky so I asked for recurring job support. The code was adapted and a call was added to main to create the jobs. This would result in a new set of jobs being added every time the program ran. It’s a bug that would be spotted if paying attention to the database, otherwise if this had been Vibe Coded then performance would have degraded over time. I asked Claude to implement an idempotent startup and to reinforce it with a database constraint. The database constraint is keyed on Job Type and a Uniqueness Key, allowing me to scope jobs.

Claude does tell me that job taking is concurrent safe. It uses Postgres’ SKIP LOCKED feature which is a great tool.

I need to manually review the code. Claude has run out of tokens for this morning so I have time (until the family wake up) to look at it. I’ll check that the job taking is a short transaction and state management is used. I’ll need to understand how failed jobs are handled.

It did the tests while I went for a run

A nice thing for the hobbyist – I left it writing the end to end tests for the system while I went for a run. This could be why it’s run out of tokens! I’ve been manually testing so far. I want to refactor the command routing infrastructure to allow me to pass continuation state to the AI Agent code and provide the AI Agent with tools to ask the user questions.

Footnote

On review of the code: There’s a fair amount of duplication in there – two almost identical registry classes for example. I also think it won’t work so well on single-shot jobs when I need them. I can fix these things over time, but I have more valuable things to spend time on now. I’ll ask Claude to factor the 70 or so lines of initialisation code out of main.go then move on.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *