# `Nous.Decisions`
[🔗](https://github.com/nyo16/nous/blob/v0.17.1/lib/nous/decisions.ex#L1)

Top-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/3` callbacks
- **Integration** -- `Plugins.Decisions` (plugin), decision tools, `ContextBuilder`

## Store Backends

| Backend | Graph Queries | Deps |
|---------|--------------|------|
| `Store.ETS` | BFS traversal | None |
| `Store.DuckDB` | DuckPGQ path matching | `duckdbex` |

# `active_goals`

```elixir
@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)

# `add_edge`

```elixir
@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)

# `add_node`

```elixir
@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)

# `ancestors`

> This function is deprecated. Call store_mod.query(state, :ancestors, ...) directly.

```elixir
@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)

# `descendants`

> This function is deprecated. Call store_mod.query(state, :descendants, ...) directly.

```elixir
@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)

# `get_node`

```elixir
@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)

# `path_between`

> This function is deprecated. Call store_mod.query(state, :path_between, ...) directly.

```elixir
@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)

# `recent_decisions`

```elixir
@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)

# `supersede`

```elixir
@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 {: .warning}
>
> 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_node`

```elixir
@spec update_node(module(), term(), String.t(), map()) ::
  {:ok, term()} | {:error, term()}
```

Update fields on an existing node.

## Examples

    {:ok, state} = Nous.Decisions.update_node(Store.ETS, state, node_id, %{status: :completed})

# `validate_config`

```elixir
@spec validate_config(map()) :: {:ok, map()} | {:error, String.t()}
```

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 to `store.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(%{})

---

*Consult [api-reference.md](api-reference.md) for complete listing*
