LearnNewsExamplesServices
Frontmatter
id15428
titlemark_read accepts messageId arrays for bulk read-marking
stateClosed
labels
enhancementaimodel-experience
assigneesneo-kimi-phoebe
createdAtJul 18, 2026, 7:20 AM
updatedAtJul 18, 2026, 9:35 PM
githubUrlhttps://github.com/neomjs/neo/issues/15428
authorneo-kimi-phoebe
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtJul 18, 2026, 9:35 PM

mark_read accepts messageId arrays for bulk read-marking

Closed Backlog/active-chunk-7 enhancementaimodel-experience
neo-kimi-phoebe
neo-kimi-phoebe commented on Jul 18, 2026, 7:20 AM

Context

Session 20781e6b (2026-07-18): after a mailbox readAt re-inflation (unread count jumped 5 → 52 mid-session — working hypothesis: a view-scoped repair rebuilt DELIVERED_TO cohorts and reset readAt on the rebuilt edges; the mechanism is a repair-lane question, see Out of Scope), draining the re-inflated mailbox took 51 sequential mark_read MCP calls — one full round-trip per message, identical payloads, one ID different each time. Operator-named fix: bulk marking.

The Problem

mark_read accepts exactly one messageId. Every bulk-clear flow — post-repair re-inflation, wake-flood triage, sunset-backlog draining — pays N round-trips for what is semantically one operation. Mailbox hygiene is a per-turn protocol obligation (AGENTS.md mailbox check + the unread-count discipline in add_memory responses), so the honest path should be cheap. A 51-call burst is pure transport overhead.

The Architectural Reality

  • MCP surface: ai/mcp/server/memory-core/openapi.yaml — the mark_read operation, messageId: string (verify exact operationId/path at implementation).
  • Service: ai/services/memory-core/MailboxService.mjsmarkRead writes readAt onto the message's DELIVERED_TO edge (broadcasts) or the MESSAGE node (DMs), appending the WAL record per message.
  • The per-message mark-write is the correct unit (per-message WAL records are the durability model). The BATCH belongs at the boundary — one MCP call fanning out to N service writes — not as a WAL/schema redesign.

The Fix

Extend mark_read to accept messageId: string | string[]. Arrays are processed in order, returning a per-element result list ({messageId, readAt, status}); per-element failures (unknown ID, unauthorized) isolate — no all-or-nothing. One code path: normalize the param to an array inside the service method. No new MCP tool (surface economy per the tool-description budget).

Optional second row (implementer's call, keep-or-drop at PR time): a mark_all_read convenience scoped to the caller's unread mailbox — sugar over list + bulk-mark, only if the wiring stays trivial.

Contract Ledger

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
MCP mark_read (messageId param) ai/mcp/server/memory-core/openapi.yaml operation + MailboxService.markRead `string string[]`; array → per-element mark + per-element result list single-string behavior byte-identical (current contract) openapi description gains the array form (single line)
(optional) MCP mark_all_read same surface marks all caller-visible unread; returns the count absent if dropped at implementation new single-line description witness: 2 unread → both read, count 2

Acceptance Criteria

  • mark_read accepts an array of messageIds; every element is marked, per-element results returned.
  • Single-string calls behave byte-identically (regression pin).
  • Per-element failure isolation: one unknown/unauthorized ID does not fail the batch.
  • openapi.yaml description updated within the tool-description budget (usage shape, no narrative).
  • mark_all_read either shipped (trivial wiring) or explicitly dropped with rationale in the PR body.

Out of Scope

  • The readAt re-inflation mechanism itself (does cohort-rebuild repair preserve readAt? — a repair-lane question adjacent to #15322 / #15369; named here as context, not bundled).
  • Bulk variants of other mailbox tools (archive_message, delete_message) — same pattern later if friction repeats.
  • Any WAL batching/schema redesign.

Related

  • #15322, #15369 (the cohort-repair work whose rebuilt edges motivated this bulk-clear flow)
  • #15415 (session-2's other friction ticket — one-review codification)

Decision Record impact: none — MCP tool signature extension on an existing surface; no ADR surface.

Live latest-open sweep: checked latest 20 open issues at 2026-07-18T05:19Z; no equivalent found. A2A in-flight claim sweep (status:all, limit 15, herd window): no competing claim on this scope.

Origin Session ID: 20781e6b-ae74-48a6-ad1a-cfaba98d6590

Retrieval Hint: query_raw_memories("mark_read bulk array unread re-inflation readAt repair cohort 51 calls")