Prerequisites
- Development environment set up per the development guide
- Familiarity with the existing integration patterns (read one integration module before starting)
Adding a Google service
Existing services (Tasks, Calendar, Gmail) all follow the same structure: a scope ingoogle/auth.py, a module under google/, a CLI subcommand in main.py, and documentation in prompts.py.
Add the OAuth scope
Add the new scope to the
SCOPES list in src/ollim_bot/google/auth.py:src/ollim_bot/google/auth.py
Create the service module
Add a new module under Key conventions:
src/ollim_bot/google/. Follow the pattern established by tasks.py and calendar.py:src/ollim_bot/google/drive.py
_get_*_service()callsget_service(api, version)— credentials are obtained fresh on every callrun_*_command(argv: list[str])is the CLI entry point, usingargparsewith subparsers- Functions called from embed button actions (like
complete_taskanddelete_event) are module-level and return the affected item’s name for confirmation messages
Register the CLI subcommand
Add an entry to the Add the subcommand to the
routes dict in _dispatch_subcommand() in src/ollim_bot/main.py:src/ollim_bot/main.py
HELP string in the same file.Document in the system prompt
Add a section to
SYSTEM_PROMPT in src/ollim_bot/prompts.py describing the new CLI commands. Follow the format of the existing Google Tasks and Calendar sections — list available subcommands, flag names, and usage patterns so the agent knows how to use the integration.Adding an MCP tool
MCP tools are defined insrc/ollim_bot/agent_tools.py using the @tool decorator from claude_agent_sdk. All tools are registered on a single server named discord.
Define the tool function
Use the Tool functions are always
@tool decorator with a name, description, and JSON Schema for parameters:src/ollim_bot/agent_tools.py
async, take a single args dict, and return a dict with a content list of text blocks.Handle execution context
Use the If the tool sends visible output in a background fork, it must respect the ping budget and busy-check gates:For accessing the Discord channel, use the
_source() helper to determine if the tool is running in the main session, an interactive fork, or a background fork:get_channel() helper from channel.py:The DM channel is set once at startup via
init_channel() in channel.py (called from on_ready after owner.create_dm()). MCP tools read it via get_channel() — there is no per-fork channel state since all forks share the same DM channel.Adding a CLI command
CLI commands are dispatched fromsrc/ollim_bot/main.py. Each subcommand is a self-contained module with its own argparse setup.
Register in main.py
Add an entry to the Lazy imports keep startup fast — subcommand modules are only loaded when invoked via
routes dict in _dispatch_subcommand() in src/ollim_bot/main.py:src/ollim_bot/main.py
importlib.import_module.Update the HELP string to include the new subcommand.Adding a webhook spec
Webhook specs are markdown files with YAML frontmatter, stored in~/.ollim-bot/webhooks/. They define how external HTTP requests trigger background agent tasks.
Create the spec file
Add a markdown file in the webhooks directory:The
~/.ollim-bot/webhooks/deploy-notify.md
fields value is a JSON Schema that validates incoming payloads. A maxLength: 500 default is injected for any string field without an explicit limit.Configure optional behavior
The YAML frontmatter supports these optional fields:
| Field | Type | Default | Description |
|---|---|---|---|
id | string | — | Unique webhook identifier (used in the URL path) |
fields | object | — | JSON Schema for payload validation |
isolated | boolean | false | Run in an isolated background fork (no main session context) |
model | string | null | Override the Claude model for this webhook |
thinking | boolean | true | Enable extended thinking |
allow-ping | boolean | true | Allow the agent to ping the user |
update-main-session | string | "on_ping" | freely, blocked, always, or on_ping |
Set the webhook secret
The
WEBHOOK_SECRET environment variable must be set. Incoming requests are authenticated via the Authorization header:Understand the dispatch pipeline
When a webhook fires:
- Validation — the payload is checked against the
fieldsJSON Schema. Invalid payloads return400. - Haiku screening — string fields are screened by a fast Claude Haiku call for prompt injection. If any fields are flagged, the webhook is not dispatched.
- Prompt construction —
build_webhook_promptcombines the scheduling preamble (active routines and reminders) with the webhook’s markdown body and fenced payload data. - Background fork — the prompt is dispatched as a background fork with the spec’s configured behavior (isolated, model, allow-ping, etc.).
Webhook data is fenced with explicit labels in the prompt —
WEBHOOK DATA (untrusted external input) is separated from TASK (from your webhook spec) — so the agent can distinguish data from instructions. The Haiku screening step runs before the main agent sees any payload content.Next steps
Discord tools
Full reference for all twelve built-in Discord tools.
System prompt
How the system prompt is structured and where to document new tools.
Webhooks
How webhooks work from the user’s perspective.
Set up Google integration
Connect Google Tasks, Calendar, and Gmail.
