openclaw - 💡(How to fix) Fix Working patch: Proactive compaction via direct filesystem flush (bypasses reactive-only compaction) [1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#54301Fetched 2026-04-08 01:29:17
View on GitHub
Comments
0
Participants
1
Timeline
0
Reactions
2
Author
Participants

Error Message

Safeguard compaction mode is entirely reactive. It only triggers after the model returns a context_overflow error. For sessions with heavy tool usage or long collaborative work, the session grows unbounded until it either hangs the model or produces silent empty replies. The memoryFlush config is loaded but never called proactively. 2. If the model returns a context_overflow error, compaction is triggered reactively

  • Throws an error to abort the current prompt log$14.warn( log$14.warn('[proactive-compaction] Vault flush complete: ' + _pcFlushFile); log$14.warn('[proactive-compaction] Flush failed: ' + String(_pcFlushErr)); log$14.warn('[proactive-compaction] Session archived'); log$14.warn('[proactive-compaction] Archive failed: ' + String(_pcArchiveErr)); throw new Error('[proactive-compaction] Session reset. Next message starts fresh.');

Root Cause

The Root Cause

Fix Action

Fix / Workaround

Instead of waiting for an official fix, I patched the dist file directly. The patch inserts a proactive token check immediately before activeSession.prompt() is called.

The Patch

  1. Find dist/reply-*.js in your OpenClaw install (npm root -g + /openclaw/dist/)
  2. Back up the file: cp reply-HASH.js reply-HASH.js.original
  3. Search for: if (imageResult.images.length > 0) await abortable(activeSession.prompt(
  4. Insert the patch block immediately before that line
  5. Restart: openclaw gateway --force

Code Example

// --- PROACTIVE COMPACTION CHECK ---
// Insert before: if (imageResult.images.length > 0) await abortable(activeSession.prompt(...))
{
  const _pcMessages = activeSession.messages || [];
  const _pcCurrentTokens = _pcMessages.reduce((sum, msg) => sum + estimateTokens(msg), 0);
  const _pcContextWindow = Math.max(1, Math.floor(
    params.model.contextWindow ?? params.model.maxTokens ?? 2e5
  ));
  const _pcCompactionCfg = params.config?.agents?.defaults?.compaction ?? {};
  const _pcReserveFloor = Math.max(0, Math.floor(
    _pcCompactionCfg.reserveTokensFloor ?? 2e4
  ));
  const _pcThreshold = _pcContextWindow - _pcReserveFloor;
  const _pcUtilization = _pcCurrentTokens / _pcContextWindow;

  if (_pcCurrentTokens > _pcThreshold && !activeSession.isCompacting) {
    log$14.warn(
      `[proactive-compaction] Token utilization ${Math.round(_pcUtilization * 100)}%` +
      ` (${_pcCurrentTokens}/${_pcContextWindow}), threshold ${_pcThreshold}.` +
      ` Forcing flush and session reset.`
    );

    // Direct filesystem write - no model call needed
    try {
      const _pcNow = new Date();
      const _pcDateStr = _pcNow.toISOString().slice(0, 10);
      const _pcTimeStr = _pcNow.toTimeString().slice(0, 8);

      // Determine agent from session key
      const _pcSessionKey = params.sessionKey || '';
      const _pcAgentMatch = _pcSessionKey.match(/^agent:([^:]+):/);
      const _pcAgentId = _pcAgentMatch ? _pcAgentMatch[1] : 'main';

      // Write to your persistent memory layer here.
      // In my case this is an Obsidian vault with per-agent namespaces.
      // Adapt the paths and format to your setup.
      const _pcFlushDir = path.join(YOUR_MEMORY_DIR, _pcAgentId, 'daily');
      const _pcFlushFile = path.join(_pcFlushDir, _pcDateStr + '.md');
      if (!fs.existsSync(_pcFlushDir)) fs.mkdirSync(_pcFlushDir, { recursive: true });

      const _pcRecentAssistant = _pcMessages
        .filter(m => m.role === 'assistant').slice(-10)
        .map(m => {
          const t = Array.isArray(m.content)
            ? m.content.filter(c => c.type === 'text').map(c => c.text).join(' ')
            : String(m.content || '');
          return t.slice(0, 200);
        }).filter(Boolean);

      const _pcFlushEntry = '\n## Proactive Compaction Flush (' + _pcTimeStr + ')\n'
        + '- Token utilization: ' + Math.round(_pcUtilization * 100) + '%'
        + ' (' + _pcCurrentTokens + '/' + _pcContextWindow + ')\n'
        + '- Session archived automatically to prevent context overflow\n'
        + '- Recent activity:\n'
        + _pcRecentAssistant.map(s => '  - ' + s.replace(/\n/g, ' ').slice(0, 150)).join('\n')
        + '\n';

      fs.appendFileSync(_pcFlushFile, _pcFlushEntry, { encoding: 'utf8' });
      log$14.warn('[proactive-compaction] Vault flush complete: ' + _pcFlushFile);
    } catch (_pcFlushErr) {
      log$14.warn('[proactive-compaction] Flush failed: ' + String(_pcFlushErr));
    }

    // Archive session file to force fresh session on next message
    try {
      if (params.sessionFile && fs.existsSync(params.sessionFile)) {
        fs.renameSync(params.sessionFile, params.sessionFile + '.compacted.' + Date.now());
        log$14.warn('[proactive-compaction] Session archived');
      }
    } catch (_pcArchiveErr) {
      log$14.warn('[proactive-compaction] Archive failed: ' + String(_pcArchiveErr));
    }

    throw new Error('[proactive-compaction] Session reset. Next message starts fresh.');
  }
}
// --- END PROACTIVE COMPACTION CHECK ---

---

cp dist/reply-HASH.js.original dist/reply-HASH.js
openclaw gateway --force
RAW_BUFFERClick to expand / collapse

The Problem

Safeguard compaction mode is entirely reactive. It only triggers after the model returns a context_overflow error. For sessions with heavy tool usage or long collaborative work, the session grows unbounded until it either hangs the model or produces silent empty replies. The memoryFlush config is loaded but never called proactively.

This has been discussed across multiple issues:

  • #15669 (safeguard triggers AFTER overflow, not before)
  • #7477 (safeguard silently fails on large contexts)
  • #24800 (no auto-compaction during tool-use loops)
  • #19527 (preflight prompt budget enforcement)
  • #10694 (no preflight guard for large tool results)
  • #5429 (lost 2 days of context to silent compaction)

The Root Cause

After reading the source (reply-*.js in dist), the compaction trigger path is clear:

  1. activeSession.prompt() sends the full session to the model
  2. If the model returns a context_overflow error, compaction is triggered reactively
  3. reserveTokens, reserveTokensFloor, and shouldRunMemoryFlush() are loaded and defined, but no code path calls them before the prompt
  4. The waitForCompactionRetry at line ~109847 waits for compaction that pi-coding-agent initiated internally, it doesn't start compaction from a promptError

So the settings exist, the threshold math exists, the flush function exists, but nobody calls them pre-request.

What I Did

Instead of waiting for an official fix, I patched the dist file directly. The patch inserts a proactive token check immediately before activeSession.prompt() is called.

The key design decision: I don't try to trigger OpenClaw's internal compaction machinery. I bypass it entirely and write directly to disk using Node.js fs. This avoids the paradox described in #20760 (compaction fails when already over context window, because the compaction call itself exceeds the window).

The Patch

Target: dist/reply-*.js, inserted immediately before the activeSession.prompt() call.

What it does on every prompt:

  1. Counts tokens in activeSession.messages using the existing estimateTokens() function
  2. Compares against contextWindow - reserveTokensFloor from config
  3. If exceeded:
    • Writes a summary entry to an Obsidian vault daily log via fs.appendFileSync() (my persistent memory layer)
    • Overwrites a session state file with compaction notice and recovery instructions
    • Archives the session file on disk (fs.renameSync with .compacted. suffix)
    • Throws an error to abort the current prompt
    • The next inbound message creates a fresh session that reads the vault state files

What it does NOT do:

  • Does not call the model for the flush (no risk of the compaction call itself exceeding context)
  • Does not rely on the agent following instructions to persist state
  • Does not modify any other code paths

Code

// --- PROACTIVE COMPACTION CHECK ---
// Insert before: if (imageResult.images.length > 0) await abortable(activeSession.prompt(...))
{
  const _pcMessages = activeSession.messages || [];
  const _pcCurrentTokens = _pcMessages.reduce((sum, msg) => sum + estimateTokens(msg), 0);
  const _pcContextWindow = Math.max(1, Math.floor(
    params.model.contextWindow ?? params.model.maxTokens ?? 2e5
  ));
  const _pcCompactionCfg = params.config?.agents?.defaults?.compaction ?? {};
  const _pcReserveFloor = Math.max(0, Math.floor(
    _pcCompactionCfg.reserveTokensFloor ?? 2e4
  ));
  const _pcThreshold = _pcContextWindow - _pcReserveFloor;
  const _pcUtilization = _pcCurrentTokens / _pcContextWindow;

  if (_pcCurrentTokens > _pcThreshold && !activeSession.isCompacting) {
    log$14.warn(
      `[proactive-compaction] Token utilization ${Math.round(_pcUtilization * 100)}%` +
      ` (${_pcCurrentTokens}/${_pcContextWindow}), threshold ${_pcThreshold}.` +
      ` Forcing flush and session reset.`
    );

    // Direct filesystem write - no model call needed
    try {
      const _pcNow = new Date();
      const _pcDateStr = _pcNow.toISOString().slice(0, 10);
      const _pcTimeStr = _pcNow.toTimeString().slice(0, 8);

      // Determine agent from session key
      const _pcSessionKey = params.sessionKey || '';
      const _pcAgentMatch = _pcSessionKey.match(/^agent:([^:]+):/);
      const _pcAgentId = _pcAgentMatch ? _pcAgentMatch[1] : 'main';

      // Write to your persistent memory layer here.
      // In my case this is an Obsidian vault with per-agent namespaces.
      // Adapt the paths and format to your setup.
      const _pcFlushDir = path.join(YOUR_MEMORY_DIR, _pcAgentId, 'daily');
      const _pcFlushFile = path.join(_pcFlushDir, _pcDateStr + '.md');
      if (!fs.existsSync(_pcFlushDir)) fs.mkdirSync(_pcFlushDir, { recursive: true });

      const _pcRecentAssistant = _pcMessages
        .filter(m => m.role === 'assistant').slice(-10)
        .map(m => {
          const t = Array.isArray(m.content)
            ? m.content.filter(c => c.type === 'text').map(c => c.text).join(' ')
            : String(m.content || '');
          return t.slice(0, 200);
        }).filter(Boolean);

      const _pcFlushEntry = '\n## Proactive Compaction Flush (' + _pcTimeStr + ')\n'
        + '- Token utilization: ' + Math.round(_pcUtilization * 100) + '%'
        + ' (' + _pcCurrentTokens + '/' + _pcContextWindow + ')\n'
        + '- Session archived automatically to prevent context overflow\n'
        + '- Recent activity:\n'
        + _pcRecentAssistant.map(s => '  - ' + s.replace(/\n/g, ' ').slice(0, 150)).join('\n')
        + '\n';

      fs.appendFileSync(_pcFlushFile, _pcFlushEntry, { encoding: 'utf8' });
      log$14.warn('[proactive-compaction] Vault flush complete: ' + _pcFlushFile);
    } catch (_pcFlushErr) {
      log$14.warn('[proactive-compaction] Flush failed: ' + String(_pcFlushErr));
    }

    // Archive session file to force fresh session on next message
    try {
      if (params.sessionFile && fs.existsSync(params.sessionFile)) {
        fs.renameSync(params.sessionFile, params.sessionFile + '.compacted.' + Date.now());
        log$14.warn('[proactive-compaction] Session archived');
      }
    } catch (_pcArchiveErr) {
      log$14.warn('[proactive-compaction] Archive failed: ' + String(_pcArchiveErr));
    }

    throw new Error('[proactive-compaction] Session reset. Next message starts fresh.');
  }
}
// --- END PROACTIVE COMPACTION CHECK ---

How to Apply

  1. Find dist/reply-*.js in your OpenClaw install (npm root -g + /openclaw/dist/)
  2. Back up the file: cp reply-HASH.js reply-HASH.js.original
  3. Search for: if (imageResult.images.length > 0) await abortable(activeSession.prompt(
  4. Insert the patch block immediately before that line
  5. Restart: openclaw gateway --force

Environment

  • OpenClaw v2026.3.13
  • Windows 11
  • Model: openai-codex/gpt-5.4 via ChatGPT Plus OAuth
  • Config: safeguard mode, reserveTokensFloor: 82000, contextWindow: 200000, memoryFlush: enabled

Why This Approach

The core philosophy: don't rely on agents to follow instructions for mandatory behaviors. The model can decide not to flush, not to persist, not to self-compact. The runtime should enforce these things deterministically in code.

Every workaround I've seen in other issues relies on either:

  • Waiting for the model to hit overflow (reactive, too late)
  • Asking the agent to flush via instructions (unreliable)
  • Feature requests for the core team to fix it (waiting)

This patch enforces it in code. The agent never gets a vote. fs.appendFileSync either writes or throws. fs.renameSync either archives or throws. No LLM judgment involved.

Status

Just applied. Gateway starts cleanly. Normal message handling works. Haven't hit the compaction threshold in production yet (just applied tonight), will update this issue with results.

Rollback

cp dist/reply-HASH.js.original dist/reply-HASH.js
openclaw gateway --force

Related Issues

  • #19527 (preflight prompt budget enforcement - this is the feature request for what this patch implements)
  • #15669 (safeguard triggers after overflow)
  • #7477 (safeguard silently fails)
  • #24800 (no auto-compaction during tool-use loops)
  • #20760 (compaction fails when already over context - our bypass avoids this)
  • #5429 (lost context to silent compaction)
  • #4836 (memoryFlush enabled but not executing)

extent analysis

Fix Plan

To implement proactive compaction, follow these steps:

  1. Locate the dist/reply-*.js file: Find the file in your OpenClaw install directory.
  2. Backup the original file: Copy the file to a backup location, e.g., reply-HASH.js.original.
  3. Insert the patch: Search for the line if (imageResult.images.length > 0) await abortable(activeSession.prompt( and insert the provided patch code before it.
  4. Restart OpenClaw: Run openclaw gateway --force to apply the changes.

Patch Code:

// --- PROACTIVE COMPACTION CHECK ---
// Insert before: if (imageResult.images.length > 0) await abortable(activeSession.prompt(...)
{
  // ... (insert the provided patch code here)
}
// --- END PROACTIVE COMPACTION CHECK ---

Example Configuration:

const params = {
  model: {
    contextWindow: 200000,
    maxTokens: 200000,
  },
  config: {
    agents: {
      defaults: {
        compaction: {
          reserveTokensFloor: 82000,
        },
      },
    },
  },
};

Verification

To verify that the fix worked:

  1. Test with a large input: Send a large input to the model and check if the proactive compaction is triggered.
  2. Check the logs: Verify that the log messages indicate a successful compaction and session reset.
  3. Check the file system: Confirm that the session file is archived and a new session is created.

Extra Tips

  • Make sure to update the YOUR_MEMORY_DIR variable in the patch code to point to your desired persistent memory layer.
  • Be cautious when applying the patch, as it modifies the dist/reply-*.js file.
  • If you encounter issues, you can rollback to the original file using the provided rollback instructions.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - 💡(How to fix) Fix Working patch: Proactive compaction via direct filesystem flush (bypasses reactive-only compaction) [1 participants]