Tutorial 8: Session Management
Tutorial 8: Session Management — easy-to-understand guide based on official docs
Tutorial 8: Session Management
Welcome back! In this tutorial, we’re diving into one of Hermes Agent’s most powerful features: sessions. If you’ve ever closed your terminal and wished you could pick up right where you left off, or wanted to search through an old conversation to find that one command you ran weeks ago, this tutorial is for you.
What Are Sessions?
Think of a session as a complete record of a conversation. Every time you chat with Hermes — whether through the CLI, Telegram, Discord, Slack, WhatsApp, or any of the other 20+ supported platforms — Hermes automatically saves the entire conversation as a session.
These sessions live in a SQLite database at ~/.hermes/state.db. This database stores everything:
- Session metadata: ID, platform, user, timestamps
- A unique, human-readable title for each session
- Model configuration and system prompt snapshots
- Full message history including tool calls and results
- Token counts for input and output
- Parent session ID for tracking session splits
The best part? The database uses FTS5 full-text search, so you can search across all your conversations instantly.
Sessions are tagged by source. Alongside the usual platforms, there are a few special ones worth knowing:
oneshot— finite non-interactive runs likehermes chat --oneshot -q,-Q,hermes -z, or-qon non-TTY stdio. These are hidden from the TUI, Desktop, and dashboard session pickers, but they still count as CLI history:hermes -c/--resume latestcontinue the last one-shot, andhermes sessions listshows it. An explicit--source <tag>always wins.kanban— Kanban dispatcher workers, readable on the board but hidden from session pickers.tool— third-party integrations (--source tool), also hidden from session pickers.homeassistant— Home Assistant events (plugin).
A session compressed mid-conversation keeps the same source, so a compression child of a --source tool or oneshot run inherits the same picker visibility.
Authenticated Gateway Session Access and Attribution
The gateway owns execution; a client does not own the running agent. Verified operators of the same gateway profile can attach to canonical local CLI, TUI, or Desktop sessions created through another operator authentication method. For example, a native local operator and an authenticated dashboard operator can access the same conversation without creating another runtime. Closing a client connection detaches that client; accepted work remains with the gateway.
This deliberately replaces creator-only access for verified operators, preserving the dashboard’s existing operator permissions. Password and OAuth identities stay distinct but do not create separate access barriers to canonical local sessions when both connections have operator access to the profile. The trusted server paths include native interactive bootstrap, authenticated dashboard tickets, internal credentials used by server-spawned PTY clients, and the verified legacy dashboard token. HTTP session mutations also accept the verified native HTTP owner grant. This does not grant operator permissions to restricted worker, messaging, room, or service credentials, or remove profile and operation-specific checks.
Attribution remains identity-specific. Dashboard identities include the provider, verified OAuth issuer (when present), and subject. Creation receipts retain the original identity; submissions and mutation retries retain the acting identity. An operator joining a session does not replace its recorded owner or merge retry identities. Native bootstrap owner keys and session IDs remain unchanged.
Older remote sessions whose receipts contain only a bare subject cannot establish which provider owned them. They remain stored but are not automatically assigned to the first authenticated account requesting them. Operator access does not permit remote first-claim adoption of unowned historical transcripts.
Cross-device attachment also requires a supported, authenticated route to the same gateway. Shared operator access does not itself expose a remote listener or add OpenAI-compatible API continuation of local sessions. A gateway crash is different from a client disconnect: interrupted work becomes unknown rather than being automatically replayed, and may need resolution before queued followers proceed.
What Actually Goes Into Context?
Here’s something important to understand: Hermes doesn’t re-send your entire history every turn. Instead, the model sees the system prompt, the current conversation window, and anything Hermes explicitly injects for that turn.
Media files are handled smartly:
- Images might be analyzed once with vision, then only a text description is kept
- Audio gets transcribed to text
- Documents have their text extracted or just a local path saved
So if you send an image and ask for a meme, Hermes inspects it once, runs the processing, and future turns don’t carry the original JPEG around. The real context hogs are verbose text — pasted transcripts, huge logs, massive tool outputs. Pro tip: prefer summaries and file paths over pasting entire documents into chat.
Resuming Conversations from the CLI
This is where sessions really shine. Let’s look at the commands:
Continue Your Last Session
# Resume the most recent CLI session
hermes --continue
hermes -c
Per-Terminal Magic
Here’s a cool new feature: a bare -c is terminal-aware. Hermes drops a small breadcrumb file for each terminal (tty, tmux pane, kitty window, etc.). So if you have two terminals side by side, each one resumes its own conversation instead of both grabbing the same one. No more cross-talk between panes!
If there’s no breadcrumb (first use or stale after 30 days), it falls back to the most recent session. You can disable this with session.terminal_continue: false in your config.
Resume by Name
If you’ve named your sessions, you can resume them directly:
hermes -c "my project"
And if there are lineage variants like “my project #2” or “#3”, it automatically grabs the most recent one.
Resume by ID or Title
# By specific session ID
hermes --resume 20250305_091523_a1b2c3d4
# By title
hermes --resume "refactoring auth"
# Most recent session
hermes --resume latest
Session IDs are shown when you exit a CLI session, and can be found with hermes sessions list. The format is YYYYMMDD_HHMMSS_<hex> — CLI/TUI sessions use a 6-char hex suffix, gateway sessions use an 8-char suffix. You can resume by ID (full or unique prefix) or by title, and both work with -c and -r.
Resume in a Specific Directory
Pass --in <dir> to change into a directory before starting or resuming. Combined with --resume latest (or -c), the most recent session for that directory’s workspace is picked — no need to cd first or remember session IDs:
# Resume the latest session that belongs to ./my-project
hermes --resume latest --in ./my-project
--in also pins the session to that directory: the resumed session’s recorded working directory is not restored (as if --no-restore-cwd were passed).
Resume Restores the Working Directory
Resuming a CLI session also cds back into the session’s recorded working directory (its git repo root or project dir), so the conversation picks up in the workspace it belonged to. If you’d rather stay where you are, pass --no-restore-cwd:
hermes --resume 20250305_091523_a1b2c3 --no-restore-cwd
A ↪ restored workspace dir: … line confirms the switch. Restore failures never break the resume itself.
Resuming an idle session picks up the profile’s current config: toolsets, MCP servers, terminal settings, max_turns, checkpoints and the default reasoning effort. The session keeps its model, its launch flags and any /reasoning or workspace change you made. Any difference starts a new prompt-cache prefix, so the first turn after the resume costs more. A change to the terminal settings, even an unrelated one like terminal.timeout, also resets an earlier cd to the session’s directory.
Filtering Sessions by Workspace
hermes sessions list accepts --workspace <needle> to show only sessions whose workspace key (git repo root, else cwd) matches — by path substring or exact directory basename:
hermes sessions list --workspace my-project
hermes sessions list --workspace ~/code/hermes-agent
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. The recap shows user messages and assistant responses, truncates long messages, collapses tool calls to a count with tool names, hides system messages and tool results, and caps at the last 10 exchanges. To disable it and keep the minimal one-liner behavior, set in ~/.hermes/config.yaml:
display:
resume_display: minimal # default: full
Naming Your Sessions
Want to find sessions easily later? Pass a name when starting fresh:
hermes /new payments-refactor
This sets the initial title up front, making it a breeze to find with /resume <name> or in the /sessions picker.
Keeping Things Tidy
Three commands to remember:
/compress— when a session gets too long, compress it/new— start a fresh threadhermes sessions prune— delete old ended sessions
And if your state.db file has just grown large (not the sessions themselves), try the non-destructive option first:
hermes sessions optimize
This merges FTS5 index segments and runs VACUUM without touching any session data. With the gateway running, prune asks the gateway to delete the sessions it selected. optimize, and prune with no gateway running, refuse while another Hermes process (Desktop, dashboard, cron) holds state.db. Stop it first, or pass --force; see Session storage recovery. Remember: compression reduces active context, it’s not a privacy delete.
Exporting and Deleting Sessions Safely
Markdown/QMD export writes one .md or .qmd file per exported session plus a manifest.jsonl with the file path, message count, lineage ids, and SHA-256. Bulk export requires at least one filter; a bare bulk export is refused. --delete-after-verified is intentionally limited to --session-id and requires --yes. Because deleting a parent session also removes its delegate/subagent sessions, this mode exports and verifies each delegate in a separate file before deleting anything. Markdown/QMD files hold the full history shown by the session, including turns archived by in-place compaction. Deletion compares that exact display transcript and the delegate set again inside the same database transaction that performs the delete; any intervening append, rewrite, rewind, compaction, or delegate change refuses deletion. The same display-history rule applies to --format html, --only user-prompts (with either Markdown or JSONL output), and /save md|html. Full-session JSON/JSONL exports (hermes sessions export without --only) and /save json (CLI, messaging, TUI, Desktop) are backups of every stored row, archived rows included. --redact scrubs secrets (API keys, tokens, credentials) from message content and tool output before writing — recommended for any export you plan to share.
Exported files contain one JSON object per line with full session metadata and every stored message, each with its active/compacted flags. That includes turns archived by in-place compaction and messages removed by rewind or edit. Importing the file (the dashboard’s session import) restores those rows as archived history, not as live model context. A /save json snapshot (CLI, messaging, TUI, or Desktop) holds the same rows. Treat a backup as holding everything the session ever contained. To share a conversation, export a display format (--format md or html, which holds only the history the session shows) with --redact.
Filtered exports include matching pinned and archived sessions. Pinning protects a session from pruning, rather than excluding it from a backup. If an explicit --session-id cannot be resolved, export exits with a non-zero status and creates no output file.
Restoring a session export (the dashboard import, or a profile adopting a stranded session) keeps the pinned, archived and hidden flags, and whether an archive came from the sessions.auto_archive sweep. A restored pinned session stays exempt from retention cleanup, an adopted Bot Mode chat stays hidden, and a restored sweep archive still comes back when you resume it. Exports that predate these flags restore as ordinary, unpinned, visible sessions.
A chat hidden by the sessions.auto_archive idle sweep comes back on its own once it is live again — when it is resumed, or when new activity compresses it into a fresh continuation. A chat you archived yourself (sidebar, API, or hermes sessions archive) stays archived until you unarchive it.
Deleting a session that is still open in a running chat does not stop that chat: its next save recreates the session under the same id with the full in-memory transcript. Close the chat first if you want the session gone.
A conversation that compression split into several sessions is pruned as a unit: its older segments stay while any later segment does. The same unit rule applies when you archive your entire history: a compacted conversation is archived as a unit through its live tip, so an old compression segment never matches on its own age, and a chat that is still active is never hidden because its history is long.
Oversized-Transcript Guards
Two settings cap how much a single session can hold: sessions.max_export_messages and sessions.max_import_messages (both default to 20000 messages; 0 disables the guard). max_export_messages applies per session to the JSON/JSONL backup (hermes sessions export, sessions export in hermes console, and /save json in the CLI, messaging, TUI and Desktop). It counts every stored row, archived included, because the backup holds all of them. A heavily compacted session with a small live tail can still exceed it. The dashboard Sessions page’s Export action streams the rows instead and is not capped. A TUI/Desktop /save refused by max_export_messages returns error code 4131.
Repairing Degraded Stored Prompts
Older builds affected by #122822 could let gateway hygiene or gateway /compress persist a detached maintenance agent’s reduced-toolset system prompt over the live session. After the root fix in PR #122825 is installed, use hermes sessions repair-prompts to find rows that were already degraded.
The scan is conservative: it only proposes a repair when the stored prompt is missing the ## Skill Safety guidance and the persisted tools[] pin contains skill_manage (which always emits that guidance). Rows with no readable pin, or with a memory-only pin (which is also a legitimate toolsets: [memory] setup), are reported as unverifiable and are not changed by this scan; clear them explicitly by SESSION_ID if needed. A memory-only row also becomes repairable on its own: once the session is resumed, its tools[] pin re-pins the full tool surface, after which a scan sees it.
Refresh Prompts After a Profile Update
A continuing session sends the same system prompt on every turn — the bytes it built on its first turn. Keeping those bytes fixed is what lets the provider reuse its prompt cache across a long conversation. The side effect: changes to SOUL.md, installed or edited skills, or project context files (AGENTS.md and friends) never reach a session that is already running. A gateway restart doesn’t change that, because the gateway restores each session’s stored prompt. Until now the only way in was /new, which also drops the conversation.
After updating a profile’s prompt sources, ask its sessions to adopt them:
# Every session in the active profile (add -p <profile> for another one)
hermes sessions refresh-prompts
# One session (id or unique prefix)
hermes sessions refresh-prompts --session SESSION_ID
The command records a refresh request in the profile’s state.db and prints how many sessions hold a stored prompt. On each session’s next turn, Hermes rebuilds the system prompt from the current sources and then sends the message. Conversation history, the session id and the session’s tool list stay as they were, and the rebuilt prompt is saved so later turns reuse it byte for byte.
- Cost: each refreshed session pays one prompt-cache miss on the turn that rebuilds; later turns are cached again.
- No restart needed: the request is read at the start of every turn, and it survives restarts, so an update script can call it before or after replacing the gateway.
- What gets rebuilt: the whole system prompt, so memory and
USER.mdblocks also pick up their latest saved state on that turn. Tool definitions do not change. Sessions started after the request already have the new prompt. - Without the command nothing changes: sessions keep replaying their stored prompt.
Local Sessions Nobody Closed
A CLI, TUI or Desktop session whose last viewer disappeared without closing it (a killed terminal, a dropped connection, or a gateway stop/restart while it was open) is ended by the gateway sessions.orphan_end_grace_s seconds later (default 300; 0 never ends it), with end_reason: ws_orphan_reap — or startup_orphan_reap after a restart, no sooner than 6 hours after its last activity. dashboard.ws_orphan_reap_grace_s: 0 turns the first off, and dashboard.startup_orphan_sweep: false turns off the after-restart sweep. A turn still running finishes first; one paused on an interrupted turn stays open until you discard it. A viewer that reconnects within the grace keeps the session open. Ending is non-destructive: the session stays resumable and its next message reopens it on the same id.
Session Continuity and Hygiene
Gateway conversations do not reset after inactivity or at a daily boundary. Use /new or /reset for an explicit new conversation; context compression remains automatic. Core ignores legacy session_reset settings, reset-policy overrides and reset-timer environment variables. If your config still sets session_reset.mode to idle, daily or both, gateway startup and hermes doctor warn about it. To keep time-based resets, install the catalog plugin that reads the same block unchanged: hermes plugins install hermes-session-reset-policy. Cached agents may be released to reclaim resources without replacing the durable conversation. Restart-recovery freshness limits automatic continuation, not the history loaded when you send a message.
Because gateway conversations never expire on their own, it is easy to run one session for weeks. That works, but it quietly defeats the learning loop and inflates costs. Memory only pays off at boundaries: MEMORY.md / USER.md are injected at session start, and session_search exists to recall what fell out of context, so in a never-ending session the agent has no reason to consult memory. Cost also grows with history — compression keeps a long session functional, but every turn still carries a large (compacted) prefix, and a fresh session with distilled memory is almost always cheaper than a month-old thread. Practical rule: end a session when you finish a task or topic, and run /new (optionally named, e.g. /new payments-refactor) at natural stopping points. Before the reset, ask the agent to “remember anything worth keeping” if the work surfaced durable preferences or procedures. Restarting the machine or the gateway is not a boundary — the same session resumes.
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.
# Inside a CLI session
/handoff telegram
The CLI validates that the platform is enabled and has a home channel set (run /sethome from the destination chat once to configure it), marks the session pending, and block-polls the gateway. It refuses if the agent is mid-turn, so wait for the current response to finish first. The gateway re-binds the destination key to your existing CLI session id and forges a synthetic user turn asking the agent to confirm and summarize, and the reply lands in the new thread. When the gateway acknowledges success, the CLI prints a /resume hint and exits cleanly. From that point, the conversation lives on the platform — reply in the new thread, and anyone authorized in that channel shares the same session. 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.
Threading depends on the platform: Telegram opens a new forum topic (DM topics if the bot owner has enabled Threaded Mode via BotFather, or a forum supergroup topic), Discord creates a 1440-min auto-archive thread under the home text channel, Slack and Matrix post a seed message and thread off it, while WhatsApp, Signal, and SMS have no native threads and fall back to the home channel directly. One limitation worth knowing: for non-thread-capable platforms with multi-user group home channels, the synthetic turn keys as a DM-style session. That’s fine for self-DM home channels (the typical setup) but not ideal for genuinely shared group chats. Threading covers Telegram, Discord, Slack, and Matrix — by far the common case — so most setups never hit this.
Wrapping Up
Sessions are the backbone of Hermes Agent’s memory. Whether you’re juggling multiple projects across different platforms or just want to pick up where you left off, session management keeps everything organized and searchable. Try the terminal-aware -c feature — it’s a game-changer for multitaskers!
In the next tutorial, we’ll explore more advanced conversation features. Until then, happy chatting!
Official interface screenshots
📖 Official Docs
This article is based on the official Hermes Agent documentation:Official docs › user-guide/sessions