Implementing User Notification Before Auto-Compaction
Guide for adding a user-facing notification system before silent context compaction to preserve critical session information and prevent data loss.
π Symptoms
Problematic Behavior
When running OpenClaw with auto-compaction enabled, the following sequence occurs silently:
1. Token count approaches max_context_tokens threshold
2. Background compaction triggers automatically
3. Silent memory flush executes without user visibility
4. Active session context is wiped
5. Agent resumes with partial/unpredictable memory stateUser-Reported Manifestations
- Lost session context β Multi-hour tax preparation sessions reset, requiring the user to re-explain the task structure
- Silent data capture β The model saves raw numerical data (e.g.,
Box 1 wages: $45,230) instead of conceptual patterns (e.g., "use Box 16 for state wages, never Box 1") - No intervention window β Users cannot redirect compaction priorities before the memory flush executes
- Agent guessing behavior β The compaction routine arbitrarily selects what to preserve, often missing domain-critical distinctions
Environment Configuration
{
"channels": ["imessage"],
"model": "claude-opus-4.6",
"compaction": {
"enabled": true,
"thresholdTokens": 75000,
"strategy": "semantic-summary"
}
}In this configuration, compaction fires at 75% context utilization with no user-facing alert.
π§ Root Cause
Architectural Analysis
The current OpenClaw compaction architecture follows a two-phase execution model:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β COMPACTION PIPELINE β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Phase 1: Token Threshold Detection β
β βββ Monitors context window via TokenCounter β
β βββ Fires at thresholdTokens (default: 85% of limit) β
β β
β Phase 2: Silent Memory Flush β
β βββ Extracts context via MemoryExtractor β
β βββ Generates semantic summary β
β βββ Writes to compaction_store.json β
β βββ Triggers AgentContext.reset() β
β β
β β οΈ NO USER NOTIFICATION STEP EXISTS β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββFailure Sequence
- TokenCounter detects
context_tokens >= thresholdTokens - CompactionManager immediately invokes
MemoryExtractor.extract() - MemoryExtractor applies heuristic scoring to rank context blocks:
- Recency weighting (most recent messages score higher)
- Length weighting (longer messages may score higher)
- No semantic importance inference
- AgentContext.reset() wipes the active session
- User receives no signal that compaction occurred
Why the Model Guesses Incorrectly
The MemoryExtractor operates on structural signals, not semantic value:
// Current extraction logic (simplified)
function extractMemory(session) {
return session.messages
.sortBy(msg => msg.relevanceScore) // Based on recency + length
.top(n) // Selects n highest-scored blocks
.map(msg => msg.content) // Raw content extraction
}This approach systematically fails to preserve:
- Domain heuristics β "Always use Box 16, not Box 1"
- User preferences β Formatting styles, communication patterns
- Cross-session patterns β Recurring tasks with established approaches
- Contextual caveats β "Except when the client is a sole proprietor"
π οΈ Step-by-Step Fix
π οΈ Step-by-Step Fix
Configuration Update
Add the userNotification block to your config.json:
Before:
{
"channels": ["imessage"],
"model": "claude-opus-4.6",
"compaction": {
"enabled": true,
"thresholdTokens": 75000,
"strategy": "semantic-summary"
}
}After:
{
"channels": ["imessage"],
"model": "claude-opus-4.6",
"compaction": {
"enabled": true,
"thresholdTokens": 75000,
"strategy": "semantic-summary",
"userNotification": {
"enabled": true,
"message": "Context window filling up β what should I remember from this session?",
"thresholdTokens": 60000,
"responseTimeoutSeconds": 120,
"resumeOnTimeout": true
}
}
}Configuration Field Reference
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable user-facing notification before compaction |
message | string | "Context filling up β what matters most?" | Custom prompt sent to user |
thresholdTokens | integer | 60% of max | Token count to trigger notification (lower than compaction threshold) |
responseTimeoutSeconds | integer | 60 | Seconds to wait for user response before proceeding |
resumeOnTimeout | boolean | true | Proceed with compaction if user doesn't respond |
Code Modification
If implementing the feature directly in the OpenClaw source, modify CompactionManager.ts:
// Location: src/core/CompactionManager.ts
async triggerCompaction(session: Session): Promise {
const config = this.config.compaction;
const tokenCount = await this.tokenCounter.getCount(session);
// NEW: Check if user notification should fire first
if (
config.userNotification?.enabled &&
tokenCount >= config.userNotification.thresholdTokens
) {
await this.sendUserNotification(session, config.userNotification);
}
// Original compaction logic continues
return this.executeMemoryFlush(session);
}
async sendUserNotification(
session: Session,
notificationConfig: UserNotificationConfig
): Promise<UserResponse | null> {
const channel = this.channelManager.getActiveChannel(session);
// Send visible message to user
await channel.send({
type: 'user-notification',
content: notificationConfig.message,
metadata: {
compactionPending: true,
tokenCount: await this.tokenCounter.getCount(session),
timeoutSeconds: notificationConfig.responseTimeoutSeconds
}
});
// Wait for user response
try {
const response = await this.waitForUserResponse(
session,
notificationConfig.responseTimeoutSeconds
);
if (response && response.content) {
// Inject user's priorities into compaction context
await this.injectUserPriorities(session, response.content);
}
return response;
} catch (TimeoutError) {
if (notificationConfig.resumeOnTimeout) {
this.logger.warn('User notification timeout β proceeding with compaction');
return null;
}
throw new CompactionAbortedError('User did not respond within timeout window');
}
}
async injectUserPriorities(session: Session, userContent: string): Promise {
// Append user's guidance to compaction extraction context
const priorityContext = {
type: 'user-priority',
content: userContent,
source: 'user-notification-response',
timestamp: Date.now()
};
await session.updateMetadata({
userPriorities: [
...(session.metadata.userPriorities || []),
priorityContext
]
});
} CLI Equivalent (Manual Trigger)
For immediate testing without config reload:
# Test user notification manually
openclaw compaction notify --session-id <session_id> \
--message "Context window filling up β what should I remember from this session?"
# Verify notification sent
openclaw compaction status --session-id <session_id>π§ͺ Verification
Test Scenarios
1. Notification Firing Verification
# Start a session and monitor compaction events
openclaw session start --channel imessage --model claude-opus-4.6
# In another terminal, monitor events
openclaw events stream --filter compaction --format jsonExpected output:
{
"event": "compaction-notification-sent",
"sessionId": "sess_abc123",
"tokenCount": 60234,
"thresholdTokens": 60000,
"channel": "imessage",
"timestamp": "2025-01-15T14:32:01Z"
}
{
"event": "user-response-received",
"sessionId": "sess_abc123",
"responseLength": 342,
"responseTimeoutSeconds": 118,
"timestamp": "2025-01-15T14:33:45Z"
}2. User Priority Injection Verification
# After user responds, check if priorities were captured
openclaw session inspect --session-id sess_abc123 --show-metadata | jq '.userPriorities'Expected output:
[
{
"type": "user-priority",
"content": "Always use Box 16 for state wages, not Box 1 federal wages. Also remember to check for dual-state filings.",
"source": "user-notification-response",
"timestamp": 1705326825000
}
]3. Compaction Store Content Verification
# After compaction completes, verify user priorities are in the store
openclaw compaction inspect --session-id sess_abc123 | jq '.extractedContexts'Expected output:
{
"semanticSummary": "Tax preparation session covering W-2 processing...",
"userGuidance": "Always use Box 16 for state wages, not Box 1 federal wages...",
"extractedBlocks": [...],
"priorityIndicators": ["user-guidance"]
}4. Timeout Behavior Verification
# Trigger notification with short timeout for testing
openclaw compaction notify --session-id sess_abc123 \
--timeout-seconds 10 \
--resume-on-timeout true
# Verify compaction proceeds after timeout
openclaw events stream --filter compaction --max-events 5Expected output:
{
"event": "compaction-started",
"sessionId": "sess_abc123",
"reason": "notification-timeout",
"timestamp": "2025-01-15T14:35:12Z"
}Exit Code Reference
| Exit Code | Meaning |
|---|---|
0 | Notification sent and user responded successfully |
1 | Notification configuration error |
2 | Channel not available for notification |
3 | Timeout with resumeOnTimeout: false |
4 | Session not found or inactive |
β οΈ Common Pitfalls
Configuration Errors
- thresholdTokens too close to compaction threshold
userNotification.thresholdTokensmust be lower thanthresholdTokens. If equal or higher, the notification never fires before compaction.// β INCORRECT β notification threshold equals compaction threshold "thresholdTokens": 75000, "userNotification": { "thresholdTokens": 75000 // Never fires in time }// β CORRECT β notification fires before compaction “thresholdTokens”: 75000, “userNotification”: { “thresholdTokens”: 60000 // Fires at 60% context }
- responseTimeoutSeconds too short for async channels
iMessage and similar async channels may have delivery delays. A 30-second timeout may expire before the user can respond.// β INCORRECT β too short for async channels "responseTimeoutSeconds": 30// β CORRECT β adequate time for user consideration “responseTimeoutSeconds”: 120
Environment-Specific Traps
- macOS Focus Assist blocking iMessage delivery
When Focus is enabled, iMessage notifications may not reach the user. TheresponseTimeoutSecondswill expire silently.# Alternative: Use a synchronous channel for critical sessions "channels": ["imessage", "terminal"], // Terminal as fallback - Docker container network isolation
In Docker deployments, the notification channel must be exposed correctly:# Ensure channel ports are mapped docker run -p 9229:9229 -p 3000:3000 openclaw/openclaw:latestVerify channel accessibility
openclaw channels list –verbose
- Windows Defender/firewall blocking notification delivery
Windows environments may block outbound notification ports. Configure firewall rules or switch to a local notification method.
User Behavior Pitfalls
- Users providing unhelpful responses
If users reply with "idk" or "nothing", theMemoryExtractorreceives empty guidance. Consider adding prompt validation:// Minimum response length check (optional enhancement) if (response.content.length < 20) { // Send follow-up: "Please provide more detail so I can preserve what matters." } - Users becoming notification-fatigued
Frequent notifications may cause users to ignore them. Balance token threshold against notification frequency.
Edge Cases
- Multiple rapid session switches
If the user switches between sessions rapidly, notifications may arrive for the wrong session context. ThesessionIdin the notification metadata ensures correct targeting. - Notification sent during active tool execution
If the agent is mid-execution when notification fires, the user's response should be queued until the tool completes.
π Related Errors
Contextual Error References
COMPACTION_001: Threshold Exceeded
Standard compaction trigger. Related when understanding the difference between compaction thresholds and notification thresholds.COMPACTION_002: Memory Extraction Failed
MemoryExtractor errors. The userNotification feature provides an additional layer: if extraction fails, the user's guidance is preserved insession.metadata.userPrioritiesfor retry.CHANNEL_003: Delivery Timeout
Channel-specific timeouts. Often misdiagnosed as notification failures when the issue is channel availability.CONTEXT_007: Reset During Active Tool
Context reset while tool is executing. The userNotification delay does not prevent this, but user guidance may be preserved in the new session.TOKEN_002: Counter Discrepancy
Token counting errors. IfTokenCountermisreports counts, the notification may fire at unexpected points.
Historical Context
The silent compaction behavior described in this issue has been a persistent pain point in OpenClaw sessions exceeding 2 hours. Community discussion threads (Issue #847, Discussion #1203) document similar experiences:
- "Compaction fires mid-tax-season without warning"
- "Model reverts to generic responses after compaction"
- "No way to tell the agent what's important before memory wipe"
The userNotification feature addresses the fundamental asymmetry: the model cannot reliably infer human values, so human guidance must precede machine memory management.
Further Reading
- OpenClaw Compaction Architecture β Deep dive into the CompactionManager pipeline
- MemoryExtractor Heuristics β Understanding how context blocks are scored
- Channel Configuration Guide β Setting up notification delivery across platforms