You need deterministic conversation state and explicit message projection to LLM input format.
After this guide, you can build and inspect history using Jido.AI.Context.
Two different data structures now coexist by design:
agent.state[:__thread__](Jido.Thread): append-only, canonical event log.agent.state[:__strategy__].context(Jido.AI.Context): materialized LLM projection view.
In ReAct, message and context lifecycle changes are represented as thread events, and the strategy context is projected from those events.
alias Jido.AI.Context
context =
Context.new(system_prompt: "You are concise.")
|> Context.append_user("Hello")
|> Context.append_assistant("Hi")
|> Context.append_user("Summarize this chat")messages = Context.to_messages(context)
# [%{role: :system, ...}, %{role: :user, ...}, ...]
recent_messages = Context.to_messages(context, limit: 2)raw = [
%{role: "user", content: "Question"},
%{role: "assistant", content: "Answer"}
]
context = Context.new() |> Context.append_messages(raw)Use Jido.AI.Turn.extract_text/1 when normalizing diverse provider response shapes.
When restoring from snapshot.details.conversation, split out one leading
system message first. Otherwise, that system message becomes a normal context
entry and may be duplicated during projection.
saved_messages = snapshot.details.conversation
{system_prompt, conversation_messages} =
case saved_messages do
[%{role: role, content: content} | rest]
when role in [:system, "system"] and is_binary(content) ->
{content, rest}
_ ->
{nil, saved_messages}
end
context =
Context.new(system_prompt: system_prompt)
|> Context.append_messages(conversation_messages)Use snapshot.details.conversation for message restore/import workflows. Tool
messages in that conversation are serialized for LLM projection, so do not parse
them to recover structured tool payloads. For completed ReAct tool outputs, use
snapshot.details[:tool_results].
Canonical strategy signal for context lifecycle:
ai.react.context.modify
Busy semantics in ReAct:
- if idle, context operation applies immediately
- if a request is active, operation is deferred and applied after terminal state
Compaction is represented as a standard context replace operation with reason metadata:
%{
op_id: "op_123",
context_ref: "default",
operation: %{
type: :replace,
reason: :compaction,
result_context: compacted_context,
meta: %{from_seq: 1, to_seq: 100}
}
}Symptom:
- assistant ignores previous turns
Fix:
- verify you append both user and assistant/tool entries
- avoid too-small
limitvalues during projection - inspect with
Context.debug_view/2orContext.pp/1
- Entries are stored reversed internally for append speed
Context.to_messages/2reorders to chronological outputlimit: nilincludes full thread
Use this when:
- you need explicit control over message windows
- you need import/export-friendly thread format
Do not use this when:
- strategy internals already manage conversation state for your use case
Jido.AI.Thread has been removed. Use Jido.AI.Context directly.
If you previously restored state with initial_state: %{thread: ...},
switch to initial_state: %{context: ...}.