Automated transcript and session hygiene for OpenClaw gateways.
.toolcache/ files, replacing inline content with lightweight stubs. Runs before trim on every turn.*.checkpoint.*.jsonl files left behind by auto-compaction once the parent session no longer has an active transcriptbash
bash skills/session-janitor/scripts/setup.sh
Setup auto-discovers all gateway installations ( ~/.openclaw/, ~/.openclaw-*/), generates config.json, installs a cron job, and installs the watcher service (systemd on Linux, launchd on macOS).
Install fswatch for the per-turn watcher:
bash
brew install fswatch
The cron-based janitor works without it (trimming every 15 min instead of within ~3s of each turn).
For architecture details, trimming mechanics, and token math, see ARCHITECTURE.md.
Edit config.json after setup to tune thresholds:
KeyDefaultDescriptiontrimMaxKB250Trim transcripts larger than this. Trim always fires when exceeded — there is no minimum pair count.keepPairs10Number of recent user/assistant pairs to keep after trim. If fewer pairs exist than this, all are kept (no skip).keepFullPairs2Most recent N assistant turns that keep full toolResult bodies (older turns are collapsed to summary lines)minArchivePairs5Informational only — no longer blocks trim. Sessions exceeding trimMaxKB are always trimmed.trimFullThresholdPct50Two-stage aggressive reduction when trimmed output still exceeds this % of trimMaxKB: (1) strip all assistant turns (tool args + thinking removed); (2) if still over threshold, drop all toolResult entries entirely. Set to 100 to disable.archiveRetentionDays7Delete old archives after this many days (applies to .reset.*, .deleted.*, .pre-trim.*, .bak-*, .purged.*, .emergency-*)keepPreTrimFiles3Max .pre-trim.* files to keep per session (oldest deleted immediately, regardless of retention period)orphanGraceMinutes30Wait before archiving orphan transcriptsstaleSubagentHours24Prune subagent session entries older than thisstaleCronSessionHours24Prune cron session entries older than thisllmExtraction.enabledtrueUse LLM to extract memories from trimmed contentllmExtraction.model``openclawModel identifier to use for extraction callsllmExtraction.maxInputChars20000Max characters of archived content sent to LLMllmExtraction.timeoutSecs60LLM API call timeout in secondsllmExtraction.maxMemories15Max memories to accept from a single LLM extractionllmExtraction.minArchived3Minimum archived messages required before running LLM extractionllmExtraction.maxPerRun1Max LLM extractions per cron cycle (cost control)memCli.enabledfalseStore extracted memories via mem CLI (requires mem)cronSchedule``*/15 * * * *How often to runsidecar.enabledtrueOffload large toolResult entries to .toolcache/ filessidecar.minEntryBytes5120Minimum toolResult content size (bytes) to trigger offloadextractOnTrim.enabledfalseFire async LLM memory extraction after each watcher-triggered trimextractOnTrim.minArchivedPairs3Minimum archived message pairs required before running extractionextractOnTrim.scene``autoMemory scene tag to use ( auto lets the LLM infer from context)extractOnTrim.salience0.5Default salience for extracted memories (0.1–1.0)watchdog.enabledfalseRun hung-session detector after each janitor passwatchdog.staleMinutes5Session updatedAt age threshold to consider stuckwatchdog.alertSlacktrueSend Slack DM when a stuck session is detectedwatchdog.slackTarget—Slack user ID or channel to notifywatchdog.autoRestartfalseAuto-trigger safe-restart script on detection (use with caution)watchdog.restartScriptSlack``~/bin/safe-slack-restart.shSafe restart script path for Slack gatewaywatchdog.restartScriptDiscord``~/bin/safe-gateway-restart.shSafe restart script path for Discord gateway
Setup scans for ~/.openclaw/openclaw.json and ~/.openclaw-*/openclaw.json. Each discovered gateway gets its own entry in config.json with:
gateway.port in config)gateway.auth.token)Works with single-gateway installs, multi-gateway (Discord + Slack), or any custom layout.
mem DB only, never to files that get auto-loaded into session contextbash
bash skills/session-janitor/scripts/janitor.sh
bash
tail -f /tmp/session-janitor.log
The watcher fires trim within ~3 seconds of each turn completing (vs. waiting for the next cron cycle). setup.sh installs it automatically as a system service.
Linux (systemd):
bash
systemctl --user status session-janitor-watcher
systemctl --user restart session-janitor-watcher
tail -f /tmp/session-janitor-watcher.log
macOS (launchd):
bash
launchctl list | grep session-janitor
launchctl unload ~/Library/LaunchAgents/ai.openclaw.session-janitor-watcher.plist
launchctl load ~/Library/LaunchAgents/ai.openclaw.session-janitor-watcher.plist
tail -f /tmp/session-janitor-watcher.log
inotifywait (from inotify-tools) and watches via inotify kernel events.fswatch (via Homebrew) and watches via FSEvents.The watchdog runs after every janitor pass and checks updatedAt on all active sessions. If any session is stale longer than watchdog.staleMinutes, it fires a Slack alert. Optional autoRestart will trigger the appropriate safe-restart script.
Alert cooldown is 10 minutes per session to prevent spam. State is tracked in the janitor state file.
bash
# Run standalone
bash skills/session-janitor/scripts/watchdog.sh 5 ~/.openclaw/session-janitor-state.json
Workers spawned via Foreman should use prefixed sessions_send messages to prevent the parent agent from spawning new sub-agents in response to status updates:
text
# Intermediate status (fire-and-forget — parent relays but does NOT spawn)
sessions_send(sessionKey="{PARENT}", message="STATUS: 🤖 [label]: doing X (60%)", timeoutSeconds=0)
# Errors/failures (normal delivery — parent may intervene)
sessions_send(sessionKey="{PARENT}", message="ERROR: 🤖 [label]: what failed — details")
# Completion (no prefix, normal delivery)
sessions_send(sessionKey="{PARENT}", message="🤖 [label]: ✅ done — summary")
STATUS: → relay-only; timeoutSeconds=0 prevents reply-back loopsERROR: → parent receives and considers recovery actiontext
skills/session-janitor/
├── SKILL.md # This file
├── ARCHITECTURE.md # Deep dive: trimming mechanics, token math, examples
├── config.json # Generated by setup (gitignored)
├── config.example.json # Reference config
├── session-janitor-watcher.service # Linux: systemd user service template
├── session-janitor-watcher.plist # macOS: launchd LaunchAgent template
└── scripts/
├── setup.sh # Discovery + config gen + cron + service install (Linux + macOS)
├── janitor.sh # Main cron entry point
├── trim.py # Transcript trimming (thinking, toolCall args, toolResult collapse)
├── sidecar.py # Large toolResult offloader (→ .toolcache/ files)
├── watcher.sh # Per-turn trigger (sidecar + trim; inotifywait/fswatch)
├── extract-llm.py # LLM memory extraction
├── prune-sessions.py # Stale session pruning
├── watchdog.sh # Hung-session detector + Slack alert
├── test-sidecar.py # Sidecar test suite (8 scenarios, 22 assertions)
└── test-sidecar.sh # Shell wrapper for sidecar tests