// DOCS

Integrations

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 name
  • connected — whether this user has it linked
  • supports.{fetch, act, adapter} — which surfaces accept this source
  • description — 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:

  1. The template engine does not support Python-style format specs ({value:.2f}). Format in the consumer (dashboard, mini-site), not in the recipe.
  2. There's no {{run_iso_hour}} built-in. Derive idempotency from the response's own data (e.g. an upstream last_updated timestamp), not from run metadata.
  3. auth.type=api_key is metadata only — the runner doesn't auto-inject the header. You must reference the secret explicitly in endpoints[].headers via {{publisher.SECRET_NAME}}. The auth block exists so the installer UI knows the recipe needs that secret in the publisher vault.
  4. 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).