From faf454ae5d9d190347ea6b644cf11ce34fb9d94e Mon Sep 17 00:00:00 2001 From: mikemolinet Date: Thu, 21 May 2026 15:49:47 -0700 Subject: [PATCH] docs(inbox): flag default state= excludes acked/expired for conversation-view consumers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a Conversation-view consumer note to GET /v1/agents/{ref}/inbox explaining that the default state filter excludes `acked` + `expired`, which is right for worker-runtime/agent-handler consumers but wrong for chat UIs / message-thread renderers / audit logs. ## Why Empirical: 2026-05-21, Dock's cue.dock.svc /api/threads consumer hit the asymmetry. Recipient pane was empty for cross-user messages because Dock's own desktop worker poll-acked them faster than the human-facing /api/threads could surface them. Sent-side bubbles rendered correctly because Dock's sent view requested all states. Substrate behavior is correct — messages WERE delivered + acked. Dock fixed Dock-side by passing the full state set on the threads fetch. But any future conversation-view consumer will hit the same trap unless flagged in the docs. ## What changed - `state=` parameter description: expanded from one-liner to call out the worker-default-vs-conversation-view distinction + give the exact state-set string conversation consumers should pass - Added "Conversation-view consumer note (v1.1.5 docs)" section to the endpoint docstring with the empirical Dock anchor No code change. Docs only. Per Mike directive 2026-05-21 ~22:48Z "in general we need to do 5x better on documentation." Backlog row: cmpg2szip000404l4ibldaq6e Jingim DX note: msg_q8zsi74xe4us --- app/routers/agents.py | 46 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/app/routers/agents.py b/app/routers/agents.py index 520f10e..454a327 100644 --- a/app/routers/agents.py +++ b/app/routers/agents.py @@ -267,7 +267,23 @@ async def regenerate_webhook_secret_endpoint( @router.get("/{ref}/inbox") async def get_inbox_endpoint( ref: str, - state: Optional[str] = Query(default=None, description="Comma-separated states; default excludes acked/expired"), + state: Optional[str] = Query( + default=None, + description=( + "Comma-separated states to include. Default excludes ``acked`` " + "+ ``expired`` (worker-runtime/agent-handler default — those " + "consumers poll for actionable messages only). " + "**Conversation/transcript consumers** (chat UI, message-thread " + "renderer, audit log, etc.) should pass an explicit " + "``state=queued,delivering,retry_ready,delivered,read,claimed,acked,expired,failed`` " + "to include the full history — otherwise messages that have " + "been ``acked`` (e.g., by the recipient's own desktop worker) " + "vanish from the conversation view while the sender's bubble " + "remains visible via the symmetric ``GET /v1/messages`` sent " + "view. See ``Conversation-view consumer note`` in the " + "docstring below." + ), + ), since: Optional[datetime] = Query(default=None), thread_id: Optional[str] = Query(default=None), counterpart: Optional[str] = Query( @@ -297,6 +313,34 @@ async def get_inbox_endpoint( from that counterpart agent only. The atomic queued→delivered UPDATE applies the same filter so polling a single counterpart thread doesn't auto-deliver other threads' queued messages. + + ## Conversation-view consumer note (v1.1.5 docs) + + The default ``state=`` filter excludes ``acked`` + ``expired``. That + default is right for **worker-runtime / agent-handler consumers** + that poll for new actionable work — they don't want to re-process + messages they've already acknowledged. + + It's **wrong** for conversation/transcript consumers (chat UIs, + message-thread renderers, audit logs, support tools) because + ``acked`` messages disappear from the recipient's inbox view while + they remain visible on the sender's side via ``GET /v1/messages`` + (sent view). That asymmetry produces a confusing UX: the sender + sees their own message in the conversation, but the recipient's + pane shows nothing — even though the message WAS delivered and + acknowledged successfully. + + **Fix for conversation views**: pass an explicit + ``state=queued,delivering,retry_ready,delivered,read,claimed,acked,expired,failed`` + on the inbox fetch. This includes the full state set + matches + the symmetric sent-side view, restoring conversation parity. + + Empirical anchor: 2026-05-21, Dock's ``cue.dock.svc`` ``/api/threads`` + consumer hit this trap — their recipient pane was empty for + cross-user messages while sent-side bubbles rendered correctly. + Substrate behavior is correct (messages WERE delivered + acked + fast by Dock's desktop worker); Dock fixed Dock-side by passing + the full state set on their threads-view fetch. """ result = await list_inbox( db,