▸ T0 · main thread · write constrained prose.sh post from build log · interactive
14400 seconds. That is how often the draft generator fires, and it does not care if I am awake.
Two launchd plists. One keeps the uvicorn server alive on port 8766 with KeepAlive, so if it crashes macOS restarts it. The other wakes up every four hours, generates a batch of drafts, and goes back to sleep.
This is what infrastructure actually looks like for a solo content operation: two XML files, a system level scheduler, and none of the "I need to remember to run this" overhead. launchd handles server resurrection. The StartInterval handles generation cadence. I did not build a cron job because macOS does not guarantee cron fires after sleep. I did not use a background thread because I do not want the generator coupled to the server process. Separate jobs, separate failure modes, separate restart logic.
The tradeoff worth naming: four hours is coarse. You miss windows. But it also means the LLM backend is not getting hammered constantly, and there is a buffer between generation and review before anything ships. That is not a limitation, that is the design.
launchd gets a bad reputation because the plist format is verbose and the documentation is scattered. But it is the right tool here. It survives reboots, handles KeepAlive semantics correctly, and does not require a process manager running in the background to babysit it. The alternative was pm2 or a systemd port, both of which add dependencies for zero gain on macOS.
Four hours. Two files. Zero babysitting.