Context by execution mode
What the agent sees depends on which mode it’s running in. This matters for both understanding agent behavior and configuring background tasks effectively.| Main session | Interactive fork | Background fork (forked) | Background fork (isolated) | |
|---|---|---|---|---|
| System prompt | build_system_prompt() (profile + operational) | Same as main | Assembled preamble | Assembled preamble |
| Conversation history | Full (persistent, resumed across restarts) | Forked from main at branch point | Forked from main | None (clean slate) |
| Pending updates | Popped (consumed) | Peeked (read-only) | Prepended to prompt | None |
| MCP tools | All 11 discord tools + docs server | Same as main | Discord tools dynamically gated | Discord tools dynamically gated |
| File tools | Read, Write, Edit, Glob, Grep (./**.md) | Same as main | Read, Glob, Grep (./**.md) | Read, Glob, Grep (./**.md) |
| Other tools | WebFetch, WebSearch, Task, Skill, CLI | Same as main | ollim-bot help, ollim-bot tasks * | ollim-bot help, ollim-bot tasks * |
| Skills | Not loaded | Not loaded | Loaded if declared in YAML skills field | Loaded if declared in YAML skills field |
| Thinking | User-controlled (/thinking) | Adaptive by default | Configurable per job (thinking field) | Configurable per job |
| Permissions | User-controlled (dontAsk default) | Inherits from main | Dynamic gating per tool | Dynamic gating per tool |
| Preamble sections | None | None | Ping, update, busy, budget/schedule, tool restrictions, skills | Same |
DEFAULT_BG_TOOLS) includes read-only file tools (Read, Glob, Grep scoped to ./**.md), the ollim-bot help and ollim-bot tasks * CLI commands, and the three reminder MCP tools (add_reminder, list_reminders, cancel_reminder). Discord MCP tools (ping_user, discord_embed, report_updates, follow_up_chain) are not in this static list — they are dynamically gated at runtime based on the job’s allow-ping and update-main-session flags. Jobs can expand the tool set via the allowed-tools YAML field — see Background forks.
Overview
Every interaction starts in the main session. When a fork branches off, it inherits the main session’s conversation history. When a background task fires, it receives an assembled preamble with scheduling awareness. When either type of fork finishes, it can feed results back to the main session through pending updates — short summaries that get prepended to the next interaction. The flow forms a loop:- Main session receives user messages with any pending updates prepended
- Interactive forks branch from main, peek at pending updates (read-only)
- Background forks branch from main (or start fresh if isolated), receive a preamble with budget and schedule context
- Forks write back to the main session via
report_updates - Main session pops those updates on the next interaction
Pending updates
Pending updates are the primary channel for passing context from forks back to the main session. They are stored in~/.ollim-bot/state/pending_updates.json as a JSON array of timestamped
messages.
Each entry is a PendingUpdate with two fields:
| Field | Type | Description |
|---|---|---|
ts | str | ISO 8601 timestamp in the configured timezone |
message | str | Summary text written by the agent |
Write path
Any fork can write a pending update by calling thereport_updates MCP
tool. The write is protected by an async lock for concurrent background
fork safety — multiple forks can safely append updates without
corruption. Updates are capped at 10 entries (MAX_PENDING_UPDATES).
When a new update would exceed this limit, the oldest entries are dropped
and a sentinel is prepended so the agent knows omission occurred — for
example, “(3 earlier update(s) omitted — cap reached)”. The sentinel
occupies one slot, leaving 9 slots for real entries.
Whether a background fork is allowed or required to call report_updates
depends on its update-main-session mode:
| Mode | Behavior |
|---|---|
on_ping | Report if the agent sent a ping or embed (default) |
always | Must call report_updates before fork exits |
freely | Reporting is optional |
blocked | report_updates returns an error |
blocked is combined with allow-ping: true, reporting is
disabled but direct pings remain available — see
Background forks
for when this combination is useful.
A stop hook (require_report_hook) enforces these modes — see the
enforcement table
for details. In on_ping mode, it blocks if the agent pinged but
didn’t report.
Read path
The main session and forks read pending updates differently. The injected header varies by context to guide the agent’s behavior:| Context | Header | Purpose |
|---|---|---|
| Main session | RECENT BACKGROUND UPDATES (mention key findings in your response) | Prompts the agent to surface findings |
| Fork (interactive or forked bg) | RECENT BACKGROUND UPDATES (read-only — main session will also see these) | Prevents the fork from acting on updates meant for the main session |
- Main session
- Interactive fork
- Isolated background fork
- Forked background fork
The main session pops pending updates — reads and clears the
file atomically. The updates are prepended to the next message the
agent sees, giving it context about what happened in the background.After the pop, the file is deleted. Each update is consumed exactly
once.
Prompt tags
Every routine, reminder, and fork injects a tag at the start of its prompt. These tags tell the agent what type of session it’s in and which task triggered it.| Tag pattern | Session type |
|---|---|
[routine:ID] | Foreground routine (runs in main session) |
[routine-bg:ID] | Background routine (runs in a fork) |
[reminder:ID] | Foreground reminder (runs in main session) |
[reminder-bg:ID] | Background reminder (runs in a fork) |
[webhook:ID] | Webhook (always background) |
[fork-started] | Interactive fork (topic, no-topic, or bg resume) |
[fork-timeout] | Idle fork timeout prompt |
[button] | Non-fork button click dispatching a prompt |
[system] | Internal system prompt (e.g., fork report button) |
Background fork preamble
Background forks receive an assembled preamble between the prompt tag and the task’s markdown body. The preamble is built bybuild_bg_preamble in scheduling/preamble.py and contains several
sections.
Preamble sections
Ping instructions
Ping instructions
Tells the agent whether
ping_user and discord_embed are
available. When allow-ping is false, the agent is told these
tools are disabled.Update instructions
Update instructions
Based on the
update-main-session mode, tells the agent when and
whether to call report_updates. Each mode has specific
instructions (must report, report if pinged, optional, or blocked).Busy state
Busy state
If the user is mid-conversation (the agent lock is held), the
preamble instructs the agent to use
report_updates instead of
direct pings — unless critical=True. This prevents interruptions
during active conversations.Budget and schedule
Budget and schedule
Included only when
allow-ping is true. Contains:- Current ping budget status (available tokens, capacity)
- Upcoming schedule: a list of tasks within the schedule window, each with fire time, label, description, and file path
- Refill estimation before the last routine or reminder
- Guidance on when to ping vs. report (the “regret test”)
- Note about
critical=Truebypass
Tool restrictions
Tool restrictions
Listed only when
allowed-tools is configured. Shows which tools
are available.Skill instructions
Skill instructions
When a routine or reminder has a
skills field referencing one or
more skills, the preamble builder adds a
REQUIRED SKILLS instruction block telling the agent to invoke each
skill before proceeding. _merge_skill_tools() adds Skill(<name> *)
patterns to the fork’s allowed_tools so the SDK permits the Skill
tool. The SDK handles content loading, dynamic context injection,
and allowed-tools enforcement natively. Skills propagate
through chain reminders via ChainContext.Forward schedule
The budget section includes a forward schedule built bybuild_upcoming_schedule. This gives the agent awareness of what other
background tasks are coming, so it can make informed decisions about
whether to spend a ping budget token now or save it.
Schedule construction uses a dynamic window:
| Parameter | Value | Purpose |
|---|---|---|
| Grace window | 15 minutes | Includes recently-fired routines |
| Base window | 3 hours | Initial forward-looking window |
| Min forward count | 3 | Extends window if fewer than 3 tasks found |
| Maximum window | 12 hours | Hard upper limit on lookahead |
| Field | Description |
|---|---|
fire_time | When the task fires |
label | Display name (e.g., “Chain reminder (2/4)“) |
description | From YAML or truncated message (60 char limit) |
file_path | Relative path (e.g., routines/daily-review.md) |
silent | Whether allow-ping=False for this task |
tag | "this task", "just fired", or None |
Reply-to-fork context
Replying to a message sent by a background fork starts an interactive fork that resumes from that background fork’s session. This is how context flows from a background task back into an interactive conversation. The mechanism relies on fork session tracking insessions.py:
- Before a background fork runs,
start_message_collector()initializes a contextvar-scoped list - When
streamer.py,ping_user, ordiscord_embedsends a Discord message,track_message(message_id)records it - After the fork completes,
flush_message_collector(fork_id, parent_id)writes the mapping to~/.ollim-bot/state/fork_messages.json - When a user replies to one of those messages,
lookup_fork_session(message_id)returns aForkLookupwith the session ID (orNoneif expired/unknown) and anexpiredflag - The bot creates an interactive fork that resumes from that session
fork_messages.json have a 7-day TTL and are pruned on
read. When a reply targets an expired record, the bot signals this to
the user (“this session has expired — starting fresh with quoted
context”) and falls back to quoting the message content instead. If
the user replies to a fork message while already in an interactive
fork, the reply is added as context to the current fork rather than
opening a new one.
Replying to a non-fork message works differently — the replied-to
message’s content is quoted and prepended as context (plain text from
.content, or title + description + fields from the first embed,
truncated to 500 characters).Chain reminder context
Chain reminders inject additional context into their prompts. When a reminder hasmax-chain > 0, the preamble includes:
- The current check number and total (e.g., “check 2/4”)
- Whether
follow_up_chain(minutes_from_now=N)is available - Instruction to acknowledge the follow-up nature in pings (e.g. “checking in again”) so the user knows it is intentional
- On the final check: the preamble warns that
follow_up_chainis not available and applies a regret heuristic — the agent pings only if the task is still unresolved and the user would regret missing it, otherwise it callsreport_updates. Iffollow_up_chainis called anyway, the tool returns an error with recovery guidance prompting escalation viaping_user.
ChainContext, so allowed-tools and skills propagate through the
entire chain.
Next steps
Session management
Session IDs, lifecycle events, and compaction.
Background forks
Isolated mode, model overrides, and tool restrictions.
Forks
Interactive forks, exit strategies, and idle timeout.
Ping budget
Refill-on-read budget for background fork pinging.
