Skip to main content
ollim-bot is a single-process Python application that bridges Discord with Claude via the Agent SDK. All modules live under src/ollim_bot/, with two sub-packages (google/ and scheduling/) for domain-specific functionality. The core agent module delegates to three extracted helpers — agent_context.py (timestamps, pending updates, ThinkingConfig), agent_streaming.py (stream consumption and auto-compaction retry), and fork_state.py (contextvars, dataclasses, idle timeouts).

Data flow

A message from Discord travels through four stages before a response appears.
1

Discord event

bot.py receives a DM. It extracts text and image attachments, resolves reply context (including fork session resumption), and acquires the agent lock.
2

Agent processing

agent.py injects the message into the active ClaudeSDKClient session. agent_context.py prepends a timestamp and any pending background updates. agent_streaming.py consumes the SDK response, yielding text deltas and StreamStatus signals through an AsyncGenerator — including transparent auto-compaction retry.
3

Streaming to Discord

streamer.py consumes the generator, buffering deltas and progressively editing a Discord message. When the message exceeds 2000 characters, it finalizes the current message and starts a new one.
4

Post-stream transitions

bot.py checks for fork transitions — if the agent called enter_fork or exit_fork during the response, the bot handles the state change (creating fork embeds, swapping clients, or discarding the fork).

Background fork execution

Scheduled routines, reminders, and webhooks run on disposable forked sessions that execute in parallel without blocking the main conversation.
The default. run_agent_background creates a client forked from the main session — the fork inherits full conversation history. Output is discarded unless the agent calls report_updates (which writes to pending_updates.json) or ping_user/discord_embed (which message the user directly, subject to ping budget).
Background forks communicate back to the main session through pending updates — summaries written to ~/.ollim-bot/state/pending_updates.json. The main session pops these updates and prepends them to the next user message. Forks peek at updates (read-only) to avoid consuming another fork’s output.
Background forks run without the agent lock. Fork state, busy state, chain context, background tracking, and fork config are all scoped via contextvars so concurrent forks don’t interfere with each other or the main session. The DM channel is a module-level global set once at startup — safe to share because it never changes.

Key architectural patterns

Session persistence

The bot maintains a single ClaudeSDKClient with a session ID persisted to ~/.ollim-bot/state/sessions.json. On restart, it resumes the existing session. All session lifecycle events (created, compacted, swapped, cleared, interactive_fork, bg_fork, isolated_bg, restarting) are logged to session_history.jsonl.

Contextvar isolation

Background forks use ContextVar instances to scope mutable state. All fork-related contextvars and dataclasses live in fork_state.py — key variables include _in_fork_var, _busy_var, _bg_tracking (a BgForkTracking dataclass holding output_sent, reported, and ping_count), and _bg_fork_config_var. _chain_context_var in agent_tools.py and _msg_collector in sessions.py follow the same pattern. This lets multiple forks run concurrently while the main session uses module-level globals for the same values.

File-based storage

All persistent data lives in ~/.ollim-bot/ as files:
  • Markdown with YAML frontmatter for human-editable data (routines, reminders, webhooks) — the agent reads and writes these
  • JSONL for append-only logs (session history)
  • JSON for small state files (session ID, ping budget, inquiries)
storage.py provides generic I/O with atomic writes (temp file + rename) and optional git auto-commit.

Dual state for tools

MCP tools in agent_tools.py maintain two parallel references — a module-level global for the main session and a ContextVar for background forks. Functions like set_chain_context / set_fork_chain_context set the appropriate reference based on execution context.

Persistent buttons

Discord buttons survive bot restarts through two mechanisms: DynamicItem[Button] in views.py reconstructs button handlers from custom_id patterns on startup, and inquiries.py persists agent-generated button prompts to disk with a 7-day TTL.

Module map

The codebase has 42 modules organized into five layers.

Core loop

The main path from a Discord message to a streamed response.

Tool system

MCP tools and the external trigger server the agent uses to interact with Discord and the outside world.

Storage and state

Persistence, configuration, and cross-cutting concerns.

Google integration (google/)

OAuth2-based integrations with Google services.

Scheduling (scheduling/)

Proactive routines and reminders via APScheduler.

Find what you need

Next steps

Session management

How sessions persist, compact, and recover across restarts.

Context flow

How context flows between main sessions, forks, and pending updates.

Streaming

How agent responses stream to Discord with throttled edits.

Configuration reference

All environment variables and data directory structure.