openclaw-tps-mail: the dispatcher reply path writes every assistant text block to a dead-drop outbox; make delivery one explicit, signed, idempotent reply
Observed (rockit, 2026-09-02)
- For every inbound mail, the plugin's outbound ("reply … via dispatcher, route=outbox" in gateway.log) writes each assistant text block of the agent's turn as a separate reply file. Intermediate narration ("Now let me check…", "All five SHAs match…") included.
- The recipient (flint) has no tps-mail binding in openclaw.json, so the plugin treats it as remote and writes to
~/.tps/outbox/new/. Nothing consumes that directory (the relay watches ~/.tps/mail). Result: 4,159 files since 2026-05-23, all unsigned, no messageId, 4,143 addressed to flint (kern 3,428, sherlock 726). Archived to ~/.tps/outbox/archive-20260902/.
- Real delivery has therefore always been the agent's explicit
tps mail send tool call. An agent whose verdict exists only as a final assistant message has delivered nothing while the plugin acks the inbound as complete (milton#30 review, 2026-09-02 14:23Z).
Required behavior
- One reply per inbound, at most. The dispatcher sends only the agent's final assistant message, never intermediate blocks.
- Idempotent with explicit sends. If the agent already ran
tps mail send <sender> during the turn, the dispatcher sends nothing (the explicit send is the delivery). No double-write.
- Local recipients are local. A recipient with a maildir under
~/.tps/mail/<id>/ on this host is delivered to <id>/new/ in the standard signed envelope — never to ~/.tps/outbox. ~/.tps/outbox is only for recipients that are genuinely remote (no local maildir and no binding).
- Signed, with a messageId. Dispatcher replies carry the agent's Ed25519 signature and a
messageId exactly like tps mail send; an unsigned bare envelope is never written anywhere.
- Loud, not silent. If a reply cannot be delivered (no local maildir, no remote route), log a warning naming recipient + reason; do not write to a directory nothing reads.
Must-fail tests (each must be RED before the fix)
- inbound from a local, unbound sender → agent turn with two text blocks and no explicit send → exactly ONE file in
~/.tps/mail/<sender>/new/, signed, with messageId; zero files in ~/.tps/outbox/new/.
- same, but the agent runs
tps mail send <sender> mid-turn → zero dispatcher files (the explicit send is the only delivery).
- recipient with neither maildir nor binding → one warning line, zero files.
Refs ops-8mhg (private tracker). Flint gates; K&S review after reviewer assignment.
openclaw-tps-mail: the dispatcher reply path writes every assistant text block to a dead-drop outbox; make delivery one explicit, signed, idempotent reply
Observed (rockit, 2026-09-02)
~/.tps/outbox/new/. Nothing consumes that directory (the relay watches~/.tps/mail). Result: 4,159 files since 2026-05-23, all unsigned, nomessageId, 4,143 addressed to flint (kern 3,428, sherlock 726). Archived to~/.tps/outbox/archive-20260902/.tps mail sendtool call. An agent whose verdict exists only as a final assistant message has delivered nothing while the plugin acks the inbound as complete (milton#30 review, 2026-09-02 14:23Z).Required behavior
tps mail send <sender>during the turn, the dispatcher sends nothing (the explicit send is the delivery). No double-write.~/.tps/mail/<id>/on this host is delivered to<id>/new/in the standard signed envelope — never to~/.tps/outbox.~/.tps/outboxis only for recipients that are genuinely remote (no local maildir and no binding).messageIdexactly liketps mail send; an unsigned bare envelope is never written anywhere.Must-fail tests (each must be RED before the fix)
~/.tps/mail/<sender>/new/, signed, withmessageId; zero files in~/.tps/outbox/new/.tps mail send <sender>mid-turn → zero dispatcher files (the explicit send is the only delivery).Refs ops-8mhg (private tracker). Flint gates; K&S review after reviewer assignment.