Agentic memory library that models memory as a knowledge graph using reinforcement-learning primitives (episodes, trajectories, rewards, value functions).
Architecture
Mnemosyne is organized in three layers:
Data Primitives - An in-memory knowledge graph with typed nodes (
Episodic,Semantic,Procedural,Subgoal,Source,Tag) connected by directed links. Mutations happen throughChangesetstructs.Pipeline - LLM-driven extraction that turns raw observation-action sequences into structured knowledge. Episodes track steps, detect trajectory boundaries via embedding similarity, and produce changesets that grow the graph.
Retrieval - Value-function-scored retrieval over the graph, combining multiple node types to produce contextually relevant memory results.
Repositories
All graph operations are scoped to a repository. A repository is an
isolated graph backend with its own MemoryStore process. Open a repo via
open_repo/2, then pass its repo_id as the first argument to all
operations.
{:ok, _pid} = Mnemosyne.open_repo("my-repo", backend: {InMemory, persistence: {DETS, path: "repo.dets"}})Write Path (Ingestion)
Complete caller-owned trajectories are stored through the blocking ingestion API. Success means the resulting graph nodes are visible.
trajectory = %Mnemosyne.Trajectory{
source_id: "task-42",
goal: "Learn Elixir patterns",
steps: [
%{observation: "Read about GenServer", action: "Implemented a cache"}
]
}
{:ok, receipt} = Mnemosyne.ingest("my-repo", trajectory)Read Path (Recall)
Recall queries the knowledge graph using value functions to score and rank nodes by relevance. Caller-provided task context can augment queries without being persisted.
{:ok, memories} = Mnemosyne.recall("my-repo", "How to implement caching?")Graph Management
Direct graph operations for inspection and bulk mutations:
graph = Mnemosyne.get_graph("my-repo")
:ok = Mnemosyne.apply_changeset("my-repo", changeset)
:ok = Mnemosyne.delete_nodes("my-repo", ["node-1", "node-2"])Supervision
Mnemosyne runs under its own supervision tree (Mnemosyne.Supervisor).
Multiple independent instances can coexist by passing a custom :supervisor
name in opts. Each supervisor owns its own RepoRegistry, TaskSupervisor,
and RepoSupervisor.
Summary
Functions
Applies a changeset to the knowledge graph asynchronously.
Closes a running memory repository.
Consolidates near-duplicate semantic nodes in the repo's graph asynchronously.
Prunes low-utility nodes from the repo's graph via decay scoring asynchronously.
Deletes nodes from the knowledge graph by their IDs asynchronously.
Returns the current knowledge graph held by the repo's MemoryStore.
Fetches nodes linked to the given node IDs.
Fetches metadata for the given node IDs.
Fetches a single node by ID from the repo's graph.
Fetches all nodes of the given types from the repo's graph.
Stores a complete trajectory in a repository.
Fetches the most recently created memories from the repo, sorted newest first.
Lists all currently open repository IDs.
Opens a new memory repository under the supervision tree.
Retrieves relevant memories from the knowledge graph for the given query.
Strips dangling link references and removes orphaned tags/intents from the repo's graph asynchronously.
Validates episodic grounding of abstract nodes in the repo's graph asynchronously.
Functions
@spec apply_changeset(String.t(), Mnemosyne.Graph.Changeset.t(), keyword()) :: :ok | {:error, Mnemosyne.Errors.Framework.NotFoundError.t()}
Applies a changeset to the knowledge graph asynchronously.
Enqueues the changeset for application via the MemoryStore write lane.
Returns immediately; the actual mutation happens in the background.
Subscribe to Notifier events (:changeset_applied) to observe completion.
@spec close_repo( String.t(), keyword() ) :: :ok | {:error, Mnemosyne.Errors.Framework.NotFoundError.t()}
Closes a running memory repository.
Terminates the MemoryStore process for the given repo_id.
Options
:supervisor- Name of the Mnemosyne supervisor. Defaults toMnemosyne.Supervisor.
@spec consolidate_semantics( String.t(), keyword() ) :: :ok | {:error, Mnemosyne.Errors.Framework.NotFoundError.t()}
Consolidates near-duplicate semantic nodes in the repo's graph asynchronously.
Discovers semantically similar nodes via tag-neighbor similarity and merges
each pair through an LLM-synthesized statement: the higher-scored node
survives with the merged proposition while the other's links and metadata
transfer to it. Weakly related pairs are kept separate. Returns immediately;
the consolidation runs in the background. Subscribe to Notifier events
(:consolidation_completed) to observe results.
Options
:supervisor- Name of the Mnemosyne supervisor. Defaults toMnemosyne.Supervisor.
@spec decay_nodes( String.t(), keyword() ) :: :ok | {:error, Mnemosyne.Errors.Framework.NotFoundError.t()}
Prunes low-utility nodes from the repo's graph via decay scoring asynchronously.
Scores nodes on recency, frequency, and reward signals and removes those
below the threshold. Cleans up orphaned Tags/Intents after deletion. Returns
immediately; pruning runs in the background. Subscribe to Notifier events
(:decay_completed) to observe results.
Options
:supervisor- Name of the Mnemosyne supervisor. Defaults toMnemosyne.Supervisor.
@spec delete_nodes(String.t(), [String.t()], keyword()) :: :ok | {:error, Mnemosyne.Errors.Framework.NotFoundError.t()}
Deletes nodes from the knowledge graph by their IDs asynchronously.
Enqueues the deletion via the MemoryStore write lane. Returns immediately;
the actual removal happens in the background. Subscribe to Notifier events
(:nodes_deleted) to observe completion.
@spec get_graph( String.t(), keyword() ) :: Mnemosyne.Graph.t() | {:error, Mnemosyne.Errors.Framework.NotFoundError.t()}
Returns the current knowledge graph held by the repo's MemoryStore.
The graph contains all committed nodes and their links. Useful for inspection, debugging, or building custom retrieval strategies.
Fetches nodes linked to the given node IDs.
@spec get_metadata(String.t(), [String.t()], keyword()) :: {:ok, %{required(String.t()) => Mnemosyne.NodeMetadata.t()}} | {:error, term()}
Fetches metadata for the given node IDs.
Fetches a single node by ID from the repo's graph.
Fetches all nodes of the given types from the repo's graph.
@spec ingest(String.t(), Mnemosyne.Trajectory.t(), keyword()) :: {:ok, Mnemosyne.IngestionReceipt.t()} | {:error, Mnemosyne.Errors.error()}
Stores a complete trajectory in a repository.
Returns only after the trajectory is committed and its graph nodes are visible. Repeating the same payload with the same source ID returns the original receipt; a different payload for that source ID returns an ingestion error.
Options
:supervisor- Name of the Mnemosyne supervisor. Defaults toMnemosyne.Supervisor.:config- AMnemosyne.Configstruct overriding the repo default for this ingestion.:llm- LLM adapter module overriding the repo default for this ingestion.:embedding- Embedding adapter module overriding the repo default for this ingestion.
:llm_opts is passed through ingestion LLM stages where used. Per-call
:embedding_opts currently applies only to write-time intent merging; ordinary
trajectory embedding calls use the selected config's embedding options.
@spec latest(String.t(), pos_integer(), keyword()) :: {:ok, [{struct(), Mnemosyne.NodeMetadata.t()}]} | {:error, term()}
Fetches the most recently created memories from the repo, sorted newest first.
Returns up to top_k nodes paired with their metadata. By default fetches
semantic and procedural nodes.
Options
:types- Node types to fetch. Defaults to[:semantic, :procedural].:supervisor- Name of the Mnemosyne supervisor. Defaults toMnemosyne.Supervisor.
Examples
{:ok, memories} = Mnemosyne.latest("my-repo", 10)
{:ok, memories} = Mnemosyne.latest("my-repo", 5, types: [:semantic])
Lists all currently open repository IDs.
Options
:supervisor- Name of the Mnemosyne supervisor. Defaults toMnemosyne.Supervisor.
@spec open_repo( String.t(), keyword() ) :: {:ok, pid()} | {:error, Mnemosyne.Errors.error()}
Opens a new memory repository under the supervision tree.
Starts a MemoryStore process registered in the RepoRegistry with the
given repo_id. Each repo has its own isolated graph backend.
Options
:backend- Required. A{module, opts}tuple for the graph backend.:supervisor- Name of the Mnemosyne supervisor. Defaults toMnemosyne.Supervisor.:config- AMnemosyne.Configstruct overriding shared defaults.:llm- LLM adapter module overriding shared defaults.:embedding- Embedding adapter module overriding shared defaults.
@spec recall(String.t(), String.t(), keyword()) :: {:ok, Mnemosyne.Pipeline.RecallResult.t()} | {:error, Mnemosyne.Errors.error()}
Retrieves relevant memories from the knowledge graph for the given query.
Runs the retrieval pipeline, which computes embeddings for the query and scores candidate nodes using value functions across all node types (episodic, semantic, procedural, subgoal, tag, source). Results are ranked and filtered by relevance.
Options
:supervisor- Name of the Mnemosyne supervisor. Defaults toMnemosyne.Supervisor. This option is used only to locate the repository.:context- Transient active-task context shaped as%{goal: goal, recent_steps: [%{observation: observation, action: action}]}. The goal and last three recent steps augment the query without being persisted.:source_id- Optional correlation identifier included in recall traces, notifier metadata, and pipeline telemetry. It does not augment the query.
Examples
{:ok, memories} = Mnemosyne.recall("my-repo", "How to handle GenServer timeouts?")
{:ok, memories} =
Mnemosyne.recall("my-repo", "What should I try next?",
source_id: "task-42",
context: %{
goal: "Diagnose intermittent timeouts",
recent_steps: [
%{observation: "Request timed out", action: "Inspected application logs"}
]
}
)
@spec repair_graph( String.t(), keyword() ) :: :ok | {:error, Mnemosyne.Errors.Framework.NotFoundError.t()}
Strips dangling link references and removes orphaned tags/intents from the repo's graph asynchronously.
Use this after upgrading from a release whose persistence layer left
back-references behind on delete, or any time the graph is suspected to
carry stale link IDs. Returns immediately; repair runs in the background.
Subscribe to Notifier events (:repair_completed) to observe results.
Options
:supervisor- Name of the Mnemosyne supervisor. Defaults toMnemosyne.Supervisor.
@spec validate_episodic( String.t(), keyword() ) :: :ok | {:error, Mnemosyne.Errors.Framework.NotFoundError.t()}
Validates episodic grounding of abstract nodes in the repo's graph asynchronously.
Walks provenance chains from semantic/procedural nodes to source nodes and penalizes nodes whose source embeddings diverge from the abstract node's embedding. Returns immediately; validation runs in the background.