> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ollim.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Context flow

> How context flows between main session, forks, background forks, and pending updates.

Context in ollim-bot moves through a cycle: the main session feeds into
forks, forks produce pending updates, and pending updates flow back into
the main session. Background forks receive additional context through a
preamble that includes ping budgets, forward schedules, and tool
restrictions.

## 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                                      |

The default background fork tool set (`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](/scheduling/background-forks#tool-restrictions).

<Tip>
  The isolated mode tradeoff: no conversation history means less context to reason over, but also no risk of acting on stale context from hours ago. Use `isolated: true` for tasks that should evaluate the current state fresh — like checking if a deadline passed.
</Tip>

## 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:

1. **Main session** receives user messages with any pending updates
   prepended
2. **Interactive forks** branch from main, peek at pending updates
   (read-only)
3. **Background forks** branch from main (or start fresh if isolated),
   receive a preamble with budget and schedule context
4. Forks write back to the main session via `report_updates`
5. 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 the `report_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                  |

When `blocked` is combined with `allow-ping: true`, reporting is
disabled but direct pings remain available — see
[Background forks](/scheduling/background-forks#reporting-to-the-main-session)
for when this combination is useful.

A stop hook (`require_report_hook`) enforces these modes — see the
[enforcement table](/extending/mcp-tools#background-fork-enforcement)
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 |

When pending updates are injected into the main session, the bot also
sends a user-visible note — "catching up on background activity..." —
so you know background context is being included.

<Tabs>
  <Tab title="Main session">
    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.
  </Tab>

  <Tab title="Interactive fork">
    Interactive forks **peek** at pending updates — read-only, no
    clearing. This preserves the updates for the main session to
    consume later. The fork still sees the context, but doesn't
    consume it.
  </Tab>

  <Tab title="Isolated background fork">
    Isolated background forks (`isolated: true`) do **not** receive
    pending updates. They start with a clean slate and only get the
    assembled preamble.
  </Tab>

  <Tab title="Forked background fork">
    Non-isolated background forks prepend pending updates to the
    prompt, similar to the main session. This gives the background
    task awareness of what other forks reported.
  </Tab>
</Tabs>

## 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) |

The agent's system prompt documents how to interpret these tags. For
foreground tasks, only the tag and the task's markdown body are injected.
For background tasks, the tag is followed by the full preamble.

## Background fork preamble

Background forks receive an assembled preamble between the prompt tag
and the task's markdown body. The preamble is built by
`build_bg_preamble` in `scheduling/preamble.py` and contains several
sections.

### Preamble sections

<AccordionGroup>
  <Accordion title="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.
  </Accordion>

  <Accordion title="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).
  </Accordion>

  <Accordion title="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.
  </Accordion>

  <Accordion title="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=True` bypass
  </Accordion>

  <Accordion title="Tool restrictions">
    Listed only when `allowed-tools` is configured. Shows which tools
    are available.
  </Accordion>

  <Accordion title="Skill instructions">
    When a routine or reminder has a `skills` field referencing one or
    more [skills](/extending/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](/extending/skills#dynamic-context-injection),
    and allowed-tools enforcement natively. Skills propagate
    through [chain reminders](/architecture/context-flow#chain-reminder-context) via `ChainContext`.
  </Accordion>
</AccordionGroup>

### Forward schedule

The budget section includes a forward schedule built by
`build_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              |

Each entry in the schedule includes:

| 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 in `sessions.py`:

1. Before a background fork runs, `start_message_collector()`
   initializes a contextvar-scoped list
2. When `streamer.py`, `ping_user`, or `discord_embed` sends a
   Discord message, `track_message(message_id)` records it
3. After the fork completes,
   `flush_message_collector(fork_id, parent_id)` writes the mapping
   to `~/.ollim-bot/state/fork_messages.json`
4. When a user replies to one of those messages,
   `lookup_fork_session(message_id)` returns a `ForkLookup` with the
   session ID (or `None` if expired/unknown) and an `expired` flag
5. The bot creates an interactive fork that resumes from that session

Records in `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.

<Note>
  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).
</Note>

## Chain reminder context

Chain reminders inject additional context into their prompts. When a
reminder has `max-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_chain` is 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 calls `report_updates`. If `follow_up_chain` is called
  anyway, the tool returns an
  [error with recovery guidance](/extending/mcp-tools#follow_up_chain)
  prompting escalation via `ping_user`.

Chain reminders inherit tool restrictions and skills from the parent via
`ChainContext`, so `allowed-tools` and `skills` propagate through the
entire chain.

## Next steps

<Columns cols={2}>
  <Card title="Session management" icon="database" href="/architecture/session-management">
    Session IDs, lifecycle events, and compaction.
  </Card>

  <Card title="Background forks" icon="gears" href="/scheduling/background-forks">
    Isolated mode, model overrides, and tool restrictions.
  </Card>

  <Card title="Forks" icon="code-branch" href="/core-usage/forks">
    Interactive forks, exit strategies, and idle timeout.
  </Card>

  <Card title="Ping budget" icon="coins" href="/scheduling/ping-budget">
    Refill-on-read budget for background fork pinging.
  </Card>
</Columns>
