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.
Core Thread vs ReAct 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.
Build Context
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")Project To Messages
messages = Context.to_messages(context)
# [%{role: :system, ...}, %{role: :user, ...}, ...]
recent_messages = Context.to_messages(context, limit: 2)Import Existing Messages
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.
Restore Snapshot Conversation Safely
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].
ReAct Context Operations
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 Replace
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}
}
}Failure Mode: Unexpected Missing Context
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
Defaults You Should Know
- Entries are stored reversed internally for append speed
Context.to_messages/2reorders to chronological outputlimit: nilincludes full thread
When To Use / Not Use
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
Breaking Change
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: ...}.