Why Overnight Coding Runs Stop After 15 Minutes: How Checkpoint Leases Fix It
Unattended agents usually stop prematurely after 12 minutes due to conversational politeness bias or stall indefinitely on a hung subprocess. Here is how AgentAFK checkpoint leases solve both.
When developers try to run a coding agent overnight in an ordinary chat window, one of two failures almost always happens by 2:00 AM. Either the model completes the first sub-step after 12 minutes and stops to ask "Would you like me to proceed with Step 2?", or a single test subprocess hangs on stdin/network I/O and blocks the entire night's queue. agentafk.si eliminates both failure modes using explicit completion predicates and heartbeat checkpoint leases.
Why chat-tuned models bail out early on long assignments
Instruction-tuned models are heavily rewarded during RLHF for short, conversational turns that defer to the user after producing a partial summary. In an interactive session, asking for confirmation is polite; in an unattended 6-hour overnight queue, it wastes the entire night.
In agentafk.si, a discrete cognitive or act step Loop node does not decide when the queue is finished by emitting conversational prose. Instead, the Loop controller evaluates an external, machine-checkable exit_condition after every turn:
- Premature-stop refusal: If the model stops calling tools while
verifier.pystill reports unmet specification rules andcalls_used < call_budget, the Loop controller automatically re-prompts the node with the exact list of remaining unverified rules. - Bounded subprocess leases (
lease_ttl = 300s): Every tool execution acquires a monotonic lease with a strict heartbeat deadline. If a test runner emits no progress heartbeat beforelease_ttlexpires, the supervisor kills the process group, records a typedsubprocess_lease_expiredreceipt, and transitions cleanly without hanging the queue.
# AgentAFK Loop Supervisor: Enforces Verifier Exit Conditions & Lease Deadlines
def supervise_overnight_task(task_spec: dict, runner, verifier) -> dict:
calls_used = 0
max_calls = task_spec["max_calls"]
while calls_used < max_calls:
calls_used += 1
step_out = runner.run_step_with_lease(
assignment=task_spec["assignment"],
lease_seconds=task_spec.get("lease_seconds", 300)
)
if step_out["status"] == "unknown_external_outcome":
return {"state": "paused_for_reconciliation", "calls_used": calls_used}
verdict = verifier.check(task_spec["invariants"])
if verdict["all_passed"]:
return {"state": "finished_verified", "calls_used": calls_used, "verdict": verdict}
runner.update_counterexample_memory(verdict["minimal_counterexample"])
return {"state": "halted_at_call_ceiling", "calls_used": calls_used}
| Navigation | Post Title | Routes |
|---|---|---|
| Previous Post | Overnight Proof Runs with Cheap Models: How Verification Gates Beat Raw Model Price | HTML · Markdown |
| Next Post | Running Unattended Overnight Solves on Ollama Cloud Without Runaway Spend or Lost Work | HTML · Markdown |
| Domain Blog Index | Return to agentafk.si/blog Index | feed.json · feed.xml |