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.
ModuleRole
main.pyCLI entry point — dispatches to bot or subcommands, sets up the SDK layout at startup
auth.pyClaude Code auth — headless login via bundled CLI, startup auth check
bot.pyDiscord interface — DMs, slash commands, reaction acks
agent.pyAgent SDK wrapper — sessions, MCP servers (discord + docs), slash routing; delegates context prep to agent_context.py and streaming to agent_streaming.py
agent_context.pyMessage context helpers — timestamps, duration formatting, pending update assembly, ThinkingConfig builder
agent_streaming.pyStream response consumer — SDK message loop, auto-compaction retry, fork interrupt, fallback tiers
streamer.pyStreams text deltas to Discord — throttled edits, 2000-char overflow
prompts.pySystem prompt for the main agent and fork prompt helpers
subagents.pyBundled agent installation (install_agents) and tool-set extraction (load_agent_tool_sets) for policy validation
subagents/Subagent specs as markdown files (ollim-bot-guide, gmail-reader, history-reviewer, responsiveness-reviewer, user-proxy)
channel.pyDM channel reference — set once at startup, read everywhere
profile.pyUser profile files: IDENTITY.md (bot persona) and USER.md (user context), bootstrap and loading
updater.pyGit-based auto-update: fetch, compare, pull (--ff-only), uv tool upgrade, restart via os.execv
doctor.pyHealth diagnostics — checks data dir, SDK layout, credentials, and reports issues

Tool system

MCP tools and the external trigger server the agent uses to interact with Discord and the outside world.
ModuleRole
agent_tools.pyMCP tools for Discord embeds, pings, forks, and chains
reminder_tools.pyMCP tool implementations for reminder management (add, list, cancel)
hooks.pyAgent SDK hooks: state_dir_guard (blocks writes to state/) and auto_commit_hook (auto-commits .md changes)
webhook.pyHTTP server for external triggers — auth, validation, Haiku screening
fork_state.pyFork state — contextvars, dataclasses (BgForkTracking, BgForkConfig), interactive fork globals
forks.pyPending updates I/O and background fork execution (run_agent_background)
tool_policy.pyTool pattern validation, per-job tool restrictions, and YAML tool policy config
views.pyPersistent button handlers via DynamicItem — delegates to google/, forks, and streamer

Storage and state

Persistence, configuration, and cross-cutting concerns.
ModuleRole
storage.pyShared JSONL and markdown I/O, git auto-commit for ~/.ollim-bot/
sessions.pyPersists Agent SDK session ID + session history JSONL log
permissions.pyTool approval — canUseTool callback, reaction-based approval
config.pyEnv vars: OLLIM_USER_NAME, OLLIM_BOT_NAME (from .env)
embeds.pyEmbed/button types and builders shared by agent_tools and views
inquiries.pyPersists button inquiry prompts to disk (7-day TTL, survives restarts)
ping_budget.pyRefill-on-read ping budget — capacity 5, refills 1 per 90 min
runtime_config.pyPersistent runtime config — model/thinking per context, timeouts, permission mode
skills.pySkill permission helpers for background fork dispatch (SDK handles skill loading natively)
formatting.pyTool-label formatting helpers shared by agent and permissions

Google integration (google/)

OAuth2-based integrations with Google services.
ModuleRole
auth.pyShared OAuth2 credentials for Tasks + Calendar + Gmail
tasks.pyGoogle Tasks CLI + API helpers (complete_task, delete_task)
calendar.pyGoogle Calendar CLI + API helpers (delete_event)
gmail.pyGmail CLI (ollim-bot gmail) — read-only access

Scheduling (scheduling/)

Proactive routines and reminders via APScheduler.
ModuleRole
scheduler.pyAPScheduler integration — polls files every 10s, registers triggers
routines.pyRoutine dataclass and markdown I/O — recurring crons in routines/*.md
reminders.pyReminder dataclass and markdown I/O — one-shot + chainable
preamble.pyBackground preamble builder — ping budget, schedule, and config
routine_cmd.pyCLI handler for ollim-bot routine (add, list, cancel)
reminder_cmd.pyCLI handler for ollim-bot reminder (add, list, cancel)

Find what you need

I want to…Go to
Understand the full message-to-response pipelineHow ollim-bot works
See how sessions persist and recover across restartsSession management
Trace context through forks and pending updatesContext flow
Learn how responses stream to DiscordStreaming & Discord
Understand the design decisions and tradeoffsDesign philosophy
See the data directory layout and env varsConfiguration reference

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.