Agents accumulate episodes (every task → a LanceDB row), but raw episodes are noisy. Reflection periodically distils an agent's recent episodes into compact, durable memory notes ("PDFs over 10MB reliably exhaust the ingester") that sharpen future retrieval — the system improving its own memory over time.
Designed to be hot-path-safe: all the expensive work runs off the task path, and the prompt-build read is O(1).
- Durable — a separate small
memory_notesLanceDB table (id, agent_id, role_label, ts, summary, source_count, confidence, embedding). Separate fromepisodesso vector search over notes stays fast (few curated rows), and reads never scan the large episodes table. - Hot read — a Redis per-role hot-cache
(
acc:{cid}:memory_notes:{role}, TTL'd, top-N summaries). Read in O(1) on the prompt-build path; miss → skip (no LanceDB hit there).
Agent._reflection_loop (a heartbeat-style coroutine) runs every
ACC_REFLECTION_INTERVAL_S seconds — default 0 = off (it makes extra
LLM calls), mirroring the Cat-B reflection_interval_s setpoint. Each
pass (_run_reflection_once), gated on the role's memory_reflection
flag + a live CognitiveCore:
- reads the agent's recent-episode ring (
CognitiveCore.recent_episodes()— fed by_persist_episode, no vector scan); acc.memory_reflection.consolidate(...)clusters related episodes (greedy cosine; MEMORY_NOTE episodes excluded — no notes-of-notes) and LLM-summarises each cluster into aMemoryNote;persist_notes(...)writes them to thememory_notestable;write_hot_cache(...)pushes the top-N to Redis.
It is best-effort — a summary/embed/IO failure is logged and skipped, never raised into the loop, and it never blocks the task loop.
In CognitiveCore.process_task, after the episode RAG and gated by the
same memory_retrieval flag, _read_memory_notes() does an O(1) Redis
read and the notes are prepended to the LLM user message:
MEMORY_NOTES (durable lessons …) ← high-level, from reflection
RECENT_RELEVANT_EPISODES (…) ← recent specifics, from RAG
<the task>
Both blocks live in the user message, so the role system prompt stays a cacheable prefix (PR-CA1). A Redis miss or no-Redis is silent.
- Set
memory_reflection: trueon the role(s) you want to self-reflect (roles/<role>/role.yaml); default is off. - Set
ACC_REFLECTION_INTERVAL_S(e.g.3600) on those agents — viaAgentSpec.extra_envincollective.yaml, or the Cat-Breflection_interval_ssetpoint mapped into the env at deploy. - Redis must be configured (the notes hot-cache + episode store rely on
it); LanceDB holds the durable
memory_notestable.
pytest tests/test_memory_reflection.py \
tests/test_reflection_loop.py \
tests/test_memory_notes_hotpath.py -v