Nous.Decisions.Store behaviour (nous v0.16.0)

View Source

Storage behaviour for decision graph backends.

Defines the interface that all decision graph storage backends must implement. Backends manage nodes and edges that form a directed graph of agent decisions.

Architecture

The store is stateless from the module's perspective -- all state is passed through as an opaque state term returned by init/1 and threaded through subsequent calls. This allows multiple independent graphs to coexist.

Backends

BackendGraph QueriesDeps
Store.ETSBFS traversalNone
Store.DuckDBDuckPGQ path matchingduckdbex

Query Types

All backends must support these query types via query/3:

  • :active_goals -- nodes where type == :goal and status == :active
  • :recent_decisions -- decision nodes sorted by created_at desc (accepts :limit)
  • :path_between -- path from one node to another (accepts :from_id, :to_id)
  • :descendants -- all reachable nodes from a given node (accepts :node_id)
  • :ancestors -- all nodes that can reach a given node (accepts :node_id)

Summary

Callbacks

Add an edge connecting two nodes.

Add a node to the graph.

Delete a node by ID.

Get edges connected to a node.

Fetch a single node by ID.

Initialize the store, returning opaque state.

Run a named query against the graph.

Update fields on an existing node.

Callbacks

add_edge(state, edge)

@callback add_edge(state :: term(), edge :: Nous.Decisions.Edge.t()) ::
  {:ok, term()} | {:error, term()}

Add an edge connecting two nodes.

Returns

  • {:ok, state} on success
  • {:error, reason} on failure

add_node(state, node)

@callback add_node(state :: term(), node :: Nous.Decisions.Node.t()) ::
  {:ok, term()} | {:error, term()}

Add a node to the graph.

Returns

  • {:ok, state} on success (state may be updated)
  • {:error, reason} if the node cannot be added (e.g., duplicate ID)

delete_node(state, id)

@callback delete_node(state :: term(), id :: String.t()) ::
  {:ok, term()} | {:error, term()}

Delete a node by ID.

Implementations should also remove edges that reference the deleted node.

Returns

  • {:ok, state} on success
  • {:error, reason} on failure

get_edges(state, node_id, direction)

@callback get_edges(
  state :: term(),
  node_id :: String.t(),
  direction :: :outgoing | :incoming
) ::
  {:ok, [Nous.Decisions.Edge.t()]}

Get edges connected to a node.

Direction

  • :outgoing -- edges where from_id matches the given node ID
  • :incoming -- edges where to_id matches the given node ID

Returns

  • {:ok, edges} -- always succeeds, returning an empty list if none found

get_node(state, id)

@callback get_node(state :: term(), id :: String.t()) ::
  {:ok, Nous.Decisions.Node.t()} | {:error, :not_found}

Fetch a single node by ID.

Returns

  • {:ok, node} if found
  • {:error, :not_found} if no node with that ID exists

init(opts)

@callback init(opts :: keyword()) :: {:ok, term()} | {:error, term()}

Initialize the store, returning opaque state.

Options

Backend-specific. See individual backend modules for details.

Returns

  • {:ok, state} on success
  • {:error, reason} on failure

query(state, query_type, opts)

@callback query(state :: term(), query_type :: atom(), opts :: keyword()) ::
  {:ok, [Nous.Decisions.Node.t()]}

Run a named query against the graph.

Query Types

  • :active_goals -- returns active goal nodes. Options: none.
  • :recent_decisions -- returns decision nodes sorted by recency. Options: :limit.
  • :path_between -- returns nodes on a path between two nodes. Options: :from_id, :to_id.
  • :descendants -- returns all nodes reachable from a node. Options: :node_id.
  • :ancestors -- returns all nodes that can reach a node. Options: :node_id.

Returns

  • {:ok, nodes} -- always succeeds, returning an empty list if no results

update_node(state, id, updates)

@callback update_node(state :: term(), id :: String.t(), updates :: map()) ::
  {:ok, term()} | {:error, term()}

Update fields on an existing node.

The updates map contains only the fields to change. The implementation must also set updated_at to the current time.

Returns

  • {:ok, state} on success
  • {:error, :not_found} if the node does not exist