Current section

Files

Jump to
nous lib nous plugins decisions.ex
Raw

lib/nous/plugins/decisions.ex

defmodule Nous.Plugins.Decisions do
@moduledoc """
Plugin for agent decision tracking with a directed graph.
Provides tools for agents to record goals, decisions, and outcomes,
and injects decision context into the system prompt.
## Usage
agent = Agent.new("openai:gpt-4",
plugins: [Nous.Plugins.Decisions],
deps: %{decisions_config: %{store: Nous.Decisions.Store.ETS}}
)
## Configuration (via `deps[:decisions_config]`)
**Required:**
* `:store` - Store backend module (e.g., `Nous.Decisions.Store.ETS`)
**Optional:**
* `:store_opts` - Options passed to `store.init/1`
* `: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`
"""
@behaviour Nous.Plugin
require Logger
alias Nous.Decisions.{ContextBuilder, Tools}
@impl true
def init(_agent, ctx) do
config = ctx.deps[:decisions_config] || %{}
store_mod = config[:store]
unless store_mod do
Logger.warning(
"Nous.Plugins.Decisions: No :store configured in deps[:decisions_config]. " <>
"Decision tools will not function."
)
ctx
else
store_opts = Map.get(config, :store_opts, [])
store_opts = if is_map(store_opts), do: Map.to_list(store_opts), else: store_opts
case store_mod.init(store_opts) do
{:ok, store_state} ->
updated_config =
config
|> Map.put(:store_state, store_state)
|> Map.put_new(:auto_inject, true)
|> Map.put_new(:inject_strategy, :first_only)
|> Map.put_new(:decision_limit, 5)
|> Map.put(:_inject_done, false)
%{ctx | deps: Map.put(ctx.deps, :decisions_config, updated_config)}
{:error, reason} ->
Logger.error("Nous.Plugins.Decisions: Store init failed: #{inspect(reason)}")
ctx
end
end
end
@impl true
def tools(_agent, _ctx) do
Tools.all_tools()
end
@impl true
def system_prompt(_agent, ctx) do
config = ctx.deps[:decisions_config] || %{}
if config[:store_state] do
store_mod = config[:store]
state = config[:store_state]
decision_limit = config[:decision_limit] || 5
context_text = ContextBuilder.build(store_mod, state, decision_limit: decision_limit)
base = """
## Decisions
You have access to a decision tracking system. Use these tools:
- `add_goal` -- Record a new goal or objective.
- `record_decision` -- Record a decision and optionally link it to a goal.
- `record_outcome` -- Record the outcome of a decision or action.
- `query_decisions` -- Query active goals, recent decisions, or graph paths.
When working on complex tasks, proactively use these tools to track your \
reasoning process. Record goals at the start, decisions as you make them, \
and outcomes when you observe results.\
"""
if context_text do
base <> "\n\n" <> context_text
else
base
end
end
end
@impl true
def before_request(_agent, ctx, tools) do
config = ctx.deps[:decisions_config] || %{}
should_inject? =
config[:auto_inject] == true &&
config[:store_state] != nil &&
should_inject_this_iteration?(config)
if should_inject? do
updated_config = Map.put(config, :_inject_done, true)
ctx = %{ctx | deps: Map.put(ctx.deps, :decisions_config, updated_config)}
{ctx, tools}
else
{ctx, tools}
end
end
defp should_inject_this_iteration?(config) do
case config[:inject_strategy] do
:every_iteration -> true
_ -> config[:_inject_done] != true
end
end
end