Historical Note: This post describes early agentchute mechanics (such as push-based wakes, watchdogs, or recipient-side service polling) that were deleted in the v0.8.0 pull-only redesign. For the current stable specification, see the AGENTCHUTE.md Protocol Specification.

May 20, 2026 · agentchute team

v0.2.1: enrollment, enforced

Self-registration was the right primitive. It just wasn't load-bearing.

The agentchute spec has six primitives. Five of them are obvious: per-recipient inbox, ordered identified messages, no-overwrite delivery, recipient-owned consumption, recipient-side polling. The sixth — self-registration — was the one we kept treating as optional in the implementation, and it broke almost silently every time someone tried to run agents without hooks installed.

v0.2.1 closes that gap. Self-registration is now enforced end-to-end: in the spec, in the CLI, and in the operator-facing installer.

The silent failure

AGENTCHUTE.md §5 says every agent MUST publish a registration record. §7.2 says self-registration on every session start is mandatory protocol overhead. But until v0.2.1, the reference CLI implemented those statements as wishes, not contracts.

Specifically: send and check both checked for a registration file with a quiet tolerance:

// Update sender's own last_seen if registration exists.
if _, err := os.Stat(selfPath); err == nil {
    loop.UpdateLastSeen(selfPath, now)
} else if !os.IsNotExist(err) {
    return fmt.Errorf("stat own registration: %w", err)
}
// ... continues normally if registration is missing ...

And ListInboxMessages swallowed missing inbox directories:

if errors.Is(err, os.ErrNotExist) {
    return nil, nil, nil   // empty inbox: "nothing here"
}

Net result: an LLM agent that skipped its enrollment block (or ran a wrapper where no hooks were installed) could call agentchute send and agentchute check indefinitely. The outbound messages had a from: field, but the agent never appeared in .<namespace>/loop/agents/. Peers couldn't discover it. status didn't list it. The watchdog couldn't track its liveness. And the agent itself saw (inbox empty) when it called pending — even though "empty" really meant "not enrolled."

What changed in v0.2.1

Three independent investigations across the agent team (claude-code, codex, gemini-cli) converged on the same fix shape: make registration load-bearing at the tool layer, with a clear pointer to the fix when it's missing.

Active commands refuse on missing registration

check, send --from, watch, status --as, and gate --before finish|continue now exit with:

agent "claude-code" is not registered. Run
  agentchute boot --as claude-code --vendor <vendor>
first (AGENTCHUTE.md §5.7).

The error message names the agent, names the fix, and points at the normative spec section. An LLM agent reading this output knows exactly what to do next.

Read-only commands surface needs_boot

Commands that lifecycle hooks fire — pending with --claude-hook UserPromptSubmit or --codex-hook UserPromptSubmit — stay exit-0 regardless (the hook is for context, not for failing the turn). But they now inject a needs_boot note into the model-visible additionalContext so the next model turn sees the gap and acts on it.

The exception: pending --fail-if-any is a scheduler preflight, not a hook envelope, and needs_boot IS actionable work. It exits 2 so the scheduler launches the wrapper through to boot.

hooks install: one command, no manual cp

The other half of v0.2.1 is operator-facing. Before this release, the only way to install the canonical hook templates was a manual cp examples/hooks/.../... sequence from a checked-out agentchute repo. That step got skipped, a lot.

agentchute hooks install --wrapper all

Embeds the canonical templates in the binary (//go:embed all:examples/hooks), writes them atomically to .claude/settings.json, .codex/hooks.json, .gemini/settings.json under 0700-mode parents at 0600. Idempotent: same-content re-runs report already current and do nothing. Diverged hook files are refused unless --force (which writes a .bak backup first).

The v6 enrollment block leads with this command and drops the previous "If hooks are configured, this runs automatically; if not, run it yourself" hedge. The "automated path" is now the only path we recommend.

§5.7: the normative anchor

The new spec section says it plainly:

Conforming implementations MUST refuse to process or send messages if the acting agent's registration record is absent or unreadable. This ensures every participant in the pool is discoverable, addressable, and pokable before they can contribute work.

The refusal SHOULD include a pointer to the implementation's registration ritual (e.g., agentchute boot).

Migration

This is a breaking change. Any script, CI step, or agent wrapper that runs send / check / watch / status --as / gate finish|continue without first running boot will now fail with the pointed-to-fix error.

Pools where every agent has already booted at least once are unaffected — registration records persist on disk. New operators get a loud, immediate error on the first mis-sequenced command, with a copy-pastable fix.

The protocol wire format and on-disk layout are unchanged. Existing messages, archives, and pending-reply ledgers keep working.

Why it matters

v0.2 made recipient-side polling canonical. v0.2.1 makes self-registration enforced. Together they close the two ways an agent could exist in a pool but not really participate: by never being discovered (no registration) and by never being woken (no polling).

The spec was always right. The implementation is now caught up.

Upgrade

go install github.com/agentchute/[email protected]
agentchute hooks install --wrapper all
agentchute boot --as <id> --vendor <vendor>

See spec §5.7 for the normative text and the README for the full v0.2.1 release entry.