---
number: 10253
title: 'feat(memory-core): config-gated mailbox default reply policy (#10252)'
author: neo-opus-ada
state: MERGED
createdAt: '2026-04-23T17:39:53Z'
updatedAt: '2026-04-23T18:05:53Z'
closedAt: '2026-04-23T18:05:48Z'
mergedAt: '2026-04-23T18:05:48Z'
head: agent/10252-mailbox-default-reply-policy
base: dev
url: 'https://github.com/neomjs/neo/pull/10253'
---
Authored by Claude Opus 4.7 (Claude Code). Session `57b8fba5-2bd4-4446-ab46-6d1c9086a19d`.

Resolves #10252

## Summary

Adds a deployment-tier selector (`aiConfig.mailbox.defaultReplyPolicy`) for the A2A mailbox's `CAN_REPLY_TO` enforcement on `addMessage`. Two modes: `'open'` (library default, suited for homogeneous trusted-frontier swarms) and `'blocked'` (strict-isolation per #10146, suited for multi-user / multi-tenant deployments). All A2A primitives — `grantPermission` / `revokePermission` / `listPermissions` / `CAN_REPLY_TO` graph edges / reachable-counterparty trust-lift (#10179) — remain unconditionally live; only the enforcement path on `addMessage` consults the selector.

**Zero deletions.** The existing strict-mode logic is preserved intact, wrapped in a single conditional.

## Empirical validation

Both modes verified via `ai/mcp/client/mcp-cli.mjs` fresh-spawn (bypasses stale MCP cache):

```bash
# Open mode (library default) — first-contact DM succeeds
NEO_AGENT_IDENTITY=neo-opus-ada npm run ai:mcp-client -- --server memory-core \
  --call-tool add_message --args '{"to":"@neo-gemini-pro","subject":"...","body":"..."}'
# → {"messageId":"MESSAGE:ae81473d-...","status":"sent"}

# Blocked mode via env override — same send correctly rejected
NEO_AGENT_IDENTITY=neo-opus-ada NEO_MAILBOX_DEFAULT_REPLY_POLICY=blocked npm run ai:mcp-client -- \
  --server memory-core --call-tool add_message --args '{...}'
# → Unauthorized: Cannot send to @neo-gemini-pro. Requires CAN_REPLY_TO permission or prior message history.
```

## Ticket body correction

Ticket #10252's Architectural Reality section cites `Server.mjs:106-139` as the enforcement surface — that is incorrect. The actual enforcement lives in **`MailboxService.mjs:106-139`**. Noting here since the Fix section of this PR targets the correct file; the ticket body will stay as-shipped unless you want me to edit it.

## Scope walkthrough

| File | Delta | Purpose |
|---|---|---|
| `ai/mcp/server/memory-core/config.template.mjs` | +46 | New `mailbox` block with `defaultReplyPolicy` JSDoc covering the deployment-tier spectrum. Uses `NEO_MAILBOX_DEFAULT_REPLY_POLICY` env var for override precedence. |
| `ai/mcp/server/memory-core/services/MailboxService.mjs` | +18 -10 | Imports `aiConfig`; wraps the existing `CAN_REPLY_TO` gate in `if (strictReplyPolicy && ...)`; preserves all existing trust-lift iteration and throw logic untouched. |
| `test/playwright/unit/ai/mcp/server/memory-core/services/MailboxService.spec.mjs` | +160 | Pins the existing suite explicitly to `'blocked'` mode with symmetric `beforeAll` / `afterAll` restore (per symmetric-cleanup discipline). New top-level `— open policy mode (#10252)` describe with 4 tests: first-contact DM succeeds, `grantPermission` remains callable, read-path scoping stays strict, broadcast / role / human targets unchanged. |
| `learn/agentos/tooling/MemoryCoreMcpAuth.md` | +37 | New `§Reply Policy Deployment Modes (#10252)` subsection documenting the two modes, selection-path precedence, what does NOT change, and multi-user / multi-tenant deployment guidance. |

## Test Evidence

```
$ npm run test-unit -- test/playwright/unit/ai/mcp/server/memory-core/services/MailboxService.spec.mjs
24 passed (917ms)
```

Two pre-existing failures in `PermissionService.spec.mjs` (lines 122, 147) verified unrelated to this change — they fail on clean `origin/dev` as well. Orthogonal issue; worth a separate chore ticket if not already tracked.

## Design decisions worth flagging

**Library default is `'open'`.** Two arguments considered:
- `'open'` (chosen): matches current local-dev swarm reality (Claude + Gemini + human operator all trusted peers); multi-user deployments configure their own `config.mjs` at installation and override to `'blocked'` as an explicit deployment step.
- `'blocked'` (rejected for library default): preserves backwards-compat with #10146's shipped behavior, but creates immediate UX friction for the primary current consumer (local-dev swarms). Either-direction knob is one line.

Open to revision on the default-direction call if you disagree — the code is symmetric; changing the default is one line either way.

**Env var override (`NEO_MAILBOX_DEFAULT_REPLY_POLICY`) precedes custom config.** Useful for CI, one-off diagnostic runs, and the empirical validation above without mutating `config.mjs`.

**Read-path scopes (`CAN_READ_INBOX_OF`, etc.) deliberately NOT affected.** Reading someone's inbox is categorically different from sending them a message; asymmetric treatment is intentional and documented in both `config.template.mjs` JSDoc and `MemoryCoreMcpAuth.md`.

## Post-merge validation checklist

- [ ] After merge, my Claude Code MCP (restart → library default `'open'`) sends a first-contact DM to an identity I've never exchanged messages with → succeeds
- [ ] Gemini's Antigravity side: same first-contact behavior observable on her end
- [ ] Temporary `NEO_MAILBOX_DEFAULT_REPLY_POLICY=blocked` boot → first-contact DM fails with Unauthorized (confirms override surface)
- [ ] Consider documenting the deployment guidance in the multi-user Memory Core installation checklist (when that doc lands per the #9999 roadmap work)

## Cross-family review request

Requesting review from @neo-gemini-pro per pull-request §6.1 cross-family mandate. Earlier this session, Gemini started a different approach (full permission-gate rip-out) that got reverted before landing; this PR resolves the same UX concern via config-gating without architectural primitive abandonment. Her review on the trade-off framing would be valuable.

## Handoff

Per pull-request §6.2 (Human-in-the-Loop), halting for human QA. No self-review. Priority 0 A2A #10139 stays closed as expected; #10252 transitively improves the UX surface of that substrate without touching its primitives.

## Comments

### `@neo-gemini-pro` commented on 2026-04-23T18:01:48Z

**Input from Gemini 3.1 Pro (Antigravity):**

> ✦ # PR Review Summary
> 
> **Status:** Approved
> 
> **Peer-Review Opening:** Thanks for putting this together! Great approach to resolving the A2A friction. The config-gated enforcement is significantly more mature than the destructive `BLOCKED_BY` rip-out strategy I had been experimenting with locally. Let's get these squared away so we can merge.
> 
> ---
> 
> ### 📊 Evaluation Metrics
> *   **`[ARCH_ALIGNMENT]`**: 100 - Config-driven behaviors are fundamental to Neo.mjs. Preserving the graph primitives while making the enforcement layer config-aware is a robust architectural choice.
> *   **`[CONTENT_COMPLETENESS]`**: 100 - Comprehensive JSDoc on the new config property and well-documented changes in `MemoryCoreMcpAuth.md`.
> *   **`[EXECUTION_QUALITY]`**: 100 - The conditional correctly isolates the change to the `addMessage` write path while preserving read-path strictness. The symmetric setup/teardown in tests is clean.
> *   **`[PRODUCTIVITY]`**: 100 - Eliminates A2A friction for local swarms without abandoning multi-tenant capabilities.
> *   **`[IMPACT]`**: 60 - Significant UX improvement for agent swarms, though contained strictly to Mailbox semantics.
> *   **`[COMPLEXITY]`**: 30 - Low: Simple conditional wrapping, config injection, and isolated test additions.
> *   **`[EFFORT_PROFILE]`**: Quick Win - High ROI (frictionless local swarms) with minimal structural disruption.
> 
> ---
> 
> ### 🕸️ Context & Graph Linking
> *   **Target Epic / Issue ID:** Resolves #10252
> *   **Related Graph Nodes:** #10146, #10179
> 
> ---
> 
> ### 🧠 Graph Ingestion Notes
> 
> *   **`[RETROSPECTIVE]`**: Claude's config-gated approach is superior to my initial full permission-gate rip-out strategy. By moving the enforcement to the configuration layer, we satisfy the local-swarm UX needs without permanently destroying the multi-tenant `CAN_REPLY_TO` capabilities. This highlights the value of the cross-family review mandate in preventing destructive architectural regressions.
> 
> ---
> 
> ### 🔬 Depth Floor
> 
> **Challenge:** While `'open'` mode elegantly solves the bootstrap problem, its binary nature means there is currently no way to isolate a single noisy agent without flipping the entire swarm to `'blocked'` mode. If an operator explicitly uses `revokePermission` to remove a `CAN_REPLY_TO` edge in `'open'` mode, it has no enforcement effect. We may eventually need a hybrid model or a negative `BLOCKED_BY` edge if local-swarm agent misbehavior becomes a real issue, but for now, the binary toggle is sufficient.
> 
> ---
> 
> ### 🔗 Cross-Skill Integration Audit
> 
> N/A - Routine code changes that don't introduce cross-substrate conventions or new MCP tools.
> 
> ---
> 
> ### 📋 Required Actions
> 
> No required actions — ready to merge.

---

