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 (Sessions)
Sessions are the write interface to the knowledge graph. A session is tied to a specific repo and collects observation-action pairs, groups them into trajectories, and uses LLM calls to extract semantic and procedural knowledge.
{:ok, session_id} = Mnemosyne.start_session("Learn Elixir patterns", repo: "my-repo")
:ok = Mnemosyne.append(session_id, "Read about GenServer", "Implemented a cache")
:ok = Mnemosyne.append(session_id, "Cache worked well", "Added TTL support")
:ok = Mnemosyne.close_and_commit(session_id)Sessions follow a state machine lifecycle:
:idle -> :collecting -> :extracting -> :ready -> (committed/discarded)
The :extracting state runs asynchronously under a Task.Supervisor, keeping
the session process responsive. If extraction fails, the session moves to
:failed and preserves the episode for retry.
Read Path (Recall)
Recall queries the knowledge graph using value functions to score and rank nodes by relevance. Session context can augment queries with the current episode's state for more targeted retrieval.
{:ok, memories} = Mnemosyne.recall("my-repo", "How to implement caching?")
{:ok, memories} = Mnemosyne.recall_in_context("my-repo", session_id, "What did I try before?")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 Registry, RepoRegistry,
TaskSupervisor, RepoSupervisor, and SessionSupervisor.
Summary
Functions
Appends an observation-action pair to the current episode.
Like append/4 but returns immediately. Accepts an optional callback that
receives :ok or {:error, reason} when the append finishes.
Applies a changeset to the knowledge graph asynchronously.
Closes the current episode, triggering asynchronous knowledge extraction.
Closes the episode, waits for extraction to complete, and commits the result.
Like close/2 but returns immediately with optional callback.
See commit_async/3 for queuing semantics.
Closes a running memory repository.
Commits the extracted changeset to the MemoryStore.
Like commit/2 but returns immediately. When the session is busy
(extracting or collecting with in-flight trajectory tasks), the commit
is queued and executes when the blocking work completes.
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.
Discards the extraction result without committing to the knowledge graph.
Like discard/2 but returns immediately with optional callback.
See commit_async/3 for queuing semantics.
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.
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.
Retrieves memories using both the query and the session's current context.
Strips dangling link references and removes orphaned tags/intents from the repo's graph asynchronously.
Returns the current state of a session.
Like start_session/2 but for resuming an existing idle session
with a new episode. Returns immediately with optional callback.
See commit_async/3 for queuing semantics.
Starts a new memory session with the given goal.
Validates episodic grounding of abstract nodes in the repo's graph asynchronously.
Functions
@spec append(String.t(), String.t(), String.t(), keyword()) :: :ok | {:error, Mnemosyne.Errors.error()}
Appends an observation-action pair to the current episode.
The observation describes what the agent perceived and the action describes what the agent did in response. Each pair becomes a step in the current trajectory. When the embedding similarity between consecutive observations drops below the threshold (0.75), a new trajectory boundary is detected automatically.
The session must be in the :collecting state.
Options
:supervisor- Name of the Mnemosyne supervisor. Defaults toMnemosyne.Supervisor.
@spec append_async( String.t(), String.t(), String.t(), (Mnemosyne.Session.append_result() -> any()) | nil, keyword() ) :: :ok | {:error, Mnemosyne.Errors.error()}
Like append/4 but returns immediately. Accepts an optional callback that
receives :ok or {:error, reason} when the append finishes.
Options
:supervisor- Name of the Mnemosyne supervisor. Defaults toMnemosyne.Supervisor.
@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( String.t(), keyword() ) :: :ok | {:error, Mnemosyne.Errors.error()}
Closes the current episode, triggering asynchronous knowledge extraction.
Moves the session from :collecting to :extracting. The extraction
pipeline runs in a supervised task and processes each trajectory to extract
semantic facts, procedural instructions, and compute returns. Once
extraction completes, the session transitions to :ready (success)
or :failed (extraction error).
Use commit/2 after extraction completes to persist the results, or
close_and_commit/2 to do both in one call.
@spec close_and_commit( String.t(), keyword() ) :: :ok | {:error, Mnemosyne.Errors.error()}
Closes the episode, waits for extraction to complete, and commits the result.
Convenience function that combines close/2, polling for extraction completion,
and commit/2 into a single blocking call. Handles transient extraction failures
by retrying up to max_retries times.
Options
:max_retries- Number of retry attempts on transient extraction failures. Defaults to2.:max_polls- Maximum number of polling iterations while waiting for extraction. Defaults to200.:poll_interval- Milliseconds between polls. Defaults to50.:supervisor- Name of the Mnemosyne supervisor. Defaults toMnemosyne.Supervisor.
Examples
:ok = Mnemosyne.close_and_commit(session_id)
:ok = Mnemosyne.close_and_commit(session_id, max_retries: 5, poll_interval: 100)
@spec close_async(String.t(), Mnemosyne.Session.op_callback(), keyword()) :: :ok | {:error, Mnemosyne.Errors.error()}
Like close/2 but returns immediately with optional callback.
See commit_async/3 for queuing semantics.
Options
:supervisor- Name of the Mnemosyne supervisor. Defaults toMnemosyne.Supervisor.
@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 commit( String.t(), keyword() ) :: :ok | {:error, Mnemosyne.Errors.error()}
Commits the extracted changeset to the MemoryStore.
Enqueues the knowledge graph changeset produced by the extraction pipeline
for application to the repo's MemoryStore. The session must be in the
:ready state. The changeset is applied asynchronously via the write lane;
subscribe to Notifier events (:changeset_applied) to observe completion.
After committing, the session transitions back to :idle and can start
a new episode.
@spec commit_async(String.t(), Mnemosyne.Session.op_callback(), keyword()) :: :ok | {:error, Mnemosyne.Errors.error()}
Like commit/2 but returns immediately. When the session is busy
(extracting or collecting with in-flight trajectory tasks), the commit
is queued and executes when the blocking work completes.
The optional callback receives {:ok, :committed} or {:error, reason}.
An :ok return means the operation was accepted, not that it succeeded.
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
deletes the lower-scored one. 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 discard( String.t(), keyword() ) :: :ok | {:error, Mnemosyne.Errors.error()}
Discards the extraction result without committing to the knowledge graph.
Drops the changeset produced by extraction. Useful when the extracted
knowledge is deemed low-quality or irrelevant. The session returns to
:idle and can start a new episode.
@spec discard_async(String.t(), Mnemosyne.Session.op_callback(), keyword()) :: :ok | {:error, Mnemosyne.Errors.error()}
Like discard/2 but returns immediately with optional callback.
See commit_async/3 for queuing semantics.
Options
:supervisor- Name of the Mnemosyne supervisor. Defaults toMnemosyne.Supervisor.
@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 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.
Examples
{:ok, memories} = Mnemosyne.recall("my-repo", "How to handle GenServer timeouts?")
@spec recall_in_context(String.t(), String.t(), String.t(), keyword()) :: {:ok, Mnemosyne.Pipeline.RecallResult.t()} | {:error, Mnemosyne.Errors.error()}
Retrieves memories using both the query and the session's current context.
Augments the query with the active episode's state (current subgoal,
recent observations) to produce more contextually relevant results.
If the session is not found, falls back to a plain recall/3.
Examples
{:ok, memories} = Mnemosyne.recall_in_context("my-repo", session_id, "What patterns apply here?")
@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 session_state( String.t(), keyword() ) :: Mnemosyne.Session.state() | {:error, Mnemosyne.Errors.Framework.NotFoundError.t()}
Returns the current state of a session.
Possible states: :idle, :collecting, :extracting, :ready, :failed.
Returns {:error, NotFoundError} if the session ID is not registered.
@spec start_episode_async( String.t(), String.t(), Mnemosyne.Session.op_callback(), keyword() ) :: :ok | {:error, Mnemosyne.Errors.error()}
Like start_session/2 but for resuming an existing idle session
with a new episode. Returns immediately with optional callback.
See commit_async/3 for queuing semantics.
Options
:supervisor- Name of the Mnemosyne supervisor. Defaults toMnemosyne.Supervisor.
Starts a new memory session with the given goal.
Creates a new Session process under the SessionSupervisor and immediately
opens an episode with the provided goal. The session begins in the :collecting
state, ready to receive observation-action pairs via append/4.
LLM, embedding, and config defaults are pulled from the repo's MemoryStore
unless explicitly overridden in opts.
Options
:repo- Required. The repo ID to bind this session to.:supervisor- Name of the Mnemosyne supervisor to use. Defaults toMnemosyne.Supervisor.:config- AMnemosyne.Configstruct overriding the stored defaults.:llm- LLM adapter module overriding the stored default.:embedding- Embedding adapter module overriding the stored default.
Examples
{:ok, session_id} = Mnemosyne.start_session("Explore caching strategies", repo: "my-repo")
@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.