Powered by AppSignal & Oban Pro

Jidoka: Memory

livebook/06_memory.livemd

Jidoka: Memory

Run in Livebook

Capture conversation memory, retrieve it on the next turn, and choose whether memory is injected into the prompt or exposed as runtime context.

Setup

Mix.install(
  [
    {:jidoka, git: "https://github.com/mikehostetler/jidoka.git", ref: "924a486f3c1b7e7a943cb3d5ceee0de65f158467"},
    {:kino, "~> 0.19.0"}
  ],
  config: [
    jidoka: [
      model_aliases: %{fast: "anthropic:claude-haiku-4-5"}
    ]
  ]
)
Jidoka.Kino.setup()

Define A Memory Agent

defmodule LivebookDemo.Memory.Agent do
  use Jidoka.Agent

  agent do
    id :livebook_memory_agent
  end

  defaults do
    model :fast
    instructions "You use relevant memory when it helps."
  end

  lifecycle do
    memory do
      mode :conversation
      namespace {:context, :session}
      capture :conversation
      retrieve limit: 4
      inject :context
    end
  end
end
LivebookDemo.Memory.Agent.memory()

Simulate Two Turns

The runtime callbacks let us prove capture and retrieval without calling a model.

runtime = LivebookDemo.Memory.Agent.runtime_module()
agent = runtime.new(id: "livebook-memory-runtime")
session = "memory-session-#{System.unique_integer([:positive])}"

{:ok, agent, _action} =
  runtime.on_before_cmd(
    agent,
    {:ai_react_start,
     %{
       query: "Remember that my favorite color is blue.",
       request_id: "req-memory-1",
       tool_context: %{session: session}
     }}
  )

agent =
  Jido.AI.Request.complete_request(
    agent,
    "req-memory-1",
    "I will remember that your favorite color is blue."
  )

{:ok, agent, []} =
  runtime.on_after_cmd(agent, {:ai_react_start, %{request_id: "req-memory-1"}}, [])

{:ok, _agent, {:ai_react_start, params}} =
  runtime.on_before_cmd(
    agent,
    {:ai_react_start,
     %{
       query: "What is my favorite color?",
       request_id: "req-memory-2",
       tool_context: %{session: session}
     }}
  )

memory = params.tool_context[:memory]

%{
  namespace: memory.namespace,
  record_count: length(memory.records),
  public_context_keys: params.tool_context |> Map.keys() |> Enum.sort()
}

Render the retrieved records.

rows =
  Enum.map(memory.records, fn record ->
    %{
      kind: record.kind,
      text: record.text,
      source: record.source
    }
  end)

Jidoka.Kino.table("Retrieved memory", rows)

Switching to inject :instructions would append a bounded Relevant memory: section to the system prompt instead of exposing context.memory.

Optional Provider Turn

{:ok, pid} =
  Jidoka.Kino.start_or_reuse("livebook-memory-agent", fn ->
    LivebookDemo.Memory.Agent.start_link(id: "livebook-memory-agent")
  end)

Jidoka.Kino.chat("Memory chat", fn ->
  LivebookDemo.Memory.Agent.chat(pid, "Remember that my favorite snack is almonds.", context: %{session: session})
end)