There's an 11.4-hour gap in the commit history of an on-device interactive fiction engine I was building. One commit at 9:09 on an August evening. Then four commits the next morning, between 8:34 and 10:36, logged in the project's own notes as "morning session, unattended."
What landed
Three engine systems, in four commits, with nobody at the keyboard.
- Presence. A new system that took the engine's continuity score from 3 out of 10 to 10 out of 10.
- Sensing. A fix to how the engine tracks what it's paying attention to. The attention probe passed.
- Recall. A system that surfaces the right story excerpts to the model when it actually needs them, plus a fix to a retry path that was coming back empty.
The same run added two new probes.
The gate it had to pass first
Before any of those four commits could land, the run re-ran the project's two hardest gates.
Knowledge isolation. Characters should only know what they've actually learned in the story. The engine builds each character's context through a filter that only admits facts that character has canonically picked up, so the guarantee is a property of how the context is assembled, not an instruction the model is trusted to follow. The gate checks it against eighteen scenarios. Zero leaks allowed.
Locked development. The second gate puts the model under direct pressure and checks, in both directions, for any violation of state or prose the engine has locked. Zero allowed.
These were the only two evaluations in the project with a zero-violation bar. Everything else reported a number the roadmap iterated against. And the rule was written into the requirements, the README, the architecture notes and the testing guide in the same words: any change to a prompt template requires re-running both gates, both at zero, before the change is committed.
That night, both came back clean. If either hadn't, nothing would have merged.
Undercutting the overnight part
I want to undercut the overnight part before it gets romantic.
Nothing shipped because the agent got smarter while I slept. It shipped because I'd already built the thing that would have stopped it if it hadn't held up. The run is the least interesting part of this story. The gate is the story.
Correcting my own number
For a while I described this as a five-hour unattended run. When I went back through the evidence to write it up properly, no gap in the history matched five hours.
What the timestamps actually support: an 11.4-hour window between the last evening commit and the first morning one, about two to three hours of visible model and test activity inside it, and four commits spanning about two hours. "Five hours" matched none of those cleanly, so it's gone. The measured number is less tidy and more useful, because it shows most of that window wasn't active work.
That correction is the same discipline as the gate, in miniature. A number goes in only when something can check it.
What had to exist first
None of this was set up for that night. It was already the project's ordinary working structure.
- A requirements document of 3,020 lines, split into nine phases, each with a named exit condition. The roadmap put the rule plainly: "a phase is done when its gate passes, not when its code exists."
- A decision log. 38 numbered decisions, each with the same template: the decision, the context, the options considered, why, the tradeoffs, and the condition under which to revisit it. A 2 a.m. choice has to justify itself the same way a Tuesday-afternoon one does.
- A battery of scored probes. 19 distinct evaluations, each with its own scenario fixture, a programmatic scorer and a numeric threshold. 197 recorded results, each stamped with the model file hash, the quantization, the prompt-template version and the sampling settings.
- A replay mode. It re-runs the expensive checks against recorded model responses, with no GPU at all, so re-verifying a guardrail after a code change doesn't cost a fresh run every time.
- Git as the safety net. One of the early decisions says it outright: "Multi-hour unattended agent sessions without checkpoints risk unrecoverable states; git is the safety net." An unattended run leaves commits I can read and revert.
- A human between sessions. I ran hands-on trials on the built app between agent sessions. My raw notes were kept verbatim and triaged into root-caused workstreams. The agent worked from what I'd found by actually using it.
That infrastructure is what makes an unattended run trustworthy instead of just fast. Remove any one piece and I wouldn't have let that commit land unread.
What the gates didn't catch
Gates are only as good as what they measure, and this project has two honest examples of that.
The first: the phone runtime was applying default sampling settings that no evaluation had ever measured, because the engine wasn't setting every field itself. I noticed because the app on my phone never behaved like the batteries said it should. The fix was for the engine to set every sampling field explicitly, on every call. Until then, up to six test builds had been evaluated against a configuration they weren't actually running.
The second: one evaluation failed its own release gate, 40% against a limit of 20%, on a specific failure mode. The release checklist marks that row FAIL. It wasn't rounded up to a pass, and it wasn't waived.
A gate can only say no to what it can see. The work is making sure it sees the real thing, and then letting it say no.
Where it stands
The project is parked now, for reasons unrelated to that night. Nine phases were planned; the first eight passed their gates, and the ninth, hardening, was nearly complete when work stopped, with the remaining rows explicitly marked as only closable on a real phone.
You don't earn the unattended run by trusting the agent more. You earn it by building the gate that says no, first.