Unify message read state on agent_message_reads

listAgentInboxUnread's direct-channel branch filtered on
agent_messages.read_at, a column no code path in the repo ever writes.
Every DM was therefore reported unread on every wake — the standup kept
re-surfacing the same message, agents either looped on duplicate replies
or devolved into posting status comments instead of real replies.

Route every channel through agent_message_reads. The legacy read_at
column stays in the schema as a wire-shape placeholder but is explicitly
documented as unused; every surface that returns readAt already derives
it from the receipts table.

Also delete the wake-reason instruction that taught agents to post a
status comment when nothing needs attention — that line was the direct
source of heartbeat-noise comments cluttering the activity feed.
This commit is contained in:
ZacharyZhang-NY committed 2026-04-19 19:08:41 -04:00
1 parent d51682b06d
commit 905473d985
2 files changed
+9 -11

No files matched your search

+8 -10
View File
@@ -264,10 +264,13 @@ export function buildMessageMethods(
* visibility rules as listAgentMessages (direct/company/incident always,
* department when agent belongs, project when assigned/lead).
*
* "Unread" means:
* - Direct messages to this agent: `agent_messages.read_at IS NULL`.
* - Channel messages: no matching row in `agent_message_reads` for
* (message_id, agent_id).
* "Unread" is defined uniformly across every channel: no row in
* `agent_message_reads` for (message_id, agent_id). The legacy
* `agent_messages.read_at` column is never written anywhere in the
* codebase and must not be used as a read signal — it only appears in
* SELECT lists for wire-shape compatibility and is overwritten from the
* read-receipts table by every caller that surfaces `readAt` to agents
* or the renderer.
*
* Messages where `from_agent_id = agentId` are excluded (agents don't
* inject their own sent messages back into their transcript).
@@ -331,12 +334,7 @@ export function buildMessageMethods(
).map((row) => row.messageId),
);
return rows.filter((message) => {
if (message.channel === "direct" && message.toAgentId === agentId) {
return !message.readAt;
}
return !readIds.has(message.id);
});
return rows.filter((message) => !readIds.has(message.id));
},
};
+1 -1
View File
@@ -28,7 +28,7 @@ export const WAKE_REASONS: Record<WakeReason, WakeReasonEntry> = {
"Call GET /api/agents/me/standup to get your full briefing",
"Review all your assigned tasks and their statuses",
"Take action on the most important item: reviews > unblocking > new work",
"If nothing needs attention, post a brief status update on your current task",
"If nothing actionable is in your briefing, return control without posting a status comment — activity noise is worse than silence.",
],
},
manual: {