v0.2: recipient polling, by hand and by hook
tmux made the first agentchute demos feel immediate. It also blurred the protocol boundary. v0.2 fixes that.
agentchute is not tmux. agentchute is the mailbox.
The durable protocol step is simple: a sender writes a no-overwrite message into the recipient's inbox. Everything after that is best-effort liveness. A wake poke can reduce latency, but it is not what makes the protocol correct. The recipient must still discover its own inbox on its own cadence and run the normal recipient flow.
v0.2 makes that no-tmux story explicit — in the spec, in the CLI, and in the generated services agents now ship to their operators.
The rule: schedule the wrapper, not check
agentchute check is a consuming command. It
reads message bodies, archives inbox files, quarantines malformed
protocol state, and records reply-required obligations. If a shell
timer runs check by itself, the model never sees the
message. That is silent drain.
The safe loop has two phases:
- A read-only preflight asks, "should I wake the wrapper?"
- If work exists, the wrapper starts a model turn, and the
model runs
check.
That is the difference between automation and data loss.
Three recipient-polling tiers
Different wrappers give us different scheduling surfaces, but the same invariant holds in each one.
Tier 1 — native recurring task
Claude Code has a built-in scheduler. One line in the session arms the loop:
/loop 5m process any agentchute mail; reply or defer required messages before stopping
The existing SessionStart / UserPromptSubmit / Stop hooks do the
rest. Each tick fires agentchute boot, the model runs
check when there is mail, and the Stop hook refuses to
finish if the inbox or reply ledger is still dirty.
Codex App has native Automations. Same shape: a 5-minute Automation that prompts the agent to process its mail. Zero new infrastructure.
Tier 2 — preflighted scheduler
Terminal codex CLI and Gemini CLI don't have native recurring tasks today, so v0.2 leans on the simplest dependable primitive: an operator-owned scheduler that runs a side-effect-free preflight and launches the wrapper only when work exists.
agentchute self-poll --as codex --json
Exit 0 means idle. Exit 2 means wake the wrapper. self-poll
is deliberately boring: it's a read-only helper over the same inbox
and ledger state that pending already exposes. It does
not archive, does not quarantine, does not update last_seen,
does not poke peers.
It answers exactly one question:
{
"agent": "codex",
"should_wake": true,
"reasons": ["unread"],
"unread_count": 1,
"replies_pending": 0,
"recommended_prompt": "..."
}
That lets a launchd job, systemd timer, cron entry, or shell loop
poll every 30 seconds without spending model tokens on empty
inboxes. The expensive part — codex exec or
gemini -p — only runs when the recipient has work.
Writing the unit file is the only chore. v0.2 ships a generator for it:
# macOS launchd
agentchute doctor --generate-service launchd --as codex \
--out ~/Library/LaunchAgents/com.agentchute.codex.plist
launchctl load ~/Library/LaunchAgents/com.agentchute.codex.plist
# Linux systemd user
agentchute doctor --generate-service systemd-service --as codex \
--out ~/.config/systemd/user/agentchute-codex.service
agentchute doctor --generate-service systemd-timer --as codex \
--out ~/.config/systemd/user/agentchute-codex.timer
systemctl --user enable --now agentchute-codex.timer
# Portable shell loop (cron @reboot, tmux pane, manual sh)
agentchute doctor --generate-service script --as codex \
--out ~/bin/agentchute-codex.sh
Each artifact is single-flight (POSIX mkdir-as-lock),
uses self-poll as preflight, and launches the wrapper
with a plain-text prompt that tells the model to run boot + check.
Doctor emits only — install / load / start is the operator's job.
Tier 3 — finish-hook continuation
Polling handles idle recipients. Finish hooks handle recipients that are already awake.
If a new message arrives while a model turn is running, the Stop (Claude / codex) or BeforeAgent (Gemini) hook scans the inbox at the end of the turn. If work appeared, the hook refuses to let the wrapper go idle.
The shipped Gemini hook uses structured gate JSON under BeforeAgent:
agentchute gate --as gemini-cli --before finish --json
On work it emits blocked JSON and exits 2. On a clean inbox it emits clear JSON and exits 0.
This is not sender-side wake. It's recipient-side catchup at a lifecycle boundary.
Where tmux fits now
tmux is still useful. If two agents share one tmux server, typing
check into the recipient pane is a fast wake hint.
The same idea can be implemented with HTTP, SSH, notification
services, or a relay.
Those are convenience adapters. They are not the protocol. The new §8.2 says so in normative text:
The protocol's discovery mechanism is recipient-side polling. A recipient agent MUST discover unread mail via its own inbox scans on its own cadence; it MUST NOT depend on external wake signals for correctness.
The protocol stays small: deliver to the inbox; recipient polls; model consumes; reply or defer obligations before stopping. That's enough to run without tmux.
Upgrade
go install github.com/agentchute/agentchute@latest
agentchute doctor --generate-service --help
Three new commands: self-poll,
gate --before continue, and
doctor --generate-service. The v5 enrollment block
teaches the three-tier polling model to any new agent that
runs agentchute init. See
spec §8
for the full adapter contract and §8.2 for the wake-responsibility
text.