DEV Community

chowyu
chowyu

Posted on

AIClaw Separates Persistent Memory From Session Search

One of the fastest ways to make an agent messy is to treat every kind of memory as the same thing.

User preferences, project conventions, environment facts, old debugging sessions, and last week's conversation snippets do not belong in one undifferentiated bucket. AIClaw's current design avoids that by splitting long-term memory into two explicit mechanisms:

  • persistent memory for durable facts
  • session search for historical conversation recall

That split is worth looking at because it solves real operating problems for self-hosted agents.

The design goal

There are three failure modes that show up quickly in agent systems:

  1. everything is stuffed into the prompt, so context grows until it becomes noisy and expensive
  2. everything is hidden in a database blob, so operators cannot inspect or edit it cleanly
  3. old conversations are either forgotten completely or reloaded too aggressively

AIClaw addresses those problems with two different tools that do two different jobs.

Persistent memory is stored as editable files

The repository documents persistent memory in README.md and implements it in internal/tools/memorytool/memory.go.

Instead of hiding long-term memory in opaque storage, AIClaw keeps it in plain text files:

  • MEMORY.md for durable agent notes, environment facts, and conventions
  • USER.md for user preferences and communication style

This is a practical choice. Operators can inspect, review, and edit those files directly when needed, which is much easier than debugging memory behavior through serialized blobs.

The current session gets a frozen snapshot

AIClaw does not continuously mutate the active system prompt every time memory changes.

At session start, it loads a snapshot of MEMORY.md and USER.md and injects that into the prompt. The loader in internal/agent/prompt_loaders.go calls memorytool.LoadSnapshot(...) and joins the resulting memory blocks into the runtime prompt.

That means memory writes during a session affect future sessions, not the already-running one.

This is a strong constraint, and I think it is the right one. It keeps prompt behavior stable inside a run and avoids a hard-to-debug class of issues where the agent silently changes its own instructions halfway through execution.

Memory growth is bounded

The memory tool also has explicit limits:

  • MEMORY.md is capped at 2200 characters
  • USER.md is capped at 1375 characters

When usage gets high enough, AIClaw switches the injected snapshot into an index mode. Instead of pushing full entry bodies into the prompt, it injects a compact list of entry IDs, tags, and short summaries.

If the agent later needs one of those full entries, it can fetch it explicitly with memory(action=recall, ids=[...]).

That is a smart tradeoff. Long-term memory stays available, but the prompt does not have to pay the full token cost every time.

Memory writes are treated as a security surface

This part is easy to miss, but important.

The memory tool scans content before saving it. The implementation rejects suspicious injection-style patterns and invisible Unicode characters that could be used to smuggle prompt instructions into future sessions.

That makes sense because memory is not just stored data in AIClaw. It is future system-prompt material.

Session search solves a different problem

Persistent memory is for stable facts. Historical conversation lookup is different.

Sometimes the agent does not need a durable note. It needs to answer questions like:

  • What did we discuss in the earlier deployment thread?
  • Which error message showed up last time?
  • What did the user say about this workflow in a prior session?

That is what internal/tools/sessionsearch/session_search.go is for.

The tool has two modes:

  • no query: return recent conversations with previews
  • query provided: search prior messages and return matching snippets

SQLite gets FTS5, other databases still work

The search implementation in internal/store/gormstore/fts.go adds a practical optimization.

When AIClaw runs on SQLite, it initializes an FTS5 virtual table plus triggers to keep the search index synchronized with new and deleted messages. It also backfills existing data on startup.

When FTS5 is unavailable, AIClaw falls back to a SQL LIKE search path.

That is exactly the kind of graceful degradation I want in a local-first system. SQLite gets a fast search path, but the feature does not disappear on other databases.

Why the split matters operationally

This is the real value of the feature:

  • durable facts go into editable memory files
  • old conversation details stay in searchable archives
  • the active prompt remains more compact
  • and both behaviors are explicit enough to inspect

That is better than trying to solve all recall problems with one giant "memory" abstraction.

A practical workflow

If I were operating AIClaw day to day, I would use it like this:

  • Save stable rules, preferences, and environment facts with the memory tool.
  • Use session_search when you need to retrieve something from older conversations.
  • Let the snapshot and index-mode design keep the active prompt lean.

That is a more durable model for long-running agents than simply loading more chat history and hoping the model figures out what still matters.

AIClaw's memory layer is not flashy, but it is disciplined. For self-hosted agent systems, that discipline is usually what keeps advanced features usable after the first demo.

Top comments (0)