Skip to content

Episodic Memory ​

IntaGrin's memory system has three tiers already: working memory (checkpointed conversation history, memory.max_messages sliding-window eviction), long-term memory (a single evolving prose summary, state["long_term_memory"], regenerated by an LLM call whenever eviction happens — see 04_Shared_State_Redux.md), and semantic/knowledge memory (rag: — document/knowledge-base retrieval, unrelated to agent experience). episodic_memory: adds the fourth: discrete, structured, individually queryable event records — "user prefers window seats," "booking BK-4471 failed: card declined" — as opposed to the single blended long_term_memory prose summary, which can't be filtered or reliably retrieved for one specific fact.

(Procedural memory — learned skills/code — isn't included; it doesn't map onto this framework's declarative YAML/tool model.)

Episodic memory here is explicit and tool-driven, not automatic per-turn logging: an agent calls remember_episode when something notable happens, and later calls recall_episodes to look it back up.

yaml
memory:
  type: "sqlite"   # or "postgres" — episodic_memory is a no-op for redis/sliding_window/buffer/custom

episodic_memory:
  embedding_model: "text-embedding-3-small"  # defaults to match rag.embedding_model's default
  scope: "session"                            # "session" (default) | "tenant" | "global"
  default_limit: 5

Setting episodic_memory: auto-registers two tools on every agent — the same "presence triggers registration" pattern rag: uses for search_knowledge_base:

  • remember_episode(event_type, content, tags=None, importance=None) — records one event. event_type is a short label you choose (e.g. "preference", "failure", "booking") used later to filter recall_episodes. tags are optional free-form labels for finer-grained filtering. importance is an optional 1-10 self-rating (clamped server-side) of how significant the episode is — 1 for a mundane detail, 10 for something that should heavily influence future behavior; omitted, it's treated as moderately important (5/10) rather than penalized.
  • recall_episodes(query=None, event_type=None, tags=None, limit=None) — recalls previously recorded episodes. Omit query for a cheap structured lookup by event_type/tags/recency alone (no embedding call); pass query for a ranked semantic search over episode content. tags, if given, requires every tag to be present (AND, not OR).

Ranking: relevance, recency, and importance together ​

A query-based recall_episodes doesn't rank by raw semantic similarity alone — that would let a technically-closer match from months ago permanently outrank something more recent and just as relevant, since embedding similarity has no sense of time or significance on its own. Instead (the same three-factor design Park et al.'s "Generative Agents" memory stream uses), each candidate episode is scored as the average of three signals, each normalized to [0, 1]:

  • relevance — cosine similarity to query.
  • recency — exponential decay from created_at (~0.995 per day; a month-old episode still keeps ~86% of its recency score, a year-old one ~16% — gentle by design, since these episodes are meant to matter for a production assistant's whole retention window, not one simulated day).
  • importance — the episode's own remember_episode rating (unrated episodes score a neutral 0.5).

This only changes ranking order among candidates that already passed the event_type/tags filters — it never surfaces an episode that wouldn't otherwise match your query.

Scope ​

episodic_memory.scope is independent of memory.shared_scope — a project might want the long_term_memory summary private per-session while episodic events are shared globally, or vice versa.

  • "session" (default): only this session_id's own episodes.
  • "tenant": shared across every session under the same authenticated caller/tenant prefix (same convention as memory.shared_scope: tenant).
  • "global": shared across every session in the project, any tenant.

Storage ​

Episodes are stored in a sqlite/postgres episodes table — the same self-managing-schema convention shared_memory/run_logs already use, living in the same database as checkpoints. Unlike shared_memory's single row per scope (last-write-wins), the episodes table is append-only: remember_episode always inserts a new row, so nothing is ever silently overwritten.

Retention ​

By default, episodes accumulate indefinitely — the same unbounded-append-only growth as any other application table with no cleanup. Set episodic_memory.retention_days to bound it:

yaml
episodic_memory:
  retention_days: 90   # rows older than this are eventually deleted; None (default) keeps everything

Pruning is opportunistic rather than a scheduled job: a small random fraction of remember_episode calls also run a single indexed DELETE ... WHERE created_at < cutoff, table-wide (not scoped to one scope_key — retention is a single project-wide policy). This keeps the amortized cost on the hot write path negligible without needing external cron infrastructure, at the cost of pruning happening "eventually" rather than exactly at the retention boundary — fine for a housekeeping policy, not a guarantee. memory.run_log_retention_days (see 04_Production_Deployment.md) works identically for the separate run_logs audit table.

Released under the Apache 2.0 License.