The agent believes its own gateToggle navigation menu
Back to Blog<br>This week my agents and I set out to evaluate buzz, the agent workspace Block open-sourced on July 22, for real: a self-hosted relay, an agent holding its own keypair, and gates on its turns that we built ourselves. Partway through, I asked that agent what its chat room was for. It told me, in writing, that its work "cannot proceed" until a human reacts to its approval request with a thumbs-up emoji.
The whole arc on camera: ask, answer, gate, sign-off, ship.
That claim is false. The way it's false is the most interesting design decision in buzz, and it took building the gate to see it clearly.
Said plainly before any critique: buzz is the most interesting collaboration infrastructure I've touched this year. Agents hold real cryptographic identity instead of bot tokens, every action lands in one signed audit log, a working gate can be built from outside the project in an afternoon, and the team merges outsider PRs in days. A platform this good deserves precise criticism.
The convention
buzz runs humans and AI agents in the same channels with the same kind of identity: a keypair. Its agent runtime supports lifecycle hooks through a convention so small it fits in a hundred-line doc (docs/MCP_DRIVEN_HOOKS.md): any MCP server can expose tools whose names start with an underscore. The agent hides them from the LLM and calls them itself at two points. _Stop fires when the model wants to end its turn, and a non-empty response is an objection that keeps the agent working. _PostCompact fires after context compaction and re-injects whatever state you return. Hooks are off unless the operator sets MCP_HOOK_SERVERS.
There is no protocol change, no plugin API, and no SDK to learn. It's the same idea as Claude Code's Stop hooks, expressed as ordinary MCP tools.
I shipped the first third-party implementations of this convention as buzz-hooks: a ci-gate that objects while your GitHub Actions run is red or unfinished, and an approval-gate that posts a sign-off request into a buzz channel and objects until a human reacts 馃憤. Block ships a first-party implementation in-tree (todo enforcement in buzz-dev-mcp); these are the first from outside. Each is a few hundred lines of TypeScript, most of it written by my own agents, and tested against a real relay with a real agent on the far side of the gate.
What the logs say
The gate writes one JSON line per hook call, and three numbers from those logs carry the whole story.
The first number is four seconds. With defaults, my approval-gate posted its request and objected three times, at 14-23ms per call, and then the turn ended anyway, about four seconds after it began gating. The agent enforces a rejection budget (BUZZ_AGENT_STOP_MAX_REJECTIONS, default 3), and once it's spent, the agent stops no matter what the hook says. A fast model burns the entire budget before a human can plausibly find the message, so at stock settings, human-in-the-loop approval through _Stop is impossible.
The second number is 105 seconds. Raise the budget to 40 and the mechanics work exactly as you'd hope: in my filmed run the agent held its turn through objections while a human read the request, thought about it, and reacted, and the next _Stop check saw the reaction and returned an empty verdict, allow, 34ms after the click. Per-call latency stayed between 11 and 50ms all day, nowhere near the timeout.
The third number is the 2.5-second hook timeout, the other half of the sovereignty story. A hook that doesn't answer in time counts as no objection, and a server that times out twice in a row gets killed. Slowness fails open by design.
Fail-open is a choice, and a defensible one
The doc frames this as a boundary and says so plainly: "hooks are advisory, not authoritative," and the constraints exist so "a buggy or malicious hook cannot trap the agent." Anyone who has watched an agent get wedged by a misbehaving tool knows what that failure mode costs. Block picked agent liveness over gate authority, and for per-turn nudges that is the right trade.
The catch is what it does to expectations. My agent told the room its work could not proceed without sign-off, and any operator reading the approval-gate's own messages would believe the same. The enforcement they're imagining lives somewhere else entirely. buzz's relay can require N signed approvals (kind:46011 events) before a branch merges, checked server-side in buzz-relay, where no rejection budget applies. Workflow-level approval gates exist in schema and UI but the executor half is still being wired (the PR is open as I write this, and a run that hits request_approval today gets marked Failed, a finding Jo茫o Queir贸s corroborated independently). The division of labor is: hooks shape behavior, the relay enforces rules. Write that distinction down for your team before an agent writes something else.
There's a second gap worth knowing about: hooks can only gate the end of a turn....