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.
1
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
2
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
3
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.4
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.5
Enable the scope in Google Cloud Console
In your Google Cloud project, enable the API for the new service (e.g., Google Drive API) under APIs & Services > Library. The OAuth consent screen must also list the new scope.
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.
1
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.2
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:3
Register the tool
Add the tool function to the
tools list in the agent_server at the bottom of agent_tools.py:src/ollim_bot/agent_tools.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.
1
Create the command module
Implement a
run_*_command(argv: list[str]) function using argparse:2
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.3
Document in the system prompt
Add CLI usage documentation to
SYSTEM_PROMPT in src/ollim_bot/prompts.py so the agent can invoke the new command via its bash tool.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.
1
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.2
Configure optional behavior
The YAML frontmatter supports these optional fields:
3
Set the webhook secret
The
WEBHOOK_SECRET environment variable must be set. Incoming requests are authenticated via the Authorization header:4
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.
