Nous.Agent.Context (nous v0.13.3)
View SourceUnified context for agent execution.
Accumulates state across the agent loop:
- Conversation messages
- Tool call history
- Usage tracking
- User dependencies
- Callbacks configuration
Example
# Create new context
ctx = Context.new(
system_prompt: "You are helpful",
deps: %{database: MyDB},
max_iterations: 15
)
# Add messages
ctx = ctx
|> Context.add_message(Message.user("Hello"))
|> Context.add_message(Message.assistant("Hi there!"))
# Check loop control
if ctx.needs_response do
# Continue execution
endCallbacks
Callbacks can be configured as a map of event handlers:
ctx = Context.new(callbacks: %{
on_llm_new_delta: fn _event, delta -> IO.write(delta) end,
on_tool_call: fn _event, call -> IO.inspect(call) end
})Process Notification
For LiveView integration, set notify_pid:
ctx = Context.new(notify_pid: self())
# Will receive: {:agent_delta, text}, {:tool_call, call}, etc.
Summary
Functions
Add a message to the context.
Add multiple messages to the context.
Record a tool call in the context.
Merge usage statistics into the context.
Get all assistant messages from the context.
Deserialize a map back into a Context struct.
Create context from an existing RunContext (migration helper).
Increment the iteration counter.
Get the last message from the context.
Check if maximum iterations has been reached.
Merge new dependencies into the context.
Create a new context with options.
Patch dangling tool calls in the conversation.
Serialize context to a JSON-encodable map.
Set needs_response flag explicitly.
Convert to RunContext for tool execution (backwards compatibility).
Types
@type t() :: %Nous.Agent.Context{ active_skills: [Nous.Skill.t()], agent_name: String.t() | nil, approval_handler: (map() -> :approve | {:edit, map()} | :reject) | nil, callbacks: %{optional(atom()) => callback_fn()}, cancellation_check: (-> :ok | {:error, term()}) | nil, deps: map(), hook_registry: Nous.Hook.Registry.t() | nil, iteration: non_neg_integer(), max_iterations: non_neg_integer(), messages: [Nous.Message.t()], needs_response: boolean(), notify_pid: pid() | nil, pubsub: module() | nil, pubsub_topic: String.t() | nil, started_at: DateTime.t() | nil, system_prompt: String.t() | nil, tool_calls: [map()], usage: Nous.Usage.t() }
Functions
@spec add_message(t(), Nous.Message.t()) :: t()
Add a message to the context.
Automatically updates needs_response based on message role and content.
Examples
iex> ctx = Context.new()
iex> ctx = Context.add_message(ctx, Message.user("Hello"))
iex> length(ctx.messages)
1
@spec add_messages(t(), [Nous.Message.t()]) :: t()
Add multiple messages to the context.
Examples
iex> ctx = Context.new()
iex> messages = [Message.user("Hi"), Message.assistant("Hello")]
iex> ctx = Context.add_messages(ctx, messages)
iex> length(ctx.messages)
2
Record a tool call in the context.
Examples
iex> ctx = Context.new()
iex> call = %{id: "call_123", name: "search", arguments: %{"q" => "test"}}
iex> ctx = Context.add_tool_call(ctx, call)
iex> length(ctx.tool_calls)
1
@spec add_usage(t(), Nous.Usage.t() | map()) :: t()
Merge usage statistics into the context.
Examples
iex> ctx = Context.new()
iex> usage = %Usage{input_tokens: 100, output_tokens: 50}
iex> ctx = Context.add_usage(ctx, usage)
iex> ctx.usage.input_tokens
100
@spec assistant_messages(t()) :: [Nous.Message.t()]
Get all assistant messages from the context.
Examples
iex> ctx = Context.new()
iex> ctx = ctx |> Context.add_message(Message.user("Hi"))
iex> ctx = ctx |> Context.add_message(Message.assistant("Hello"))
iex> length(Context.assistant_messages(ctx))
1
Deserialize a map back into a Context struct.
Handles version migrations and restores messages, usage, and metadata. Functions, PIDs, and callbacks are not restored and will use defaults.
Returns {:ok, context} or {:error, reason}.
Examples
iex> ctx = Context.new(system_prompt: "Be helpful")
iex> data = Context.serialize(ctx)
iex> {:ok, restored} = Context.deserialize(data)
iex> restored.system_prompt
"Be helpful"
@spec from_run_context( Nous.RunContext.t(), keyword() ) :: t()
Create context from an existing RunContext (migration helper).
Examples
iex> run_ctx = Nous.RunContext.new(%{key: "value"})
iex> ctx = Context.from_run_context(run_ctx)
iex> ctx.deps.key
"value"
Increment the iteration counter.
Examples
iex> ctx = Context.new()
iex> ctx = Context.increment_iteration(ctx)
iex> ctx.iteration
1
@spec last_message(t()) :: Nous.Message.t() | nil
Get the last message from the context.
Examples
iex> ctx = Context.new() |> Context.add_message(Message.user("Hello"))
iex> Context.last_message(ctx).content
"Hello"
iex> ctx = Context.new()
iex> Context.last_message(ctx)
nil
Check if maximum iterations has been reached.
Examples
iex> ctx = Context.new(max_iterations: 5, iteration: 5)
iex> Context.max_iterations_reached?(ctx)
true
iex> ctx = Context.new(max_iterations: 5, iteration: 3)
iex> Context.max_iterations_reached?(ctx)
false
Merge new dependencies into the context.
Used by tools to update context state via __update_context__ or ContextUpdate.
Examples
iex> ctx = Context.new(deps: %{count: 0})
iex> ctx = Context.merge_deps(ctx, %{count: 1, new_key: "value"})
iex> ctx.deps.count
1
iex> ctx.deps.new_key
"value"
Create a new context with options.
Options
:messages- Initial message list (default: []):system_prompt- System prompt string:deps- User dependencies map (default: %{}):max_iterations- Maximum loop iterations (default: 10):callbacks- Map of callback functions:notify_pid- PID to receive event messages:agent_name- Name for telemetry/logging:cancellation_check- Function to check for cancellation:approval_handler- Function called for tools withrequires_approval: true
Examples
iex> ctx = Context.new(system_prompt: "Be helpful", max_iterations: 5)
iex> ctx.max_iterations
5
iex> ctx = Context.new(deps: %{user_id: 123})
iex> ctx.deps.user_id
123
Patch dangling tool calls in the conversation.
Scans messages for assistant messages with tool_calls that have no corresponding tool result message. Injects synthetic tool results for unmatched calls with a message indicating the call was interrupted.
This is critical when resuming from a persisted context where the session was interrupted mid-tool-execution.
Examples
iex> ctx = Context.new(messages: [
...> Message.assistant("Let me search", tool_calls: [%{id: "call_1", name: "search"}])
...> ])
iex> ctx = Context.patch_dangling_tool_calls(ctx)
iex> length(ctx.messages)
2
Serialize context to a JSON-encodable map.
Persists messages, usage, metadata. Never persists functions, PIDs, or modules.
Includes a version field for future migrations.
Examples
iex> ctx = Context.new(system_prompt: "Be helpful", max_iterations: 5)
iex> data = Context.serialize(ctx)
iex> data.version
1
iex> data.system_prompt
"Be helpful"
Set needs_response flag explicitly.
Examples
iex> ctx = Context.new()
iex> ctx = Context.set_needs_response(ctx, false)
iex> ctx.needs_response
false
@spec to_run_context(t()) :: Nous.RunContext.t()
Convert to RunContext for tool execution (backwards compatibility).
This allows tools to continue using the existing RunContext interface.
Examples
iex> ctx = Context.new(deps: %{db: :postgres})
iex> run_ctx = Context.to_run_context(ctx)
iex> run_ctx.deps.db
:postgres