Skip to main content
The agent communicates structured information — task lists, calendar events, email digests, status updates — through Discord embeds. Each embed can include interactive buttons that trigger actions directly or route prompts back to the agent. The entire system is persistent: buttons survive bot restarts.

Overview

ollim-bot uses a single MCP tool, discord_embed, to send rich embed messages. The agent decides when and what to send — there is no manual embed API. Buttons attached to embeds follow an action pattern that either performs a direct operation (complete a task, delete an event) or opens a conversation with the agent through the inquiry system. Embeds sent from background forks or interactive forks are tagged with a footer indicating their source context.

The discord_embed tool

The agent calls discord_embed with a title (required) and optional description, color, fields, buttons, and critical flag.

Parameters

ParameterTypeRequiredDefaultDescription
titlestringYesEmbed heading (emoji stripped automatically)
descriptionstringNoBody text below the title
colorstringNo"blue"Embed accent color
fieldsarrayNo[]Structured name/value pairs
buttonsarrayNo[]Interactive buttons attached below the embed
criticalbooleanNofalseBypasses per-session limit, busy check, and ping budget in background forks

Colors

ValueMeaning
blueInformational (default)
greenSuccess / completion
redUrgent / error
yellowWarning
purpleUsed internally for fork entry embeds

Fields

Each field is an object with name, value, and optional inline (defaults to true). Fields render as labeled sections within the embed — inline fields appear side-by-side, non-inline fields stack vertically.

Buttons

Each button has a label, an action string, and an optional style (defaults to "secondary").
StyleAppearance
primaryBlurple (Discord accent)
secondaryGrey
successGreen
dangerRed
Discord limits embeds to 25 buttons. Buttons beyond this limit are silently dropped.

Button actions

Button behavior is determined by the action string. There are two categories: direct actions that execute immediately, and agent inquiries that route a prompt back through the agent.
Direct actions perform a single operation and respond with an ephemeral message. No agent involvement. If the Google API returns an error, the error reason is shown as an ephemeral message.
Action patternHandlerWhat it does
task_done:<task_id>_handle_task_doneMarks a Google Tasks item complete
task_del:<task_id>_handle_task_deleteDeletes a Google Tasks item
event_del:<event_id>_handle_event_deleteDeletes a Google Calendar event
dismiss_handle_dismissDeletes the embed message
All button custom IDs follow the pattern act:<action>:<data>, parsed by the ActionButton dynamic item handler in views.py.

Inquiry flow

When the agent sends a button with an agent: action, the prompt is persisted to disk so it survives bot restarts. Clicking the button sends the stored prompt to the agent. If the button was sent by a background fork, clicking it enters an interactive fork that resumes that fork’s session; otherwise the prompt is processed in the current session.
1

Agent sends embed with inquiry button

The agent calls discord_embed with a button whose action starts with agent:. The build_view function extracts the prompt, registers it via inquiries.register(), and gets back an 8-character hex UUID. The button’s custom ID becomes act:agent:{uid}.
2

User clicks the button

Discord dispatches the interaction to ActionButton, which matches the act:agent:{uid} pattern and calls _handle_agent_inquiry.
3

Inquiry is popped and validated

The handler calls inquiries.pop(uid) to retrieve and remove the stored prompt. If the inquiry has expired (older than 7 days) or was already consumed, the button responds with an expiration notice.
4

Agent processes the prompt

If the button’s message has a fork session ID (i.e., it was sent by a background fork), the handler enters an interactive fork that resumes that session, sends a fork entry embed, and streams the agent’s response. Otherwise, the prompt is sent directly to the agent in the current session prefixed with [button]. Either way, the agent streams its response to the channel.
Inquiry prompts are stored in ~/.ollim-bot/state/inquiries.json with a 7-day TTL. Expired entries are cleaned up on read.

Fork embeds

Forks produce their own embeds at entry and exit, separate from the discord_embed tool.

Fork entry

When a fork starts (via /fork, the enter_fork MCP tool, or an inquiry button), a purple “Forked Session” embed is sent with three buttons:
ButtonStyleAction
Save Contextsuccess (green)Promotes fork context to the main session
Reportprimary (blurple)Exits the fork and sends a summary to the main session
Exit Forkdanger (red)Discards the fork cleanly
These buttons map to the fork_save, fork_report, and fork_exit handlers in views.py. They are equivalent to the save_context, report_updates, and exit_fork MCP tools.

Fork exit

When a fork ends, a “Fork Ended” embed is sent with a color indicating the exit strategy:
Exit strategyColor
SaveGreen
ReportBlue
Exit (discard)Grey

Persistence

Buttons remain functional across bot restarts through two mechanisms:
  • DynamicItem[Button]: Discord.py reconstructs button handlers from custom IDs using a regex pattern (act:(?P<action>[a-z_]+):(?P<data>.+)). The ActionButton class matches this pattern and dispatches to the correct handler.
  • Inquiry storage: Agent inquiry prompts are written to ~/.ollim-bot/state/inquiries.json so the prompt text is available even after a restart. Each entry includes a timestamp for TTL enforcement.
Direct action buttons (task operations, dismiss, fork controls) are fully persistent with no expiration — their data is encoded in the custom ID itself. Only agent inquiry buttons expire after 7 days.

Background fork behavior

Embeds sent from the main session or interactive forks are always delivered. In background forks, embeds are subject to additional constraints:
  • Ping budget: Each embed consumes one ping from the refill-on-read budget. When the budget is exhausted, discord_embed returns an error to the agent.
  • critical: true: Bypasses all three checks (per-session limit, busy state, and budget). Critical pings are tracked in a separate critical_used counter but never blocked.
  • allow_ping: false: When set in the background fork config, discord_embed is disabled entirely — critical: true does not override this.
  • Busy state: When the main agent is actively responding (lock held), non-critical embeds from background forks return errors. The agent should use report_updates instead.
Background fork embeds include a “bg” footer to distinguish them from main session messages. Interactive fork embeds show “fork”.

Next steps