Drop unused agent_messages.read_at column via migration v49 (M1)

The `agent_messages.read_at` column has had no production writer since
v12 moved read tracking into the per-viewer `agent_message_reads`
receipts table. It survived anyway, carrying a NULL through every SELECT
list and begging the next maintainer to accidentally read stale data
from it.

- v49 migration: `ALTER TABLE agent_messages DROP COLUMN read_at`
  (idempotent: skips if absent).
- messages.ts: SELECTs no longer fetch the column; per-viewer `readAt`
  is computed from `agent_message_reads` as before, with `null` for
  call-sites that have no viewer context (getAgentMessage /
  searchMessages / listAgentInboxUnread).
- runtime-bridge.ts / runtime-message-injector.ts: comments referencing
  the dropped column updated to name the read-receipts table instead.
This commit is contained in:
ZacharyZhang-NY committed 2026-04-20 12:20:32 -04:00
1 parent dade5eb333
commit d7e267031f
6 files changed
+90 -36

No files matched your search

+10 -10
View File
@@ -562,16 +562,16 @@ export class RuntimeBridge {
// as plain user turns in its context.
//
// Replay safety lives entirely on the `agent_message_projections`
// ledger. We deliberately do NOT touch `agent_messages.read_at`
// here even though that column exists: "we put the message into
// the agent's transcript" and "the agent has acknowledged the
// message" are two different facts, and welding them onto one
// column made every projected message immediately invisible to
// the agent's `?unreadOnly=true` API call. The agent then read
// its IC prompt ("respond to ALL unread messages"), called the
// API, got an empty list, and posted a status comment instead of
// replying. The explicit POST /messages/{id}/read is now the
// sole signal that flips `read_at`.
// ledger. "We put the message into the agent's transcript" and
// "the agent has acknowledged the message" are two different
// facts, tracked in two different tables: projections are append-
// only on dispatch, while reads are inserted into
// `agent_message_reads` only when the agent POSTs
// /messages/{id}/read. Conflating the two (as an earlier build
// that wrote to `agent_messages.read_at` at projection time did)
// made every projected message immediately invisible to
// `?unreadOnly=true`, so agents posted status comments instead of
// replies. The explicit read endpoint is the sole signal.
const store = this.getOrCreateStore(payload.taskSessionId);
const inboxRecords = this.deps.db.listAgentInboxUnread(payload.companyId, payload.agentId);
if (inboxRecords.length > 0) {