← BlogContact

Context Is a Finite Resource: I Built a 24/7 Autonomous Handoff Protocol

There are two ways an AI session becomes unsafe. It can keep running after its useful state has degraded. Or it can die with work still in flight. I wanted the system to handle both without pretending that a prompt is a lifecycle manager.

The analogy I trust is a drivetrain control system. If the controller loses state, the machine does not become autonomous; it becomes dangerous. The same is true of a long-running agent session with hanging subprocesses and no reliable handoff.

Autonomous does not mean unsupervised. It means self-governed.

The protocol has a narrow shape

Built on 2026-09-08, it has three hooks and one detached worker. The old session arms the protocol and responds to a completed turn. A new session adopts the saved state. The detached runner performs the slow work away from the prompt path.

OLD SESSIONNEW SESSIONRead usage blockarm at 75%Stop hookatomic lock, detachSessionStartadopt memoryDetached runnerquiesce, save, spawnConfirmation filememory was readRETIRE ONLY AFTER ALL CHECKS PASSconfirmation exists · PID start time matches · session id and cwd matchquiesced marker exists · no live descendants · no index.lockany failure writes a blocked marker and signals nothing
← swipe sideways to see the full diagram →
The old session does not disappear because a new process was requested. It retires only after the new session proves it read the handoff and the process identity still matches.

Arm on evidence, silently

context_budget_guard.py reads the last transcript message carrying a usage block. Its ground truth is input + cache_creation + cache_read + output, not a guess based on text length. At the threshold, it writes an arm marker and says nothing.

Trigger only at Stop

ctx_handoff_stop.py acts only when the turn is definitively complete: no active tool call, no running subprocess, and an atomic O_EXCL lock. It detaches the runner and exits immediately. Slow work on the prompt path would turn every prompt into a queue.

Quiesce before saving

ctx_handoff_runner.py waits for subprocesses, with a ten-minute cap. It refuses to continue if it finds half-written files such as *.tmp, *.partial, or swap files, or a Git operation in flight such as index.lock, MERGE_HEAD, or rebase markers.

Save, then spawn

A separate claude -p writes the handoff memory, including git status --porcelain and diff --stat. The quiesced marker is written last: its existence means the memory is on disk. Then osascript tells Terminal to start the new session under Terminal.app, not under the old session.

Adopt, verify, retire

ctx_handoff_adopt.py reads the memory and writes a confirmation. It then checks the confirmation, kernel PID start time, session id, working directory, quiesced marker, descendants, and Git lock. Only a fully verified handoff may terminate the old session.

The gates are the product

The protocol does not assume that a successful spawn means a successful handoff. It treats process identity and filesystem markers as evidence. A reused PID fails the start-time check. A session that never reads the memory leaves no confirmation. Either case blocks retirement.

FAULTRESPONSEsubprocess still runningwait; cap the wait;abort the handoffGit commit mid-flightblocked marker;do not quiesceuncommitted workwrite porcelain statusinto the memorysummariser failsabort; leave theoriginal session intactno confirmation / reused PIDblocked marker;signal nothing
← swipe sideways to see the full table →
Every failure mode has a conservative response. The system can leave the operator with the old session; it must not leave them with no trustworthy session.

Testing found the defects

The honest part was not the design. It was the test evidence. The macOS spawn originally used open -a Terminal <cwd> --args claude. The man open documentation says those arguments are not opened or interpreted by the open tool. Terminal ignored them. The branch was written in a Linux-reporting sandbox, so the macOS path had never executed.

The confirmation gate had no test. Removing it entirely left all twenty-six tests green. With the gate restored and tested, three fail. That is the useful failure: the test suite now notices when the old session could be killed before the new one confirms it read the handoff.

The suite after the work is 1228 passed, 0 failed. That result matters only alongside the negative tests for the handoff gates. Green is evidence of the cases exercised; it is not permission to skip the case that decides whether killing is safe.

The operating rule

I do not trust the AI to manage its own life-cycle, so I built a machine to do it for it. The machine measures context, waits for a quiet repository, preserves unfinished work in a memory, starts a new process outside the old session, and retires the old process only after independent evidence says it is safe.

That is enough. The protocol is deliberately narrow: it manages state transfer and process retirement. It does not pretend to solve every long-context problem, and it does not make unsupervised execution safe by declaration.

Meharban Singh

Meharban Singh

AI systems / delivery architect. I build software with AI agents governed by rules, hooks, gates and independent review.