Patient Waiting — wait without burning tokens
One incident to remember: a background until-loop watcher kept getting
killed, so the main agent “recovered” by rescheduling itself hourly via
ScheduleWakeup to cat a status file. The loop had no idle cap and ran for
a week — ~140 full-context premium-model turns spent confirming that nothing
had changed. Every rule below exists because of that.
Harness selection
The ladder below uses Claude Code background tasks and Monitor. For Codex
wall-clock work, use the until skill (delayed-work plugin): capability-detect
codex queue (verified on CLI 0.160.1), then arm a detached one-shot timer targeting
the exact current thread. It costs no model tokens while waiting and needs a live
queue receiver at fire time. Queue acceptance is not delivery proof. If unavailable,
report the missing capability and offer a manual nudge or short active-turn wait.
This timed path does not establish a general Codex file/event watcher contract.
For Codex queued callback payloads, follow the canonical Codex queued sender
labels section in $maestro:conduct.
The waiting ladder (Claude Code, in order — never skip down)
- Background bash
untilloop (run_in_background). Zero tokens while waiting; one completion notification when the condition flips.until <condition-check>; do sleep 3; done; echo "signal"; <print state> Monitortool, only inside its reported deadline. The harness runs your poll script shell-side and invokes the model ONLY when a line is emitted. Zero tokens idle, but Claude Code 2.1.272 still applies a 30-minute cap when it acceptspersistent: true; accepted is not the same as honored. Use it only when the event fits inside the returned deadline. Cover failure states too — emit on the watched thing dying, not just succeeding.- Both keep dying → STOP and hand the loop to the human. One line: “the watcher can’t survive in this environment — when you’ve done X, nudge me.” Do NOT fall through to model-in-the-loop polling.
Hard rules
- Never machine-poll a human. If the event is human-triggered (review submitted, doc approved, “when I’m ready”, “after my meeting”), the human can close the loop by speaking. A scheduled model wake adds cost and nothing else. Zero-token watcher or nothing.
ScheduleWakeuploops are for machine-paced state only (CI runs, deploys, data jobs, remote queues) — and only when neither ladder rung 1 nor 2 can see the state. Even then, three backstops are mandatory:- Max quiet iterations: 3. Three polls with no change → stop the loop and leave a one-line resume instruction instead of rescheduling.
- No off-hours polling. If the human is asleep, nothing you’re waiting on them for is happening. Stop; resume on their next message.
- Count quiet polls in each reschedule’s
reason(“quiet poll 2/3”) so drift is visible in telemetry and to the user.
- A killed watcher is a blocker, not a license to escalate. The failure of a cheap mechanism never justifies substituting an expensive one of the same shape. Name the problem, present options (including “do nothing — you nudge me”), and wait.
- Know what a wake costs. A main-agent wake is a full-context turn on
the session’s model — possibly the scarcest premium model. A status poll
is worth zero of those. If the work on wake is
cat file+ “no change”, the model should not be in the loop.
Quick decision table
| Waiting on | Mechanism |
|---|---|
| Local file/status flag to flip once | Background bash until loop |
| Wall-clock time / “run X at 9pm” | The until skill (delayed-work plugin) — Claude background Bash / deadline-bounded Monitor; Codex capability-detected same-thread queue timer |
| Recurring events (log errors, PR comments) | Monitor only when its reported deadline covers the watch; otherwise stop |
| Human action (submit, approve, “when I’m ready”) | Watcher from above — or just tell them to nudge you. Never scheduled wakes. |
| CI/deploy the harness can’t see | ScheduleWakeup, interval matched to the job, max 3 quiet polls |
| Watcher keeps getting killed | Stop. Ask. Don’t escalate. |