Mnemosyne.GraphBackend behaviour (mnemosyne v0.4.0)

Copy Markdown View Source

Behaviour for unified graph persistence and querying backends.

Implementations handle storing, retrieving, and querying knowledge graph nodes through a single interface, replacing the separate Storage and in-memory Graph modules with a database-agnostic contract.

Callbacks

  • init/1 - Initialize the backend with configuration options.
  • apply_changeset/2 - Persist a batch of node additions and links.
  • get_ingestion/2 - Fetch a durable ingestion record by source ID.
  • commit_ingestion/3 - Compare-and-set graph changes and an ingestion record, reporting whether it inserted or found an equal record.
  • delete_nodes/2 - Remove nodes by their IDs.
  • find_candidates/6 - Query for nodes matching type/embedding/tag criteria.
  • get_node/2 - Fetch a single node by ID.
  • get_linked_nodes/3 - Fetch linked nodes, optionally filtered by edge type.

Read callbacks (find_candidates, get_node, get_linked_nodes, get_ingestion) return state for interface uniformity but must not rely on state mutation — callers may discard the returned state in read-only contexts.

Backends used with access control must persist NodeMetadata.audience, preserve it during usage updates and consolidation, and reject changes to an assigned audience. An absent audience is legacy, unclassified data. Protected reads enumerate built-in node types to create an authorized snapshot before scoring.

Summary

Types

ingestion_record()

@type ingestion_record() :: %{
  optional(:audience) => Mnemosyne.AccessControl.audience() | nil,
  source_id: String.t(),
  payload_digest: binary(),
  fingerprint_version: pos_integer(),
  receipt: Mnemosyne.IngestionReceipt.t()
}

scored_node()

@type scored_node() :: {struct(), float()}

state()

@type state() :: term()

Callbacks

apply_changeset(t, state)

@callback apply_changeset(Mnemosyne.Graph.Changeset.t(), state()) ::
  {:ok, state()} | {:error, Mnemosyne.Errors.error()}

commit_ingestion(ingestion_record, t, state)

@callback commit_ingestion(ingestion_record(), Mnemosyne.Graph.Changeset.t(), state()) ::
  {:ok, :inserted | :existing, Mnemosyne.IngestionReceipt.t(), state()}
  | {:error, Mnemosyne.Errors.error()}

delete_metadata(list, state)

@callback delete_metadata([String.t()], state()) :: {:ok, state()}

delete_nodes(list, state)

@callback delete_nodes([String.t()], state()) ::
  {:ok, state()} | {:error, Mnemosyne.Errors.error()}

find_candidates(node_types, query_embedding, tag_embeddings, value_fn_config, opts, state)

@callback find_candidates(
  node_types :: [atom()],
  query_embedding :: [float()],
  tag_embeddings :: [[float()]],
  value_fn_config :: %{module: module(), params: %{required(atom()) => map()}},
  opts :: keyword(),
  state()
) :: {:ok, [scored_node()], state()} | {:error, Mnemosyne.Errors.error()}

get_ingestion(t, state)

@callback get_ingestion(String.t(), state()) ::
  {:ok, ingestion_record() | nil, state()} | {:error, Mnemosyne.Errors.error()}

get_linked_nodes(list, arg2, state)

@callback get_linked_nodes([String.t()], Mnemosyne.Graph.Edge.edge_type() | nil, state()) ::
  {:ok, [struct()], state()}

get_metadata(list, state)

@callback get_metadata([String.t()], state()) ::
  {:ok, %{required(String.t()) => struct()}, state()}

get_node(t, state)

@callback get_node(String.t(), state()) :: {:ok, struct() | nil, state()}

get_nodes_by_type(node_types, state)

@callback get_nodes_by_type(node_types :: [atom()], state()) ::
  {:ok, [struct()], state()} | {:error, Mnemosyne.Errors.error()}

init(opts)

@callback init(opts :: keyword()) :: {:ok, state()} | {:error, Mnemosyne.Errors.error()}

update_metadata(map, state)

@callback update_metadata(%{required(String.t()) => struct()}, state()) ::
  {:ok, state()} | {:error, Mnemosyne.Errors.error()}