Five Hours Unattended

On a Friday evening in late August I gave an agent a five-hour budget, a repo, and a sentence: focus on code quality, UI, UX, and helping people understand their usage. No regressions. Ship each change through the normal pipeline. Two closing deliverables, a merged main and a standalone HTML overview page.

Then I stopped watching. This is what the ledger says happened.

The Ledger

The run writes to a single markdown file in a gitignored folder. It never rides a commit. It has a table of ranked items and an append-only log with epoch timestamps. That file is the only reason I can tell you any of this. It is also the only reason the run survived what happened at 1:39 in the morning.

The first entry says the run started. The second says it was resumed in a fresh session, because the first session died before the analysis swarm returned. About sixteen minutes in, the analysis phase finished: ten agents, 724 thousand tokens, six minutes of wall clock, 27 eligible items, zero refuted, zero dropped. The baseline was re-checked in that session: 70 tests, typecheck clean.

Then the pacing entries start, and they are the part I did not expect to find interesting.

Pacing

The first pacing note does the math. Total estimate about 17 hours of work against 4 hours 45 minutes left, minus an hour reserved for the closing deliverable. So batch one is the three non-UI security and performance wins that can go inline. The two highest-ranked items, both UI, get pushed into a combined unit with other visual items, to spread the cost of running them as competitions. The log calls this out as a deliberate break from strict rank order and gives the ratios: they rank 0.2 against the next item's 0.13, but they are UI-gated.

Batch one landed in 19 minutes against a 70-minute estimate. The next pacing note says the real times are running about three times faster than the estimates, and re-plans on that.

An hour in, the remaining items either conflict with files a competition is already touching or are estimated at 90 minutes or more. So the run pulls the closing deliverable forward into the gap. New file, zero conflicts, removes the deadline risk. The overview page shipped at about the 45-minute mark, screenshot-verified top and bottom through headless Chrome, with a README pointer. The next entry says: deadline risk on the closing deliverable is gone.

I have managed projects with humans that did not do that. I have run a few myself that did not.

Measured, Not Claimed

Every item that landed has a commit SHA, a test count, and where it matters, a measurement.

Caching per-file line counts so the overview does not re-read the corpus: measured on the live corpus, 5,729 milliseconds cold to 42 milliseconds warm, across 168 projects and 3,440 files. The log also records what was deliberately not done: a client-side memo was skipped because the server cache made it pointless and it would have let the landing page go stale.

Metrics rebuild after the cache expires: 8,072 milliseconds to 42, a 202 times improvement, with the metrics JSON byte-identical to the previous version on the live corpus. The other half of that item, the words view, stayed in the backlog as non-blocking, and the log says so instead of claiming the whole item.

Cold search over twelve thousand transcripts: 5,858 milliseconds to 3,653. A smaller win, reported as a smaller win.

Competitions

The UI and UX items did not go inline. They went through a process I run for anything visual. Two agents build competing versions in separate worktrees, a gate checks that the two really differ in structure and are not cosmetic variants, and a judge scores both against a versioned rubric. The winner ships only if it strictly beats the reigning champion. A tie is a loss.

Five of those ran during the five hours. The log records each judgment with its reasoning. Route-driven navigation beat an accordion 42 to 37 because the routes file was the established direction. A capped server read beat a windowed component 38 to 35. The windowed one was cleaner on the client, but it sat on an uncapped server core re-parsing 248 megabytes per request. A resize-reactive chart beat a measure-once chart 40 to 36, with the note that permanence beats staleness and the one-shot measurement overshot by about 20 percent on phones.

Each of those verdicts records the loser's flaws too, in a per-feature ledger, as the opening for the next run. The run did not just ship. It left a list of what it knew was still wrong.

1:39 AM

The run died on a monthly spend limit. Not the five-hour clock. The account.

Three agent worktrees still held complete, uncommitted work. A competition had been built and had passed its gate and was never judged, because the judge died with the run.

The next log entry is from a fresh session, in a new context, with no memory of the previous one. It read the ledger. Before anything else, it archived all four in-flight patches to a scratch directory. Then it finished the one remaining inline item from its worktree. It checked that the change only moved code around, using a whole-file diff that ignores whitespace, merged it, and recorded 124 of 124 tests green. Backlog 27 of 27.

Then it wrote down what was still open. The unjudged competition, with both patches archived and both worktrees left intact so a later session could judge it.

That is the whole reason the ledger exists. Not for me to read afterward, though I did. For the next context to pick up cold without dropping anything that was in flight. The run did not fail gracefully because it was designed to be graceful. It failed gracefully because the state lived outside the process that died.

What I Would Change

Two things.

The competitions are expensive and the log shows it: two builders, a gate, a judge, for each unit. For a small repo the win margins were narrow, 42 to 37, 38 to 35. I do not think five points on a fifty-point rubric is nothing, but I would want to see a run where the competition changed the outcome more before I defaulted to it.

And the spend limit is the real deadline, not the five hours. I set a wall-clock budget and the account budget fired first. Next time the ledger gets the account state too.

The rest I would keep. Twenty-seven items, all measured, all shipped through the same pipeline I use by hand, and a file that let a cold session finish the job. The tests went from 70 to 124. I was asleep for most of it. I read the ledger the next morning the way you would read a contractor's notes.

-- Justin Higgins. Software Engineer, Midwest. Gave an agent five hours and read what it wrote about itself.


Companion pieces: The Work Looks Like Nothing - the log preserves judgment. Git Could Not Tell Me Who Wrote It - the transcript is the work.

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