Skip to content

fix: stop consuming a mailbox from costing a round trip per message (#159) - #165

Merged
yonidavidson merged 1 commit into
mainfrom
fix/159-batch-consume
Aug 9, 2026
Merged

yonidavidson merged 1 commit into
mainfrom
fix/159-batch-consume

Conversation

@yonidavidson

Copy link
Copy Markdown
Owner

Closes #159.

Consuming a mailbox archived each message with its own Backend.move, and the daemon forwarded move synchronously to the real store while put was acked from its disk outbox. On a git bus that is fetch → commit → push per message: N messages = N sequential round trips, so inbox blew through any command timeout while peek (served from the warm mirror) returned instantly. inbox --as <alias> — the documented recovery path when mail sits at another alias — was unusable for the same reason.

What changed

  • Batchable capability (src/types.ts)moveMany(moves), feature-detected like Claimable/Waitable. Bus.archive uses it for the whole mailbox and falls back to per-key moves if the batch is refused, so a partial failure still archives what it can.
  • src/backends/git.tsmoveMany puts every add/remove in ONE commit and pushes once. Also caps each git invocation (AGENTCOMM_GIT_TIMEOUT_MS, default 120s): a remote that accepts the connection and goes quiet used to hang forever, which reaches the caller as a command that simply never returns.
  • src/daemon.ts — the outbox now carries moves and deletes, not just puts, so consuming acks in milliseconds like a send does. The poll replays the undelivered outbox over the store's view, so an accepted-but-not-yet-flushed archive can't resurrect the messages it consumed. Old spool entries (puts without an op) still deliver.
  • src/backends/socket.tsmoveMany over the daemon protocol; --sync still forces straight-through delivery for moves and deletes.

Tests

  • test/git.e2e.test.ts: consuming a 4-message mailbox adds exactly one commit to the bus branch.
  • test/daemon.e2e.test.ts: consuming acks from the outbox, a poll before the drain does not re-deliver, and daemon stop lands the archive on the store exactly once.
  • test/bus.test.ts: moveMany is used once per archive, and a refused batch falls back to per-key moves.

…159)

`inbox` archived each message with its own `move`, and the daemon forwarded
`move` synchronously to the real store while `put` acked from its disk
outbox. On a git bus that is fetch → commit → push per message, so a full
mailbox blew through any command timeout while `peek` — served from the
warm mirror — returned instantly. `inbox --as <alias>`, the documented
recovery path when mail sits at another alias, was unusable for the same
reason.

Archiving is now one operation. A new `Batchable` capability (`moveMany`)
is feature-detected like `Claimable`/`Waitable`; the git backend implements
it as a single commit for the whole mailbox, and `Bus.archive` falls back
to per-key moves if a batch is refused so partial progress still happens.

The daemon's outbox carries moves and deletes too, so consuming acks in
milliseconds like a send. Its poll replays the undelivered outbox over the
store's view — without that, a poll between the ack and the flush would
resurrect the messages the archive already consumed and deliver them twice.

Finally, a git invocation that hangs is now a clear, attributable error
after AGENTCOMM_GIT_TIMEOUT_MS (default 120s) instead of silence that is
indistinguishable from a deadlock.
@yonidavidson
yonidavidson force-pushed the fix/159-batch-consume branch from e4dcae8 to c46d592 Compare August 9, 2026 07:47
@yonidavidson
yonidavidson merged commit 9628e3b into main Aug 9, 2026
2 checks passed
@yonidavidson
yonidavidson deleted the fix/159-batch-consume branch August 9, 2026 08:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Consuming a mailbox is O(messages) remote round-trips — inbox times out where peek is instant

1 participant