Pre-Compaction Memory Flush

Save durable user facts to long-term memory before context compaction discards them

Save durable user facts to long-term memory before context compaction discards the older messages that contain them.

Diagram

Pre-Compaction Memory Flush

💬 Turn near limit

🧠 Flush child

🔍 search_memory

💾 store_memory

✂️ Older slice compacted

✅ New turn recalls facts

Context compaction is lossy — messages that fall outside the compaction window are gone. This feature runs one bounded child agent with search_memory and store_memory before compaction, so stable user-provided facts in the older slice are saved to long-term memory first. It never blocks compaction: if the child times out or fails, compaction still runs.

Supported Backends

Every built-in Memory backend now implements commit_memory_batch / acommit_memory_batch, so the flush persists facts before compaction on all of them.

BackendDurable pre-compaction flush
file✅
sqlite✅
chroma✅
mongodb✅
mem0✅
learn✅
Diagram

🧠 Pre-compaction flush

✅ file

✅ sqlite

✅ chroma

✅ mongodb

✅ mem0

✅ learn

Quick Start

  1. Enable with True

    One bool on ExecutionConfig turns the flush on. It stays off by default with zero overhead — the child agent is never constructed.

    python
    from praisonaiagents import Agent, ExecutionConfig
    
    agent = Agent(
        name="Support Agent",
        instructions="Help the user with their account.",
        memory=True,
        execution=ExecutionConfig(
            context_compaction=True,
            pre_compaction_memory_flush=True,   # the whole feature
        ),
    )
    
    agent.start("Remember my customer id is C-99. My preferred language is French.")
  2. Tune with the config class

    Swap the bool for PreCompactionMemoryFlushConfig to set the deadline, token cap, minimum turns, or a cheaper model for the child.

    python
    from praisonaiagents import Agent, ExecutionConfig, PreCompactionMemoryFlushConfig
    
    agent = Agent(
        name="Support Agent",
        instructions="Help the user with their account.",
        memory=True,
        execution=ExecutionConfig(
            context_compaction=True,
            pre_compaction_memory_flush=PreCompactionMemoryFlushConfig(
                timeout_seconds=15,
                max_flush_tokens=6000,
                llm="gpt-4o-mini",
            ),
        ),
    )
    
    agent.start("Remember my customer id is C-99. My preferred language is French.")
    # Long conversation → compaction fires → the flush saves "customer id = C-99"
    # and "preferred language = French" to long-term memory before those turns are removed.

How It Works

The flush fires at the older-slice boundary — exactly the messages that are about to disappear — searches memory to avoid duplicates, stages writes, and commits them only if the child finishes in time.

Diagram
MemoryFlush ChildCompactorAgentMemoryFlush ChildCompactorAgentpreview older slicemessages about to be droppedrun_pre_compaction_flush(transcript)search_memory (dedupe)store_memory (staged)staged writescommit_memory_batch (shared deadline)proceed with older slice

The child agent has zero autonomy budget (max_steps=3, no streaming, no verbose) and cannot recurse into another compaction. Only user and assistant text is serialised — tool messages and non-text parts are dropped.


MemoryFlushResult

The flush returns a frozen MemoryFlushResult (importable from praisonaiagents.compaction) describing what happened.

FieldTypeMeaning
attemptedboolTrue if the child agent actually ran.
completedboolTrue if the child finished and staged writes committed in time.
reasonstrOne of the seven values below.
messages_consideredintCount of user/assistant text messages the flush looked at.
reasonPlain English
disabledFeature turned off in config or via env var.
no_memoryParent agent has no memory backend.
below_minimumFewer eligible turns than min_turns_to_flush.
emptyNo user/assistant text to flush after filtering.
timeoutChild exceeded timeout_seconds; staged writes discarded.
errorChild raised; compaction continues unchanged.
completedChild finished and writes committed.

Configuration Options

PreCompactionMemoryFlushConfig controls the child agent’s budget.

OptionTypeDefaultDescription
enabledboolTrueWhether the flush runs when this config is assigned to ExecutionConfig.pre_compaction_memory_flush.
timeout_secondsfloat20.0Hard wall-clock deadline for the whole flush. Must be finite and positive.
min_turns_to_flushint2Skip unless at least this many eligible user/assistant messages exist in the older slice.
max_flush_tokensint8000Cap on the transcript handed to the child; oversized transcripts are truncated with a [transcript truncated] sentinel.
llmOptional[str]NoneModel for the child agent. None falls back to the parent’s agent.llm.

Environment Overrides

Two environment variables override the code-level config at runtime.

Env varEffect
PRAISONAI_PRE_COMPACTION_FLUSH1/true/yes/on force-enables; 0/false/no/off force-disables. Overrides enabled.
PRAISONAI_PRE_COMPACTION_FLUSH_TIMEOUTPositive float overrides timeout_seconds.

Invalid values are ignored with a logged warning — the code-level config stands.


Behaviour Guarantees

  • Never blocks compaction. Every failure path returns a MemoryFlushResult(attempted=True, completed=False, ...) and logs a warning; compaction proceeds unchanged.
  • Atomic commit-or-nothing. Child writes are staged and committed only after successful, in-time completion. A timed-out worker cannot mutate the parent memory store. Each staged write is re-gated by the shared deadline and commit_guard; a cancelled or expired flush persists nothing on any backend.
  • Works on every backend. commit_memory_batch maps each staged add_ write to the backend’s store_ method (add_short_term → store_short_term, add_long_term → store_long_term) so sqlite / chroma / mongodb / mem0 / learn durably persist facts, not just file.
  • Sync path in a daemon thread. A stuck provider cannot delay process shutdown.
  • Tool payloads excluded. Only user and assistant text is serialised.
  • Prompt-injection posture. The child treats the transcript as data, not instructions, refuses to store credentials or secrets, and uses only the two provided memory tools.

The async variant acommit_memory_batch prefers a store__async method when the adapter exposes one; otherwise it offloads the blocking store_ call to asyncio.to_thread so the event loop stays responsive. Each write is gated by the same deadline / cancellation guard as the sync path, so late or cancelled facts are never persisted after compaction resumes.


Common Patterns

Save user preferences before a chat session’s context rotates by enabling the flush alongside compaction:

python
from praisonaiagents import Agent, ExecutionConfig

agent = Agent(
    name="Concierge",
    instructions="Assist the guest across a long conversation.",
    memory=True,
    execution=ExecutionConfig(
        context_compaction=True,
        pre_compaction_memory_flush=True,
    ),
)

Point a cheaper model at the flush child than the parent uses for the main loop:

python
from praisonaiagents import ExecutionConfig, PreCompactionMemoryFlushConfig

execution = ExecutionConfig(
    context_compaction=True,
    pre_compaction_memory_flush=PreCompactionMemoryFlushConfig(llm="gpt-4o-mini"),
)

Wire an app-level metric on MemoryFlushResult.reason (count timeout vs completed) to tune timeout_seconds over time.


Best Practices

Leave the default timeout unless the flush times out

The child is capped at max_steps=3 and rarely needs more than the default timeout_seconds=20.0. Raise it only if you observe reason == "timeout" dominating.

Set a cheap dedicated model for the flush

When the parent uses an expensive model, pass llm="gpt-4o-mini" (or similar). The flush job is short and doesn’t need the top-tier model.

Tune min_turns_to_flush to skip tiny compactions

The default min_turns_to_flush=2 avoids firing on trivial compactions. Raise it if reason == "below_minimum" dominates your metrics.

Respect the child's security boundary

The child agent shares the parent’s memory backend but cannot access its tools — that’s a deliberate boundary. Don’t try to bypass it by mutating the child.


Advanced

Framework authors wiring the flush into a custom compactor path can call run_pre_compaction_flush (async) or run_pre_compaction_flush_sync directly. Both need a parent_agent shaped like Agent — it must expose _memory_instance and .llm.

python
from praisonaiagents.compaction.memory_flush import run_pre_compaction_flush_sync

result = run_pre_compaction_flush_sync(agent, messages_to_flush, config=True)
print(result.reason)

Related