AgentEngineering
articleAgent MemoryMemory ArchitectureMulti-Agent Systems

Agent Memory Management for Multi-Agent Systems: Types, Strategies, Architecture, and Practical Applications

A production-oriented guide to multi-agent memory: ownership, sharing boundaries, architecture choices, consistency controls, ontology-gated writes, and runnable Python patterns for agent namespaces, team memory, and memory-aware handoffs.

David Akuma16 min read
ShareY

Single-agent memory guidance does not fully answer multi-agent questions:

  • Who owns which facts?
  • Which facts can be shared, and under what policy?
  • How do we keep memory coherent when multiple agents write concurrently?

In production systems, memory is not just a retrieval layer. It is a subsystem with explicit data ownership, access control, lifecycle rules, and consistency semantics.

This article builds on Memory & State Management in LLM Agents, RAG for Agents: Retrieval as a First-Class Tool, and Multi-Agent Orchestration, and extends them to collaborative agent systems.

Why Multi-Agent Memory Is a Different Problem

In a single-agent loop, memory design is mostly about retrieval quality and context limits. In multi-agent systems, you add distributed-systems constraints:

  • Multiple writers mutate shared state.
  • Different agents have different trust levels and capabilities.
  • Context has to move between agents without leaking irrelevant or sensitive data.

That is why patterns from distributed computing are now directly relevant: event sourcing, optimistic concurrency control, conflict-free replicated data types (CRDTs), and explicit authorization boundaries [1][2][3][4].

Memory Types and Scopes for Multi-Agent Systems

The CoALA framework maps language-agent memory to four cognitive categories: working, episodic, semantic, and procedural [5]. In multi-agent systems, you should add two operational scopes on top: private and shared.

1. Working Memory (Per-Run, Short-Lived)

  • Scope: one agent invocation or one short interaction window.
  • Storage: in-process state plus active model context.
  • Use: intermediate tool outputs, temporary plans, scratch variables.

Working memory should be treated as disposable by default. Persist only what downstream agents actually need.

2. Episodic Memory (Event History)

  • Scope: time-ordered records of what happened.
  • Storage: append-only event log or checkpoint store.
  • Use: auditability, replay, debugging, and post-hoc evaluation.

For production systems, append-only logs are valuable because they preserve provenance and support replay-based debugging [2][6].

3. Semantic Memory (Facts and Documents)

  • Scope: durable knowledge used across sessions.
  • Storage: vector store, document store, knowledge graph, or hybrid retrieval stack.
  • Use: policies, docs, runbooks, domain facts.

RAG remains the standard pattern for external factual memory in LLM systems [7].

4. Procedural Memory (How-To Behavior)

  • Scope: instructions, policies, and learned routines.
  • Storage: system prompts, tool contracts, policy engines, and in some cases model fine-tuning.
  • Use: stable workflows and action constraints.

Do not treat procedural memory as a dumping ground for mutable facts. Mutable facts belong in semantic or episodic stores.

5. Private Memory (Agent-Local)

  • Scope: isolated to one agent role or identity.
  • Use: role-specific heuristics, drafts, local confidence traces.

Private memory is a least-privilege boundary, not just a convenience.

6. Shared Memory (Team-Visible)

  • Scope: cross-agent coordination state.
  • Use: task ledger, validated findings, handoff packets, and decisions.

Shared memory should be small, structured, and policy-gated. If everything is shared, nothing is trustworthy.

Ontology: A Shared Meaning Layer for Agent Memory

Memory scopes answer who can see a fact. An ontology answers a harder question: do two agents mean the same thing by it?

What an Ontology Is

In knowledge engineering, the canonical definition comes from Gruber (1993): an ontology is "an explicit specification of a conceptualization" [13]. Studer, Benjamins, and Fensel (1998) sharpened it to "a formal, explicit specification of a shared conceptualisation" [14] — and every word carries weight for multi-agent systems:

  • Formal: machine-interpretable, not prose in a wiki.
  • Explicit: concepts, relations, and constraints are declared, not implied.
  • Shared: the vocabulary is agreed across agents, not private to one.

Concretely, an ontology defines:

  • Classes and their hierarchy (BugTicket is a Ticket is an Artifact).
  • Relations/properties with domain and range constraints (assigned_to links a Ticket to an Agent, never the reverse).
  • Axioms such as disjointness or cardinality (a ticket has exactly one canonical status).
  • Instances — the actual facts, often materialized as a knowledge graph. In description-logic terms, the schema is the TBox and the instance data is the ABox.

The W3C standardized this stack: OWL 2 is the ontology language, with formally defined semantics that let reasoners check consistency and derive entailed facts automatically [15].

This is not a new idea bolted onto LLM agents. The FIPA ACL standard for agent communication (2002) includes a dedicated ontology message parameter, defined as denoting "the ontology(s) used to give a meaning to the symbols in the content expression" [16] — classical multi-agent systems treated shared meaning as a first-class protocol concern two decades before LLMs.

How an Ontology Improves Agent Memory and Cognition

An ontology upgrades each lifecycle strategy from convention to enforcement:

  1. Write-time validation. Domain/range constraints reject malformed facts at the boundary (assigned_to(agent, ticket) fails type checking). Schema-validated writes stop vocabulary drift before it reaches shared memory.
  2. Entity canonicalization. Shared identifiers for concepts mean the researcher's "customer", the support agent's "client", and the biller's "account holder" resolve to one node instead of three near-duplicate embeddings.
  3. Structure-aware retrieval. Class hierarchies enable query expansion (asking for Ticket facts also returns BugTicket facts), and graph structure supports multi-hop retrieval that flat vector similarity misses. Microsoft Research's GraphRAG showed that building an entity knowledge graph over a corpus and retrieving through it produced substantial improvements over a conventional vector-RAG baseline in comprehensiveness and diversity of answers for global sensemaking questions over ~1M-token corpora [17].
  4. Better grounding for reasoning. Pan et al.'s IEEE TKDE roadmap documents how structured knowledge enhances LLM inference and interpretability, while LLMs help construct and complete the graphs — the two are complementary, not competing [18].
  5. Mechanical conflict detection. OWL axioms make contradictions detectable by a reasoner (two disjoint classes asserted for one entity; a cardinality violation) instead of waiting for a human to notice [15].
  6. Interoperable provenance. W3C PROV-O is itself an OWL ontology for provenance — a standard vocabulary for which agent produced which fact from which source, portable across systems [19].

The pattern is proven outside AI at serious scale. The Gene Ontology gave biology "a structured, precisely defined, common, controlled vocabulary" so independent teams annotating different organism databases could produce interoperable knowledge [20]. Schema.org did the same for the web: one shared vocabulary consumed by competing search engines [21]. A multi-agent system is the same coordination problem with faster writers.

A Minimal Ontology-Gated Memory in Python

from dataclasses import dataclass
from typing import Dict, List, Optional, Set, Tuple


class MiniOntology:
    def __init__(self):
        self.parents: Dict[str, Optional[str]] = {
            "Entity": None,
            "Agent": "Entity",
            "Artifact": "Entity",
            "Ticket": "Artifact",
            "BugTicket": "Ticket",
        }
        # relation -> (domain class, range class)
        self.relations: Dict[str, Tuple[str, str]] = {
            "assigned_to": ("Ticket", "Agent"),
            "duplicate_of": ("Ticket", "Ticket"),
        }

    def is_a(self, cls: Optional[str], ancestor: str) -> bool:
        while cls is not None:
            if cls == ancestor:
                return True
            cls = self.parents.get(cls)
        return False

    def descendants(self, ancestor: str) -> Set[str]:
        return {c for c in self.parents if self.is_a(c, ancestor)}


@dataclass
class Fact:
    subject: str
    subject_class: str
    relation: str
    obj: str
    obj_class: str
    written_by: str


class OntologyGatedMemory:
    def __init__(self, onto: MiniOntology):
        self.onto = onto
        self.facts: List[Fact] = []

    def write(self, fact: Fact) -> None:
        if fact.relation not in self.onto.relations:
            raise ValueError(f"Unknown relation {fact.relation!r}")
        domain, rng = self.onto.relations[fact.relation]
        if not self.onto.is_a(fact.subject_class, domain):
            raise TypeError(f"{fact.subject_class} is not a {domain}")
        if not self.onto.is_a(fact.obj_class, rng):
            raise TypeError(f"{fact.obj_class} is not a {rng}")
        self.facts.append(fact)

    def query(self, relation: str, subject_class: str) -> List[Fact]:
        # Subclass-aware retrieval: asking for Ticket facts also returns BugTicket facts.
        classes = self.onto.descendants(subject_class)
        return [f for f in self.facts if f.relation == relation and f.subject_class in classes]


if __name__ == "__main__":
    memory = OntologyGatedMemory(MiniOntology())
    memory.write(Fact("TCK-1", "BugTicket", "assigned_to", "triage-agent", "Agent", written_by="router"))
    try:
        memory.write(Fact("triage-agent", "Agent", "assigned_to", "TCK-1", "Ticket", written_by="router"))
    except TypeError as e:
        print("rejected:", e)
    hits = memory.query("assigned_to", "Ticket")
    print("ticket assignments:", [(f.subject, f.obj) for f in hits])

What this demonstrates:

  • Domain/range validation rejects semantically inverted facts at write time.
  • Class-hierarchy expansion at query time retrieves subclass facts a flat key-value store would miss.
  • The ontology is data, so it can be versioned and promoted like any other shared artifact.

Adoption Guidance

  • Start with a controlled vocabulary (canonical entity types, relation names, status enums), not a full OWL ontology. Formality can grow with need.
  • Reuse existing vocabularies where they fit — schema.org for common entities [21], PROV-O for provenance [19] — rather than inventing your own.
  • Version the ontology like code: shared-memory writes should declare which ontology version validated them, mirroring FIPA's explicit ontology message field [16].

Architecture Patterns: Centralized, Distributed, Hybrid

A. Centralized Blackboard

All agents read/write a common state store ("blackboard"). This pattern is simple and observably clear for small teams of agents.

Pros:

  • Single source of truth.
  • Straightforward traceability.
  • Easy global querying.

Cons:

  • Write contention.
  • Potential single bottleneck.
  • Broader blast radius on permission mistakes.

Blackboard-style coordination is a classic AI pattern and remains practical when concurrency is moderate [8].

B. Distributed Per-Agent Stores

Each agent maintains local memory and exchanges messages or snapshots at handoff boundaries.

Pros:

  • Natural ownership boundaries.
  • Better horizontal scaling.
  • Reduced accidental coupling.

Cons:

  • Harder global consistency.
  • More complex reconciliation.
  • Higher integration overhead.

Use this when agent roles are strongly separated (for example, planner, retriever, executor, reviewer).

  • Private, per-agent memory for volatile/local state.
  • Shared blackboard for validated coordination artifacts.
  • Event log for immutable provenance.

This mirrors modern distributed system design: local autonomy plus durable shared facts [2][3].

Memory Lifecycle Strategies

A practical memory subsystem needs explicit policies for write, retrieval, summarization, retention, provenance, access control, and conflict resolution.

Write Strategy

  • Require schema validation on writes (typed envelopes).
  • Separate drafts from validated facts.
  • Attach metadata: agent_id, timestamp, source, confidence, version.

Retrieval Strategy

  • Route queries by scope first (private -> shared -> global semantic).
  • Apply authorization filters before ranking.
  • Use metadata filters in vector retrieval to avoid cross-tenant leakage [9][10].

Summarization Strategy

Use progressive summarization for long episodes, but keep links to raw events so summaries are reversible for audits [6].

Retention Strategy

  • TTL for volatile working artifacts.
  • Durable retention for compliance-relevant logs.
  • Explicit deletion workflows for personal data where applicable.

Retention policies should align with legal/privacy obligations (for example, storage limitation principles in privacy regulations) [11].

Provenance Strategy

Every shared fact should carry source pointers (document IDs, message IDs, tool run IDs). Provenance is what turns memory into evidence instead of rumor.

Access-Control Strategy

Apply least privilege: not every agent should read or write every namespace. NIST guidance emphasizes centralized policy and continuous authorization decisions for distributed systems [4].

Conflict-Resolution Strategy

For concurrent updates:

  • Use optimistic concurrency control (OCC) with version checks for structured records [12].
  • Use CRDTs for commutative data (sets/counters) when you need multi-writer convergence without locks [3].
  • Keep append-only event logs as arbitration ground when state snapshots diverge [2].

Runnable Python Examples

The examples below are intentionally small and runnable with Python 3.10+.

Example 1: Per-Agent Namespaces with Policy-Gated Writes

from dataclasses import dataclass, asdict
from datetime import datetime, timezone
from typing import Dict, Any


@dataclass
class MemoryRecord:
    key: str
    value: Any
    agent_id: str
    ts: str
    source: str
    confidence: float


class NamespacedMemory:
    def __init__(self):
        self.private: Dict[str, Dict[str, MemoryRecord]] = {}
        self.shared: Dict[str, MemoryRecord] = {}

    def write_private(self, agent_id: str, key: str, value: Any, source: str, confidence: float = 1.0):
        ns = self.private.setdefault(agent_id, {})
        ns[key] = MemoryRecord(
            key=key,
            value=value,
            agent_id=agent_id,
            ts=datetime.now(timezone.utc).isoformat(),
            source=source,
            confidence=confidence,
        )

    def promote_to_shared(self, agent_id: str, key: str):
        record = self.private.get(agent_id, {}).get(key)
        if not record:
            raise KeyError(f"No private record {key!r} for {agent_id}")
        # Policy example: only high-confidence records can be shared.
        if record.confidence < 0.8:
            raise PermissionError("Record confidence below promotion threshold")
        self.shared[key] = record


if __name__ == "__main__":
    mem = NamespacedMemory()
    mem.write_private("researcher", "aws_mcp_date", "2026-07-28", source="whitepaper", confidence=0.92)
    mem.promote_to_shared("researcher", "aws_mcp_date")
    print(asdict(mem.shared["aws_mcp_date"]))

What this demonstrates:

  • Private namespaces by agent identity.
  • Explicit promotion boundary into shared memory.
  • Metadata required for later traceability.

Example 2: Shared Team Memory with Optimistic Concurrency Control (OCC)

from dataclasses import dataclass
from threading import Thread, Lock, Barrier
from typing import Dict


@dataclass
class VersionedValue:
    value: str
    version: int


class SharedStore:
    def __init__(self):
        self._data: Dict[str, VersionedValue] = {}
        self._lock = Lock()

    def read(self, key: str) -> VersionedValue:
        return self._data.get(key, VersionedValue("", 0))

    def compare_and_set(self, key: str, expected_version: int, new_value: str) -> bool:
        with self._lock:
            current = self._data.get(key, VersionedValue("", 0))
            if current.version != expected_version:
                return False
            self._data[key] = VersionedValue(new_value, current.version + 1)
            return True


barrier = Barrier(2)  # forces both agents to read before either writes


def writer(store: SharedStore, agent: str):
    current = store.read("task_status")
    barrier.wait()
    proposal = f"updated by {agent}"
    ok = store.compare_and_set("task_status", current.version, proposal)
    print(agent, "commit", "ok" if ok else "conflict")


if __name__ == "__main__":
    s = SharedStore()
    t1 = Thread(target=writer, args=(s, "planner"))
    t2 = Thread(target=writer, args=(s, "reviewer"))
    t1.start(); t2.start(); t1.join(); t2.join()
    final = s.read("task_status")
    print("final:", final.value, "version:", final.version)

What this demonstrates:

  • Multi-writer update races.
  • Version-based conflict detection instead of silent overwrites.
  • Deterministic retry point when conflict is detected.

Example 3: Memory-Aware Handoff Packet

from dataclasses import dataclass, asdict
from typing import List, Dict


@dataclass
class HandoffPacket:
    task_id: str
    from_agent: str
    to_agent: str
    objective: str
    shared_facts: List[Dict]
    open_questions: List[str]
    provenance_refs: List[str]


def build_handoff(task_id: str) -> HandoffPacket:
    return HandoffPacket(
        task_id=task_id,
        from_agent="researcher",
        to_agent="writer",
        objective="Draft section on conflict-resolution strategies",
        shared_facts=[
            {
                "fact": "Use OCC for structured shared records",
                "confidence": 0.95,
                "source": "Kung & Robinson 1981",
            },
            {
                "fact": "Use CRDTs for convergent multi-writer sets",
                "confidence": 0.93,
                "source": "Shapiro et al. 2011",
            },
        ],
        open_questions=["Should team memory store full events or summaries only?"],
        provenance_refs=["doi:10.1145/319566.319567", "doi:10.1007/978-3-642-24550-3_29"],
    )


if __name__ == "__main__":
    packet = build_handoff("task-42")
    print(asdict(packet))

What this demonstrates:

  • Handoffs as structured memory transfer, not raw chat transcripts.
  • Explicit provenance for downstream verification.
  • Reduced context bloat and better downstream reliability.

Practical Production Patterns by Use Case

1. Research Agent Teams

Pattern:

  • Private memory for search hypotheses and dead ends.
  • Shared memory for source-verified facts only.
  • Event log for citation audits and replay.

Why it works:

  • Reduces contamination from low-confidence intermediate findings.
  • Preserves evidence chain for editorial or legal review.

2. Software Engineering Agents

Pattern:

  • Private memory per role (planner, coder, tester, reviewer).
  • Shared memory for accepted requirements, invariants, and test outcomes.
  • OCC on issue/task state transitions.

Why it works:

  • Prevents accidental overwrite of task truth.
  • Makes agent-generated changes explainable during PR review.

3. Customer Support Agent Teams

Pattern:

  • Private memory for role-specific context (classification, policy lookup).
  • Shared memory for canonical ticket state and customer-visible decisions.
  • Strict authorization boundaries around PII.

Why it works:

  • Preserves least privilege.
  • Supports auditable outcomes for escalations and compliance.

Common Failure Modes

  1. Global shared memory without ownership semantics. Result: noisy, conflicting state and poor retrieval precision.

  2. No versioning on shared writes. Result: last-writer-wins bugs and silent regressions.

  3. Summaries without provenance pointers. Result: unverifiable facts and brittle downstream decisions.

  4. Retrieval before authorization filters. Result: potential data leakage across roles/tenants.

  5. Treating memory as only a vector database. Result: no audit trail, weak conflict handling, and poor operational trust.

Implementation Checklist

  • Define memory classes: working, episodic, semantic, procedural.
  • Define scopes: private namespaces and shared coordination memory.
  • Enforce write schemas with metadata (source, confidence, version).
  • Adopt a shared ontology or controlled vocabulary for shared memory; validate domain/range on write.
  • Add OCC for mutable shared records; CRDTs where commutativity helps.
  • Keep append-only event logs for provenance and replay.
  • Add policy-gated promotion from private to shared memory.
  • Apply authorization filters before retrieval/reranking.
  • Attach source references to every shared fact used for decisions.
  • Add retention and deletion policies by data class.

Closing

Multi-agent systems fail less from model quality than from state quality. If ownership, consistency, provenance, and access control are not explicit, memory becomes an untrusted rumor channel.

Treat memory as a first-class subsystem. Model it like distributed state, govern it like security-sensitive data, and expose it like an auditable interface. That is what makes multi-agent collaboration reliable in production.

Sources

  1. Martin Kleppmann. Designing Data-Intensive Applications. O'Reilly, 2017. https://dataintensive.net/
  2. Pat Helland. "Immutability Changes Everything." Communications of the ACM 59(1), 2016. https://cacm.acm.org/research/immutability-changes-everything/
  3. Marc Shapiro, Nuno Preguiça, Carlos Baquero, Marek Zawirski. "Conflict-Free Replicated Data Types." SSS 2011, pp. 386–400. https://doi.org/10.1007/978-3-642-24550-3_29 (open access: https://inria.hal.science/inria-00609399)
  4. NIST SP 800-207. Zero Trust Architecture, 2020. https://csrc.nist.gov/pubs/sp/800/207/final
  5. Theodore R. Sumers et al. "Cognitive Architectures for Language Agents" (CoALA), 2023. https://arxiv.org/abs/2309.02427
  6. Jay Kreps. "The Log: What every software engineer should know about real-time data's unifying abstraction." 2013. https://engineering.linkedin.com/distributed-systems/log-what-every-software-engineer-should-know-about-real-time-datas-unifying
  7. Patrick Lewis et al. "Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks." NeurIPS 2020. https://arxiv.org/abs/2005.11401
  8. H. P. Nii. "The Blackboard Model of Problem Solving and the Evolution of Blackboard Architectures." AI Magazine 7(2), 1986. https://ojs.aaai.org/aimagazine/index.php/aimagazine/article/view/537
  9. PostgreSQL documentation: Row Security Policies (RLS). https://www.postgresql.org/docs/current/ddl-rowsecurity.html
  10. OpenSearch documentation: Filtering vector search results. https://docs.opensearch.org/latest/vector-search/filter-search-knn/index/
  11. GDPR (EU) 2016/679, Article 5 (Principles relating to processing of personal data). https://eur-lex.europa.eu/eli/reg/2016/679/oj
  12. H. T. Kung and John T. Robinson. "On Optimistic Methods for Concurrency Control." ACM Transactions on Database Systems 6(2), 1981, pp. 213–226. https://doi.org/10.1145/319566.319567
  13. Thomas R. Gruber. "A Translation Approach to Portable Ontology Specifications." Knowledge Acquisition 5(2), 1993, pp. 199–220. https://doi.org/10.1006/knac.1993.1008
  14. Rudi Studer, V. Richard Benjamins, Dieter Fensel. "Knowledge Engineering: Principles and Methods." Data & Knowledge Engineering 25(1–2), 1998, pp. 161–197. https://doi.org/10.1016/S0169-023X(97)00034-7
  15. W3C. OWL 2 Web Ontology Language Document Overview (Second Edition). W3C Recommendation, 11 December 2012. https://www.w3.org/TR/owl2-overview/
  16. FIPA. FIPA ACL Message Structure Specification, SC00061G, Standard, 2002. http://www.fipa.org/specs/fipa00061/SC00061G.html
  17. Darren Edge et al. "From Local to Global: A Graph RAG Approach to Query-Focused Summarization." Microsoft Research, 2024. https://arxiv.org/abs/2404.16130
  18. Shirui Pan et al. "Unifying Large Language Models and Knowledge Graphs: A Roadmap." IEEE Transactions on Knowledge and Data Engineering, 2024. https://doi.org/10.1109/TKDE.2024.3352100 (preprint: https://arxiv.org/abs/2306.08302)
  19. W3C. PROV-O: The PROV Ontology. W3C Recommendation, 30 April 2013. https://www.w3.org/TR/prov-o/
  20. The Gene Ontology Consortium. "Gene Ontology: Tool for the Unification of Biology." Nature Genetics 25(1), 2000, pp. 25–29. https://doi.org/10.1038/75556
  21. R. V. Guha, Dan Brickley, Steve Macbeth. "Schema.org: Evolution of Structured Data on the Web." Communications of the ACM 59(2), 2016, pp. 44–51. https://doi.org/10.1145/2844544
ShareY

Cite this article

@article{agentengineering2026,
  title   = {Agent Memory Management for Multi-Agent Systems: Types, Strategies, Architecture, and Practical Applications},
  author  = {David Akuma},
  journal = {AgentEngineering},
  year    = {2026},
  url     = {https://agentengineering.io/topics/articles/agent-memory-management-for-multi-agent-systems}
}

More in Articles