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.- Forked mode
- Isolated mode
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).~/.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 singleClaudeSDKClient 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 useContextVar 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 inagent_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.Full module map (42 modules)
Full module map (42 modules)
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.
