Compaction, Memory, and Dream

Corresponding source

MechanismSource
Memory files and archivenanobot/agent/memory.py::MemoryStore
Session consolidationnanobot/agent/memory.py::Consolidator
Automatic compaction schedulingnanobot/agent/autocompact.py::AutoCompact
Memory injection into model contextnanobot/agent/context.py::ContextBuilder.build_system_prompt
Dream prompt and Git snapshotsnanobot/templates/agent/dream.md, nanobot/utils/gitstore.py

Learning objectives

After this lesson, you should be able to:

  • distinguish four forms of “compression”;
  • explain how Session content enters history.jsonl;
  • explain how Dream updates long-term files;
  • describe the purpose of each cursor;
  • identify which Dream configuration fields work and which are deprecated.

Prerequisites

1. Separate four mechanisms first

MechanismTriggerTargetResult
Session replay trimmingevery BUILDhistory for this Turnreplay fewer messages
Runner context governanceevery iterationmessages_for_modeltemporarily compress/trim tool results and history
Consolidatortoken pressure or idle compactionan older safe Session segmentappend a summary to history.jsonl
DreamGateway Cron or /dreamnew history + long-term filesupdate SOUL/USER/MEMORY

Calling all four “Memory compression” hides their different boundaries.

2. Files managed by MemoryStore

Default workspace:

workspace/
├── SOUL.md
├── USER.md
└── memory/
    ├── MEMORY.md
    ├── history.jsonl
    ├── .cursor
    ├── .dream_cursor
    └── .git/

Roles:

FileRole
SOUL.mdlong-term Agent tone, identity, and communication style
USER.mdstable user information and preferences
MEMORY.mdproject facts, decisions, and long-term context
history.jsonlstructured historical material produced by Consolidator
.cursorcursor for history writes
.dream_cursorlast position consumed by Dream
.gitchange history for long-term memory files

3. history.jsonl is source material, not final long-term Memory

A record resembles:

{
  "cursor": 42,
  "timestamp": "2026-07-24 10:00",
  "content": "- The user selected a particular approach",
  "session_key": "cli:direct"
}

Verify optional fields in the current source.

append_history():

  • serializes cursor allocation and append with a thread lock;
  • removes leaked thinking content;
  • bounds each record’s content;
  • updates .cursor.

compact_history() and rewrite paths use atomic replacement. Ordinary append and complete rewrite use different persistence strategies, so do not claim that every Memory write uses both fsync and rename.

4. Consolidator

Consolidator.maybe_consolidate_by_tokens():

  1. acquires the Session’s consolidation lock;
  2. refreshes the Session reference;
  3. estimates tokens for the full prompt;
  4. handles replay-message overflow first;
  5. compares usage with the input budget;
  6. chooses a legal consolidation boundary;
  7. asks the LLM to summarize the older segment;
  8. appends the summary to history.jsonl;
  9. advances last_consolidated;
  10. saves _last_summary.
flowchart TD
    S["Session.messages"] --> EST["Estimate prompt tokens"]
    EST -->|Below budget| KEEP["Do not archive"]
    EST -->|At budget| B["Choose safe boundary"]
    B --> LLM["LLM summary"]
    LLM -->|Success| H["Summary in history.jsonl"]
    LLM -->|Failure| RAW["Raw archive breadcrumb"]
    H --> CUR["Advance last_consolidated"]
    RAW --> CUR
    CUR --> META["Save _last_summary"]

If LLM summarization fails:

  • a bounded raw-archive breadcrumb remains;
  • the boundary still advances so the same segment is not archived repeatedly;
  • the current operation stops instead of continuing to hammer a degraded Provider.

5. Token budgets

Consolidator’s input budget reserves:

  • maximum completion tokens;
  • a safety buffer.

consolidation_ratio controls the target proportion after compression, not “how many messages to delete each time.”

Use the current AgentDefaults.consolidation_ratio as authority; it is currently 0.5.

6. AutoCompact

AutoCompact handles idle Sessions:

  • the serialized configuration name is idleCompactAfterMinutes;
  • the current default is 15 minutes;
  • 0 disables it;
  • AgentLoop checks stale Sessions when inbound queue consumption times out;
  • an active Session must not compact concurrently;
  • Consolidator’s lock protects hard trimming;
  • the summary is stored both in memory and Session metadata so the cold path works after restart.

The next prepare_session() injects _last_summary into the system prompt as Archived Context Summary.

7. Dream

Dream is a system Cron job registered by Gateway and can also be triggered with /dream.

flowchart TD
    H["New history.jsonl records"] --> BP["MemoryStore.build_dream_prompt"]
    BP --> DS["Independent Dream Session / Agent run"]
    DS --> TOOLS["Dream-specific file-editing Tools"]
    TOOLS --> FILES["SOUL.md / USER.md / MEMORY.md"]
    FILES --> GIT["GitStore records changes"]
    DS --> CUR["Advance .dream_cursor"]

Dream reads:

  • unconsumed history;
  • current SOUL;
  • current USER;
  • current MEMORY;
  • runtime information such as the Skill creator path.

It does not copy all of history.jsonl into MEMORY.md. It uses Tools to make focused edits to long-term files.

8. Dream configuration boundary

Current fields in AgentDefaults.dream:

FieldCurrent behavior
enabledwhether to register the periodic Dream job
intervalHdefault interval
croncompatible Cron override
modelOverridenot implemented
maxBatchSizedeprecated and unused
maxIterationsdeprecated and unused
annotateLineAgesdeprecated and unused

A field remaining in the schema is not proof that the feature currently works.

9. How Memory enters the next model request

ContextBuilder may add:

  • customized MEMORY.md;
  • recent history after .dream_cursor;
  • the Session’s _last_summary;
  • SOUL/USER as bootstrap files.

This explains why a Memory update does not “overwrite the current Session immediately”:

  • the current conversation still depends mainly on Session history;
  • Consolidator first produces history;
  • Dream periodically organizes long-term files;
  • a later ContextBuilder run reads them again.

Observation task

  1. Locate the memory directory in the learning workspace.
  2. Complete several conversation rounds.
  3. Check whether the Session and history.jsonl changed.
  4. Compare SOUL/USER/MEMORY before and after /dream.
  5. Use /dream-log to inspect GitStore changes.
  6. Do not put real personal information into the experiment.

Source and tests

  • nanobot/agent/memory.py
  • nanobot/agent/autocompact.py
  • nanobot/agent/context.py
  • nanobot/templates/agent/dream.md
  • nanobot/utils/gitstore.py
  • tests/agent/test_memory_store.py
  • tests/agent/test_consolidator.py
  • tests/agent/test_consolidation_ratio.py
  • tests/agent/test_auto_compact.py
  • tests/agent/test_dream.py
  • tests/agent/test_dream_session.py
  • tests/agent/test_git_store.py

Self-check

  1. Why is Session replay trimming not Consolidation?
  2. Why is history.jsonl not final Memory?
  3. Why does Consolidator advance its boundary after a summarization failure?
  4. What do .cursor and .dream_cursor track separately?
  5. Why must modelOverride be marked as not implemented?

Next step

Read Goals and Continuations.