Skip to content

memory_agent

Level 3 · Memory — persistent context across turns using DocumentStore.

Documents written to a store persist between agent turns, and a ContextInjectionNode loads them into the prompt so the model can reason over them. This example writes facts, verifies they survive across simulated turns (including a write mid-conversation), then runs an agent backed by that store.

  • Lines: ~44
  • Prerequisites: none
  • Key concepts: DocumentStore, InMemoryDocumentStore, memory persistence

Run it

Installed via pip? Copy this example into your project with the CLI (no checkout needed):

pip install "symfonic-core[cli]"
symfonic examples add memory_agent
python -m memory_agent

Or, from a source checkout (run from the repo root):

python -m examples.memory_agent

Expected output:

Turn 1 — found 1 doc(s): The project uses DI.
Turn 2 — found 1 doc(s): Phase 5 adds examples.
Agent: Memory recalled.

Full code

"""Memory agent -- persistent memory across agent turns using DocumentStore.

Demonstrates ContextInjectionNode loading stored documents into prompt context.
InMemory only, zero external network calls.
Usage: python -m examples.memory_agent
"""

import asyncio

from symfonic.core import AgentGraph, AgentRuntime, BaseAgentDeps, ModelProvider
from symfonic.core.documents.in_memory import InMemoryDocumentStore
from symfonic.core.models import Document
from symfonic.core.protocols import DocumentStore
from symfonic.core.testing import MockModelProvider


async def main() -> None:
    # 1. Populate document store (simulates persistent memory)
    doc_store = InMemoryDocumentStore()
    await doc_store.write(Document(id="fact-1", content="The project uses DI."))
    await doc_store.write(Document(id="fact-2", content="All storage is async."))

    # 2. Inject all concrete implementations at entry point
    deps = BaseAgentDeps()
    deps.register(ModelProvider, MockModelProvider(response="Memory recalled."))
    deps.register(DocumentStore, doc_store)

    # 3. Verify documents persist across reads (simulates turns)
    found = await doc_store.search("DI")
    print(f"Turn 1 — found {len(found)} doc(s): {found[0].content}")

    await doc_store.write(Document(id="fact-3", content="Phase 5 adds examples."))
    found2 = await doc_store.search("Phase")
    print(f"Turn 2 — found {len(found2)} doc(s): {found2[0].content}")

    # 4. Run agent with memory-backed deps
    graph = AgentGraph()
    runtime = AgentRuntime(graph=graph, deps=deps)
    result = await runtime.run("What do you remember?")
    print(f"Agent: {result['final_response']}")


if __name__ == "__main__":
    asyncio.run(main())

Step by step

1. Populate the store

doc_store = InMemoryDocumentStore()
await doc_store.write(Document(id="fact-1", content="The project uses DI."))
await doc_store.write(Document(id="fact-2", content="All storage is async."))

Each Document has a stable id and a content body. Writing with the same id again would update in place. This store stands in for any persistence backend — swap InMemoryDocumentStore for a Postgres- or Mongo-backed one and the rest of the code is unchanged.

2. Register the store

deps = BaseAgentDeps()
deps.register(ModelProvider, MockModelProvider(response="Memory recalled."))
deps.register(DocumentStore, doc_store)

The store is injected by the DocumentStore protocol. The graph's context nodes resolve it from deps at run time.

3. Confirm persistence across turns

found = await doc_store.search("DI")
print(f"Turn 1 — found {len(found)} doc(s): {found[0].content}")

await doc_store.write(Document(id="fact-3", content="Phase 5 adds examples."))
found2 = await doc_store.search("Phase")
print(f"Turn 2 — found {len(found2)} doc(s): {found2[0].content}")

search(query) returns documents matching the query. The key idea: the write in "Turn 2" is visible to subsequent reads — state accumulates across turns rather than resetting. This is what makes the agent remember within a session.

4. Run the agent over its memory

graph = AgentGraph()
runtime = AgentRuntime(graph=graph, deps=deps)
result = await runtime.run("What do you remember?")
print(f"Agent: {result['final_response']}")

The runtime's context-injection node reads from the same DocumentStore and splices matching documents into the prompt before the model runs — so the model's answer is grounded in what was stored. (With MockModelProvider the reply is canned; with a real provider the documents shape the response.)

DocumentStore vs the 5-layer HMS

This example uses the raw DocumentStore — the simplest persistence primitive. The high-level SymfonicAgent builds on top of it with the full Hierarchical Memory System (Semantic, Episodic, Working, Procedural, Prospective layers), which adds consolidation, spreading activation, and automatic hydration. Start here to understand the primitive; graduate to the HMS for production memory.

What to try next

  • Round-trip memory tools (memory_read / memory_write / memory_search) → see agent_with_memory in the Examples Index
  • The full memory model → Memory (5-Pentad)

See also