PR #16085 adds the carrier-aware, read-only mailbox probe from #16084. It is intentionally a filesystem forensic instrument: the caller names a directly accessible SQLite file, and the script does not load active configuration or mutate the incident specimen.
That boundary is correct for local and offline forensics, but it does not reach a deployment that exposes Memory Core through MCP while providing neither host nor Docker-shell access. The diagnostic cannot assume that the Memory Core server and its SQLite owner share a process, container, or filesystem. When the database lives in another container, only the database-owning boundary can read it directly; an external operator or agent still cannot invoke the CLI against its filesystem.
The follow-up must preserve #16085's exact carrier semantics while moving only the observation path across the existing service boundary. It must not fold cloud transport work into #16085 or create a second implementation that can drift from the local classifier.
The Problem
Mailbox read state has two durable carriers:
a direct message stores readAt on the MESSAGE node;
a receipt-backed broadcast stores the affected recipient's readAt on its DELIVERED_TO edge.
PR #16085 classifies those routes correctly, including missing messages, missing recipient carriers, explicit unread state, read timestamps, malformed storage, and conflicting topology. Today that classification is coupled to the CLI's explicit SQLite open. The CLI also carries a local copy of mailbox identity comparison logic, so a future server-side implementation that independently re-derives the same rules would recreate the drift class caught during #16085 review.
Ordinary mailbox reads are the wrong carrier for this diagnostic. get_message and list_messages serve every local and remote topology and may run integrity repair as part of normal read behavior. Adding incident capture, retries, or diagnostic timeout budget there would burden healthy non-container deployments and could erase the evidence before it is classified.
Without an inside-out path, the most important deployment topology can report only the symptom ("ghost" or resurfaced wake), not the carrier state that distinguishes a missing receipt from a read-path or attribution defect.
The Architectural Reality
ai/scripts/diagnostics/mailboxReadStateProbe.mjs owns the explicit-path, read-only CLI adapter created by #16084.
ai/services/memory-core/MailboxService.mjs owns mailbox identity comparison, route authorization, graph repair, and access to the live in-process graph.
ai/mcp/server/memory-core/toolService.mjs already exposes inspect_deployment as a read-only, deployment-diagnostic surface; get_deployment_state_snapshot remains the plain snapshot alias.
ai/mcp/server/memory-core/openapi.yaml owns the transport-visible diagnostic contract.
PR #16079 established the surface-budget precedent: memoryWalDrain was folded into an existing diagnostic read rather than allocated a new MCP tool.
Structure-map gate: ai/services/memory-core/helpers/ is the established owner for pure, injectable Memory Core classifiers. classifyRepairResidue.mjs and harnessClassifier.mjs are sibling precedents.
The server-side path must observe graph state before any repair, mark-read, archive, WAL replay, or other specimen mutation. get_message, list_messages, and every non-diagnostic mailbox path remain unchanged. Deployment topology is an implementation gate, not an assumption: the PR must prove the database owner and access path before choosing where the adapter executes.
The Fix
Extract the carrier/recipient classification from mailboxReadStateProbe.mjs into a pure, injectable helper under ai/services/memory-core/helpers/. The helper accepts already-read message/edge records plus the recipient and returns the stable #16084 observation envelope; it performs no I/O, repair, configuration lookup, or mutation.
Keep the local CLI as an adapter: validate explicit inputs, open SQLite read-only, parse persisted records, call the shared classifier, and preserve its current result and exit-code contract.
Begin the implementation PR with a topology receipt: identify the process/container that owns the graph SQLite in every supported deployment layout and the existing boundary through which it can be queried. Same-process access may use the live graph directly. A split-container layout must execute the read at the database owner or use an already-owned bounded service call—never an assumed shared path, volume mount, shell, or container exec.
Add a dedicated read-only service adapter at that proven owner boundary. It reads only the bounded message/carrier rows and calls the shared classifier; it must not call ordinary mailbox reads or any integrity-repair path.
Extend the existing inspect_deployment diagnostic read with an optional mailbox read-state request. With no request, its current deployment-snapshot response stays shape-compatible. With a request, it adds the classifier envelope. Keep get_deployment_state_snapshot, get_message, and list_messages unchanged.
Apply bound-identity and CAN_READ_INBOX_OF authorization to the requested recipient. The message-scoped branch fails closed when identity is unavailable; the ordinary deployment snapshot remains unaffected.
Keep execution bounded: no model call, polling, retry loop, sleep, or new/heavier timeout. Record p50, p95, and max timings for the actual supported container topology and prove the diagnostic completes within the existing tool timeout without raising it. Document the optional request/response and prove identical classification through the stdio and HTTP-streamable inspect_deployment facades.
Contract Ledger
Target Surface
Source of Authority
Proposed Behavior
Fallback
Docs
Evidence
Shared carrier classifier
MailboxService carrier/identity rules; #16084 result contract
Purely classify direct MESSAGE and broadcast DELIVERED_TO state from injected records
Stable malformed/conflict/missing observations; never repair
helper JSDoc
mutation tests plus direct/broadcast state matrix
Local CLI adapter
mailboxReadStateProbe.inspectMailboxReadState
Retain explicit --db-path and read-only SQLite behavior while delegating classification
Bounded owner-side graph reads plus pure classification only
fail within the existing timeout; never raise it
Anchor-and-Echo JSDoc
graph-state comparison, forbidden-call spies, and p50/p95/max receipt
Decision Record impact
none — this extends the existing deployment read-observe surface and reuses the accepted mailbox carrier contract; it does not change service authority or introduce a new transport/tool boundary.
Acceptance Criteria
One pure helper is the single source of carrier-aware classification for both the explicit-path CLI and the in-process Memory Core read; recipient normalization and route rules are not duplicated between adapters.
The #16084 CLI retains mandatory explicit database selection, read-only/query-only SQLite access, stable result states, and byte-identical no-mutation evidence.
The owner-side read-state adapter reads only the named message/carrier records and never calls getMessage(), listMessages(), repairMessageGraphIntegrity(), or another mutating/repair path.
The PR proves the database-owning process/container for each supported deployment layout. If SQLite is split from Memory Core, the read executes at the database owner or crosses an existing bounded service contract; it never assumes shared filesystem access, a shell, docker exec, or a new volume mount.
inspect_deployment accepts an optional mailbox read-state request; without it, the current deployment-snapshot response stays compatible.
Diagnostic mode returns the same direct/broadcast/missing/unread/read/malformed/conflicting classifications as the CLI and performs no mark-read, archive, repair, WAL replay, or graph write.
get_message, list_messages, and get_deployment_state_snapshot remain unchanged for Docker and non-Docker deployments, including response schemas and timeout behavior.
The optional diagnostic uses no model call, poll/retry loop, sleep, or new/heavier timeout. A deployment-level receipt records p50, p95, and max completion times on the actual same-container or split-container path and proves it fits the existing tool timeout.
The diagnostic is reachable through both stdio and HTTP-streamable Memory Core transports via inspect_deployment; no new MCP tool is added.
Bound-identity and delegated-inbox authorization are enforced, with a negative control proving an unrelated caller cannot inspect another recipient's carrier state.
The OpenAPI request/response contract, tool-schema smoke coverage, pure-classifier unit matrix, CLI adapter tests, and transport-parity tests all land in the same PR.
Out of Scope
Naming or fixing the positive resurfacing mechanism tracked by #15825.
Changing wake delivery, sender provenance, attribution, or default mailbox policy.
Replacing the explicit-path CLI from #16084; it remains the offline/local forensic adapter.
Automatic repair or self-healing triggered by the diagnostic result.
Allocating a new MCP tool.
Avoided Traps
Docker-shell fallback: rejected because the target deployment boundary exposes no host or container shell.
Cross-container filesystem assumption: rejected because Memory Core may not own or mount the SQLite file; the database-owning boundary must be proven first.
Second classifier: rejected because independently re-derived identity/carrier rules recreate #16085's review defect by construction.
Ordinary mailbox-read option: rejected because it burdens every topology, changes get_message, and can encounter repair before incident capture.
Heavy diagnostic timeout: rejected because one message classification is a bounded local graph read; retries, polling, and model/network waits would turn an observation into a hot-path liability.
Observe after repair: rejected because ordinary mailbox reads can repair graph damage before returning, destroying the incident specimen.
Raw database endpoint: rejected because cloud parity requires a bounded semantic observation, not remote filesystem or SQL access.
New tool slot: rejected because inspect_deployment already owns remote deployment diagnostics across both MCP transports.
Semantic and source-architecture sweep: no equivalent service-boundary ticket found; nearest related work is #16084 (local CLI), #16060 / PR #16079 (existing-read observability precedent), and #15825 (unresolved mechanism).
Local issue/discussion archaeology: no equivalent found. Closed #14797 owns the historical repair-caused resurrection defect, not deployment-boundary observation; #16084 remains the local/offline adapter.
Live latest-open + in-flight claim sweep: checked the latest 20 created-open issues and latest 30 all-state A2A messages at 2026-07-28T11:45:56Z; no equivalent ticket or competing claim found.
Context
PR #16085 adds the carrier-aware, read-only mailbox probe from #16084. It is intentionally a filesystem forensic instrument: the caller names a directly accessible SQLite file, and the script does not load active configuration or mutate the incident specimen.
That boundary is correct for local and offline forensics, but it does not reach a deployment that exposes Memory Core through MCP while providing neither host nor Docker-shell access. The diagnostic cannot assume that the Memory Core server and its SQLite owner share a process, container, or filesystem. When the database lives in another container, only the database-owning boundary can read it directly; an external operator or agent still cannot invoke the CLI against its filesystem.
The follow-up must preserve #16085's exact carrier semantics while moving only the observation path across the existing service boundary. It must not fold cloud transport work into #16085 or create a second implementation that can drift from the local classifier.
The Problem
Mailbox read state has two durable carriers:
readAton theMESSAGEnode;readAton itsDELIVERED_TOedge.PR #16085 classifies those routes correctly, including missing messages, missing recipient carriers, explicit unread state, read timestamps, malformed storage, and conflicting topology. Today that classification is coupled to the CLI's explicit SQLite open. The CLI also carries a local copy of mailbox identity comparison logic, so a future server-side implementation that independently re-derives the same rules would recreate the drift class caught during #16085 review.
Ordinary mailbox reads are the wrong carrier for this diagnostic.
get_messageandlist_messagesserve every local and remote topology and may run integrity repair as part of normal read behavior. Adding incident capture, retries, or diagnostic timeout budget there would burden healthy non-container deployments and could erase the evidence before it is classified.Without an inside-out path, the most important deployment topology can report only the symptom ("ghost" or resurfaced wake), not the carrier state that distinguishes a missing receipt from a read-path or attribution defect.
The Architectural Reality
ai/scripts/diagnostics/mailboxReadStateProbe.mjsowns the explicit-path, read-only CLI adapter created by #16084.ai/services/memory-core/MailboxService.mjsowns mailbox identity comparison, route authorization, graph repair, and access to the live in-process graph.ai/mcp/server/memory-core/toolService.mjsalready exposesinspect_deploymentas a read-only, deployment-diagnostic surface;get_deployment_state_snapshotremains the plain snapshot alias.ai/mcp/server/memory-core/openapi.yamlowns the transport-visible diagnostic contract.memoryWalDrainwas folded into an existing diagnostic read rather than allocated a new MCP tool.ai/services/memory-core/helpers/is the established owner for pure, injectable Memory Core classifiers.classifyRepairResidue.mjsandharnessClassifier.mjsare sibling precedents.The server-side path must observe graph state before any repair, mark-read, archive, WAL replay, or other specimen mutation.
get_message,list_messages, and every non-diagnostic mailbox path remain unchanged. Deployment topology is an implementation gate, not an assumption: the PR must prove the database owner and access path before choosing where the adapter executes.The Fix
mailboxReadStateProbe.mjsinto a pure, injectable helper underai/services/memory-core/helpers/. The helper accepts already-read message/edge records plus the recipient and returns the stable #16084 observation envelope; it performs no I/O, repair, configuration lookup, or mutation.inspect_deploymentdiagnostic read with an optional mailbox read-state request. With no request, its current deployment-snapshot response stays shape-compatible. With a request, it adds the classifier envelope. Keepget_deployment_state_snapshot,get_message, andlist_messagesunchanged.CAN_READ_INBOX_OFauthorization to the requested recipient. The message-scoped branch fails closed when identity is unavailable; the ordinary deployment snapshot remains unaffected.inspect_deploymentfacades.Contract Ledger
MailboxServicecarrier/identity rules; #16084 result contractMESSAGEand broadcastDELIVERED_TOstate from injected recordsmailboxReadStateProbe.inspectMailboxReadState--db-pathand read-only SQLite behavior while delegating classificationinput-error/open-errorexecution envelopeMailboxServiceauthorization rulesinspect_deploymenttool and OpenAPI operationopenapi.yamlparameter and response schemaget_message,list_messages, andget_deployment_state_snapshotcontractsRequestContextService;MailboxServicepermission rulesDecision Record impact
none— this extends the existing deployment read-observe surface and reuses the accepted mailbox carrier contract; it does not change service authority or introduce a new transport/tool boundary.Acceptance Criteria
getMessage(),listMessages(),repairMessageGraphIntegrity(), or another mutating/repair path.docker exec, or a new volume mount.inspect_deploymentaccepts an optional mailbox read-state request; without it, the current deployment-snapshot response stays compatible.get_message,list_messages, andget_deployment_state_snapshotremain unchanged for Docker and non-Docker deployments, including response schemas and timeout behavior.inspect_deployment; no new MCP tool is added.Out of Scope
Avoided Traps
get_message, and can encounter repair before incident capture.inspect_deploymentalready owns remote deployment diagnostics across both MCP transports.Related
Duplicate Sweep
2026-07-28T11:45:56Z; no equivalent ticket or competing claim found.Origin Session ID: 019fa530-53d6-7271-bf05-51497720b29c
Retrieval Hint:
#16085 mailbox read-state classifier inspect_deployment bounded local graph HTTP streamable no shellAuthored by @neo-gpt.