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:- SQLite database (
~/.mibyan/state.db) — structured session metadata with FTS5 full-text search, plus full message history
- 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
Mibyan 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 Mibyan 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.
Session Sources
Each session is tagged with its source platform:
A session compressed mid-conversation continues under the same source: the compression child of a
--source tool or oneshot run is tagged the same way, so it inherits the same picker visibility.
CLI Session Resume
Resume previous conversations from the CLI using--continue or --resume:
Continue Last Session
cli session from the SQLite database and loads its full conversation history.
Per-Terminal Continue
A bare-c is terminal-aware: each CLI session drops a small breadcrumb file under ~/.mibyan/terminal-sessions/ keyed by the terminal it runs in (tty device, tmux pane, kitty window, wezterm pane, Zellij pane, Windows Terminal session, …). When you run mibyan -c again in the same terminal, Mibyan resumes that terminal’s own session — so two panes side by side each continue their own conversation instead of both grabbing the globally most-recent one. If there’s no breadcrumb for the terminal (first use, deleted session, or a stale breadcrumb older than 30 days), -c falls back to the most-recent-session behavior. -c "name" and --resume are unaffected. Disable with session.terminal_continue: false in config.yaml.
Resume by Name
If you’ve given a session a title (see Session Naming below), you can resume it by name:Resume Specific Session
mibyan sessions list.
latest is a reserved keyword for --resume. A session literally titled “latest” is still reachable by its ID or via -c latest (title match).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:
--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 alsocds 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:
↪ restored workspace dir: … line confirms the switch. Restore failures never break the resume itself.
Filtering Sessions by Workspace
mibyan 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:
Conversation Recap on Resume
When you resume a session, Mibyan displays a compact recap of the previous conversation in a styled panel before the input prompt: 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
~/.mibyan/config.yaml:
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.
-
The CLI validates that
<platform>is enabled and has a home channel set (run/sethomefrom the destination chat once to configure it). - 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.
-
The gateway watcher claims the handoff and asks the destination adapter for a fresh thread:
- 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 — posts a seed message and uses its
tsas the thread anchor. - Matrix — posts a seed message and uses its event id as the thread root (
m.threadrelation). - WhatsApp / Signal / SMS — no native threads, falls back to the home channel directly.
- 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.
-
When the gateway acknowledges success, the CLI prints a
/resumehint and exits cleanly: -
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 <title> (or mibyan -r "<title>" from the shell) and pick up where the platform left off.
Failure modes:
- No home channel configured → CLI refuses with a
/sethomehint. - Gateway not running (nothing ever claims the request) → CLI times out at 60s with a clear message and your CLI session stays intact.
- Slow transfer: once the gateway claims the handoff it replays your full session through a real agent turn, which can take a few minutes on long sessions. The CLI shows “Still transferring…” heartbeats and waits up to 15 minutes — it never misreports a slow transfer as “gateway not running”.
- 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.sendfails (rate limit, transient API error) → handoff marked failed with the reason; the row clears so you can retry.
Session Naming
Give sessions human-readable titles so you can find and resume them easily.Auto-Generated Titles
Mibyan 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 withmibyan sessions list or mibyan 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 before sending your first message), it’s queued and applied once the session starts.
You can also rename existing sessions from the command line:
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), Mibyan creates a new continuation session. If the original had a title, the new session automatically gets a numbered title:
mibyan -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
Mibyan provides a full set of session management commands viamibyan sessions:
List Sessions
--limit allows, the listing ends with a … more not shown (use --limit N to see more) footer, so a capped page is never mistaken for the full list.
When sessions have titles, the output shows titles, previews, and relative timestamps:
Export Sessions
mibyan sessions export is one surface for every export format, selected with --format:
Plus
--only user-prompts for a prompts-only view (jsonl or md).
All formats share the same selection knobs: --session-id for one session, or the full prune/archive filter set for bulk — --older-than / --newer-than / --before / --after (durations like 5h/2d/1w, bare days, or ISO timestamps), --source, --title, --model, --provider, --cwd, --min/--max-messages, --min/--max-tokens, --min/--max-cost, --min/--max-tool-calls, --user, --chat-id, --chat-type, --branch, --end-reason. --dry-run previews the match set without writing. --redact scrubs secrets (API keys, tokens, credentials) from exported content on any format — recommended for anything you plan to share. Note: bulk filters match ended sessions; unfiltered export dumps everything, including active ones.
JSONL (default)
timings block derived from the message timestamps, so a reader of an export attached to a bug report can tell a single long model gap from many small tool round-trips without reconstructing it by hand. It holds only ids, roles, counts and durations — wall_clock_ms, largest_gap_ms, role_counts, tool_calls_emitted and per-message intervals — never prompt text, tool arguments or results, so it survives --redact unchanged. Mibyan does not persist a model/tool stopwatch, so complete is always false; when a session has no timestamped messages, available is false and unavailable_reason says why. The block is rebuilt on every export and ignored (and not counted toward size limits) on import.
HTML
--format html writes a single self-contained HTML file — no remote dependencies — with styled message bubbles, collapsible tool output, and (for multi-session exports) a sidebar to switch between sessions:
Prompts Only
--only user-prompts exports just the prompts you wrote — no assistant replies, tool output, or system context. Useful for building prompt libraries or reviewing what you asked:
--format jsonl (default) or md, honors the same filters for bulk export, and combines with --redact.
Traces (HF Agent Trace Viewer)
--format trace emits Claude Code JSONL — the transcript shape the Hugging Face Hub auto-detects for its Agent Trace Viewer. Write it locally, or add --upload to push it to your own private mibyan-traces dataset (reads HF_TOKEN):
--no-redact opts out after manual review. --upload is private unless --public. Bulk trace export with filters writes one <id>.trace.jsonl per session.
Markdown / QMD
Pass--format md or --format qmd when you want a readable, file-based archive before hiding or deleting old sessions. Markdown/QMD exports write one file per session into a directory (default: ~/.mibyan/session-exports).
.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 and /save json remain live-only because importing archived turns would restore them as live model context. --redact scrubs secrets (API keys, tokens, credentials) from message content and tool output before writing — recommended for any export you plan to share.
Delete a Session
Rename a Session
Pin a Session
Pinning sets a durable “keep” flag: pinned sessions are exempt from thesessions.auto_archive stale sweep and always appear in listings. It is the
same flag the Desktop sidebar’s Pinned section uses — pin from either surface
and both see it.
Prune Old Sessions
--older-than, --newer-than, --before, --after) accept a
duration (5h, 30m, 2d, 1w), a bare number of days, or an ISO
timestamp (2026-07-05, 2026-07-05 14:30). --older-than/--before set
the upper bound; --newer-than/--after set the lower bound. The
--older-than/--newer-than pair uses last activity — the freshest of live
activity, latest message, or session start — while --before/--after
explicitly use session start time. Combine either pair for a window.
Attribute filters: --source (platform, exact), --title / --model /
--branch (case-insensitive substring), --provider (billing provider,
exact), --end-reason, --user, --chat-id, --chat-type (exact),
--cwd (path prefix), plus numeric bounds --min/--max-messages,
--min/--max-tokens (input+output), --min/--max-cost (USD, actual falling
back to estimated), and --min/--max-tool-calls. Using any filter disables
the implicit 90-day default, so mibyan sessions prune --source cron or
--model gpt-4o matches all ages — add a time flag to narrow it. Only a
completely bare mibyan sessions prune keeps the 90-day cutoff. Every
non---yes run shows the match count plus the oldest and newest matching
session before asking for confirmation.
Archived sessions are skipped by default; pass --include-archived to
delete them too.
Pruning only deletes ended sessions (sessions that have been explicitly ended or auto-reset). Active sessions are never pruned.
A conversation that compression split into several sessions is pruned as a unit: its older segments stay while any later segment does.
Bulk-Archive Sessions
If you want sessions out of your listings without deleting anything,mibyan sessions archive takes the same filters as prune but soft-hides
matching sessions instead (sets the same archived flag as archiving a single
session from the Desktop/Dashboard UI — messages and search stay intact):
mibyan sessions archive refuses to
archive your entire history. A compacted conversation is archived as a unit
through its live tip: an old compression segment never matches on its own age,
so a chat that is still active is never hidden because its history is long.
Archived sessions are hidden from
mibyan sessions list and /resume but remain in the database and can be
unarchived from the Desktop/Dashboard session list.
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
mibyan sessions archive) stays archived until you unarchive it.
Session Statistics
mibyan insights.
Repair Stranded Gateway Sessions
If a gateway conversation ever “jumps back in time” after a restart — resuming a days-old topic as though recent messages never happened — the live conversation may be stranded in a session row that lost its routing identity (the damage class fixed in the v0.21 session-continuity work; current versions prevent it by construction and self-heal at runtime).mibyan sessions repair-routing finds message-bearing session rows with no
routing identity and re-attaches each one to the conversation it continues —
but only when the evidence is unambiguous:
- lineage — the orphan’s
parent_session_idpoints at a keyed row of the same platform (a recorded fact; no time window applies) - contiguity — exactly one keyed row of the same platform fell quiet within the window of the orphan’s start
superseded_by_repair, so restart recovery can never resurrect it.
Repair is deliberately not automatic: if the chat has since built up a
second history, choosing which thread it continues is your call. The stranded
conversation stays readable via /resume and session search either way —
routing is the only thing the repair changes. Back up first
(cp ~/.mibyan/state.db ~/.mibyan/state.db.bak).
Repair 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
mibyan 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
skill_manage without the Skill Safety guidance and clears it.
Stored system prompt ... is null; rebuilding from scratch warning for each
repaired session. That warning is the consequence of this explicit repair, not
evidence of a new corruption.
Run the repair only after the #122822 root fix is present; otherwise a later
maintenance compaction can degrade the row again.
A running gateway keeps each cached session’s old prompt in memory, so restart
the gateway after --apply (mibyan gateway restart) for repaired rows to
take effect.
Repair State Crossed Between Profiles
Every profile owns onestate.db, and every gateway session key names the
profile that owns the conversation (agent:main:… for the default profile,
agent:<name>:… for a named one). Older releases could leave the two
disagreeing — a named profile’s rows written into the default store, a child
session inheriting from another profile’s row, a routing row copied into the
wrong store, a Telegram topic or /voice setting saved without the bot’s
profile. Current versions put new state in the right place; mibyan sessions repair-profiles settles what is already crossed.
Two cases are reported but never repaired without being told what they are:
rows keyed to a profile that does not exist (create the profile, or
mibyan profile migrate-identity <old> <new>), and agent:main:… rows inside
a named profile’s store. The latter are either the history of a gateway that
used to run standalone for that profile (--legacy-main rekey gives them the
profile’s namespace) or default-profile chats that leaked in under a scoped
write (--legacy-main move sends them to the default store) — the rows
themselves cannot tell the two apart.
--apply refuses while a gateway owns any of the stores (it holds the routing
index in memory and would write it back), and is safe to re-run: a second run
finds nothing.
Convert the Store Between WAL and DELETE Journal Mode
database.journal_mode: delete only applies to databases Mibyan creates. An
existing state.db that is already in WAL mode is never live-downgraded at
open — other gateway, dashboard or cron processes may hold uncheckpointed WAL
commits, and a downgrade underneath them destroys those commits — so Mibyan
keeps WAL and logs one ERROR per process telling you the configured delete
did not apply. The self-service conversion is:
-wal/-shm sidecars, switches the mode without
waiting out openers (a holder that appears mid-way makes SQLite refuse instead
of racing it), and verifies the file header reports the new mode. It reminds
you to set database.journal_mode to the same value when the config disagrees,
because the next open re-applies the configured mode. The holder scan is local
(open-file tables on Linux/macOS, the Restart Manager on Windows), so it
cannot see a process in another container or VM sharing the volume. If the
scan itself fails the command refuses because it cannot prove the store is
quiet; --force waives only that case after you have stopped every Mibyan
process yourself — a process the scan does find is always refused. Enabling
WAL is also refused when the store sits on a cross-VM filesystem (virtiofs/9p),
where WAL shared memory corrupts silently.
Importing Sessions from Claude Code and Codex CLI
Started a conversation in another agent CLI? You can pull it into Mibyan and continue it here. Mibyan reads Claude Code’s session logs (~/.claude/projects/, or $CLAUDE_CONFIG_DIR/projects/ when Claude Code’s
config dir is relocated) and Codex CLI’s rollouts (~/.codex/sessions/, or
$CODEX_HOME/sessions/) — the foreign files are only read, never modified.
mibyan sessions import creates a new Mibyan session titled
Imported from Claude Code: <first user message> (or Codex CLI) and prints
the id plus a ready-to-paste mibyan --resume <id> command.
--resume @claude / --resume @codex show the same picker and drop you
straight into the imported conversation.
Mibyan Desktop has the same importer in the command palette (Import
session). It lists the logs on the machine the
connected backend runs on — not the computer running the app — shows a
read-only preview, and Continue in Mibyan copies the conversation into the
selected profile. Browsing never writes to your session store, importing never
touches the source file, and importing the same log twice opens the existing
copy instead of making another.
What carries over: the ordered user/assistant conversation, with tool
activity condensed to short [ran tool: …] notes inside assistant turns.
System prompts, injected context, reasoning traces, and raw tool output are
left behind — the import is a clean transcript, not a byte-for-byte replay.
Session Search Tool
The agent has a built-insession_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. It makes no LLM calls and returns views of actual messages from the DB rather than generating summaries.
Four calling shapes
The tool infers what you want from which arguments you set. There’s nomode parameter.
1. Discovery — pass query:
detail="full" to fully hydrate every result.
Each result carries:
session_id,title,when,sourcesnippet— FTS5-highlighted match excerptdetail—fullorcompactbookend_start/bookend_end— first/last 3 user+assistant messages for full results; empty lists for compact resultsmessages— ±5 messages around the FTS5 match for full results; only the flagged anchor message for compact resultsmatch_message_id,messages_before,messages_after
session_id + around_message_id:
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].idback asaround_message_id - To scroll backward: pass
messages[0].idback asaround_message_id - The boundary message appears in both windows as an orientation marker
- When
messages_beforeormessages_afteris less thanwindow, you’re at the start or end of the session
session_id without an anchor:
@session:<profile>/<id> link.
4. Browse — no args:
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—newestoroldest, on top of FTS5 ranking. Omit for relevance-only ordering (the default; suitable for exploratory recall). Usenewestfor “where did we leave X” questions,oldestfor “how did X start” questions.detail—adaptive(default) fully hydrates only the top discovery result;fullhydrates every discovery result.role_filter— comma-separated roles to include. Discovery defaults touser,assistant(tool output is usually noise). Passuser,assistant,toolto include tool output (debugging tool behaviour) ortoolto 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:
When Mibyan 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, Mibyan usesgroup_sessions_per_user: true in config.yaml. That means:
- Alice and Bob can both talk to Mibyan in the same Discord channel without sharing transcript history
- one user’s long tool-heavy task does not pollute another user’s context window
- a running turn is keyed to the sender that started it, but
/stopstill reaches it — see below
/stop means “stop what is running in this chat”: it first tries the caller’s own session key,
then any live turn in this chat — other participants’ runs in the caller’s own thread included —
authorization-gated, and never another room, workspace or profile. So an idle Alice’s /stop can
end a turn Bob (or a bot) started in the room she is in. A /stop sent from inside a thread is
narrower: it reaches runs belonging to that thread and a room-wide run that carries no thread slot
(the rolling-DM shape), but never another thread of the same channel and never a peer’s per-sender
top-level run.
If you want one shared “room brain” instead, set:
Session continuity
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 mibyan doctor warn about it. To keep time-based resets,
install the catalog plugin that reads the same block unchanged:
mibyan plugins install mibyan-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.
Session hygiene: why you should still run /new
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.mdare injected at session start, andsession_searchexists to recall what fell out of context. In a never-ending session everything is still in context, so the agent has no reason to consult memory — the “self-learning” machinery barely runs. Memory distillation (the save before reset) also only happens when a session actually ends. - Cost grows with history. Compression keeps a long session functional, but every turn still carries a large (compacted) prefix. A fresh session with distilled memory is almost always cheaper than a month-old thread.
/new
(optionally named, e.g. /new payments-refactor) at natural stopping points —
daily or per-project both work. Before the reset, ask the agent to “remember
anything worth keeping” if the work surfaced durable preferences or
procedures; it saves memories and skills from the expiring session
automatically, but an explicit nudge helps. Restarting the machine or the
gateway is not a boundary — the same session resumes.
See Memory for what gets carried across boundaries.
Continuity After Crashes and Restarts
A gateway chat is designed to be one continuous session — compacted repeatedly as it grows — until you explicitly run/new (or /reset). This
holds across gateway crashes, restarts, and updates:
- Session identity (routing key, chat, origin) is written atomically when
the session row is created, on every creation path (
/new, first message,/branchchildren). If that write ever fails, the very next turn’s routing refresh repairs the row automatically. - After a restart, the gateway re-resolves each chat to the session with the most recent actual activity — an older, stale row can never win over the conversation you were actually having.
- Recovery respects
/newboundaries: if the most recent event for a chat is an intentional reset, recovery starts fresh rather than reaching behind the reset to resurrect an older session. Elapsed time alone never prevents recovery of a durable conversation.
Storage Locations
The SQLite database uses WAL mode for concurrent readers and a single writer, which suits the gateway’s multi-platform architecture well.
Legacy JSONL transcriptsSessions created before state.db became canonical may have leftover
*.jsonl files in ~/.mibyan/sessions/. They are no longer written or
read by Mibyan. Safe to delete after verifying the corresponding session
exists in state.db.Database Schema
Key tables instate.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 conversations persist across inactivity; use
/newor/resetfor an explicit boundary - Before reset, the agent saves memories and skills from the expiring session
- Auto-pruning (on by default since #54189): when
sessions.auto_pruneistrue, ended sessions inactive forsessions.retention_days(default 90) are pruned at CLI/gateway/cron startup sessions.retention_daysmust be a whole number of days>= 0. A negative value (or a missing one) is rejected: startup maintenance logs a warning naming the allowed range and skips the sweep instead of treating the future cutoff as “everything” —sessions.auto_prune: falseis the switch that disables pruning- After a prune that actually removed rows,
state.dbisVACUUMed to reclaim disk space only when both gates pass: at leastsessions.min_vacuum_interval_days(default 30) have elapsed since the last successfulVACUUM, and more than 25% of the file’s pages are reclaimable (PRAGMA freelist_count / page_count). A dense database never pays for a full rewrite to reclaim a few MB (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 insidestate.dbitself so it’s shared across every Mibyan process in the samemibyan_HOME
state.db grows without bound — multi-GB files within weeks were reported on gateway + cron installs. If you would rather keep every ended session forever (the pre-#54189 behavior), turn it off in ~/.mibyan/config.yaml:
cli, cron, kanban, acp, api_server,
subagent, tool, plus the recovered placeholders that
mibyan sessions recover synthesizes for orphaned messages) whose last
activity is older than retention_days
(end_reason: startup_orphan_reap). Closing is non-destructive — the
session stays resumable — and the row is aged from its close, so it is only
deleted by a later pass after a further full retention window. Messaging
platform sessions (Telegram, Discord, …), TUI/desktop sessions, pinned
sessions, and sessions with a live turn or compression in progress are
never closed by this sweep.
Oversized-Transcript Guards
Two limits stop a runaway transcript from being loaded into memory all at once (both default to20000 active messages; 0 disables the guard):
max_resume_messages bounds what the resume actually loads, not the whole
history of the conversation:
- A plain interactive resume (CLI
--resume, the TUI) materializes the full compression lineage — every compacted segment plus the live tip — so it is bounded across the lineage. - Desktop’s cold resume pages the transcript over REST and only holds the live tip segment in memory, so it is bounded by the tip alone. A long-lived chat that has been compacted many times (dozens of segments, tens of thousands of archived rows behind a small tip) is exactly what compression is meant to produce and opens normally; its footer message count reflects the stored lineage, not the live prompt.
4130 with the count
and the scope it was measured against (across its lineage or
in its tip segment). mibyan sessions export still works for such sessions.

