mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-29 06:31:32 +00:00
The locale switcher appeared broken because hardcoded markdown links (`](/docs/X)`) got double-prefixed by Docusaurus to `/docs/<locale>/docs/X` (404) in non-English locales, and the MDX hero `<a href>` on the index page escaped locale routing entirely. Changes: - Rewrite 922 `](/docs/X)` -> `](/X)` across 166 docs files (strip trailing .md too). Docusaurus prepends locale + baseUrl itself. - docs/index.md -> index.mdx; hero "Get Started" anchor -> Docusaurus <Link> so it stays inside the active locale. - Drop `ko` locale entirely from docusaurus.config.ts + delete i18n/ko/ (4 stale auto-translated kanban pages, <2% coverage, misleading). Verified `npm run build` succeeds for both en and zh-Hans; `build/zh-Hans/ index.html` has no /docs/zh-Hans/docs/... double-prefixed paths. PR2 will translate the 335 English docs into i18n/zh-Hans/.
548 lines
23 KiB
Markdown
548 lines
23 KiB
Markdown
---
|
||
sidebar_position: 7
|
||
title: "Sessions"
|
||
description: "Session persistence, resume, search, management, and per-platform session tracking"
|
||
---
|
||
|
||
# Sessions
|
||
|
||
Hermes Agent automatically saves every conversation as a session. Sessions enable conversation resume, cross-session search, and full conversation history management.
|
||
|
||
## How Sessions Work
|
||
|
||
Every conversation — whether from the CLI, Telegram, Discord, Slack, WhatsApp, Signal, Matrix, Teams, or any other messaging platform — is stored as a session with full message history. Sessions are tracked in:
|
||
|
||
1. **SQLite database** (`~/.hermes/state.db`) — structured session metadata with FTS5 full-text search, plus full message history
|
||
|
||
The SQLite database stores:
|
||
- Session ID, source platform, user ID
|
||
- **Session title** (unique, human-readable name)
|
||
- Model name and configuration
|
||
- System prompt snapshot
|
||
- Full message history (role, content, tool calls, tool results)
|
||
- Token counts (input/output)
|
||
- Timestamps (started_at, ended_at)
|
||
- Parent session ID (for compression-triggered session splitting)
|
||
|
||
### What Counts Toward Context
|
||
|
||
Hermes stores session history so it can resume conversations, but it does not
|
||
keep re-sending every byte it has ever handled. On each turn, the model sees
|
||
the selected system prompt, the current conversation window, and any content
|
||
Hermes explicitly injects for that turn.
|
||
|
||
Media attachments are handled as turn-scoped inputs:
|
||
|
||
- Images may be attached natively to the next model call, or pre-analyzed into
|
||
a text description when the active model does not support native vision.
|
||
- Audio is transcribed into text when speech-to-text is configured.
|
||
- Text documents can have their extracted text included; other document types
|
||
are usually represented by a saved local path and a short note.
|
||
- Attachment paths and extracted/derived text can appear in the transcript, but
|
||
the raw image, audio, or binary file bytes are not repeatedly copied into
|
||
future prompts.
|
||
|
||
For example, if a user sends an image and asks Hermes to make a meme from it,
|
||
Hermes may inspect that image once with vision and run an image-processing
|
||
script. Future turns do not automatically carry the original JPEG in context.
|
||
They carry only whatever was written into the conversation, such as the user's
|
||
request, a short image description, a local cache path, or the final assistant
|
||
response.
|
||
|
||
The most common cause of context growth is not the media file itself. It is
|
||
verbose text: pasted transcripts, full logs, large tool outputs, long diffs,
|
||
repeated status reports, and detailed proof dumps. Prefer summaries, file
|
||
paths, focused excerpts, and tool-backed lookups over copying large artifacts
|
||
into chat.
|
||
|
||
:::tip
|
||
Use `/compress` when a session gets long, `/new` for a fresh thread, and
|
||
`hermes sessions prune` only when you want to delete old ended sessions from
|
||
storage. Compression reduces the active context; it is not a privacy delete.
|
||
Pass a name to `/new` (e.g. `/new payments-refactor`) to set the new session's
|
||
initial title up front — useful for finding it later with `/resume <name>` or
|
||
in the `/sessions` picker.
|
||
:::
|
||
|
||
### Session Sources
|
||
|
||
Each session is tagged with its source platform:
|
||
|
||
| Source | Description |
|
||
|--------|-------------|
|
||
| `cli` | Interactive CLI (`hermes` or `hermes chat`) |
|
||
| `telegram` | Telegram messenger |
|
||
| `discord` | Discord server/DM |
|
||
| `slack` | Slack workspace |
|
||
| `whatsapp` | WhatsApp messenger |
|
||
| `signal` | Signal messenger |
|
||
| `matrix` | Matrix rooms and DMs |
|
||
| `mattermost` | Mattermost channels |
|
||
| `email` | Email (IMAP/SMTP) |
|
||
| `sms` | SMS via Twilio |
|
||
| `dingtalk` | DingTalk messenger |
|
||
| `feishu` | Feishu/Lark messenger |
|
||
| `wecom` | WeCom (WeChat Work) |
|
||
| `weixin` | Weixin (personal WeChat) |
|
||
| `bluebubbles` | Apple iMessage via BlueBubbles macOS server |
|
||
| `qqbot` | QQ Bot (Tencent QQ) via Official API v2 |
|
||
| `homeassistant` | Home Assistant conversation |
|
||
| `webhook` | Incoming webhooks |
|
||
| `api-server` | API server requests |
|
||
| `acp` | ACP editor integration |
|
||
| `cron` | Scheduled cron jobs |
|
||
| `batch` | Batch processing runs |
|
||
|
||
## CLI Session Resume
|
||
|
||
Resume previous conversations from the CLI using `--continue` or `--resume`:
|
||
|
||
### Continue Last Session
|
||
|
||
```bash
|
||
# Resume the most recent CLI session
|
||
hermes --continue
|
||
hermes -c
|
||
|
||
# Or with the chat subcommand
|
||
hermes chat --continue
|
||
hermes chat -c
|
||
```
|
||
|
||
This looks up the most recent `cli` session from the SQLite database and loads its full conversation history.
|
||
|
||
### Resume by Name
|
||
|
||
If you've given a session a title (see [Session Naming](#session-naming) below), you can resume it by name:
|
||
|
||
```bash
|
||
# Resume a named session
|
||
hermes -c "my project"
|
||
|
||
# If there are lineage variants (my project, my project #2, my project #3),
|
||
# this automatically resumes the most recent one
|
||
hermes -c "my project" # → resumes "my project #3"
|
||
```
|
||
|
||
### Resume Specific Session
|
||
|
||
```bash
|
||
# Resume a specific session by ID
|
||
hermes --resume 20250305_091523_a1b2c3d4
|
||
hermes -r 20250305_091523_a1b2c3d4
|
||
|
||
# Resume by title
|
||
hermes --resume "refactoring auth"
|
||
|
||
# Or with the chat subcommand
|
||
hermes chat --resume 20250305_091523_a1b2c3d4
|
||
```
|
||
|
||
Session IDs are shown when you exit a CLI session, and can be found with `hermes sessions list`.
|
||
|
||
### Conversation Recap on Resume
|
||
|
||
When you resume a session, Hermes displays a compact recap of the previous conversation in a styled panel before the input prompt:
|
||
|
||
<img className="docs-terminal-figure" src="/img/docs/session-recap.svg" alt="Stylized preview of the Previous Conversation recap panel shown when resuming a Hermes session." />
|
||
<p className="docs-figure-caption">Resume mode shows a compact recap panel with recent user and assistant turns before returning you to the live prompt.</p>
|
||
|
||
The recap:
|
||
- Shows **user messages** (gold `●`) and **assistant responses** (green `◆`)
|
||
- **Truncates** long messages (300 chars for user, 200 chars / 3 lines for assistant)
|
||
- **Collapses tool calls** to a count with tool names (e.g., `[3 tool calls: terminal, web_search]`)
|
||
- **Hides** system messages, tool results, and internal reasoning
|
||
- **Caps** at the last 10 exchanges with a "... N earlier messages ..." indicator
|
||
- Uses **dim styling** to distinguish from the active conversation
|
||
|
||
To disable the recap and keep the minimal one-liner behavior, set in `~/.hermes/config.yaml`:
|
||
|
||
```yaml
|
||
display:
|
||
resume_display: minimal # default: full
|
||
```
|
||
|
||
:::tip
|
||
Session IDs follow the format `YYYYMMDD_HHMMSS_<hex>` — CLI/TUI sessions use a 6-char hex suffix (e.g. `20250305_091523_a1b2c3`), gateway sessions use an 8-char suffix (e.g. `20250305_091523_a1b2c3d4`). You can resume by ID (full or unique prefix) or by title — both work with `-c` and `-r`.
|
||
:::
|
||
|
||
## Cross-Platform Handoff
|
||
|
||
Use `/handoff <platform>` from a CLI session to transfer the live conversation to a messaging platform's home channel. The agent picks up exactly where the CLI left off — same session id, full role-aware transcript, tool calls and all.
|
||
|
||
```bash
|
||
# Inside a CLI session
|
||
/handoff telegram
|
||
```
|
||
|
||
What happens:
|
||
|
||
1. The CLI validates that `<platform>` is enabled and has a home channel set (run `/sethome` from the destination chat once to configure it).
|
||
2. The CLI marks the session pending and **block-polls the gateway**. It refuses if the agent is mid-turn — wait for the current response to finish first.
|
||
3. The gateway watcher claims the handoff and asks the destination adapter for a fresh thread:
|
||
- **Telegram** — opens a new forum topic (DM topics if Bot API 9.4+ Topics mode is enabled in the chat, or a forum supergroup topic).
|
||
- **Discord** — creates a 1440-min auto-archive thread under the home text channel.
|
||
- **Slack** — posts a seed message and uses its `ts` as the thread anchor.
|
||
- **WhatsApp / Signal / Matrix / SMS** — no native threads, falls back to the home channel directly.
|
||
4. The gateway re-binds the destination key to your existing CLI session id, then forges a synthetic user turn asking the agent to confirm and summarize. The reply lands in the new thread.
|
||
5. When the gateway acknowledges success, the CLI prints a `/resume` hint and exits cleanly:
|
||
|
||
```
|
||
↻ Handoff complete. The session is now active on telegram.
|
||
Resume it on this CLI later with: /resume my-session-title
|
||
```
|
||
|
||
6. From that point, the conversation lives on the platform. Reply in the new thread — anyone authorized in that channel shares the same session, and any later real user message in the thread joins seamlessly because thread sessions key without `user_id`.
|
||
|
||
**Resume back to CLI:** when you want to come back to a desktop, just run `/resume <title>` (or `hermes -r "<title>"` from the shell) and pick up where the platform left off.
|
||
|
||
**Failure modes:**
|
||
- No home channel configured → CLI refuses with a `/sethome` hint.
|
||
- Platform not enabled / gateway not running → CLI times out at 60s with a clear message and your CLI session stays intact.
|
||
- Thread creation fails (permissions, topics-mode off) → falls back to the home channel directly and still completes; no thread isolation but the handoff itself works.
|
||
- `adapter.send` fails (rate limit, transient API error) → handoff marked failed with the reason; the row clears so you can retry.
|
||
|
||
**Limitation worth knowing:** for non-thread-capable platforms with multi-user group home channels, the synthetic turn keys as a DM-style session. This works for self-DM home channels (the typical setup) but isn't ideal for genuinely shared group chats. Threading covers Telegram / Discord / Slack — by far the common case — so most setups never hit this.
|
||
|
||
## Session Naming
|
||
|
||
Give sessions human-readable titles so you can find and resume them easily.
|
||
|
||
### Auto-Generated Titles
|
||
|
||
Hermes automatically generates a short descriptive title (3–7 words) for each session after the first exchange. This runs in a background thread using a fast auxiliary model, so it adds no latency. You'll see auto-generated titles when browsing sessions with `hermes sessions list` or `hermes sessions browse`.
|
||
|
||
Auto-titling only fires once per session and is skipped if you've already set a title manually.
|
||
|
||
### Setting a Title Manually
|
||
|
||
Use the `/title` slash command inside any chat session (CLI or gateway):
|
||
|
||
```
|
||
/title my research project
|
||
```
|
||
|
||
The title is applied immediately. If the session hasn't been created in the database yet (e.g., you run `/title` before sending your first message), it's queued and applied once the session starts.
|
||
|
||
You can also rename existing sessions from the command line:
|
||
|
||
```bash
|
||
hermes sessions rename 20250305_091523_a1b2c3d4 "refactoring auth module"
|
||
```
|
||
|
||
### Title Rules
|
||
|
||
- **Unique** — no two sessions can share the same title
|
||
- **Max 100 characters** — keeps listing output clean
|
||
- **Sanitized** — control characters, zero-width chars, and RTL overrides are stripped automatically
|
||
- **Normal Unicode is fine** — emoji, CJK, accented characters all work
|
||
|
||
### Auto-Lineage on Compression
|
||
|
||
When a session's context is compressed (manually via `/compress` or automatically), Hermes creates a new continuation session. If the original had a title, the new session automatically gets a numbered title:
|
||
|
||
```
|
||
"my project" → "my project #2" → "my project #3"
|
||
```
|
||
|
||
When you resume by name (`hermes -c "my project"`), it automatically picks the most recent session in the lineage.
|
||
|
||
### /title in Messaging Platforms
|
||
|
||
The `/title` command works in all gateway platforms (Telegram, Discord, Slack, WhatsApp):
|
||
|
||
- `/title My Research` — set the session title
|
||
- `/title` — show the current title
|
||
|
||
## Session Management Commands
|
||
|
||
Hermes provides a full set of session management commands via `hermes sessions`:
|
||
|
||
### List Sessions
|
||
|
||
```bash
|
||
# List recent sessions (default: last 20)
|
||
hermes sessions list
|
||
|
||
# Filter by platform
|
||
hermes sessions list --source telegram
|
||
|
||
# Show more sessions
|
||
hermes sessions list --limit 50
|
||
```
|
||
|
||
When sessions have titles, the output shows titles, previews, and relative timestamps:
|
||
|
||
```
|
||
Title Preview Last Active ID
|
||
────────────────────────────────────────────────────────────────────────────────────────────────
|
||
refactoring auth Help me refactor the auth module please 2h ago 20250305_091523_a
|
||
my project #3 Can you check the test failures? yesterday 20250304_143022_e
|
||
— What's the weather in Las Vegas? 3d ago 20250303_101500_f
|
||
```
|
||
|
||
When no sessions have titles, a simpler format is used:
|
||
|
||
```
|
||
Preview Last Active Src ID
|
||
──────────────────────────────────────────────────────────────────────────────────────
|
||
Help me refactor the auth module please 2h ago cli 20250305_091523_a
|
||
What's the weather in Las Vegas? 3d ago tele 20250303_101500_f
|
||
```
|
||
|
||
### Export Sessions
|
||
|
||
```bash
|
||
# Export all sessions to a JSONL file
|
||
hermes sessions export backup.jsonl
|
||
|
||
# Export sessions from a specific platform
|
||
hermes sessions export telegram-history.jsonl --source telegram
|
||
|
||
# Export a single session
|
||
hermes sessions export session.jsonl --session-id 20250305_091523_a1b2c3d4
|
||
```
|
||
|
||
Exported files contain one JSON object per line with full session metadata and all messages.
|
||
|
||
### Delete a Session
|
||
|
||
```bash
|
||
# Delete a specific session (with confirmation)
|
||
hermes sessions delete 20250305_091523_a1b2c3d4
|
||
|
||
# Delete without confirmation
|
||
hermes sessions delete 20250305_091523_a1b2c3d4 --yes
|
||
```
|
||
|
||
### Rename a Session
|
||
|
||
```bash
|
||
# Set or change a session's title
|
||
hermes sessions rename 20250305_091523_a1b2c3d4 "debugging auth flow"
|
||
|
||
# Multi-word titles don't need quotes in the CLI
|
||
hermes sessions rename 20250305_091523_a1b2c3d4 debugging auth flow
|
||
```
|
||
|
||
If the title is already in use by another session, an error is shown.
|
||
|
||
### Prune Old Sessions
|
||
|
||
```bash
|
||
# Delete ended sessions older than 90 days (default)
|
||
hermes sessions prune
|
||
|
||
# Custom age threshold
|
||
hermes sessions prune --older-than 30
|
||
|
||
# Only prune sessions from a specific platform
|
||
hermes sessions prune --source telegram --older-than 60
|
||
|
||
# Skip confirmation
|
||
hermes sessions prune --older-than 30 --yes
|
||
```
|
||
|
||
:::info
|
||
Pruning only deletes **ended** sessions (sessions that have been explicitly ended or auto-reset). Active sessions are never pruned.
|
||
:::
|
||
|
||
### Session Statistics
|
||
|
||
```bash
|
||
hermes sessions stats
|
||
```
|
||
|
||
Output:
|
||
|
||
```
|
||
Total sessions: 142
|
||
Total messages: 3847
|
||
cli: 89 sessions
|
||
telegram: 38 sessions
|
||
discord: 15 sessions
|
||
Database size: 12.4 MB
|
||
```
|
||
|
||
For deeper analytics — token usage, cost estimates, tool breakdown, and activity patterns — use [`hermes insights`](/reference/cli-commands#hermes-insights).
|
||
|
||
## Session Search Tool
|
||
|
||
The agent has a built-in `session_search` tool that performs full-text search across all past conversations using SQLite's FTS5 engine — and lets the agent scroll through any session it finds. No LLM calls, no summarization, no truncation. Every shape returns actual messages from the DB.
|
||
|
||
### Three calling shapes
|
||
|
||
The tool infers what you want from which arguments you set. There's no `mode` parameter.
|
||
|
||
**1. Discovery — pass `query`:**
|
||
|
||
```python
|
||
session_search(query="auth refactor", limit=3)
|
||
```
|
||
|
||
Runs FTS5, dedupes hits by session lineage, returns the top N sessions. Each result carries:
|
||
|
||
- `session_id`, `title`, `when`, `source`
|
||
- `snippet` — FTS5-highlighted match excerpt
|
||
- `bookend_start` — first 3 user+assistant messages of the session (the goal/kickoff)
|
||
- `messages` — ±5 messages around the FTS5 match, with the anchor message flagged (the hit in context)
|
||
- `bookend_end` — last 3 user+assistant messages of the session (the resolution/decisions)
|
||
- `match_message_id`, `messages_before`, `messages_after`
|
||
|
||
Bookends + window together reconstruct goal → match → resolution without paying for the whole transcript. Typical wall time: 15–50ms on a real session DB.
|
||
|
||
**2. Scroll — pass `session_id` + `around_message_id`:**
|
||
|
||
```python
|
||
session_search(session_id="20260510_174648_805cc2", around_message_id=590803, window=10)
|
||
```
|
||
|
||
Returns a window of ±`window` messages centered on the anchor. No FTS5, no bookends — just the slice. Use after a discovery call when you need more context than the ±5 default window.
|
||
|
||
- To scroll **forward**: pass `messages[-1].id` back as `around_message_id`
|
||
- To scroll **backward**: pass `messages[0].id` back as `around_message_id`
|
||
- The boundary message appears in both windows as an orientation marker
|
||
- When `messages_before` or `messages_after` is less than `window`, you're at the start or end of the session
|
||
|
||
Typical wall time: 1–2ms per scroll call.
|
||
|
||
**3. Browse — no args:**
|
||
|
||
```python
|
||
session_search()
|
||
```
|
||
|
||
Returns recent sessions chronologically (titles, previews, timestamps). Useful when the user asks "what was I working on" without naming a topic.
|
||
|
||
### FTS5 query syntax
|
||
|
||
The keyword mode supports standard FTS5 query syntax:
|
||
|
||
- Simple keywords: `docker deployment` (FTS5 defaults to AND)
|
||
- Phrases: `"exact phrase"`
|
||
- Boolean: `docker OR kubernetes`, `python NOT java`
|
||
- Prefix: `deploy*`
|
||
|
||
### Optional parameters
|
||
|
||
- `sort` — `newest` or `oldest`, on top of FTS5 ranking. Omit for relevance-only ordering (the default; suitable for exploratory recall). Use `newest` for "where did we leave X" questions, `oldest` for "how did X start" questions.
|
||
- `role_filter` — comma-separated roles to include. Discovery defaults to `user,assistant` (tool output is usually noise). Pass `user,assistant,tool` to include tool output (debugging tool behaviour) or `tool` to search tool output only.
|
||
|
||
### When It's Used
|
||
|
||
The agent is prompted to use session search automatically:
|
||
|
||
> *"When the user references something from a past conversation or you suspect relevant prior context exists, use session_search to recall it before asking them to repeat themselves."*
|
||
|
||
Typical triggers: "we did this before", "remember when", "last time", "as I mentioned", or any reference to a project/person/concept that isn't in the current window.
|
||
|
||
## Per-Platform Session Tracking
|
||
|
||
### Gateway Sessions
|
||
|
||
On messaging platforms, sessions are keyed by a deterministic session key built from the message source:
|
||
|
||
| Chat Type | Default Key Format | Behavior |
|
||
|-----------|--------------------|----------|
|
||
| Telegram DM | `agent:main:telegram:dm:<chat_id>` | One session per DM chat |
|
||
| Discord DM | `agent:main:discord:dm:<chat_id>` | One session per DM chat |
|
||
| WhatsApp DM | `agent:main:whatsapp:dm:<canonical_identifier>` | One session per DM user (LID/phone aliases collapse to one identity when mapping exists) |
|
||
| Group chat | `agent:main:<platform>:group:<chat_id>:<user_id>` | Per-user inside the group when the platform exposes a user ID |
|
||
| Group thread/topic | `agent:main:<platform>:group:<chat_id>:<thread_id>` | Shared session for all thread participants (default). Per-user with `thread_sessions_per_user: true`. |
|
||
| Channel | `agent:main:<platform>:channel:<chat_id>:<user_id>` | Per-user inside the channel when the platform exposes a user ID |
|
||
|
||
When Hermes cannot get a participant identifier for a shared chat, it falls back to one shared session for that room.
|
||
|
||
### Shared vs Isolated Group Sessions
|
||
|
||
By default, Hermes uses `group_sessions_per_user: true` in `config.yaml`. That means:
|
||
|
||
- Alice and Bob can both talk to Hermes in the same Discord channel without sharing transcript history
|
||
- one user's long tool-heavy task does not pollute another user's context window
|
||
- interrupt handling also stays per-user because the running-agent key matches the isolated session key
|
||
|
||
If you want one shared "room brain" instead, set:
|
||
|
||
```yaml
|
||
group_sessions_per_user: false
|
||
```
|
||
|
||
That reverts groups/channels to a single shared session per room, which preserves shared conversational context but also shares token costs, interrupt state, and context growth.
|
||
|
||
### Session Reset Policies
|
||
|
||
Gateway sessions are automatically reset based on configurable policies:
|
||
|
||
- **idle** — reset after N minutes of inactivity
|
||
- **daily** — reset at a specific hour each day
|
||
- **both** — reset on whichever comes first (idle or daily)
|
||
- **none** — never auto-reset
|
||
|
||
Before a session is auto-reset, the agent is given a turn to save any important memories or skills from the conversation.
|
||
|
||
Sessions with **active background processes** are never auto-reset, regardless of policy.
|
||
|
||
## Storage Locations
|
||
|
||
| What | Path | Description |
|
||
|------|------|-------------|
|
||
| SQLite database | `~/.hermes/state.db` | All session metadata + messages with FTS5 |
|
||
| Gateway messages | `~/.hermes/state.db` | SQLite — canonical store for all session messages |
|
||
| Gateway routing index | `~/.hermes/sessions/sessions.json` | Maps session keys to active session IDs (origin metadata, expiry flags) |
|
||
|
||
The SQLite database uses WAL mode for concurrent readers and a single writer, which suits the gateway's multi-platform architecture well.
|
||
|
||
:::note Legacy JSONL transcripts
|
||
Sessions created before state.db became canonical may have leftover
|
||
`*.jsonl` files in `~/.hermes/sessions/`. They are no longer written or
|
||
read by Hermes. Safe to delete after verifying the corresponding session
|
||
exists in state.db.
|
||
:::
|
||
|
||
### Database Schema
|
||
|
||
Key tables in `state.db`:
|
||
|
||
- **sessions** — session metadata (id, source, user_id, model, title, timestamps, token counts). Titles have a unique index (NULL titles allowed, only non-NULL must be unique).
|
||
- **messages** — full message history (role, content, tool_calls, tool_name, token_count)
|
||
- **messages_fts** — FTS5 virtual table for full-text search across message content
|
||
|
||
## Session Expiry and Cleanup
|
||
|
||
### Automatic Cleanup
|
||
|
||
- Gateway sessions auto-reset based on the configured reset policy
|
||
- Before reset, the agent saves memories and skills from the expiring session
|
||
- Opt-in auto-pruning: when `sessions.auto_prune` is `true`, ended sessions older than `sessions.retention_days` (default 90) are pruned at CLI/gateway startup
|
||
- After a prune that actually removed rows, `state.db` is `VACUUM`ed to reclaim disk space (SQLite does not shrink the file on plain DELETE)
|
||
- Pruning runs at most once per `sessions.min_interval_hours` (default 24); the last-run timestamp is tracked inside `state.db` itself so it's shared across every Hermes process in the same `HERMES_HOME`
|
||
|
||
Default is **off** — session history is valuable for `session_search` recall, and silently deleting it could surprise users. Enable in `~/.hermes/config.yaml`:
|
||
|
||
```yaml
|
||
sessions:
|
||
auto_prune: true # opt in — default is false
|
||
retention_days: 90 # keep ended sessions this many days
|
||
vacuum_after_prune: true # reclaim disk space after a pruning sweep
|
||
min_interval_hours: 24 # don't re-run the sweep more often than this
|
||
```
|
||
|
||
Active sessions are never auto-pruned, regardless of age.
|
||
|
||
### Manual Cleanup
|
||
|
||
```bash
|
||
# Prune sessions older than 90 days
|
||
hermes sessions prune
|
||
|
||
# Delete a specific session
|
||
hermes sessions delete <session_id>
|
||
|
||
# Export before pruning (backup)
|
||
hermes sessions export backup.jsonl
|
||
hermes sessions prune --older-than 30 --yes
|
||
```
|
||
|
||
:::tip
|
||
The database grows slowly (typical: 10-15 MB for hundreds of sessions) and session history powers `session_search` recall across past conversations, so auto-prune ships disabled. Enable it if you're running a heavy gateway/cron workload where `state.db` is meaningfully affecting performance (observed failure mode: 384 MB state.db with ~1000 sessions slowing down FTS5 inserts and `/resume` listing). Use `hermes sessions prune` for one-off cleanup without turning on the automatic sweep.
|
||
:::
|