Every external system brains talks to. Some are read sources — they poll outside services and write pages into your brain. Some are write targets — they accept a natural-language request, return a draft, and only send after you confirm. Most are both.
Integrations are themselves codex recipes — declarative or sandboxed packages anyone can install, upgrade, or publish. The built-in ones (Gmail, Calendar, Drive, GitHub, Monday, Telegram, Revolt) ship in the default codex; you can author your own with the same shape.
What's connected
Ask Claude:
"What integrations do I have?"
Claude calls list_integrations which returns one row per known
integration with:
name— the source key (e.g.gmail,monday)label— display nameconnected— whether this user has it linkedsupports.{fetch, act, adapter}— which surfaces accept this sourcedescription— one-line summary
The source enums on fetch_from_integration / act_on_integration
are filtered per-user — if a source isn't in the schema's enum, you
haven't connected it. Connect from /integrations/<source>.
The built-in catalog
| Integration | Read | Write | Notes |
|---|---|---|---|
| Gmail | yes | yes | OAuth + offline access. Threads → email pages, attachments parsed inline. |
| Google Calendar | yes | yes | OAuth. Events → calendar_event pages with parsed start/end/location/attendees. |
| Google Drive | yes | yes | OAuth. Metadata-only ingest today; body extraction on a follow-up cron. |
| GitHub | yes (board-link adapter) | — | OAuth. Connect a board to a repo, pull issues/PRs/releases on a schedule. |
| Monday.com | yes (board-link adapter) | — | OAuth. Materialize Monday items into a brains board. |
| Telegram | yes | yes | Per-user Bot tokens. Text your brain from your phone; automations push to it. |
| Revolt (BrainChat) | yes | yes | Vendored Revolt fork — DM your brain, draft+confirm outgoing messages. |
| Discord | — | — | Sign-in only today; channel ingest is planned. |
| CoinMarketCap Top 10 | yes | — | First from-scratch declarative recipe — hourly snapshot of top 10 coins. |
Plus internal infra: Anthropic (every service uses Claude for LLM calls), OpenAI (embeddings for hybrid search), Groq (cheap fallback for short Telegram turns). You don't connect those per-user — they're served by the platform.
Install flow
Every integration is a codex recipe. Installing one is a single tool call:
await brains.apply_starter_recipe({ slug: "gmail-inbox" });
Or, for the standard set in one shot:
await brains.apply_starter_recipe({ slug: "starter-pack" });
The starter pack bundles Gmail, Calendar, Drive, GitHub, and Monday
together so a new brain is one call away from being fully wired. For
OAuth-backed integrations the installer flow runs the consent dance
the first time you use the integration (or the first time you visit
/integrations/<source>).
Reading from an integration — fetch_from_integration
When you ask "any emails from Noah about Ethera?" and brains returns
nothing, the cron ingestor may not have caught up.
fetch_from_integration pulls fresh data from
Gmail/Calendar/Drive/etc., writes new pages into your brain, and
returns. After it returns, re-run your search / query /
list_pages and the data will be there.
await brains.fetch_from_integration({
source: "gmail",
input: { query: "from:noah ethera after:2026/04/01", limit: 100 },
});
// → { ingested_count: N, page_slugs: [...] }
// (then re-call search / query / list_pages)
For gmail and drive you can pass either an explicit
input: { query } (recommended — matches the codex action shape) or
a bare request string (the routing shim maps it to {query: request}).
For calendar you must pass input: { start, end }.
Prefer fetch_from_integration over the raw Google MCPs because it
persists into your brain — the same data is queryable from the web
app and every future Claude session.
Writing to an integration — act_on_integration (two-phase)
Risky writes — sending an email, creating a calendar event, creating a Drive doc, posting a Monday update — are two-phase: draft, then confirm. The tools never reach the external service on their own.
// 0. Resolve the install_id from the user's gmail-inbox install
// (via brains.query type=integration_action — the slug encodes it).
// 1. Draft.
const draft = await brains.act_on_integration({
install_id: gmailInstallId,
action_name: "send_email",
input: {
to: ["noah@a16z.com"],
subject: "Re: next week",
body: "We're in. Let me know what day works.",
thread_id: priorThreadId, // optional, for proper threading
},
});
// draft = { kind: "draft", draft_id, action, preview, payload, expires_at }
// 2a. Confirm — sends to Gmail.
await brains.confirm_action({ draft_id: draft.draft_id });
// → { status: "confirmed", result: { message_id: "<id>" } }
// 2b. Discard — no external side effect.
await brains.discard_action({ draft_id: draft.draft_id });
Always show the preview to the user before confirming. Drafts
expire after one hour. The draft lives in your brain so it's
auditable and resumable from /inbox — and the same draft can be
confirmed from web, from Telegram, or from your CLI; the row is the
same.
Auto-executing read-only actions
Each codex action declares requires_confirmation. Actions that
default false (Gmail query_emails, mark_read, add_labels;
Monday add_comment; etc.) execute inline when act_on_integration
is called — no draft, no confirm step. The dispatcher returns
{ kind: "auto_executed", action_name, result, audit_id } instead of
a draft, and an audit row is written so /inbox shows what fired
retroactively.
Optional edits at confirm time
confirm_action accepts an edits object that patches whitelisted
fields right before sending:
| Action | Editable fields |
|---|---|
email |
to, cc, bcc, subject, body |
event |
summary, description, location, start, end, attendees, send_updates |
file |
name, content |
Batching drafts in one turn
If you're producing N drafts in one turn (e.g. "send a follow-up to
everyone I met this week"), mint one UUID per turn and pass it as
batch_id on every draft. /inbox groups them together so the user
approves them as a set.
How board-link integrations work
GitHub and Monday are different — they don't draft and confirm. They're board-link adapters: a board points at the external resource, and a recipe materializes data on a schedule.
const link = await brains.create_board_link({
board_id,
kind: "github",
ref: { owner: "ssvlabs", repo: "brains" },
display_name: "brains repo PRs",
});
await brains.create_dataset_recipe({
link_id: link.id,
dataset_name: "prs",
adapter_op: "github.list_open_prs",
refresh_cron: "0 * * * *",
dedupe_key_field: "id",
dedupe_target_field: "pr_id",
});
See boards for the full link/recipe surface.
Building your own integration
There are two ways to add a new integration: declarative recipes (YAML-ish JSON, no code) or sandboxed recipes (TypeScript inside a Deno sandbox — the same sandbox as a user automation). Always try declarative first.
Declarative recipes — the CoinMarketCap pattern
A declarative integration is a single object that says: trigger on this cron, GET this URL with these headers, map each response item into a brains page. The runtime handles auth, retries, rate-limiting, idempotency, and metering. No code.
add({
slug: "coinmarketcap-top10",
name: "CoinMarketCap Top 10",
description: "Hourly snapshot of the top 10 cryptocurrencies.",
publisher: "<your-user-id>",
auth: { type: "api_key", header: "X-CMC_PRO_API_KEY", secret_name: "CMC_API_KEY" },
trigger: { kind: "cron", cron: "0 * * * *" },
endpoints: [{
op: "snapshot",
method: "GET",
url: "https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest?limit=10",
headers: { "X-CMC_PRO_API_KEY": "{{publisher.CMC_API_KEY}}" },
}],
mapping: {
iterate: "$.data[*]", // JSONPath over response
page_type: "crypto_coin_snapshot",
source_ref_prefix: "cmc:",
source_ref: "{{symbol}}@{{last_updated}}", // idempotency key
title: "{{name}} ({{symbol}}) — ${{quote.USD.price}}",
frontmatter: {
symbol: "{{symbol}}",
price_usd: "{{quote.USD.price}}",
market_cap_usd: "{{quote.USD.market_cap}}",
last_updated: "{{last_updated}}",
},
},
});
The source_ref field is the idempotency key — same source_ref →
no duplicate page write. Pick something that's stable per snapshot.
A few quirks to know up front:
- The template engine does not support Python-style format
specs (
{value:.2f}). Format in the consumer (dashboard, mini-site), not in the recipe. - There's no
{{run_iso_hour}}built-in. Derive idempotency from the response's own data (e.g. an upstreamlast_updatedtimestamp), not from run metadata. auth.type=api_keyis metadata only — the runner doesn't auto-inject the header. You must reference the secret explicitly inendpoints[].headersvia{{publisher.SECRET_NAME}}. Theauthblock exists so the installer UI knows the recipe needs that secret in the publisher vault.- Frontmatter values are strings — numeric fields become stringified numbers. Downstream consumers parse as needed.
Sandboxed recipes — when declarative isn't enough
If your integration needs OAuth, pagination, conditional fetches, cursor management, or any non-trivial logic, drop into a sandboxed recipe — same Deno sandbox as a user automation, just authored as a recipe so others can install it.
The shape mirrors a normal automation: a main.ts, a tool_grants
list, http_fetch hosts, and the same {{secret_name}} substitution
for credentials. The difference is that a recipe is installable —
other users can add it to their brain with one click.
The Google and Monday integrations are all sandboxed recipes (they predate the declarative path). CoinMarketCap is the first declarative one built from scratch.
Lifecycle — install, upgrade, uninstall
Every integration recipe has a publisher and N installers. When you publish a new version:
- Existing installers see "Update available" in the codex.
- Installs can be standalone or bundled (installed via a parent bundle). Bundled installs upgrade with the bundle; standalone installs upgrade individually.
- Per-install state (cursors, dedupe sets, OAuth tokens) is preserved across upgrades.
uninstall_recipe slug=<bundle> cascades — one call removes the
bundle and every child. Uninstalled entities sit in a 7-day recovery
window before they're hard-deleted; recover_recipe entity_id=...
restores them inside that window.
Tool surface
| Tool | Purpose |
|---|---|
list_integrations |
Discover what's available + connected for the caller. |
list_starter_recipes |
Browse the codex catalog. |
apply_starter_recipe |
Install a recipe (bundle or single). |
check_recipe_dependencies |
Pre-flight an install. |
check_recipe_upgrade / upgrade_recipe |
Bump installed entities to the latest recipe version. |
uninstall_recipe / list_uninstalled / recover_recipe |
Remove and restore inside the 7-day window. |
fetch_from_integration |
On-demand read; persists pages into the brain. |
act_on_integration |
Draft (or auto-execute) an integration action. |
confirm_action / confirm_actions |
One-shot execute on a draft (single / batch). |
discard_action / discard_actions |
Drop a draft. |
list_pending_actions |
List outstanding drafts. |
create_board_link / create_dataset_recipe |
Board-link adapter surface (GitHub, Monday, ICS, http_json). |