Skip to content

Scoping and sharing memory

When several agents share one Postgres store, each memory needs an answer to one question: who may read it? Meaning Memory answers it with scope groups.

  • A memory with no scope group is private to the agent that wrote it.
  • A memory written into a group, such as team:accounts, is visible to every agent that is a member of that group.
  • An access policy says which groups each agent may use. Agents can only write into groups the policy allows, and they become members of those groups.
  • Removing an agent from a group does not take back what it could already see. It keeps reading what was shared while it was a member, and for an all_history group that includes everything shared before it joined. See Removing an agent before you design groups for sensitive material.

For a worked example, see a support team with triage, billing and escalations agents sharing case history.

On the file backend each workspace belongs to one agent, so there is nothing to share and this page does not apply.

The policy is a YAML file. This one has two agents that share a team:accounts group:

/srv/scope_auth.yaml
version: 1
agents:
support-bot:
allowed_scope_groups: [agent:support-bot, team:accounts]
account-mgr:
allowed_scope_groups: [agent:account-mgr, team:accounts]
groups:
agent:support-bot: { backfill_policy: forward_only }
agent:account-mgr: { backfill_policy: forward_only }
team:accounts: { backfill_policy: all_history }
  • Group names are a prefix and a name: team:, role:, org: or project:, then lowercase letters, digits, - or _. A write naming a group without a prefix, such as support, is refused.
  • agent:<name> is each agent’s private group. Every agent needs its own, and it is never shared.
  • backfill_policy decides what a new member sees:
    • all_history: everything ever shared into the group, including memories written before the agent joined.
    • forward_only: only memories shared after the agent joined.

Point each agent’s MCP server at the file with MM_SCOPE_AUTH_CONFIG, as in the Quickstart.

The policy says who may share. The database records who is a member, and when they joined. meaning-memory-admin group sync makes the database match the policy. Preview it first:

Terminal window
MM_BACKEND=postgres MM_POSTGRES_DSN="$MM_POSTGRES_DSN" \
meaning-memory-admin --config /srv/scope_auth.yaml group sync --dry-run
Would group declared: team:accounts (shares past memories) [tenant default]
Would member added: account-mgr → team:accounts (and its past memories) [tenant default]
Would member added: support-bot → team:accounts (and its past memories) [tenant default]
3 would change.

Then run it without --dry-run:

group declared: team:accounts (shares past memories) [tenant default]
member added: account-mgr → team:accounts (and its past memories) [tenant default]
member added: support-bot → team:accounts (and its past memories) [tenant default]
3 changed.

Running it again changes nothing:

In sync: the database already matches the access policy.

Until you run it, no memory is shared: agents see only what they wrote themselves.

An agent shares a memory by writing it into a group with scope_groups. Without scope_groups, it stays private:

mm_remember({"source": "agent", "text": "Zephyr Logistics asked for a discount on the March renewal; I said I'd check with the account manager."})
mm_remember({"source": "agent", "text": "Zephyr Logistics renews its support contract every March.",
"scope_groups": ["team:accounts"]})

A group the policy does not allow is refused when the agent writes:

mm_remember({"source": "agent", "text": "Zephyr deal notes for ops.", "scope_groups": ["team:ops"]})
**Error:** Validation failed: scope_groups_authz_failed: agent 'support-bot' cannot write into groups ['team:ops'] per AuthContext config (allowed: ['agent:support-bot', 'team:accounts'])

Shared memories reach other agents after the next consolidation moves them into the store. On Postgres, set MM_BACKEND=postgres and the access policy on the consolidation command:

Terminal window
MM_BACKEND=postgres MM_POSTGRES_DSN="$MM_POSTGRES_DSN" MM_SCOPE_AUTH_CONFIG=/srv/scope_auth.yaml \
meaning-memory consolidate --agent-id support-bot --drain-only \
--workspace /srv/support-bot-mm --config-dir /srv/support-bot-mm

Consolidation checks every queued memory against the same access policy before storing it, so point MM_SCOPE_AUTH_CONFIG at the file the MCP server uses. A memory written into a group the agent may not use is moved to the workspace’s .mm-journal/quarantine/ folder with the reason, and the rest are stored as usual. Consolidation re-reads the file on every run, so a policy change applies from the next run. If the policy is missing or can’t be read, the run fails and every queued memory stays queued until it can.

Afterwards, account-mgr finds the shared memory and not the private one:

mm_search({"query": "Zephyr"})
**Meaning Memory** — account-mgr | 1 entries | fingerprint: `139c465af527`
| # | mem_id | Sig | Eligible | Category | Date | Text |
|---|--------|-----|----------|----------|------|------|
| 1 | `5e220350a7a76f3f` | 0.80 | - | fact | 2026-10-03 | Zephyr Logistics renews its support contract every March. |

support-bot finds both.

These tools apply the same rule: for a memory the agent cannot see, the answer is “not found”, so they never confirm that someone else’s private memory exists.

  • mm_search, mm_provenance, mm_rejections, mm_explain_visibility
  • mm_feedback and feedback_mark, feedback_promote, feedback_decay, feedback_correct
  • mm_divergences, mm_divergence_dismiss

The other tools that take a memory id do not check visibility yet: the commitment tools (mm_close_commitment, mm_satisfy_commitment and the rest), the governance tools (mm_get_governance, mm_tag_pii and the rest), mm_queue_status, mm_grant_residency, and the relationship tools (mm_traverse_relationships, mm_explore_relationships, mm_explain_path). Given another agent’s memory id, they can return its details. An agent normally learns memory ids only from results it is allowed to see, but don’t rely on these tools to keep a memory’s existence private.

After you edit the policy:

  1. Run group sync again. It adds new memberships and closes the ones the policy no longer grants.
  2. Restart each agent’s MCP server. The server reads the policy when it starts, and uses it to check which groups the agent may write into.

Memberships are dated. A memory shared into a group is visible to every agent whose membership covers the moment it was written. When an agent is removed from a group:

  • It stops seeing memories shared into the group after the removal.
  • It keeps seeing memories shared while it was a member. For an all_history group, its membership counts from the start, so that includes everything shared before it joined.
  • If it is added back to an all_history group, it also sees what was shared while it was out. Re-adding it to a forward_only group shows only what is shared from then on.

Removal is not a revoke: nothing it could read before is taken away. To keep a memory from an agent for good, share it into a forward_only group that agent is not a member of, or into a group it will never be added to.

A support department runs three agents. All of them need the customer and case history; only billing and escalations should see payment details; each keeps working notes to itself.

/srv/support/scope_auth.yaml
version: 1
agents:
triage-bot:
allowed_scope_groups: [agent:triage-bot, team:support]
billing-bot:
allowed_scope_groups: [agent:billing-bot, team:support, role:billing]
escalations-bot:
allowed_scope_groups: [agent:escalations-bot, team:support, role:billing]
groups:
agent:triage-bot: { backfill_policy: forward_only }
agent:billing-bot: { backfill_policy: forward_only }
agent:escalations-bot: { backfill_policy: forward_only }
team:support: { backfill_policy: all_history }
role:billing: { backfill_policy: forward_only }

team:support uses all_history, so an agent added to the team later reads the full case history. role:billing uses forward_only, so a new member sees payment details only from the day it joins.

group declared: role:billing (shares new memories only) [tenant default]
group declared: team:support (shares past memories) [tenant default]
member added: billing-bot → role:billing (from now on) [tenant default]
member added: escalations-bot → role:billing (from now on) [tenant default]
member added: billing-bot → team:support (and its past memories) [tenant default]
member added: escalations-bot → team:support (and its past memories) [tenant default]
member added: triage-bot → team:support (and its past memories) [tenant default]
7 changed.

Then the agents write:

Written by Memory Shared into
triage-bot Case 4411: Zephyr Logistics says invoices bounce since their billing address changed. team:support
billing-bot Zephyr Logistics: refund for the two bounced invoices approved; card on file expires 11/26. role:billing
billing-bot Zephyr Logistics asked twice about a discount; not raised with the account team yet. private

After consolidation, each agent’s search for “Zephyr” returns:

Agent Case note Refund and card Discount note
triage-bot yes no no
billing-bot yes yes yes (its own)
escalations-bot yes yes no

If escalations-bot is later taken out of both groups, it stops seeing new case notes and payment details from that moment, but it can still read case 4411, the refund note, and every other case note written before the removal, including those from before it joined team:support. Plan offboarding with that in mind: to cut an agent off from past cases, stop running it; a policy change alone won’t.

A policy can hold several tenants, each with its own agents and groups. Tenants never see each other’s memories:

version: 1
tenants:
acme:
agents:
support-bot:
allowed_scope_groups: [agent:support-bot, team:accounts]
groups:
agent:support-bot: { backfill_policy: forward_only }
team:accounts: { backfill_policy: all_history }
globex:
agents:
support-bot:
allowed_scope_groups: [agent:support-bot]
groups:
agent:support-bot: { backfill_policy: forward_only }

group sync handles every tenant in the file; --tenant acme limits it to one.