Role
Lets the agent “remember facts across sessions” while keeping the conversation context bounded. Harness splits memory into two layers:- Layer 1 · daily log
memory/YYYY-MM-DD.md— append-only each day, raw and not deduped; - Layer 2 · curated long-term
MEMORY.md— periodically merged + deduped by the LLM; loaded once per Agent call and supplied as HARNESS_CONTEXT reference material, not System.
- Conversation compaction — summarizes history and keeps a recent tail when context is too long;
- Overflow safety net — when the model actually errors, force a compaction and retry;
- Large tool-result offloading — offload to disk + placeholder when a single tool returns too much.
The three LLM calls at a glance
The memory pipeline runs three independent LLM calls, each with its own prompt and triggering rules. This is the easiest place to get confused when customizing:
The first two are “long-term memory settling” and live on
MemoryConfig; the third is “in-context compression” and lives on CompactionConfig. All three LLM calls share the agent’s primary model by default, but MemoryConfig and CompactionConfig each support a .model(...) override so you can use a lighter model for these auxiliary operations.
How the two layers work
Key points:- Layer 1 only appends, never dedupes; Layer 2 is periodically rewritten as a whole; the two layers never overwrite each other.
- Layer 2 is the only one injected into the prompt; Layer 1 waits to be merged.
- Native Session Log already records messages and execution facts. Compaction changes working context only;
session_history/session_searchread full history without duplicate offloading.
When flush fires
Flush (path 1) is triggered at three different moments:- End of every
call()— the defaultMemoryFlushMiddlewarebehaviour. Can be retuned toNEVERorTHROTTLED(Duration)viaflushTrigger. - Pre-compaction extraction — when
CompactionConfig.flushBeforeCompact = true(default), the conversation prefix is flushed once before being summarized. - Overflow safety net — when the model actually returns
context_length_exceeded, the framework runs an emergency compaction that includes a flush.
flushPrompt, so customizing it changes all three.
Per-call long-term memory flush runs in the background; pre-compaction flush belongs to the compaction step. Native Session Log commits at execution boundaries: it is not a background copy, and failure to commit required facts stops further execution.
Tune context compaction
Harness enables context compaction by default. The following configuration adjusts its triggers and retention policy. See Context management for how compaction fits into each model request.
Auto-recovery on overflow: when the model returns
context_length_exceeded or a similar error, the framework attempts forced compaction unless compaction is disabled. In the default EVENT_LOG execution mode, it retries the current reasoning step once within the same execution. No explicit .compaction(...) call is needed to enable this; a repeated overflow propagates to the caller.
Want it lighter? Trim arguments first
Tool calls likewrite_file carry huge arguments that nobody reads later. Before LLM summarization you can run a non-LLM string truncation:
Customizing the memory pipeline: MemoryConfig
MemoryConfig is the single place to configure flush / consolidation prompts, throttling, retention, and the per-call flush trigger. Every field has a default; not calling .memory(...) reproduces the historical behaviour bit-for-bit.
Per-call flush and background consolidation have independent throttle windows. In both cases, the first eligible call() runs the work immediately; a minimum gap limits only subsequent runs and is not an initial delay.
Example 1: throttle per-call flush to save tokens
A flush LLM call after every agent invocation can add up on long sessions. Throttle it to at most once every 10 minutes:THROTTLEDonly affects path 1 (per-call flush). The flush embedded in compaction (path 2) and the overflow flush (path 3) still fire on their own triggers — compaction is rare, so those two are infrequent by construction.- The first eligible call flushes immediately;
Duration.ofMinutes(10)limits only later per-call flushes. - Native history is unaffected: flush throttling does not disable Session Log, history search or checkpoint recovery.
Example 2: disable per-call flush entirely
To turn off flush and background maintenance use.disableMemoryHooks();flushTrigger(NEVER)only stops the per-call flush — background consolidation still runs.
Example 3: extend the default prompt with project rules
Example 4: fully custom consolidation prompt
Important: a custom consolidation prompt must contain exactly two%dplaceholders (max-tokens then max-chars). The Builder rejects anything else at construction time so you don’t hit a runtimeMissingFormatArgumentException.
Example 5: tune background maintenance
Example 6: use a smaller model for memory operations
Flush and consolidation don’t need the full power of the primary reasoning model — use a cheaper one to save cost:model(String) resolves via ModelRegistry.resolve(); you can also pass a Model instance. When not set, falls back to the agent’s primary model.
MemoryConfig field reference
Large tool-result offloading
Tool-result offloading works independently of conversation summarization and is enabled by default in Harness. While preparing model input, an oversized tool result in history is written to the workspace. The model receives a head/tail preview and a file path, which it can use withread_file to retrieve the full text as needed. The following shows the default configuration; see Context management for tuning and details:
- Triggered at 80K characters
- Keeps ~2K chars at head + tail + a line “full content at
{path}” read_fileis excluded by default (to avoid re-offloading what was just read back)
ToolResultEvictionConfig.builder()...build().
Tools the agent can use itself
When memory is enabled, the agent gets four tools:memory_search query="..."— keyword scan overMEMORY.md+memory/*.mdmemory_get path="memory/2026-06-02.md" startLine=10 endLine=40— read a specific line rangememory_save content="..."— persist a memory viaMEMORY.mdand the daily ledgersession_search query="..."— search past session transcripts
memory_search to look further back.
memory_search and session_search accept an optional matchMode:
For example,
query="deploy blue" matchMode="all" matches deploy using the blue configuration, while the default phrase mode does not.
A record is one line for Memory and one entry for Session; keywords are not combined across records.
Matching is case-insensitive and regex metacharacters are literal. There is no automatic Chinese word segmentation or date-expression parsing.
An omitted or null mode defaults to phrase; other values (including an empty string) return an error. Multi-keyword modes ignore extra whitespace and duplicate terms; whitespace-only queries never match all records.
Result formatting, ordering and limits are unchanged; any does not introduce relevance ranking. Existing Java method signatures remain available.
Background maintenance
When memory is enabled, a throttled background job also runs. The first eligiblecall() runs it immediately; later calls observe the minimum gap (30 minutes by default):
- Archives daily logs older than
dailyFileRetentionDays(default 90 days) tomemory/archive/ - Runs one
MEMORY.mdconsolidation pass
FlushTrigger.never() does not disable this maintenance path.
All thresholds are tunable via .memory(MemoryConfig.builder()...), though most projects don’t need to touch them.
Turn it off entirely
If you want to handle memory yourself or wire your own tools:HARNESS_CONTEXT (MEMORY.md) injection while keeping Domain Knowledge / AGENTS / knowledge context.
disableMemoryHooks() is the nuclear option for background memory work; if you only want to throttle, use .memory(MemoryConfig.builder().flushTrigger(...).build()) instead.
Related Pages
- Workspace — where
MEMORY.md/memory/live in the workspace - Session logs and recovery — full history, checkpoints and search
- Architecture — how facts in long conversations settle into
MEMORY.md
sessionRetentionDays have been removed. Native history has no automatic retention policy, and memory maintenance no longer deletes old archives.