Human-in-the-loop automation usually fails at the loop's least glamorous edge: what happens when the human does not answer. An approval message goes out, the person is busy, a timeout fires — and somewhere a code path treats "no answer" as "no objection". The action runs. Nobody decided it.
For a solopreneur this is not a corner case; it is the *normal* case. I am the only approver in my operation, and I am regularly asleep, traveling, or heads-down while my agents work. So the rule my whole approval layer is built on — published as [fail-closed-gate](https://github.com/yagebin79386/fail-closed-gate) — is blunt: **expiry is not approval, silence is not consent, and an unanswered request fails closed.**
The mechanics
Every consequential action my agents take — publishing content, changing configuration, anything irreversible — raises a gate: a JSON request in a pending directory, with a deadline. A sweep closes overdue requests as `expired`, and the expired decision record deliberately carries **no decider**, a rule the schema itself enforces. Downstream code asks one question — *may I proceed?* — and gets a fail-closed answer on every edge: unknown request, overdue-but-unswept request, and every outcome except an explicit approval all read as *blocked*. Decisions append to a daily ledger that grep can query; nothing is ever deleted.
The bugs that wrote the rules
Two production incidents shaped the protocol more than any design session. First, duplicate cards: pipeline re-runs used to stack approval requests nobody remembered, so request ids became deterministic — the same gate re-raised is the same id. Second, and worse: a re-raised gate once bounced every fresh decision against its *old* archived outcome, and three approvals were silently dropped before anyone noticed. The fix is that superseding a decided gate is explicit and auditable — the old archive is renamed, never deleted, and can never again speak for a request it no longer describes.
Why this beats "just ping me again"
Reminder-based flows optimize for the human answering faster. Fail-closed flows optimize for the system being *safe while unanswered* — which is the actual requirement of one-person automation. The cost is honest: things I do not approve in time do not happen, and the ledger shows a string of expired requests on my busiest weeks. That record is a feature. It is the difference between "the system did nothing because nobody said yes" and "the system did something because nobody said no."
Limits
The published tool is transport-agnostic by design: it stores requests, records decisions, and sweeps expiries, but showing requests to a human — chat bot, email, a terminal — is an adapter you wire yourself. And a gate only governs what flows through it; the discipline of routing *every* irreversible action through the gate is operational, not enforceable by the library.