Skip to content

feat: add idle compaction trigger (idleCompactMinutes)#102

Open
drdotdot wants to merge 1 commit intoMartian-Engineering:mainfrom
drdotdot:feat/idle-compaction
Open

feat: add idle compaction trigger (idleCompactMinutes)#102
drdotdot wants to merge 1 commit intoMartian-Engineering:mainfrom
drdotdot:feat/idle-compaction

Conversation

@drdotdot
Copy link

Summary

Adds time-based compaction that triggers when a session has been idle beyond a configurable threshold (idleCompactMinutes). This addresses stale context in sessions that go through periods of inactivity — overnight, between work sessions, or during long-running background jobs.

Motivation

Currently, LCM only compacts when context exceeds the contextThreshold (turn-based trigger). Sessions that accumulate significant context but never quite hit the threshold — or sessions that go idle for hours/days — can resume with stale, unorganized raw context. This is especially common in agentic workflows with periodic cron/watchdog messages.

Idle compaction ensures returning users get lean, organized context instead of a wall of raw messages from hours ago.

Enables practical use of large context windows (1M tokens). With models like Gemini offering 1M-token context at no pricing premium, the turn-based threshold trigger alone isn't sufficient — sessions can accumulate hundreds of thousands of tokens of raw conversation over hours without ever hitting the 75% threshold. Idle compaction provides the missing time-based dimension: after periods of inactivity, context is proactively organized into the summary DAG regardless of size, keeping large-window sessions navigable and performant over long lifespans.

How it works

  1. Config: idleCompactMinutes (env: LCM_IDLE_COMPACT_MINUTES, default: 0 = disabled)
  2. Detection: getLastActivityTimestamp() queries the most recent user or assistant message. Heartbeats (never ingested) don't reset the timer. SystemEvents and sub-agent completions do.
  3. Leaf pass in assemble(): When idle threshold is exceeded, runs a single compactLeaf() with force: true before assembling context. Protected by a 15-second timeout to bound first-message latency.
  4. Full cascade in afterTurn(): Flags the session so afterTurn() forces a full compaction sweep regardless of context threshold, ensuring the leaf pass doesn't leave the DAG in a partial state.

Changes

File Change
src/db/config.ts Added idleCompactMinutes to LcmConfig type and resolveLcmConfig()
src/store/conversation-store.ts Added getLastActivityTimestamp() method
src/compaction.ts Added evaluateIdleTrigger() method
src/engine.ts Idle leaf pass in assemble(), forced cascade in afterTurn(), session tracking map
openclaw.plugin.json Added config schema + UI hints
docs/configuration.md Full documentation section

Configuration

{
  "plugins": {
    "entries": {
      "lossless-claw": {
        "config": {
          "idleCompactMinutes": 180
        }
      }
    }
  }
}

Or via environment: LCM_IDLE_COMPACT_MINUTES=180

Idle timer policy

  • User/assistant messages → reset timer ✅
  • Heartbeats → do NOT reset (never ingested) ✅
  • SystemEvents (cron, watchdog) → reset timer (genuine new information) ✅
  • Sub-agent completions → reset timer ✅

Testing

Tested in production OpenClaw deployment with idleCompactMinutes: 180 across multiple concurrent sessions (main, autoresearch, data collection). Gateway restart successful, config persisted, no errors in logs.

Breaking changes

None. Default is 0 (disabled). Existing behavior unchanged unless explicitly enabled.

Add time-based compaction that triggers when a session has been idle
beyond a configurable threshold. This addresses stale context in sessions
that go through periods of inactivity (overnight, between work sessions).

When enabled (idleCompactMinutes > 0), the first assemble() call after
the idle threshold runs a lightweight leaf pass (15s timeout), followed
by a forced full compaction cascade in afterTurn().

Changes:
- Config: idleCompactMinutes option (env: LCM_IDLE_COMPACT_MINUTES, default: 0)
- ConversationStore: getLastActivityTimestamp() filtering user/assistant roles
- CompactionEngine: evaluateIdleTrigger() method
- Engine: idle leaf pass in assemble() with 15s timeout + forced cascade in afterTurn()
- Plugin schema + UI hints for new config key
- Documentation with usage guidance and idle timer policy
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant