Jido.Memory

Hex.pmHex DocsCILicenseWebsiteEcosystemDiscord

Jido.Memory is the memory integration package for Jido.Agent.

The main end-user story is simple:

Everything else in the package exists to support that goal.

The plugin story is the center of the package. The runtime, provider contract, and canonical structs are important, but they are plumbing for making memory integration with Jido agents stable and predictable.

Why This Exists

Agents usually need memory for one or more of these reasons:

Without a dedicated package, every agent ends up inventing its own memory shape, storage wiring, retrieval rules, and signal/action surface.

jido_memory gives Jido a shared memory story:

What You Get

For agent builders, the package provides:

For provider authors and advanced integrations, the package also provides:

The key distinction is:

The Main Story: Add Memory To A Jido Agent

The built-in integration path in core is Jido.Memory.BasicPlugin.

BasicPlugin is intentionally specific to the built-in store-backed path in core. It is not a generic adapter for every possible memory backend. That is a feature, not a limitation: it keeps the Jido integration in core clean and easy to understand.

BasicPlugin handles:

Quick Start

1. Add the dependency

defp deps do
  [
    {:jido_memory, path: "../jido_memory"}
  ]
end

2. Attach the plugin to your agent

defmodule MyApp.SupportAgent do
  use Jido.Agent,
    name: "support_agent",
    description: "A Jido agent with built-in memory",
    default_plugins: %{
      __memory__:
        {Jido.Memory.BasicPlugin,
         %{
           store: {Jido.Memory.Store.ETS, [table: :support_agent_memory]},
           namespace_mode: :per_agent,
           auto_capture: true
         }}
    }
end

Jido.Memory.BasicPlugin uses the same canonical :__memory__ state key as Jido's built-in memory plugin. Install it as the __memory__ default-plugin replacement so Jido.Memory.Runtime can discover memory state through the normal plugin path.

3. Make sure the ETS table exists

alias Jido.Memory.Store.ETS

:ok = ETS.ensure_ready(table: :support_agent_memory)

4. Start your Jido runtime and agent

{:ok, _jido} = Jido.start(name: MyApp.Jido, otp_app: :my_app)

{:ok, pid} =
  Jido.AgentServer.start_link(
    agent: MyApp.SupportAgent,
    id: "agent-1",
    jido: MyApp.Jido
  )

5. Use memory through the agent

The plugin exposes memory actions under the memory.* signal namespace:

alias Jido.Signal

{:ok, _agent} =
  Jido.AgentServer.call(
    pid,
    Signal.new!("memory.remember", %{
      class: :semantic,
      kind: :fact,
      text: "The user prefers concise answers.",
      tags: ["preferences", "user"]
    })
  )

{:ok, agent_after_retrieve} =
  Jido.AgentServer.call(
    pid,
    Signal.new!("memory.retrieve", %{
      text_contains: "concise",
      limit: 5
    })
  )

By default, the retrieve action returns a canonical memory result under memory_result.

What The Plugin Actually Does

Jido.Memory.BasicPlugin keeps the agent integration narrow and practical.

When the plugin mounts, it resolves:

It then keeps only lightweight plugin state in the agent:

That lightweight state is enough for memory-aware actions and runtime calls to resolve the built-in provider correctly without bloating the agent state with backend-specific details.

Namespace Behavior

The built-in plugin supports two namespace modes.

Per-agent namespace:

{Jido.Memory.BasicPlugin,
 %{
   store: {Jido.Memory.Store.ETS, [table: :my_memory]},
   namespace_mode: :per_agent
 }}

This resolves to namespaces like:

Shared namespace:

{Jido.Memory.BasicPlugin,
 %{
   store: {Jido.Memory.Store.ETS, [table: :shared_memory]},
   namespace_mode: :shared,
   shared_namespace: "team"
 }}

This resolves to:

Use per-agent mode when each agent should have isolated memory. Use shared mode when multiple agents should collaborate over the same memory pool.

Memory Actions

Core ships three explicit actions for the built-in memory path:

These are what BasicPlugin exposes on the agent.

Expected action behavior:

This is the primary developer experience for agent-side memory in core.

Custom Agent Actions

Most real agents need more than raw remember and retrieve calls. They need domain-specific behavior built on top of memory.

That is where your own Jido actions come in.

Example:

defmodule MyApp.Actions.RetrieveUserPreferences do
  use Jido.Action,
    name: "retrieve_user_preferences",
    description: "Retrieve preference memories for the current agent",
    schema: [
      query: [type: :string, required: true],
      limit: [type: :integer, required: false, default: 5]
    ]

  alias Jido.Memory.{RetrieveResult, Runtime}

  @impl true
  def run(params, context) do
    case Runtime.retrieve(context, %{
           kinds: [:fact],
           tags_any: ["preferences"],
           text_contains: params[:query],
           limit: params[:limit] || 5
         }) do
      {:ok, result} ->
        {:ok,
         %{
           preferences: Enum.map(RetrieveResult.records(result), & &1.text),
           memory_result: result
         }}

      {:error, reason} ->
        {:error, reason}
    end
  end
end

That is the intended layering:

Auto-Capture

BasicPlugin can automatically persist selected signals as memory records.

Default capture patterns include:

This is useful when you want a memory trail of agent interactions without writing explicit remember calls for every signal.

You can customize capture behavior:

{Jido.Memory.BasicPlugin,
 %{
   store: {Jido.Memory.Store.ETS, [table: :captured_memory]},
   namespace_mode: :per_agent,
   capture_signal_patterns: ["ai.react.query", "bt.*"],
   capture_rules: %{
     "bt.node.enter" => %{
       class: :episodic,
       kind: :fact,
       text: "entered node",
       tags: ["bt"],
       metadata: %{phase: "entry"}
     }
   }
 }}

You can also disable it:

{Jido.Memory.BasicPlugin,
 %{
   store: {Jido.Memory.Store.ETS, [table: :my_memory]},
   namespace_mode: :per_agent,
   auto_capture: false
 }}

Use auto-capture deliberately. It is convenient, but it also means the agent is writing memory implicitly in response to signal traffic.

The Built-In Memory Provider

The provider behind BasicPlugin is Jido.Memory.Provider.Basic.

Basic is the reference memory engine in core:

It uses Jido.Memory.Store underneath, and the default practical store is ETS.

Basic can also use a Redis-backed store when you want durable storage without changing the provider identity. Core now ships both :basic and :redis; Redis can either sit underneath :basic as a store implementation or be chosen explicitly as the provider.

Example:

defmodule MyApp.MemoryRedis do
  def command(args), do: Redix.command(:memory_redis, args)
end

{Jido.Memory.BasicPlugin,
 %{
   store: {Jido.Memory.Store.Redis,
    [
      command_fn: &MyApp.MemoryRedis.command/1,
      prefix: "my_app:memory"
    ]},
   namespace_mode: :per_agent
 }}

The same store can be used through explicit runtime opts:

Jido.Memory.Runtime.remember(%{id: "agent-1"}, %{
  class: :semantic,
  kind: :fact,
  text: "Persist this in Redis."
},
  provider: :basic,
  provider_opts: [
    store:
      {Jido.Memory.Store.Redis,
       [command_fn: &MyApp.MemoryRedis.command/1, prefix: "my_app:memory"]}
  ]
)

Postgres can also back Basic when your application already owns an Ecto repo. The Ecto and Postgrex dependencies are optional in jido_memory; add them to your application when you use this store.

provider: :basic,
provider_opts: [
  namespace: "agent:my-agent",
  store: {Jido.Memory.Store.Postgres, repo: MyApp.Repo}
]

Create the table with an application migration:

create table(:jido_memory_records, primary_key: false) do
  add :namespace, :text, null: false, primary_key: true
  add :id, :text, null: false, primary_key: true
  add :class, :text, null: false
  add :kind, :text, null: false
  add :text, :text
  add :source, :text
  add :observed_at, :bigint, null: false
  add :expires_at, :bigint
  add :record, :binary, null: false
end

create index(:jido_memory_records, [:namespace, :observed_at, :id])
create index(:jido_memory_records, [:namespace, :class, :observed_at, :id])
create index(:jido_memory_records, [:namespace, :kind, :observed_at, :id])
create index(:jido_memory_records, [:expires_at], where: "expires_at IS NOT NULL")

This is durable structured storage for canonical records. It is not pgvector, semantic retrieval, or an analytics schema.

If you want Redis to be the explicit provider identity in core, use :redis:

Jido.Memory.Runtime.remember(%{id: "agent-1"}, %{
  class: :semantic,
  kind: :fact,
  text: "Use the built-in redis provider."
},
  provider: :redis,
  provider_opts: [
    namespace: "agent:agent-1",
    command_fn: &MyApp.MemoryRedis.command/1,
    prefix: "my_app:memory"
  ]
)

Basic supports:

What Basic does not try to be:

Its job is to be the clean default path for Jido agents.

Different Memory Providers

Core jido_memory intentionally keeps the provider story narrow.

Built into core:

Implemented in separate packages:

That split is intentional. Different memory systems have different data models, retrieval semantics, and infrastructure requirements. Core should not absorb all of that complexity just to give agents a stable memory interface.

For external providers, core supports atom-based alias registration:

config :jido_memory, :provider_aliases,
  mempalace: Jido.Memory.Provider.MemPalace,
  mem0: Jido.Memory.Provider.Mem0

Then application or provider-level code can select a provider explicitly:

opts = [
  provider: :mempalace,
  provider_opts: [namespace: "agent:agent-1"]
]

The key point is that core agent code should not need to understand provider internals just to work with memory.

The Plugin Story For Other Providers

Core ships exactly one plugin story:

That plugin is for the built-in store-backed path. :redis can be used through runtime provider selection, while agent/plugin integrations can still configure Redis under BasicPlugin.

If another provider wants deeper Jido ergonomics, that provider package should ship its own plugin rather than expanding core into a generic backend adapter.

This keeps responsibilities clean:

Runtime, Providers, and Stores

These layers matter, but mainly as support for the agent story.

Runtime

Jido.Memory.Runtime is the stable dispatch layer used by actions, examples, tests, and provider-aware integration code.

It exists so application code and actions do not need to call provider modules directly.

Providers

Providers implement the memory behavior behind the runtime.

Core contract:

Optional capability behaviors:

Stores

Stores are low-level persistence infrastructure.

They still matter for Basic, but they are no longer the top-level story of the package.

That is an important design point:

Canonical Memory Structs

Core exposes stable structs so agent tooling and provider packages can speak the same language:

Two of these deserve special attention.

CapabilitySet

CapabilitySet is the canonical way for a provider to describe what it supports.

It includes:

ProviderInfo

ProviderInfo is the richer provider metadata surface.

It can describe:

This belongs in core because tools and applications may want to inspect provider metadata regardless of which provider package is installed.

Examples

The repo includes runnable example code focused on Jido agent integration.

See:

The examples prove two paths:

Example tests live in:

They are tagged :examples and are excluded from the default suite.

Run them explicitly with:

mix test --only examples

Guides

For deeper documentation, start here:

Summary

If you are integrating memory into a Jido agent, start with BasicPlugin.

That is the primary user-facing value of this package.

If you need a different memory system later, core already gives you the stable plumbing to swap providers without throwing away your agent-facing memory story.