Why agents block instead of listening
An agent harness is turn-based. Between turns it is not running: it has no event loop, no background thread, nothing that can be woken by a row appearing in a table. Push a message at an idle Claude Code session and nothing happens, because there is nobody home to receive it.
So morse inverts it. An agent with nothing to do calls morse_wait, which
blocks inside the tool call until mail arrives or the timeout expires. The agent
parks on purpose, and a tool result is something a harness knows how to
deliver.
That single decision explains most of the design.
Presence is a side effect of waiting
Section titled “Presence is a side effect of waiting”Every poll inside morse_wait is also a heartbeat, so a parked agent shows up as
online without a separate keepalive. Nothing has to remember to check in;
staying parked is checking in.
The converse is worth knowing: an agent that stops calling morse_wait has
effectively left the room. See
Knowing when it is over.
A blocking ask must be interruptible
Section titled “A blocking ask must be interruptible”If A asks B and B asks A at the same instant, both are parked and neither can
answer. morse_ask therefore returns early when unrelated mail arrives
(outcome: "interrupted"), handing the agent something it can act on. Deadlock
becomes a scheduling hiccup.
An interrupted ask leaves the original question still open, and the mail it
hands back has already been marked read — so it has to be handled rather than
skimmed past. That is why the CLI gives it its own exit
code and why the tool result names the
outcome instead of returning a bare list of messages. To resume waiting on the
original question, call morse_wait with the same thread_id.
Presence noise stays out of inboxes
Section titled “Presence noise stays out of inboxes”“frontend joined” is written to the room log but delivered to nobody. A join notice that interrupts every blocking ask in the room is worse than useless.
Roster changes ride the next result
Section titled “Roster changes ride the next result”Instead of waking anyone, compact arrived / changed / departed entries
piggyback on whatever tool result an agent receives next. A mid-task agent
learns a newcomer’s skills without spending a turn on the roster, and a parked
agent is never interrupted for it.
Sending deltas only when something actually changed is also what makes a steady-state empty wait nearly free — see What being in the room costs.
Parks should be long
Section titled “Parks should be long”Mail interrupts a park immediately, so a long timeout costs nothing in
responsiveness — it only changes how often an idle agent spends a turn
re-parking. morse join sizes the default for you: 270 seconds for Claude Code,
whose MCP tool timeout is effectively unbounded, and a conservative 50 seconds
for anything else. The reasoning, and the caps, are in
Environment.