LearnNewsExamplesServices
Frontmatter
id10180
titleMailbox polish: countMessages primitive + concept provenance docs
stateClosed
labels
documentationenhancementai
assigneesneo-opus-ada
createdAtApr 22, 2026, 5:39 PM
updatedAtJun 7, 2026, 7:20 PM
githubUrlhttps://github.com/neomjs/neo/issues/10180
authorneo-opus-ada
commentsCount0
parentIssue10139
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtMay 17, 2026, 10:56 AM

Mailbox polish: countMessages primitive + concept provenance docs

Closed v13.0.0/archive-v13-0-0-chunk-5 documentationenhancementai
neo-opus-ada
neo-opus-ada commented on Apr 22, 2026, 5:39 PM

Mailbox polish: countMessages primitive + concept provenance docs

Context

Two small Phase-4-followup items surfaced during the cross-model review cycle on #10175 and #10177. Aggregated here because both are mailbox-substrate polish, neither is load-bearing for current functionality, and splitting them into separate tickets would add review-overhead proportional to review value rather than change value. Filed so knowledge doesn't get lost between the merge of #10177 and whenever the next Phase 4 polish cycle fires.

The Problem

Problem 1 — getHealthcheckPreview unread count caps at 100.

MailboxService.getHealthcheckPreview (introduced in #10177) counts unreads by fetching this.listMessages({box: 'inbox', limit: 100}) and iterating for !msg.readAt. The limit of 100 is the current effective upper bound on unreadCount — an inbox with more than 100 unread messages will under-report. Fine at current swarm volumes (3 agents, per-agent inbox typically 0–5), but the cap is silently-incorrect rather than explicitly documented.

A natural fix is a dedicated MailboxService.countMessages({status, box, fromIdentity?}) primitive that returns a scalar count via direct SQL — patterned after #10178's buildMailboxDelta, which does the same thing in MemoryService.addMemory via a SELECT COUNT(DISTINCT e.source) against the edge table. Shared primitive retires the limit-100 proxy pattern entirely.

Problem 2 — auto_extracted concept-provenance flag is undocumented.

#10175 introduced an auto_extracted: true property on CONCEPT:* and CLASS:* nodes auto-seeded during MailboxService.addMessageSemanticGraphExtractor.extractMessageConcepts fire-and-forget. The flag distinguishes agent-manually-tagged concepts (no flag; curated) from LLM-auto-extracted concepts (flag set; provenance stamped). Downstream consumers reading concept nodes from the Native Edge Graph (DreamService, Librarian traversal, future GraphRAG query) have no way to distinguish the two populations without this signal — but the signal isn't documented anywhere outside the PR source code.

learn/agentos/ConceptOntology.md is the canonical docs home for concept-node conventions; currently silent on the auto_extracted contract.

The Architectural Reality

Problem 1 files:

  • ai/mcp/server/memory-core/services/MailboxService.mjs — new countMessages method; getHealthcheckPreview refactored to call it
  • ai/mcp/server/memory-core/services/MemoryService.mjsbuildMailboxDelta may optionally delegate to the new primitive (DRY; keep the direct-SQL fast-path to sidestep the in-memory cache coherence limitation described in #10178)
  • ai/mcp/server/memory-core/openapi.yaml — new count_messages tool exposed via MCP surface (or keep as internal service method; see Out of Scope)
  • test/playwright/unit/ai/mcp/server/memory-core/services/MailboxService.spec.mjs — regression coverage for >100 inbox depth

Problem 2 files:

  • learn/agentos/ConceptOntology.md — new subsection documenting auto_extracted provenance flag, the write path (addMessageSemanticGraphExtractor.extractMessageConcepts), the edge-weight distinction (auto-extracted 0.8 vs manual 1.0), and read-time consumer guidance (distinguishing curated from auto for ranking or filtering)
  • ai/daemons/services/SemanticGraphExtractor.mjsextractMessageConcepts JSDoc extended with @see link to the docs page

The Fix

  1. Add MailboxService.countMessages({status, box, fromIdentity?}) returning {count: Number} via direct SQL (follow buildMailboxDelta's pattern in MemoryService.mjsprepare + get against Edges joined with Nodes filtering on SENT_TO / SENT_BY + readAt IS NULL as appropriate).
  2. Refactor getHealthcheckPreview to call countMessages for unreadCount instead of fetching 100 messages to count them. Keep limit: 3 for the preview items themselves.
  3. Update learn/agentos/ConceptOntology.md with the auto_extracted provenance section (describe write path + consumer read pattern + edge-weight convention).
  4. Extend SemanticGraphExtractor.extractMessageConcepts JSDoc with @see pointing to the new docs section.

Acceptance Criteria

  • MailboxService.countMessages({status, box, fromIdentity?}) implemented with direct-SQL path
  • getHealthcheckPreview uses countMessages for unreadCount; no longer uses limit: 100 proxy
  • Playwright regression: inbox with 150+ unread messages — unreadCount returns the correct value (not capped)
  • Playwright regression: countMessages matches listMessages({status: 'unread'}).length for small inboxes
  • learn/agentos/ConceptOntology.md gains an auto_extracted provenance section
  • SemanticGraphExtractor.extractMessageConcepts JSDoc carries @see cross-reference

Out of Scope

  • MCP tool surface for countMessages — start as internal service method; add MCP tool only if an external consumer (Dream daemon, Librarian, operator healthcheck dashboard) needs it. Avoids growing the tool surface for a function only getHealthcheckPreview currently calls.
  • Migration of buildMailboxDelta#10178's direct-SQL path already works correctly; refactoring to use countMessages is a DRY concern, not a correctness concern; defer to a separate follow-up if it becomes a drift risk.
  • Concurrency back-pressure on extractMessageConcepts — separate concern; already noted in #10175 review; belongs to a dedicated ticket if bursty-traffic rate-limit issues materialize.

Avoided Traps

  • "Just raise limit to 10000." Rejected. It's still a silent cap; fetching 10000 messages to count them is pathologically expensive at scale. Direct SQL COUNT is O(indexed) for both correctness and performance.
  • "Document auto_extracted in the PR description only." Rejected. PR descriptions are ephemeral for the Retrospective daemon; canonical docs in learn/agentos/ is the A2A memory bridge downstream consumers actually query.

Related

  • Parent: #10139 Mailbox A2A primitive (epic)
  • Surfaced by: #10175 review ([KB_GAP] tag on the auto_extracted convention), #10177 review (100-cap observation on getHealthcheckPreview)
  • Builds on: #10178 (direct-SQL pattern in buildMailboxDelta is the template for countMessages)
  • Adjacent: #10172 node-ID case canonicalization (CONCEPT:/CLASS: case coordination — see #10172 comment cross-referencing this ticket)

Origin Session ID: 7ee23599-3f94-4f75-b54c-97ba92c9aaef

tobiu closed this issue on May 17, 2026, 10:56 AM