Skip to content

Memory

pkg/agent/memory/ provides the conversation- and knowledge-state backends an agent can use. Every backend implements the same Memory interface, so an agent's memory: block can be swapped from a short-lived in-process buffer to a durable Redis-backed store without touching the agent code. Eight first-party backends are available; a Middleware family adds cross-cutting concerns (logging, encryption, …) composably, and a SessionMemoryManager lets one shared agent service many concurrent sessions with isolated memory.

What it does

type Memory interface {
    Add(ctx context.Context, message *llm.Message) error
    Get(ctx context.Context) ([]*llm.Message, error)
    Search(ctx context.Context, query string, limit int) ([]*llm.Message, error)
    Clear(ctx context.Context) error
}

Every ReAct turn does:

  1. mem.Get(ctx) — seed the LLM context with history.
  2. After the user message and after the assistant turn, mem.Add(ctx, msg) — persist the new turn.
  3. Search is not used by the agent loop directly; it is exposed for tools / Studio / RAG-style lookups.

The choice of backend governs what Get returns: full history, the last N messages, a summary + recent window, or a vector-search shaped lookup. The agent itself is backend-agnostic.

Agent.SetMemory swaps the backend atomically via atomic.Pointer — concurrent Runs never see a torn state. SessionMemoryManager uses this to give every session its own backend while sharing one agent instance.

Backends

ConversationMemory

In-process append-only message log — the default when no memory: block is set. Search uses a case-insensitive substring match. Thread-safe via sync.RWMutex. Use only when conversation length stays small or when you intentionally want full retention.

YAML: type: conversation

SlidingWindowMemory

Keeps only the last windowSize messages. The first system message is always preserved and does not count toward the window. On Add, trim() drops the oldest non-system messages until size <= window.

YAML: type: sliding-window + window_size: N

SummaryMemory

When the message count exceeds maxMessages, summarises the oldest half using a SummaryFunc and stores the summary as a running text. Get returns the running summary as a synthetic system message followed by the recent messages. Use NewSummaryMemoryWithModel(model, maxMessages) for a turnkey LLM-backed summariser. This pattern keeps context bounded without losing earlier facts.

YAML: type: summary + max_messages: N

TokenWindowMemory

Like SlidingWindowMemory but the window is in TOKENS, not messages. Uses llm.EstimateTokens (a punctuation- and word-aware estimator, replacing the old len/4 heuristic that systematically undercounted JSON/code by ~25%). Estimated token count is capped at 1,000,000 for safety. System message is always preserved.

YAML: type: token-window + max_tokens: N

SmartMemory

A token-budgeted memory tuned for tool-heavy agents (coding, RAG, analysis). It:

  • Tracks tokens, not turns (large tool results count properly).
  • Compresses old tool results to summaries when evicted.
  • Keeps tool-call / tool-response pairs together (never orphans).
  • Maintains a "file context map" so it remembers path → summary for files it has read or written, even after eviction.

NewSmartMemory(maxTokens) — pass 0 to get a 100K-token default.

YAML: type: smart (also accepts type: code for back-compat) + max_tokens: N

LongTermMemory

Persistent memory with a pluggable Store interface — backed by InMemoryStore (for tests), or SQLDBLongTermMemory / RedisLongTermStore / MemxLongTermMemory for production. Messages are scoped by an agent ID (namespace); Search is substring-match in the in-memory store, ILIKE in SQL, and Redis-keyspace-scan in Redis.

YAML: type: long-term + store: memory | redis | postgres

SQLDBLongTermMemory

PostgreSQL-backed memory using the generic SQLDB interface (so any database/sql driver works, including pgx/v5 via its stdlib adapter). Table name defaults to agent_memory; custom names validated against alphanumeric+underscore-only and the SQL keyword list. EnsureTable(ctx) creates the schema if absent.

YAML: type: sql-long-term + dsn: postgres://... + (session_id: or session_id_header: X-Session-ID)

RedisConversationMemory + RedisSlidingWindowMemory + RedisTokenWindowMemory + RedisLongTermStore

Redis-backed implementations of the conversation / sliding-window / token-window / long-term shapes. Keys are namespaced helix:memory:<namespace>:<sessionID> so a multi-tenant deployment can share one Redis cluster. Optional TTL and max-length (LTRIM) caps.

YAML: type: conversation | sliding-window | token-window | long-term with store: redis + redis_url: redis://... + namespace: + (session_id: or session_id_header:).

VectorMemory

Semantic-search memory. Add embeds each message and Upserts into a VectorStore; Search embeds the query and returns the top-K nearest. Get returns an empty slice — vector memory is search-only, NOT a sequential history. Use it as ONE backend inside a CompositeMemory next to a sequential backend.

NewVectorMemory takes an EmbedFunc + VectorStore + agentID + topK. NewVectorMemoryWithEmbedder is the same with an Embedder interface. The package ships InMemoryVectorStore (brute-force cosine for tests, capped at 100K entries) and MemxVectorStore (memx-backed, EMBEDDING column + MATCH_DENSE).

YAML: type: vector + top_k: N + embed: { provider, model, ... } + a store: directive.

CompositeMemory

Combines multiple backends. Add writes to ALL; Get and Search merge their results with dedup on (role, content). Use this to pair a sliding-window for recent turns with a vector store for historical recall, or a Redis-backed short-term with a SQL long-term archive.

YAML: type: composite + backends: [ ... ]

FileBackedMemory

Wraps any Memory with JSON-file persistence. Loads on construction if the file exists, saves on every Add and Clear. Designed for CLI agents that need memory across restarts without Redis/Postgres.

YAML: type: <any> with store: file + file_path: .helix/memory/agent.json

Middleware

Middleware is func(Memory) Memory — composable wrappers, identical to tool.Middleware and llm.ModelMiddleware. ChainMiddleware(a, b, c) produces outermost-first composition.

Built-in:

  • WithLogging(logf) — logs every Add / Get / Search / Clear call (defaults to slog at debug level when logf is nil).

YAML names it: memory_middleware: [logging]. Custom middlewares register their factory with the reifier.

SessionMemoryManager

SessionMemoryManager keeps a sync.Map keyed by session ID, each holding its own Memory instance, created on first use via a MemoryFactory. The agent uses Agent.SetMemory(...) per request to swap in the session's memory. This is the standard pattern for a single agent serving many users without leaking conversation state.

Configuration

YAML reference (MemoryDefinition in pkg/workflow/definition.go):

Field Type Used by Description
type string all conversation / sliding-window / smart (alias code) / summary / token-window / long-term / sql-long-term / vector / composite.
store string redis/file backends Storage backend: memory, redis, postgres, file.
namespace string redis Key prefix in Redis.
window_size int sliding-window Message count cap.
max_tokens int token-window, smart Token budget.
max_messages int summary Threshold for triggering summarisation.
top_k int vector Default search depth.
dsn string sql-long-term Postgres connection string.
redis_url string redis Redis connection URL.
file_path string file-backed Path on disk.
session_id string sql/redis Static session scope.
session_id_header string sql/redis Header name for per-request session ID.
ttl string redis Key expiration (e.g. 24h, 7d).
embed block vector { provider, model, api_key, base_url }.
backends list composite Nested MemoryDefinitions.

Examples

A sliding window of 30 messages with logging:

agents:
  - id: chat
    type: react
    memory:
      type: sliding-window
      window_size: 30
    memory_middleware: [logging]

A composite of recent-window + vector recall:

memory:
  type: composite
  backends:
    - { type: sliding-window, window_size: 20 }
    - { type: vector, top_k: 5,
        embed: { provider: openai, model: text-embedding-3-small },
        store: memory }

A multi-tenant Redis-backed conversation memory:

memory:
  type: conversation
  store: redis
  namespace: prod-chat
  session_id_header: X-Session-ID
  redis_url: redis://redis:6379
  ttl: 168h        # 7 days

Source

  • pkg/agent/memory/memory.go — Memory interface, ConversationMemory, SlidingWindowMemory, SummaryMemory, TokenWindowMemory, LongTermMemory + InMemoryStore, SQLDBLongTermMemory, VectorMemory, CompositeMemory, InMemoryVectorStore.
  • pkg/agent/memory/smart_memory.go — SmartMemory.
  • pkg/agent/memory/redis_store.go — Redis variants.
  • pkg/agent/memory/file_store.go — FileBackedMemory.
  • pkg/agent/memory/memx.go — MemxVectorStore.
  • pkg/agent/memory/middleware.go — Middleware framework + WithLogging.
  • pkg/agent/memory/session.go — SessionMemoryManager.
  • pkg/dsl/reifier.go — reifyMemory (the YAML → instance bridge).

See also

  • tools — the other agent state subsystem.
  • react — how the loop uses memory each turn.
  • skills — progressive-disclosure for skill content (a related-but-distinct knowledge surface).