Skip to main content
ollim-bot maintains a persistent conversation across bot restarts. A single session ID, stored as a plain string on disk, allows the Claude Agent SDK client to resume where it left off. Every lifecycle transition — creation, compaction, clearing, forking — is recorded in an append-only JSONL log for debugging and history review.

Overview

The bot is single-user, so it maintains exactly one main session at a time. When the bot starts (or reconnects after a crash), it reads the stored session ID and passes it as the resume parameter to ClaudeSDKClient. If no session ID exists, a fresh conversation begins. Session persistence is handled by sessions.py. The Agent SDK handles conversation history internally — ollim-bot only needs to track the session ID string.

Session persistence

The main session ID is stored at ~/.ollim-bot/state/sessions.json. Despite the .json extension, this file contains a plain session ID string, not JSON.
FunctionBehavior
load_session_id()Returns the session ID string, or None if the file is missing, empty, or starts with {
save_session_id(id)Atomic write via tempfile + os.replace. Logs lifecycle events (suppressed during swaps)
delete_session_id()Removes the file. Called by Agent.clear()
Writes are atomic — a temporary file is written first, then moved into place with os.replace. This prevents corruption if the bot crashes mid-write.
On reconnect, Agent._get_client() loads the session ID and creates the SDK client with resume=session_id:
session_id = load_session_id()
opts = replace(self.options, resume=session_id) if session_id else self.options
client = ClaudeSDKClient(opts)
await client.connect()

Session lifecycle events

Every session transition is recorded in ~/.ollim-bot/state/session_history.jsonl. Each line is a SessionEvent:
FieldTypeDescription
session_idstrThe session ID involved in this event
eventSessionEventTypeOne of the event types below
timestampstrISO 8601 timestamp in the configured timezone
parent_session_idstr | NoneThe session this one branched from (for forks, compaction, swaps)

Event types

EventTrigger
createdsave_session_id() called with no prior session ID on disk
compactedAuto-compaction (detected by save_session_id()), or manual /compact (logged by Agent.compact())
swappedAgent.swap_client() promotes a fork to the main session
clearedAgent.clear() called (via /clear command)
interactive_forkFirst StreamEvent or ResultMessage received on a fork client
bg_forkBackground fork session started
isolated_bgIsolated background fork (no parent session) started
save_session_id() automatically detects created and compacted events by comparing the new ID against the current one on disk. During swap_client(), a _swap_in_progress flag suppresses this auto-detection so that the swap is logged as swapped rather than compacted.

Compaction

Compaction is detected through two paths:
  • Manual (/compact): Agent.compact() streams the SDK response and looks for a compact_boundary SystemMessage. When found, it extracts pre_tokens from the compact metadata, logs a compacted event, and returns productivity stats (turns, session age, tokens compacted).
  • Automatic: The SDK may auto-compact during normal conversation. save_session_id() detects this by comparing the new session ID against the one on disk — if they differ, it logs a compacted event with the old ID as parent_session_id.

The /clear lifecycle

When a user runs /clear, Agent.clear() executes the following sequence:
1

Reset permissions

Clears the session-allowed tool set via reset_permissions().
2

Exit any active fork

If an interactive fork is active, exits it with ForkExitAction.EXIT (clean discard).
3

Log the cleared event

Reads the current session ID and logs a cleared event to session_history.jsonl.
4

Drop the client

Sets _client = None, then interrupts and disconnects the SDK client. Errors are suppressed since the subprocess may have already exited.
5

Delete the session file

Removes ~/.ollim-bot/state/sessions.json. The next message will start a fresh session.

Fork session tracking

When a background fork sends messages to Discord, ollim-bot needs to map those Discord message IDs back to the fork’s session ID. This enables the reply-to-fork feature: replying to a background fork message resumes that fork’s session as an interactive fork. Tracking uses a contextvar-based collector pattern:
FunctionPurpose
start_message_collector()Initializes a contextvar list before a background fork runs
track_message(message_id)Appends a Discord message ID to the active collector (no-op if no collector)
flush_message_collector(fork_id, parent_id)Writes collected IDs to fork_messages.json, clears collector
cancel_message_collector()Discards collected IDs without writing
lookup_fork_session(message_id)Returns the fork session ID for a Discord message, or None
The mapping is stored in ~/.ollim-bot/state/fork_messages.json as a JSON array of records:
FieldTypeDescription
message_idintDiscord message ID
fork_session_idstrThe fork session that sent this message
parent_session_idstr | NoneThe main session at the time of the fork
tsfloatUnix timestamp of when the record was written
Records older than 7 days are automatically pruned on read.

Files reference

FileFormatPurpose
~/.ollim-bot/state/sessions.jsonPlain stringCurrent main session ID
~/.ollim-bot/state/session_history.jsonlJSONL (SessionEvent)Append-only lifecycle event log
~/.ollim-bot/state/fork_messages.jsonJSON arrayDiscord message to fork session mapping

Next steps