Skip to main content

Purpose

Enable the agent to “remember facts across sessions” while preventing conversation context from growing unboundedly. Harness splits memory into two layers: high-frequency low-curation “daily logs” + low-frequency high-curation “long-term memory”, supplemented by FTS5 full-text search and background maintenance.

Trigger Points

Key Logic

Two-Layer Memory Model

  • Layer 1 — Daily log memory/YYYY-MM-DD.md: owned by MemoryFlushManager, append-only, no deduplication; a raw record of “what was just being discussed”.
  • Layer 2 — Curated long-term memory MEMORY.md: owned by MemoryConsolidator, complete rewrite; MemoryFlushManager never touches it. Injected into system prompt by WorkspaceContextHook on every reasoning turn.
  • Index MemoryIndex: fully indexed at startup with indexAllFromWorkspace; incrementally rebuilt for today’s file after each flush; SQLite file at <workspace_parent>/memory_index.db.

Conversation Compaction (ConversationCompactor)

Default values (all configurable):

TruncateArgsConfig — Lightweight Pre-processing (Optional)

Before LLM summarization, a no-LLM pre-pass can truncate ToolUseBlock arguments in older messages (default threshold: 25 messages / 40k tokens, arguments exceeding 2000 characters are trimmed). Useful for scenarios like write_file where large argument bodies are not needed later.

Automatic Context Overflow Recovery

When the model returns a context_length_exceeded / maximum context style error, HarnessAgent.recoverFromOverflowforceCompactAndRetry builds a temporary CompactionConfig with triggerMessages=1, runs one compaction round, clears Memory, and retries. Prerequisite: compaction(...) must be configured; otherwise the error is rethrown directly.

Memory Extraction (MemoryFlushManager)

  • flushMemories(messages): hands the current MEMORY.md and today’s log to the LLM as “deduplication reference”, requesting only newly added bullets. “NO_REPLY” means nothing to write.
  • Write location is always memory/YYYY-MM-DD.md, never MEMORY.md (to prevent layer 1 overwriting layer 2).
  • After writing, immediately calls indexFromString to rebuild the file index, then calls MemoryMaintenanceScheduler.requestConsolidation() to signal “consolidate when you can”.

Secondary Consolidation (MemoryConsolidator)

  • Reads daily logs with mtime exceeding the watermark + current MEMORY.md, calls LLM to merge, deduplicate, and trim.
  • Output limit: default maxMemoryTokens=4000 (~16k characters); the prompt communicates this as a character budget to the LLM.
  • After writing, advances the watermark stored in memory/.consolidation_state; next run only looks at files with mtime past the watermark.
  • Consolidation only runs on the background executor: triggered by a periodic tick or requestConsolidation(), never blocking the reasoning loop.

Background Maintenance (MemoryMaintenanceScheduler)

Auto-created and start()ed inside HarnessAgent.build(); each tick runs in sequence:
  1. expireDailyFiles — archive daily files older than dailyFileRetentionDays to memory/archive/ (default 90 days)
  2. consolidateMemory — call MemoryConsolidator.consolidate()
  3. pruneOldSessions — delete session files with mtime older than sessionRetentionDays (default 180 days)
  4. reindexMemoryIndex.indexAllFromWorkspace
Default interval: Duration.ofHours(6); opportunistic calls are throttled to 30-minute intervals to avoid hammering the LLM with frequent flushes.

Tool Result Eviction (ToolResultEvictionConfig)

Independent from compaction. When a tool_call return text exceeds the threshold, the full content is written to a file under evictionPath, and the original position is replaced with a “head+tail preview + path” placeholder. The agent calls read_file when it needs the full content.

Configuration and Code Examples

  • Toolmemory_search / memory_get parameters and call examples
  • WorkspaceMEMORY.md / memory/*.md location in the workspace
  • Session — how .log.jsonl / .jsonl feeds back into memory extraction
  • ArchitectureCompactionHook / MemoryFlushHook / ToolResultEvictionHook position in the lifecycle