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/agentchute@v0.2.1
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.