Skip to content

Connectors Overview

A connector reads one communication channel — an email inbox, a Discord server, iMessage/SMS, a WhatsApp export — and turns its messages into normalized rows in the shared cap_items inbox. From there the rest of the stack (classify, enrich, chat/RAG) treats every channel the same way.

Connectors come in two shapes:

  • Live — an incremental poll, driven by an enabled row in cap_connectors, that the scheduler runs on a cron.
  • Backfill — a one-shot CLI import of a bulk export or local database (python -m capture_agent.backfill <source> ...), for history that predates the live poll or for channels with no live half.

Capture is disabled by default

A fresh install ingests nothing. The live poll only processes cap_connectors rows where enabled = true, and that collection starts empty — you opt in per channel by creating a row.

The enable gate: cap_connectors

Every live connector is gated by one PocketBase collection, cap_connectors.

Field Type Purpose
kind select Which connector implementation polls this row: email, discord, or imessage.
enabled bool The poll only processes rows where this is true.
config json Connector-specific settings (IMAP host/port/folder, Discord channel allowlist, ...).
secret_ref text Non-secret name of the env-var group holding this row's credentials (see below). Never the credentials themselves.
owner_addresses json The owner's own handles/addresses on this channel (e.g. an email address, a Discord user id), used to tag items the owner sent as direction="out".
cursor json Opaque incremental position the connector owns and rewrites after every poll (per-folder IMAP UID, per-channel Discord snowflake, ...).
last_poll_at date Timestamp of the row's most recent poll attempt.
last_error text The last error message for this row; empty when the row is healthy.

Secrets: secret_ref + a key → an env var

Credentials are never stored in PocketBase — only the non-secret secret_ref name that points at an environment-variable group. A connector declares which keys it needs (email: USER + PASSWORD; a Discord bot: TOKEN), and each is resolved from:

JARVIS_{secret_ref}_{KEY}

For example, secret_ref=IMAP_MAIN with key USER resolves to JARVIS_IMAP_MAIN_USER.

If any required env var is missing when the poll runs, that row's last_error is set to:

missing credentials (set the secret_ref env vars)

and the poll moves on — one connector's missing creds never stop the others (per-connector isolation).

Where each connector runs

The email and discord connectors poll inside the container scheduler service, on the same cron as the rest of capture. imessage is host-only: chat.db is a local file behind macOS Full Disk Access, so it's excluded from the containerized poll and instead run by the owner under a launchd job on the Mac. The Homelab connector is a separate module (not a cap_connectors kind) and is likewise host-only — see Homelab.

Connectors at a glance

Connector Live poll Backfill Runs where kind value
Email / IMAP Yes Yes (mbox) Container (scheduler) email
Discord Yes Yes (data export package) Container (scheduler) discord
iMessage / SMS Yes Yes Host (launchd) imessage
WhatsApp No Yes (chat export) n/a — backfill CLI only not a cap_connectors kind
Homelab Host (launchd) not a cap_connectors kind

Creating a cap_connectors row

There's no dedicated setup UI yet — rows are created directly in the PocketBase admin:

  1. Open http://127.0.0.1:8091/_/ and log in with the superuser credentials (see Quickstart).
  2. Go to Collections → cap_connectors → New record.
  3. Fill in kind, enabled, config, secret_ref, and owner_addresses as described on the connector's own page.

See Email / IMAP for a full worked example, including the env vars, the exact config JSON, and how to verify the row is polling successfully — later connector pages follow the same shape.