← Back to PRs

#13540: feat: persist channel histories to disk

by carrotRakko open 2026-02-10 17:39 View on GitHub →
channel: imessage channel: signal channel: slack channel: telegram channel: whatsapp-web stale
## Summary Channel histories (pending messages from non-mentioned users in group chats) are currently stored in an in-memory `Map` that is lost on every process restart. This means the agent loses all "between-reply" conversational context whenever the process is restarted (e.g. during deployments). This PR adds transparent disk persistence to channel histories so that context survives process restarts. ## Changes - **New file `src/auto-reply/reply/history-persistence.ts`** - `createPersistentHistoryMap<T>(filePath, debounceMs?)` — drop-in replacement for `new Map()` - Loads existing data from disk on creation - Hooks `Map.set` / `Map.delete` to schedule debounced writes (default 5s) - Atomic write (tmp file → rename) for crash safety - Synchronous flush on process exit to prevent data loss during graceful shutdown - Timer is `unref()`'d so it doesn't keep the process alive - **`src/config/sessions/paths.ts`** - Added `resolveChannelHistoriesPath(channel, accountId?, agentId?)` for consistent file path resolution - File path: `~/.openclaw/agents/<agentId>/sessions/channel-histories-<channel>-<accountId>.json` - **All 5 channel monitors updated** (one-line change each): - `src/slack/monitor/context.ts` - `src/telegram/bot.ts` - `src/signal/monitor.ts` - `src/web/auto-reply/monitor.ts` - `src/imessage/monitor/monitor-provider.ts` ## What is NOT changed - All existing history functions (`appendHistoryEntry`, `clearHistoryEntries`, `buildHistoryContextFromMap`, etc.) are completely unchanged - No new dependencies added - No configuration required (works out of the box) ## Design decisions - **Debounce over immediate write**: Group chats can receive bursts of messages; debouncing avoids excessive I/O while still persisting within seconds - **Atomic write**: Uses tmp file + rename pattern (same as session store) to prevent corruption from partial writes - **Per-channel files**: Each channel + account combination gets its own JSON file, avoiding contention between channels - **Generic type parameter**: `createPersistentHistoryMap<T>` works with any entry type (WhatsApp uses extended fields beyond `HistoryEntry`) ## Related - Closes #13449 ✍️ **Author**: Claude Code with @carrotRakko (AI-written, human-approved) <!-- greptile_comment --> <h2>Greptile Overview</h2> <h3>Greptile Summary</h3> This PR replaces in-memory channel/group history `Map`s with a persistent map that loads/saves JSON on disk, and adds a `resolveChannelHistoriesPath()` helper under the sessions paths module. The monitors for Slack/Telegram/Signal/iMessage/WhatsApp web are updated to construct their history map via `createPersistentHistoryMap(...)` so pending “between reply” context can survive process restarts. Persistence is implemented via synchronous JSON read on startup and debounced synchronous writes (tmp+rename on non-Windows) on `Map.set`/`Map.delete`, with a synchronous flush on process exit. <h3>Confidence Score: 3/5</h3> - This PR introduces persistence but has correctness risks in how histories are scoped and how process exit listeners are managed. - Main concerns are that persistence is currently keyed only by provider/account/agent, which will conflate histories from multiple concurrent group chats for the same account, and that each persistent map instance registers a global `process.on("exit")` handler without cleanup (risking listener leaks / duplicated flushes). - src/config/sessions/paths.ts, src/auto-reply/reply/history-persistence.ts <!-- greptile_other_comments_section --> <sub>(4/5) You can add custom instructions or style guidelines for the agent [here](https://app.greptile.com/review/github)!</sub> <!-- /greptile_comment -->

Most Similar PRs