Portable conversation state shared across agents and patterns.
Holds the message history, metadata, and an optional parent reference for linking back to an orchestrator in the subagent pattern.
Summary
Functions
Appends a message to the context.
Returns the last assistant message from the context, or nil if none exists.
Creates a new context with optional initial values.
Drops the oldest messages, keeping at most max of them.
Types
@type t() :: %ExAgent.Context{ messages: [ExAgent.Message.t()], metadata: map(), parent_ref: reference() | nil }
Functions
@spec add_message(t(), ExAgent.Message.t()) :: t()
Appends a message to the context.
Examples
iex> {:ok, msg} = ExAgent.Message.new(role: :user, content: "Hello")
iex> ctx = ExAgent.Context.new() |> ExAgent.Context.add_message(msg)
iex> length(ctx.messages)
1
@spec get_last_assistant_message(t()) :: ExAgent.Message.t() | nil
Returns the last assistant message from the context, or nil if none exists.
Examples
iex> ExAgent.Context.get_last_assistant_message(ExAgent.Context.new())
nil
Creates a new context with optional initial values.
Examples
iex> ctx = ExAgent.Context.new()
iex> ctx.messages
[]
iex> ctx = ExAgent.Context.new(metadata: %{session: "abc"})
iex> ctx.metadata
%{session: "abc"}
@spec trim(t(), pos_integer()) :: t()
Drops the oldest messages, keeping at most max of them.
Conversation history otherwise grows without bound: every turn resends the
whole transcript, so cost climbs turn over turn until the model returns
:context_length and the agent is stuck. Trimming is opt-in - silently
forgetting what a user said is a decision the caller has to make.
Leading :system messages are always kept: they carry instructions that must
outlive the window. A :tool result is never left without the :assistant
message that requested it, since providers reject an orphaned result.
Examples
iex> {:ok, a} = ExAgent.Message.new(role: :user, content: "one")
iex> {:ok, b} = ExAgent.Message.new(role: :user, content: "two")
iex> ctx = ExAgent.Context.new(messages: [a, b])
iex> ExAgent.Context.trim(ctx, 1).messages |> Enum.map(& &1.content)
["two"]