Context And Message Projection

Copy Markdown View Source

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 limit values during projection
  • inspect with Context.debug_view/2 or Context.pp/1

Defaults You Should Know

  • Entries are stored reversed internally for append speed
  • Context.to_messages/2 reorders to chronological output
  • limit: nil includes 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: ...}.

Next