LearnNewsExamplesServices
Frontmatter
id16960
titleMailbox listing re-scans every edge for every matched message
stateClosed
labels
bugaiperformanceagent-os
assigneesneo-gpt-emmy
createdAtAug 11, 2026, 1:13 PM
updatedAtAug 11, 2026, 3:04 PM
githubUrlhttps://github.com/neomjs/neo/issues/16960
authorneo-gpt-emmy
commentsCount0
parentIssue16677
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 11, 2026, 3:04 PM

Mailbox listing re-scans every edge for every matched message

Closed Backlog/active-chunk-15 bugaiperformanceagent-os
neo-gpt-emmy
neo-gpt-emmy commented on Aug 11, 2026, 1:13 PM

Context

The post-admission half of parent #16677 has a deterministic source-level work amplifier even after closed #16767 removed the non-converging WAL-repair floor.

On the canonical one-CPU plane, fixed-window telemetry measured Fleet-bound list_messages calls averaging 10,642 ms and reaching 21,606 ms. In a separate fixed ingress window, 42/47 Memory Core POST reset intervals overlapped a Fleet list_messages execution interval. That overlap is correlation, not request-identity proof; this ticket owns only the independently verified list-projection cost below.

At current dev@82470ab084:

The incident store measured 93,765 edges and 10,704 MESSAGE nodes. limit:50 bounds the returned page, but the repeated projection work scales with the whole edge cache multiplied by the number of matches.

The Problem

MailboxService.listMessages() has an effectively quadratic projection phase.

For E cached edges and M matched messages, the current implementation performs:

  1. one E-edge candidate scan;
  2. an O(M²) linear-array duplicate check;
  3. one additional E-edge metadata scan per matched message;
  4. one additional E-edge related-ticket scan per matched message;
  5. repeated full-edge broadcast-delivery filters on the legacy/current broadcast branch.

Pagination cannot protect the server because totalCount intentionally describes all filter matches and the slice happens last. The contract still needs the full match count; it does not need to rediscover the same edges for each row.

Closed #16767 is adjacent, not duplicate: it made graph/WAL repair convergent. This leaf starts after that repair and removes repeated graph projection scans from the ordinary list.

The Architectural Reality

The scoped structure map establishes the existing owners:

  • ai/services/memory-core/MailboxService.mjs owns mailbox authorization, graph hydration, filtering, projection, completeness, and pagination;
  • ai/services/fleet/fleetA2AActivityAdapter.mjs is a consumer of the existing list_messages contract;
  • GraphService.db.getAdjacentNodes() is still required to hydrate peer-process writes before projection;
  • ai/graph/Store.mjs already maintains source- and target-keyed secondary indexes, and Database.mjs constructs the edge store with both indexes;
  • the public MCP request/response authority remains ai/mcp/server/memory-core/openapi.yaml.

The fix belongs in the existing mailbox projection seam and must consume the existing graph indexes. It does not require a new MCP tool, a Fleet-private mailbox, a polling change, a second per-call edge index, or a GraphService-wide index.

The Fix

Replace the cache-wide and per-message scans with indexed graph reads:

  1. After the current target/sentinel vicinity hydration, union db.edges.getByIndex('target', variant) for the caller's storage variants and AGENT:* where the box requires them.
  2. Collect and de-duplicate candidate message ids with a Set, preserving the current direct, outbox, current-broadcast, and legacy-broadcast visibility rules.
  3. Hydrate each candidate message's outbound vicinity so peer-process SENT_BY, SENT_TO, PART_OF_THREAD, TAGGED_CONCEPT, REFERENCES_TICKET, and DELIVERED_TO edges remain visible.
  4. Read each candidate's metadata once through db.edges.getByIndex('source', messageId). Use that indexed slice for sender/recipient/thread/tag/ticket and broadcast-delivery resolution.
  5. Preserve archive/read/retraction semantics, authorization, sorting, totalCount, truncated, nextOffset, and the existing public envelope.
  6. Add a production-bound witness that fails if the full edge collection is enumerated after indexed vicinity hydration.

Contract Ledger

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
routing candidates hydrated graph target indexes + caller identity/box enumerate only routing edges addressed to the relevant identity variants/sentinel; de-duplicate message ids by Set absent indexed rows produce an empty candidate set, not a global scan method JSDoc inbox/outbox/all + legacy-variant + broadcast matrix
per-message metadata hydrated graph source index sender, recipient, thread, tags, delivery state, and ticket refs use one indexed edge slice per message absent optional edges retain current null/absent behavior method/helper JSDoc projection parity controls
completeness and pagination filtered projected set preserve newest-first totalCount, truncated, nextOffset, limit, and offset exactly invalid pagination remains rejected existing MCP docs unchanged multi-page exact-envelope control
Fleet activity consumer fleetA2AActivityAdapter unchanged list_messages request and response shape existing degraded/error handling unchanged none adapter compatibility control
projection work bound Graph Store indexes consumed by MailboxService the complete edge collection is not enumerated after indexed vicinity hydration explicit test failure if an indexed lookup regresses to db.edges.items test JSDoc poisoned-full-iterator mutation witness

Decision Record impact

none — this enforces the existing mailbox read contract inside its current owner. It changes neither service ownership nor public protocol semantics.

Acceptance Criteria

Implementation receipt: PR #16962 at e25f972812; exact current-dev mailbox + Fleet slice 168/168 green.

  • listMessages() no longer calls messages.find() to de-duplicate candidate rows.
  • Candidate discovery uses the existing target index for every canonical/legacy identity storage variant and the broadcast sentinel required by the selected box.
  • Message metadata, related-ticket lookup, and broadcast-delivery lookup use the existing source index rather than scanning the complete edge cache once per projected message.
  • A production-bound fixture fully hydrates target/sentinel/message vicinities, poisons full edge-store iteration while leaving secondary indexes intact, and still projects direct, broadcast, threaded, tagged, and related-ticket rows correctly.
  • Restoring a db.edges.items traversal makes the named work-bound fixture red.
  • Direct inbox, outbox, current per-recipient broadcast, and legacy shared-read broadcast rows remain visible to the same identities.
  • Read/unread, archived, retracted, sender, thread, tagged-concept, related-ticket, and task projections remain byte-compatible.
  • Sorting, totalCount, truncated, nextOffset, limit, and offset remain correct across at least three pages.
  • The Fleet activity adapter continues to consume the unchanged public envelope.
  • The canonical Neo unit command targets the affected mailbox and Fleet adapter specs explicitly; no default npx playwright test invocation is used.

Out of Scope

  • Replacing truthful totalCount with an estimate.
  • The graph/WAL repair contract delivered by closed #16767.
  • Changing Fleet's 15-second cadence or adding a Fleet-private mailbox store.
  • PAT admission caching, transport deadlines, load shedding, container restart policy, or generic GraphService indexing.
  • Claiming this one amplification explains every reset recorded on #16677.

Avoided Traps

  • Slice sooner and report less. That breaks the completeness contract; the repair removes repeated discovery, not truthful totalCount.
  • Build a second per-call edge index. The graph Store already owns maintained source/target indexes; duplicating them adds drift and retains an avoidable global scan.
  • Skip hydration because an index exists. Indexed reads before peer-process vicinity hydration would be fast and stale.
  • Move the workload into Fleet. Mailbox graph semantics and authorization remain Memory Core-owned.
  • Raise the timeout. More caller patience does not remove server work.
  • Call the correlation causation. The reset overlap motivates priority; the current-source nested scans independently establish this defect.

Related

Parent: #16677

Successor to #16767

Related: #16748

Duplicate and Collision Sweep

  • Live latest-open sweep: checked the newest 20 open issues created-descending immediately before filing; no equivalent ticket existed.
  • Recent A2A sweep: checked the latest 30 all-state messages immediately before filing; no competing claim overlapped this MailboxService projection scope.
  • Exact GitHub and repository searches for list_messages performance, Fleet mailbox saturation, MailboxService edge scan, and quadratic/full-graph mailbox projection found parent #16677 and closed #16767, not an open owner.
  • Semantic Memory Core query for #16677 Fleet list_messages full graph scan 15 second saturation returned no relevant prior artifact. The Knowledge Base semantic leg was unavailable because the identity-bound client could not obtain a canonical identity; current GitHub and source were used as the live authority.

Origin Session ID: 019fe5e8-b963-7e93-8762-c8e4af16bdec

Retrieval Hint: "#16677 Fleet list_messages 93,765 edges graph Store source target index pagination after projection"

tobiu referenced in commit 6c2fdd0 - "fix(memory-core): index mailbox list projection (#16960) (#16962) on Aug 11, 2026, 3:04 PM
tobiu closed this issue on Aug 11, 2026, 3:04 PM