Nous.Decisions (nous v0.15.8)
View SourceTop-level module for the Nous Decision Graph system.
Provides a directed graph for tracking agent goals, decisions, options, actions, and outcomes. The graph persists the reasoning process and enables agents to revisit, supersede, or build on prior decisions.
Quick Start
# Minimal setup (ETS store)
agent = Agent.new("openai:gpt-4",
plugins: [Nous.Plugins.Decisions],
deps: %{decisions_config: %{store: Nous.Decisions.Store.ETS}}
)Architecture
Three layers, all plain modules and structs (no GenServer):
- Data Layer --
Node(struct),Edge(struct),Store(behaviour + backends) - Query Layer -- Graph traversal via store
query/3callbacks - Integration --
Plugins.Decisions(plugin), decision tools,ContextBuilder
Store Backends
| Backend | Graph Queries | Deps |
|---|---|---|
Store.ETS | BFS traversal | None |
Store.DuckDB | DuckPGQ path matching | duckdbex |
Summary
Functions
Get all active goal nodes.
Add an edge connecting two nodes.
Add a node to the decision graph.
Get all ancestor nodes that can reach a given node.
Get all descendant nodes reachable from a given node.
Fetch a single node by ID.
Find a path between two nodes.
Get recent decision nodes, sorted by creation time descending.
Supersede a node with a new one.
Update fields on an existing node.
Validate a decisions configuration map.
Functions
@spec active_goals(module(), term()) :: {:ok, [Nous.Decisions.Node.t()]}
Get all active goal nodes.
Examples
{:ok, goals} = Nous.Decisions.active_goals(Store.ETS, state)
@spec add_edge(module(), term(), Nous.Decisions.Edge.t()) :: {:ok, term()} | {:error, term()}
Add an edge connecting two nodes.
Examples
edge = Edge.new(%{from_id: goal.id, to_id: decision.id, edge_type: :leads_to})
{:ok, state} = Nous.Decisions.add_edge(Store.ETS, state, edge)
@spec add_node(module(), term(), Nous.Decisions.Node.t()) :: {:ok, term()} | {:error, term()}
Add a node to the decision graph.
Examples
node = Node.new(%{type: :goal, label: "Implement auth"})
{:ok, state} = Nous.Decisions.add_node(Store.ETS, state, node)
@spec ancestors(module(), term(), String.t()) :: {:ok, [Nous.Decisions.Node.t()]}
Get all ancestor nodes that can reach a given node.
Examples
{:ok, ancestors} = Nous.Decisions.ancestors(Store.ETS, state, node_id)
@spec descendants(module(), term(), String.t()) :: {:ok, [Nous.Decisions.Node.t()]}
Get all descendant nodes reachable from a given node.
Examples
{:ok, descendants} = Nous.Decisions.descendants(Store.ETS, state, node_id)
@spec get_node(module(), term(), String.t()) :: {:ok, Nous.Decisions.Node.t()} | {:error, :not_found}
Fetch a single node by ID.
Examples
{:ok, node} = Nous.Decisions.get_node(Store.ETS, state, node_id)
@spec path_between(module(), term(), String.t(), String.t()) :: {:ok, [Nous.Decisions.Node.t()]}
Find a path between two nodes.
Returns the nodes along the shortest path, or an empty list if no path exists.
Examples
{:ok, path} = Nous.Decisions.path_between(Store.ETS, state, from_id, to_id)
@spec recent_decisions(module(), term(), keyword()) :: {:ok, [Nous.Decisions.Node.t()]}
Get recent decision nodes, sorted by creation time descending.
Options
:limit- maximum number of decisions (default: 10)
Examples
{:ok, decisions} = Nous.Decisions.recent_decisions(Store.ETS, state, limit: 5)
@spec supersede(module(), term(), String.t(), String.t(), String.t() | nil) :: {:ok, term()} | {:error, term()}
Supersede a node with a new one.
Marks the old node as :superseded and adds a :supersedes edge
from the new node to the old node.
Best-effort, not atomic
This function performs two backend writes (update_node then
add_edge). If update_node succeeds but add_edge fails (network
blip, lock contention, NIF failure), the old node is left marked
:superseded with no edge connecting the new and old. There is no
automatic rollback. The Store behaviour does not currently expose a
transaction primitive; once it does, this should be wrapped in one.
Options
rationale- reason for superseding (stored on the old node)
Examples
{:ok, state} = Nous.Decisions.supersede(Store.ETS, state, old_id, new_id, "Better approach found")
Update fields on an existing node.
Examples
{:ok, state} = Nous.Decisions.update_node(Store.ETS, state, node_id, %{status: :completed})
Validate a decisions configuration map.
Returns {:ok, config} with defaults applied, or {:error, reason}.
Required Keys
:store- Store backend module (e.g.,Nous.Decisions.Store.ETS)
Optional Keys
:store_opts- Options passed tostore.init/1(default:[]):decision_limit- Max recent decisions in context (default: 5):auto_inject- Inject decision context into system prompt (default: true):inject_strategy-:first_only(default) or:every_iteration
Examples
{:ok, config} = Nous.Decisions.validate_config(%{store: Nous.Decisions.Store.ETS})
{:error, reason} = Nous.Decisions.validate_config(%{})