Files
openclaw_openclaw/docs/concepts/active-memory.md
2026-04-08 20:49:56 -05:00

14 KiB

title, summary, read_when
title summary read_when
Active Memory A plugin-owned blocking memory subagent that injects relevant memory into interactive chat sessions
You want to understand what active memory is for
You want to turn active memory on for a conversational agent
You want to tune active memory behavior without enabling it everywhere

Active Memory

Active memory is an optional plugin-owned blocking memory subagent that runs before the main reply for eligible conversational sessions.

It exists because most memory systems are capable but reactive. They rely on the main agent to decide when to search memory, or on the user to say things like "remember this" or "search memory." By then, the moment where memory would have made the reply feel natural has already passed.

Active memory gives the system one bounded chance to surface relevant memory before the main reply is generated.

Paste This Into Your Agent

Paste this into your agent if you want it to enable Active Memory with a self-contained, safe-default setup:

{
  plugins: {
    entries: {
      "active-memory": {
        enabled: true,
        config: {
          agents: ["main"],
          allowedChatTypes: ["direct"],
          modelFallbackPolicy: "default-remote",
          queryMode: "recent",
          timeoutMs: 15000,
          maxSummaryChars: 220,
          persistTranscripts: false,
          logging: true,
        },
      },
    },
  },
}

This turns the plugin on for the main agent, keeps it limited to direct-message style sessions by default, lets it inherit the current session model first, and still allows the built-in remote fallback if no explicit or inherited model is available.

After that, restart the gateway:

node scripts/run-node.mjs gateway --profile dev

To inspect it live in a conversation:

/verbose on

Turn active memory on

The safest setup is:

  1. enable the plugin
  2. target one conversational agent
  3. keep logging on only while tuning

Start with this in openclaw.json:

{
  plugins: {
    entries: {
      "active-memory": {
        enabled: true,
        config: {
          agents: ["main"],
          allowedChatTypes: ["direct"],
          modelFallbackPolicy: "default-remote",
          queryMode: "recent",
          timeoutMs: 15000,
          maxSummaryChars: 220,
          persistTranscripts: false,
          logging: true,
        },
      },
    },
  },
}

Then restart the gateway:

node scripts/run-node.mjs gateway --profile dev

What this means:

  • plugins.entries.active-memory.enabled: true turns the plugin on
  • config.agents: ["main"] opts only the main agent into active memory
  • config.allowedChatTypes: ["direct"] keeps active memory on for direct-message style sessions only by default
  • if config.model is unset, active memory inherits the current session model first
  • config.modelFallbackPolicy: "default-remote" keeps the built-in remote fallback as the default when no explicit or inherited model is available
  • active memory still runs only on eligible interactive persistent chat sessions

How to see it

Active memory injects hidden system context for the model. It does not expose raw <active_memory_plugin>...</active_memory_plugin> tags to the client.

If you want to see what active memory is doing in a live session, turn verbose mode on for that session:

/verbose on

With verbose enabled, OpenClaw can show:

  • an active memory status line such as Active Memory: ok 842ms recent 34 chars
  • a readable debug summary such as Active Memory Debug: Lemon pepper wings with blue cheese.

Those lines are derived from the same active memory pass that feeds the hidden system context, but they are formatted for humans instead of exposing raw prompt markup.

By default, the blocking memory subagent transcript is temporary and deleted after the run completes.

Example flow:

/verbose on
what wings should i order?

Expected visible reply shape:

...normal assistant reply...

🧩 Active Memory: ok 842ms recent 34 chars
🔎 Active Memory Debug: Lemon pepper wings with blue cheese.

When it runs

Active memory uses two gates:

  1. Config opt-in The plugin must be enabled, and the current agent id must appear in plugins.entries.active-memory.config.agents.
  2. Strict runtime eligibility Even when enabled and targeted, active memory only runs for eligible interactive persistent chat sessions.

The actual rule is:

plugin enabled
+
agent id targeted
+
allowed chat type
+
eligible interactive persistent chat session
=
active memory runs

If any of those fail, active memory does not run.

Session types

config.allowedChatTypes controls which kinds of conversations may run Active Memory at all.

The default is:

allowedChatTypes: ["direct"]

That means Active Memory runs by default in direct-message style sessions, but not in group or channel sessions unless you opt them in explicitly.

Examples:

allowedChatTypes: ["direct"]
allowedChatTypes: ["direct", "group"]
allowedChatTypes: ["direct", "group", "channel"]

Where it runs

Active memory is a conversational enrichment feature, not a platform-wide inference feature.

Surface Runs active memory?
Control UI / web chat persistent sessions Yes, if the plugin is enabled and the agent is targeted
Other interactive channel sessions on the same persistent chat path Yes, if the plugin is enabled and the agent is targeted
Headless one-shot runs No
Heartbeat/background runs No
Generic internal agent-command paths No
Subagent/internal helper execution No

Why use it

Use active memory when:

  • the session is persistent and user-facing
  • the agent has meaningful long-term memory to search
  • continuity and personalization matter more than raw prompt determinism

It works especially well for:

  • stable preferences
  • recurring habits
  • long-term user context that should surface naturally

It is a poor fit for:

  • automation
  • internal workers
  • one-shot API tasks
  • places where hidden personalization would be surprising

How it works

The runtime shape is:

flowchart LR
  U["User Message"] --> Q["Build Memory Query"]
  Q --> R["Active Memory Blocking Memory Subagent"]
  R -->|NONE or empty| M["Main Reply"]
  R -->|relevant summary| I["Append Hidden active_memory_plugin System Context"]
  I --> M["Main Reply"]

The blocking memory subagent can use only:

  • memory_search
  • memory_get

If the connection is weak, it should return NONE.

Query modes

config.queryMode controls how much conversation the blocking memory subagent sees.

Model fallback policy

If config.model is unset, Active Memory tries to resolve a model in this order:

explicit plugin model
-> current session model
-> agent primary model
-> optional built-in remote fallback

config.modelFallbackPolicy controls the last step.

Default:

modelFallbackPolicy: "default-remote"

Other option:

modelFallbackPolicy: "resolved-only"

Use resolved-only if you want Active Memory to skip recall instead of falling back to the built-in remote default when no explicit or inherited model is available.

message

Only the latest user message is sent.

Latest user message only

Use this when:

  • you want the fastest behavior
  • you want the strongest bias toward stable preference recall
  • follow-up turns do not need conversational context

Recommended timeout:

  • start around 3000 to 5000 ms

recent

The latest user message plus a small recent conversational tail is sent.

Recent conversation tail:
user: ...
assistant: ...
user: ...

Latest user message:
...

Use this when:

  • you want a better balance of speed and conversational grounding
  • follow-up questions often depend on the last few turns

Recommended timeout:

  • start around 15000 ms

full

The full conversation is sent to the blocking memory subagent.

Full conversation context:
user: ...
assistant: ...
user: ...
...

Use this when:

  • the strongest recall quality matters more than latency
  • the conversation contains important setup far back in the thread

Recommended timeout:

  • increase it substantially compared with message or recent
  • start around 15000 ms or higher depending on thread size

In general, timeout should increase with context size:

message < recent < full

Transcript persistence

Active memory blocking memory subagent runs create a real session.jsonl transcript during the blocking memory subagent call.

By default, that transcript is temporary:

  • it is written to a temp directory
  • it is used only for the blocking memory subagent run
  • it is deleted immediately after the run finishes

If you want to keep those blocking memory subagent transcripts on disk for debugging or inspection, turn persistence on explicitly:

{
  plugins: {
    entries: {
      "active-memory": {
        enabled: true,
        config: {
          agents: ["main"],
          persistTranscripts: true,
          transcriptDir: "active-memory",
        },
      },
    },
  },
}

When enabled, active memory stores transcripts in a separate directory under the target agent's sessions folder, not in the main user conversation transcript path.

The default layout is conceptually:

agents/<agent>/sessions/active-memory/<blocking-memory-subagent-session-id>.jsonl

You can change the relative subdirectory with config.transcriptDir.

Use this carefully:

  • blocking memory subagent transcripts can accumulate quickly on busy sessions
  • full query mode can duplicate a lot of conversation context
  • these transcripts contain hidden prompt context and recalled memories

Configuration

All active memory configuration lives under:

plugins.entries.active-memory

The most important fields are:

Key Type Meaning
enabled boolean Enables the plugin itself
config.agents string[] Agent ids that may use active memory
config.model string Optional blocking memory subagent model ref; when unset, active memory uses the current session model
config.queryMode "message" | "recent" | "full" Controls how much conversation the blocking memory subagent sees
config.timeoutMs number Hard timeout for the blocking memory subagent
config.maxSummaryChars number Maximum total characters allowed in the active-memory summary
config.logging boolean Emits active memory logs while tuning
config.persistTranscripts boolean Keeps blocking memory subagent transcripts on disk instead of deleting temp files
config.transcriptDir string Relative blocking memory subagent transcript directory under the agent sessions folder

Useful tuning fields:

Key Type Meaning
config.maxSummaryChars number Maximum total characters allowed in the active-memory summary
config.recentUserTurns number Prior user turns to include when queryMode is recent
config.recentAssistantTurns number Prior assistant turns to include when queryMode is recent
config.recentUserChars number Max chars per recent user turn
config.recentAssistantChars number Max chars per recent assistant turn
config.cacheTtlMs number Cache reuse for repeated identical queries

Start with recent.

{
  plugins: {
    entries: {
      "active-memory": {
        enabled: true,
        config: {
          agents: ["main"],
          queryMode: "recent",
          timeoutMs: 15000,
          maxSummaryChars: 220,
          logging: true,
        },
      },
    },
  },
}

If you want to inspect live behavior while tuning, use /verbose on in the session instead of looking for a separate active-memory debug command.

Then move to:

  • message if you want lower latency
  • full if you decide extra context is worth the slower blocking memory subagent

Debugging

If active memory is not showing up where you expect:

  1. Confirm the plugin is enabled under plugins.entries.active-memory.enabled.
  2. Confirm the current agent id is listed in config.agents.
  3. Confirm you are testing through an interactive persistent chat session.
  4. Turn on config.logging: true and watch the gateway logs.
  5. Verify memory search itself works with openclaw memory status --deep.

If memory hits are noisy, tighten:

  • maxSummaryChars

If active memory is too slow:

  • lower queryMode
  • lower timeoutMs
  • reduce recent turn counts
  • reduce per-turn char caps