Multi-Repository Isolation

Copy Markdown View Source

Mnemosyne supports multiple isolated knowledge graphs under a single supervision tree. This guide covers when and how to use multiple repositories.

When to Use Multiple Repos

Each repository gets its own MemoryStore process and GraphBackend instance. Knowledge in one repo is completely invisible to another. Use separate repos when:

  • Different projects or domains should not cross-pollinate knowledge
  • Per-user isolation in multi-tenant applications
  • Separate contexts within the same agent (e.g., work knowledge vs. personal knowledge)
  • Testing alongside production graphs

Opening and Closing Repos

# Open repos with different backends
{:ok, _} = Mnemosyne.open_repo("project-alpha",
  backend: {Mnemosyne.GraphBackends.InMemory,
    persistence: {Mnemosyne.GraphBackends.Persistence.DETS, path: "priv/memory/alpha.dets"}})

{:ok, _} = Mnemosyne.open_repo("project-beta",
  backend: {Mnemosyne.GraphBackends.InMemory, []})

# List open repos
["project-alpha", "project-beta"] = Mnemosyne.list_repos()

# Close when done
:ok = Mnemosyne.close_repo("project-alpha")

Opening a repo that's already open returns an error:

{:error, %Mnemosyne.Errors.Framework.RepoError{reason: :already_open}} =
  Mnemosyne.open_repo("project-alpha", backend: {Mnemosyne.GraphBackends.InMemory, []})

Per-Repo Configuration

Shared configuration (LLM model, embedding model) is set once at supervisor startup. Each repo inherits these defaults but can override them:

# Shared defaults from supervisor
{Mnemosyne.Supervisor,
  config: %Mnemosyne.Config{
    llm: %{model: "gpt-4o-mini", opts: %{}},
    embedding: %{model: "text-embedding-3-small", opts: %{}}
  },
  llm: Mnemosyne.Adapters.SycophantLLM,
  embedding: Mnemosyne.Adapters.SycophantEmbedding}

# This repo uses a different LLM adapter
Mnemosyne.open_repo("special-project",
  backend: {Mnemosyne.GraphBackends.InMemory, []},
  llm: MyApp.ClaudeLLMAdapter)

Backend configuration is always per-repo since each repo needs its own storage.

Sessions and Repos

Sessions are bound to a specific repo via the :repo option:

{:ok, session_id} = Mnemosyne.start_session("Explore caching", repo: "project-alpha")

All session operations (append, close, commit) route to the bound repo's MemoryStore. You cannot move a session between repos.

All Operations Are Repo-Scoped

Every operation takes a repo_id as its first argument:

# Recall
{:ok, memories} = Mnemosyne.recall("project-alpha", "How does caching work?")

# Graph inspection
graph = Mnemosyne.get_graph("project-alpha")

# Direct mutations
:ok = Mnemosyne.apply_changeset("project-alpha", changeset)
:ok = Mnemosyne.delete_nodes("project-alpha", ["node-1"])

# Maintenance
{:ok, _} = Mnemosyne.consolidate_semantics("project-alpha")
{:ok, _} = Mnemosyne.decay_nodes("project-alpha")

Operating on a closed or nonexistent repo returns a NotFoundError:

{:error, %Mnemosyne.Errors.Framework.NotFoundError{resource: :repo}} =
  Mnemosyne.recall("nonexistent", "query")

Supervision Architecture

Under the hood, each repo is a child of the RepoSupervisor (a DynamicSupervisor):

Mnemosyne.Supervisor (rest_for_one)
  |-- SessionRegistry
  |-- RepoRegistry
  |-- TaskSupervisor
  |-- RepoSupervisor (DynamicSupervisor)
  |     |-- MemoryStore "project-alpha"
  |     |-- MemoryStore "project-beta"
  |-- SessionSupervisor (DynamicSupervisor)
        |-- Session "session_abc123"

The rest_for_one strategy means if the RepoSupervisor crashes, all sessions restart too. Each MemoryStore is registered in the RepoRegistry by its string ID.

Multiple Supervisor Instances

You can run multiple independent Mnemosyne instances by passing a custom :name:

{Mnemosyne.Supervisor,
  name: MyApp.WorkMemory,
  config: work_config,
  llm: work_llm,
  embedding: work_embedding}

{Mnemosyne.Supervisor,
  name: MyApp.PersonalMemory,
  config: personal_config,
  llm: personal_llm,
  embedding: personal_embedding}

Then pass :supervisor in all operations:

Mnemosyne.open_repo("repo", backend: backend, supervisor: MyApp.WorkMemory)
Mnemosyne.start_session("goal", repo: "repo", supervisor: MyApp.WorkMemory)
Mnemosyne.recall("repo", "query", supervisor: MyApp.WorkMemory)

Next Steps