docs

Agents

Connectors

A connector is a tool program in the connectors overlay space that wraps one external API. The API key never enters guest code: every request names a credential ref, and the runtime injects the header after the request is recorded, so the key is absent from the trace, the model context and the space.

The connectors

Program Summary
linear@v1 Linear issue-tracker connector (GraphQL) — issues, search, writes (create_issue, update_issue, create_comment); gql() is the raw escape hatch.
github@v1 The user's GitHub work context — issues, PRs, commits, repos, files, notifications; request() is the one raw write path, pinned to api.github.com.
granola@v1 Read-only Granola connector — AI meeting notes, transcripts, folders.
attio@v1 Read-only Attio CRM connector — records, lists, notes, members.
figma@v1 Read-only Figma connector — file metadata, comments, extracted text content.
intercom@v1 Read-only Intercom connector — conversations, contacts, help-center articles.
googleAuth@v1 Google account connection — one consent covers gmail/calendar/drive/sheets; connect, status, disconnect.
gmail@v1 Read-only Gmail connector — search, messages, threads, labels.
gmailSync@v1 Gmail → space sync: chunked full sync, history ticks, clean_html; mail lands as email_messages records.
googleCalendar@v1 Read-only Google Calendar connector — calendars, events, upcoming view, incremental sync tokens.
googleDrive@v1 Google Meet transcripts (primary) plus Drive Doc-export fallback, read-only.
googleSheets@v1 Read-only Google Sheets connector — discovery, tabs, ranges, header-mapped records.

Every method returns {ok, ...} or {ok: False, error} with an actionable message — a missing key explains how to connect, never a traceback. Fields keep the upstream API's own names, trimmed rather than renamed, so what the model knows about the API transfers.

ln = use("connectors:linear@v1")
ln.my_issues()                       # {ok, issues: [...]}
gh = use("connectors:github@v1")
gh.get_issue("anyproto/any", 42)

Credentials

A connector's requests carry credential: {ref, header, prefix}; the host resolves ref against the device-local secret store at request time.

Ref kind Shape Resolution
static connector.key.<name>, llm.key.<provider> a stored string injected as-is
managed OAuth connector.oauth.<provider> a handle: the host caches the access token in memory, refreshes it from the stored refresh token when stale, and injects Authorization: Bearer …

Secrets are seeded from a dotenv-style .connectors.env next to anybao.toml (or anyrt serve --secrets-file <path>), keyed by the ref itself:

llm.key.anthropic=sk-ant-…
connector.key.linear=lin_api_…
connector.key.github=github_pat_…
connector.key.granola=            # empty value deletes the stored secret

On every serve start each ref in the file is written through to the store — missing becomes bootstrapped, different becomes rotated, empty becomes removed; refs absent from the file are untouched. The ref set is open, so a new connector needs no runtime change. In the desktop app the same mechanism is fed from memory via Help → Import connector keys.

Stored values live as never-synced local fields in the agent_secrets dataset; the synced part of a record is only {key, secret: true}. Guest reads of that dataset are refused before execution, so the refusal is the recorded fact and no secret ever reaches a trace. Full detail: Credentials.

OAuth: Google without tokens in guest code

Static keys do not fit Google — the credential is minted by a user consent flow and expires hourly, so acquisition and renewal are themselves side effects. The custody rule: no access token, refresh token or authorization code is ever returned to guest code, written to a synced field, or recorded in a trace. The guest also never receives the consent URL.

ga = use("connectors:googleAuth@v1")
ga.connect()      # runs consent in the user's browser; blocks ≤120s
                  # → {ok, provider, grantedScopes, account}
ga.status()       # → {connected, pending, scopes, account, expiresAt}
ga.disconnect()   # revokes at Google AND deletes the local grant

Bring your own OAuth client (Google Cloud Console → Credentials → OAuth client ID → Desktop app) and seed it through the same file:

connector.oauth.google.client_id=…
connector.oauth.google.client_secret=…

The host runs authorization-code + PKCE over a loopback listener, stores the refresh token as connector.oauth.google.refresh (device-local), and the four Google connectors share the one connector.oauth.google ref. connect is for user-facing turns only, never cron; after a consent_timeout the consent window stays open a few minutes — poll status(). Provider descriptors (authorize/token/revoke URLs, default scopes, auth params) are a host-side table, so adding another provider is a table row.

Note. An empty connector.oauth.google.refresh= in the seed file deletes the token locally only — the grant stays live at Google until disconnect() revokes it.

Gmail sync

gmailSync@v1 turns a mailbox into space data: one mailbox object per address, one email_messages record per message (record id = Gmail message id, body = cleaned markdown, labelIds the only provider-mutable field), declared as a runtime dataset with an email search scope and written through upsert. Each cron tick lists a bounded slice, hydrates in 25-message batches, checkpoints after commit, and exits early when fuel runs low; steady state is history.list increments coalesced per message. The initial drain is a self-chaining once trigger chain with a circuit breaker, and the final hop nudges the agent to post the outcome in chat. The _gmailSync skill tells the agent to smoke-check the credential and settle the scope (time window, exclusions) with the user before arming a backfill.