An Agent With Eight Minutes

On one afternoon in July I had an agent firing every ten minutes on a schedule, from half past one until a quarter to six. Each firing did one small unit of work across two repos, verified it, logged it, and stopped. Twenty-seven commits shipped to main before the deadline fire wrote its own final report and tore the scheduler down. This is about what it took to make that loop safe to leave alone, and about the two things I got wrong before it was.

Not Cron

The first thing I got wrong was the scheduler.

Claude's credentials on a Mac live in the login keychain, not in a file. Classic cron runs in a background context that cannot reach the login keychain. I checked the direct way: a run with the environment stripped returned "Not logged in." A launch agent registered in the user's GUI session runs inside the login session, with keychain access. I verified that end to end with a one-shot probe that printed a single token when it succeeded.

So the loop is a launch agent, not a cron entry. A second launch agent runs the system's keep-awake utility for the duration, so sleep cannot defer a firing. Obvious in retrospect. It cost me an evening.

One Unit, Bounded

Each firing launches one fresh, bounded process. A hard wall clock of 540 seconds, enforced from outside the process, not by asking it to be quick. A single-flight lock made from a directory creation, so a tick that runs long gets skipped rather than queued and firings cannot stack. The full check suite is banned inside a firing because it takes three minutes of a nine-minute box. Each firing runs one fast check on the one thing it touched.

The model for each firing is the middle tier, not the top. A firing has a hard box, and the middle tier finishes small units reliably where the top tier is more likely to run over. That is a case where the cheaper model is the correct one for a reason that has nothing to do with cost.

The Prompt Is the Brain

The whole loop lives in one prompt that each firing reads cold. The important parts of it are short enough to quote.

You have a fresh context and about eight usable minutes. Do exactly one unit of work, verify it, log it, and stop. The ledgers are the system of record. Trust them, not memory.

Deadline self-check first. Print the local time. If it is already past the cutoff, stop immediately and do nothing.

Never start a unit that cannot finish or reach a safe committed checkpoint within the tick.

If you took any decision a human might have been asked about, add one line under a heading called "Decisions taken without asking."

Be loud. A human is tailing the log to confirm you are alive. Do not work silently.

Work only in the repo named in the header. Never touch the other repo. Never bind its ports.

Keep it tight and honest. No inflation. Never fake completion.

That last pair matters more than anything about the scheduler. An eight-minute agent with no memory has every incentive to report success, because reporting success is the fastest way to end the turn. The prompt has to say, in so many words, that doing nothing honestly is a fine outcome. Otherwise the loop starts padding.

The Honest No-Op

The second thing I got wrong took a hundred firings to see.

A different loop ran the night before, a hardening track across about 28 repos. It shipped roughly a hundred small verified commits before stopping itself at four in the morning. At some point the test-coverage track ran out. The remaining candidates were marginal tests that would exist to make a number go up. What I wrote into the ledger when I noticed: stop the coverage track, it is tapped. Next firings default to honest no-op. Do not pad with marginal tests.

And then the loop caught its own regression. A firing had added a configuration option to make a flaky test suite deterministic and logged it as fixed. A later firing found that the option had been removed from the current version of the test runner and was silently ignored. The fix did nothing, and the suite still flaked. The real cause turned out to be the dev server handing back a stale copy of a file I had just edited. Three firings of confident fixes, one firing of actually reading the failure.

A loop whose most important behavior is doing nothing, and whose second most important behavior is noticing that a previous firing's fix was not one. I did not design either of those in. They showed up in the ledger because the ledger was honest and I read it.

The Takeover

With about ninety minutes left on a hard deadline and only nine firings left in the queue, I paused the launch agent. Then I ran two aggressive per-repo sessions in parallel, each with a takeover prompt that inherited the ledger and the boundaries. That is not a failure of the loop. It is what the loop is for: bounded, predictable progress when I am not looking, and a clean handoff to something faster when I am.

The finalize prompt for the end of the day has one line I would put on the wall. Keep it tight and honest, no inflation. Every loop I run now starts and ends with some version of it, because the alternative is a ledger full of green that means nothing.

-- Justin Higgins. Software Engineer, Midwest. Gave an agent eight minutes at a time and taught it to say nothing happened.


Companion pieces: Five Hours Unattended - the longer form of the same discipline. The Rules Are Downstream of the Work - where "no inflation" came from.

Reactions, disagreements, war stories: jchigg2000.dev@gmail.com