mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-20 23:21:44 +00:00
* refactor(sessions): migrate runtime storage to sqlite * test(sessions): fix sqlite CI regressions * test(sessions): align remaining sqlite fixtures * fix(codex): require sqlite trajectory recorder * test(sessions): align orphan recovery sqlite fixture * test(sessions): align sqlite rebase fixtures * fix(sessions): finish current-main integration of the sqlite flip Resolve the whole-store SDK removal across its owner boundary: drop the loadSessionStore re-export and the registry whole-store wrappers, wire hasTrackedActiveSessionRun into gateway chat, complete the preserveLockedHarnessIds cleanup contract, flip the codex thread-history import to storePath targets, and port remaining main-side tests from file-store helpers to session accessor reads. * chore: drop committed pebbles log, revert plugin-inspector bump, refresh generated docs Remove the 1.8k-line .pebbles/events.jsonl work log from the branch, restore the plugin-inspector advisory lane to main's pinned 0.3.10 so the supply-chain bump gets its own review, and regenerate docs_map, the plugin SDK API baseline, and the export-surface ratchet for the merged tree. * feat(sessions): keep archived transcripts by default with zstd cold storage Codex-style retention: deleting or resetting a session archives its transcript as a zstd-compressed JSONL artifact (plain when the runtime lacks node:zlib zstd) and keeps it until the disk budget evicts oldest first. resetArchiveRetention now governs both deleted and reset archives and defaults to keep; maxDiskBytes defaults to 2gb so retention stays bounded, with archives evicted before live sessions. The cron reaper follows the same knob instead of deleting archives on its own timer. * fix(state): converge agent DB migration lineages and bound database growth Merge coherence: run both structure-gated legacy memory-schema repairs (flip-lineage drop, main-lineage identity rebuild) before the flip migration so pre-flip v1/v2 and pre-merge flip v1/v4 databases all converge, and hoist foreign_keys=OFF outside the schema transaction where the pragma was silently ignored and the v1 sessions rebuild cascade-deleted session_entries. Growth guards: fresh agent DBs enable auto_vacuum=INCREMENTAL, WAL maintenance releases freed pages in bounded passes (never a blocking full VACUUM), and doctor reports state/agent DB bloat from freelist stats. * fix(codex): resolve the store path for thread-history import via the SDK The supervision catalog passed the legacy sessionFile locator to the storePath-targeted transcript mirror; resolve the agent store path with the session-store SDK helper instead of a runtime-object seam so test fakes and headless callers need no extra surface. Drop the obsolete missing-session-id preprocessing case: sessions rows are NOT NULL on session_id and upsert repairs id-less patches at write time. * fix(sessions): fail safe on malformed disk-budget config and doctor stat errors A malformed explicit maxDiskBytes disables the budget instead of falling back to the destructive 2gb default the user never chose, and the doctor bloat check skips databases whose paths stat-fail instead of aborting doctor. * fix(sessions): complete sqlite conflict translations * test(sqlite): align hardening checks with maintenance * test(sessions): inspect compressed transcript archives * fix(tests): await session seeds and drop unused helpers flagged by CI lint The five unawaited writeSessionStoreSeed calls raced their SQLite seeds against the assertions, failing compact shards; the bloat probe drops a useless initializer and the merged tests drop now-unused helpers. * test(sessions): type legacy proof events directly * test(sessions): align hardening contracts * perf(sessions): read usage transcript sizes from SQL aggregates Usage/cost scans walked every session and materialized every transcript event just to re-stringify it for a byte estimate — the #86718 stall class reborn on the DB. readTranscriptStatsSync sums stored JSON bytes in SQLite without loading a single row. * fix(sessions): re-root foreign-root transcript paths onto the current sessions dir Restored backups, moved OPENCLAW_STATE_DIR, and rehearsal copies carry absolute sessionFile paths from the old root; the containment fallback kept those foreign paths, so migration read (and would archive) files in the original root and reported local copies missing. Re-root the canonical agents/<id>/sessions suffix onto the current dir when the file exists there; genuine cross-root layouts still fall through unchanged. * test(agents): seed harness admission through sqlite * fix(sqlite): close agent db on pragma setup failure * fix(doctor): compact and retrofit incremental auto-vacuum after session import The migration is the sanctioned offline window: post-import compact reclaims import churn and applies auto_vacuum=INCREMENTAL to databases created before the fresh-DB pragma existed, so runtime maintenance can release pages in bounded passes on every install. --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
205 lines
8.1 KiB
Markdown
205 lines
8.1 KiB
Markdown
---
|
|
summary: "How OpenClaw manages conversation sessions"
|
|
read_when:
|
|
- You want to understand session routing and isolation
|
|
- You want to configure DM scope for multi-user setups
|
|
- You are debugging daily or idle session resets
|
|
title: "Session management"
|
|
---
|
|
|
|
OpenClaw routes every inbound message to a **session** based on where it came
|
|
from: DMs, group chats, cron jobs, etc. All session state is owned by the
|
|
**gateway**; UI clients query the gateway for session data.
|
|
|
|
## How messages are routed
|
|
|
|
| Source | Behavior |
|
|
| --------------- | ------------------------- |
|
|
| Direct messages | Shared session by default |
|
|
| Group chats | Isolated per group |
|
|
| Rooms/channels | Isolated per room |
|
|
| Cron jobs | Fresh session per run |
|
|
| Webhooks | Isolated per hook |
|
|
|
|
## DM isolation
|
|
|
|
By default, all DMs share one session for continuity, which is fine for
|
|
single-user setups.
|
|
|
|
<Warning>
|
|
If multiple people can message your agent, enable DM isolation. Without it, all
|
|
users share the same conversation context, so Alice's private messages would be
|
|
visible to Bob.
|
|
</Warning>
|
|
|
|
```json5
|
|
{
|
|
session: {
|
|
dmScope: "per-channel-peer", // isolate by channel + sender
|
|
},
|
|
}
|
|
```
|
|
|
|
`session.dmScope` options:
|
|
|
|
| Value | Behavior |
|
|
| -------------------------- | ----------------------------------------- |
|
|
| `main` (default) | All DMs share one session |
|
|
| `per-peer` | Isolate by sender, across channels |
|
|
| `per-channel-peer` | Isolate by channel + sender (recommended) |
|
|
| `per-account-channel-peer` | Isolate by account + channel + sender |
|
|
|
|
<Tip>
|
|
If the same person contacts you from multiple channels, use
|
|
`session.identityLinks` to map their identities to one canonical peer id so
|
|
they share a session.
|
|
</Tip>
|
|
|
|
### Dock linked channels
|
|
|
|
Dock commands move the current direct-chat session's reply route to another
|
|
linked channel without starting a new session. See
|
|
[Channel docking](/concepts/channel-docking) for examples, config, and
|
|
troubleshooting.
|
|
|
|
Verify your setup with `openclaw security audit`.
|
|
|
|
## Session lifecycle
|
|
|
|
Sessions are reused until they expire under `session.reset`:
|
|
|
|
- **Daily reset** (default `mode: "daily"`) - new session at a configured local
|
|
hour (`session.reset.atHour`, default `4`, 0-23) on the gateway host. Daily
|
|
freshness is based on when the current `sessionId` started, not on later
|
|
metadata writes.
|
|
- **Idle reset** (`mode: "idle"`) - new session after `session.reset.idleMinutes`
|
|
of inactivity. Idle freshness is based on the last real user/channel
|
|
interaction, so heartbeat, cron, and exec system events do not keep the
|
|
session alive.
|
|
- **Manual reset** - type `/new` or `/reset` in chat. `/new <model>` also
|
|
switches the model.
|
|
|
|
When both daily and idle resets are configured, whichever expires first wins.
|
|
Heartbeat, cron, exec, and other system-event turns may write session metadata,
|
|
but those writes do not extend daily or idle reset freshness. When a reset
|
|
rolls the session, queued system-event notices for the old session are
|
|
discarded so stale background updates are not prepended to the first prompt in
|
|
the new session.
|
|
|
|
Sessions with an active provider-owned CLI session are not cut by the implicit
|
|
daily default. Use `/reset` or configure `session.reset` explicitly when those
|
|
sessions should expire on a timer.
|
|
|
|
Override the default per chat type or per channel:
|
|
|
|
```json5
|
|
{
|
|
session: {
|
|
reset: { mode: "daily", atHour: 4 },
|
|
resetByType: {
|
|
group: { mode: "idle", idleMinutes: 120 },
|
|
thread: { mode: "daily", atHour: 6 },
|
|
},
|
|
resetByChannel: {
|
|
discord: { mode: "idle", idleMinutes: 10080 },
|
|
},
|
|
},
|
|
}
|
|
```
|
|
|
|
`resetByType` supports `direct` (legacy alias `dm`), `group`, and `thread`.
|
|
Legacy top-level `session.idleMinutes` still works as a compatibility alias for
|
|
an idle-mode default when no `session.reset`/`resetByType` block is set.
|
|
|
|
## Where state lives
|
|
|
|
- **Runtime session rows:** `~/.openclaw/agents/<agentId>/agent/openclaw-agent.sqlite`
|
|
- **Archived transcript files:** `~/.openclaw/agents/<agentId>/sessions/`
|
|
- **Legacy row migration source:** `~/.openclaw/agents/<agentId>/sessions/sessions.json`
|
|
|
|
The session rows in the per-agent SQLite database keep separate lifecycle
|
|
timestamps:
|
|
|
|
- `sessionStartedAt`: when the current `sessionId` began; daily reset uses this.
|
|
- `lastInteractionAt`: last user/channel interaction that extends idle lifetime.
|
|
- `updatedAt`: last store-row mutation; useful for listing and pruning, but not
|
|
authoritative for daily/idle reset freshness.
|
|
|
|
During migration from older installs, gateway startup and `openclaw doctor
|
|
--fix` import legacy `sessions.json` rows and hot transcript JSONL history into
|
|
SQLite automatically. Rows without `sessionStartedAt` are resolved from the
|
|
legacy transcript JSONL session header when available. If an older row also
|
|
lacks `lastInteractionAt`, idle freshness falls back to that session start time,
|
|
not to later bookkeeping writes. Use `openclaw doctor --session-sqlite inspect
|
|
--session-sqlite-all-agents` and the [Doctor migration
|
|
sequence](/cli/doctor#session-sqlite-migration) when you want explicit
|
|
inspection or validation evidence.
|
|
|
|
## Session maintenance
|
|
|
|
OpenClaw bounds session storage over time via `session.maintenance`, defaults
|
|
shown:
|
|
|
|
```json5
|
|
{
|
|
session: {
|
|
maintenance: {
|
|
mode: "enforce", // "enforce" applies cleanup; "warn" only reports
|
|
pruneAfter: "30d",
|
|
maxEntries: 500,
|
|
},
|
|
},
|
|
}
|
|
```
|
|
|
|
For production-sized `maxEntries` limits, Gateway runtime writes use a small
|
|
high-water buffer and clean back down to the configured cap in batches.
|
|
Session store reads do not prune or cap entries during Gateway startup, so
|
|
startup and isolated cron sessions do not pay for a full store cleanup.
|
|
`openclaw sessions cleanup --enforce` applies the cap immediately.
|
|
|
|
Gateway model-run probe sessions are short-lived by default. Rows matching
|
|
`agent:*:explicit:model-run-<uuid>` use fixed `24h` retention, but cleanup is
|
|
pressure-gated: it only removes stale probe rows when session-entry
|
|
maintenance/cap pressure is reached, and runs before the broader stale-entry
|
|
age cutoff and entry cap. Normal direct, group, thread, cron, hook, heartbeat,
|
|
ACP, and sub-agent sessions do not inherit this 24h retention.
|
|
|
|
Maintenance preserves durable external conversation pointers, including group
|
|
sessions and thread-scoped chat sessions, while still allowing synthetic cron,
|
|
hook, heartbeat, ACP, and sub-agent entries to age out.
|
|
|
|
If you previously used DM isolation and later returned `session.dmScope` to
|
|
`main`, preview stale peer-keyed DM rows with
|
|
`openclaw sessions cleanup --dry-run --fix-dm-scope`. Applying the same flag
|
|
retires those old direct-DM rows and keeps their transcripts as deleted
|
|
archives.
|
|
|
|
Preview any maintenance run with `openclaw sessions cleanup --dry-run`.
|
|
|
|
## Inspecting sessions
|
|
|
|
| Command | Shows |
|
|
| -------------------------- | ----------------------------------------------- |
|
|
| `openclaw status` | Session store path and recent activity |
|
|
| `openclaw sessions --json` | All sessions (filter with `--active <minutes>`) |
|
|
| `/status` in chat | Context usage, model, and toggles |
|
|
| `/context list` | What is in the system prompt |
|
|
|
|
## Further reading
|
|
|
|
- [Session Pruning](/concepts/session-pruning) - trimming tool results
|
|
- [Compaction](/concepts/compaction) - summarizing long conversations
|
|
- [Session Tools](/concepts/session-tool) - agent tools for cross-session work
|
|
- [Session Management Deep Dive](/reference/session-management-compaction) -
|
|
store schema, transcripts, send policy, origin metadata, and advanced config
|
|
- [Multi-Agent](/concepts/multi-agent) - routing and session isolation across agents
|
|
- [Background Tasks](/automation/tasks) - how detached work creates task records with session references
|
|
- [Channel Routing](/channels/channel-routing) - how inbound messages are routed to sessions
|
|
|
|
## Related
|
|
|
|
- [Session pruning](/concepts/session-pruning)
|
|
- [Session tools](/concepts/session-tool)
|
|
- [Command queue](/concepts/queue)
|