Add ARO-context tag on DM sends so multi-ARO senders route correctly
Today a DM carries no ARO context. If the sender is in multiple AROs and DMs the PM (or any peer) of one of them, the receiver and chat-duo cannot tell which ARO conversation the DM belongs to. Result: PM replies land in a separate per-sender DM bucket instead of the originating ARO room (observed today, msg id 403 in /var/lib/llmmsg-srv/llmmsg.sqlite). Need a wire-level field/tag (e.g. 'originAro' or 'aroContext') that the sender attaches to a DM, the hub stores, and the MCP/chat-duo use to bucket the message and threaded reply. Decisions to make: (a) name and storage column in messages; (b) whether replies auto-inherit the sender's tag (re-chain); (c) MCP send tool surface; (d) chat-duo outbound default = current-room ARO when bucket is aro:*; inbound routing = use originAro to bucket. Cross-component: hub schema + MCP shim + chat-duo gui._on_event/_on_send + bridge (Codex pass-through).
Questions
Activity
-
Filed from analysis of msg 403 (pluto-pm-cc-w → elazar). Outbound was bucketed aro:pluto via optimistic add; inbound landed in DM bucket pluto-pm-cc-w because the SSE event has no origin_aro on a DM. Logger added in chat-duo to capture future routing decisions.
payload
{"observedMsgId":403,"affectedComponents":["hub","mcp-shim","chat-duo","bridge"],"observedSenderBucketSplit":["aro:pluto","pluto-pm-cc-w"]} -
Investigation update: scope is hub + client, not client-only. (1) Hub /send (hub.mjs:1090) does not destructure origin_aro from the request body; it auto-tags origin_aro only on aro:* fan-out (line 1189). DM-to-PM sends are stored with origin_aro=NULL. (2) stmtHistoryAro (hub.mjs:331) filters by origin_aro=? OR re IN (tags with origin_aro=?). DMs never qualify, so /history?bucket=aro:pluto returns []. (3) Confirmed empirically against /var/lib/llmmsg-srv/llmmsg.sqlite: messages 402-405 (elazar<->pluto-pm-cc-w) all have origin_aro=NULL even though sender intended an aro:pluto context. Implementation steps: (a) hub /send: accept body.origin_aro; validate that from and to are both members of that ARO (or that to is the ARO's pm_agent); pass to sendMessage(...,originAro,...). (b) chat-duo hub_client.send(): add origin_aro param; gui._on_send: pass current bucket if it starts with aro: and target is a DM. (c) chat-duo MCP shim send tool: same param. (d) Verify /history returns the DM in the ARO bucket once origin_aro is stamped. (e) Reply chain inheritance: if sender replies (re=<tag>) and the parent has origin_aro set, default the reply origin_aro to the same value (stmtReplyRoute already exposes this).
payload
{"hubFile":"hub/hub.mjs","sendHandlerLine":1090,"historyAroQueryLine":331,"observedNullOriginAroIds":[402,403,404,405]} -
Shipped. Hub /send accepts body.origin_aro on DMs and validates that both endpoints are members; sendMessage propagates origin_aro through to the messages row; spectator mirroring honors the explicit ARO-context tag instead of duplicating across every shared ARO. Reply-chain inheritance: when re=<tag> and parent has origin_aro, the reply is allowed to be a peer DM (both endpoints in that ARO) and auto-inherits origin_aro. chat-duo hub_client.send() and gui._on_send() now pass origin_aro when sending from an aro:* room. MCP shim send tool exposes origin_aro on inputSchema and forwards it. Verified end-to-end: msg 406 (elazar→pluto-pm with origin_aro=aro:pluto) shows up in /history?bucket=aro:pluto; msg 407 (pluto-pm reply) auto-inherited origin_aro=aro:pluto. Negative case verified: aro:srv-engineering DM to a non-member rejected with origin_aro_membership.
payload
{"hubFile":"hub/hub.mjs","mcpShim":"hub/llmmsg-srv-mcp.mjs","clientFiles":["chat-duo/chat_duo/hub_client.py","chat-duo/chat_duo/gui.py"],"verifiedMsgIds":[406,407,411],"rejectedExample":"origin_aro_membership"}