Google Loops — Setup and Daily Operation
The connector ingests Gmail threads, calendar events, and contacts into the
brain and maintains the open-loop record behind gbrain waiting. Full
references: docs/guides/google-connect.md (setup + every error and its
fix) and docs/guides/open-loops.md (how detection works).
Contract for the harness (read first)
- Relay
[SHOW USER]blocks verbatim. Setup commands print fenced[SHOW USER] ... [/SHOW USER]blocks — numbered steps with deep links. Pass them to the user unchanged (paraphrasing loses load-bearing detail like "Desktop app, NOT Web application"). Batch everything into ONE message per block. - The whole setup is exactly two user interactions. (1) The Google Cloud checklist + the user hands back the downloaded client JSON. (2) The user clicks one consent URL. If you find yourself asking a third question, re-read the block you skipped.
- Never put secrets in argv or chat when avoidable. When the user drops
client_secret_*.jsoninto chat, save it to a file (mode 0600) and pass the path:gbrain google connect --client-json <path>. Env (GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET) also works. Raw--client-id/--client-secretflags are the last resort. - Every command speaks JSON. Add
--jsonand read{ ok, status, next_action: { command, user_message }, error }. Whennext_action.user_messageis present, that IS the message to show the user; whennext_action.commandis present, that is your next call. Errors carry{ code, problem, cause, fix, doc_url }— show the userproblem+fix, nothing else. - Re-running is always safe.
gbrain google connectandgbrain google setupare idempotent state machines — the documented fix for most errors is "run it again."
Setup (the one command)
gbrain google setup --json
Handles: credential intake (prints the GCP checklist when nothing is on
file) → consent (loopback locally; auto paste-back over SSH/headless —
non-TTY flows complete via a second call:
gbrain google connect --code "<pasted-redirect-url>") → source
registration → a budgeted first sync (newest mail first; the deep backfill
resumes on later syncs automatically) → the first gbrain waiting digest.
Multiple accounts: repeat with --account work@example.com.
Already holding Google access another way (a Google CLI with its own auth,
gcloud, a credential gateway that mints tokens)? Skip OAuth and point the
source at it — no credential enters gbrain:
gbrain sources add gmail-work --kind google --account you@example.com \
--access command --token-command "<command that prints an access token>"
(--access env --token-env <VAR> reads an externally-refreshed token from
the environment instead.) Then gbrain sync --source gmail-work and
gbrain waiting work identically.
Verify health afterwards: gbrain google status --json (per-account
refresh probe) — and gbrain doctor carries a google_oauth check that
warns once a Testing-mode account goes 5+ days without a successful
refresh. An actively-syncing account gets no pre-warning before the 7-day
Testing-mode expiry — publishing to Production is the real fix.
Daily operation
gbrain waiting --json # the killer output: ranked people waiting
gbrain loops done <id> # user handled it
gbrain loops drop <id> # user is not going to do it
gbrain loops mute sender <email> # never track this sender again
waitingREFUSES on stale data (no successful sync in 24h) and names the exact fix (gbrain sync --source <id>). Run the sync, then retry. Only use--stale-okwhen the user explicitly accepts stale results.- When presenting loops, show: the counterparty, what's owed (summary), the
evidence quote, the deep link (opens the exact Gmail thread in the right
account), and the due date when present. The trusted-local result already
carries a paste-ready
textdigest — reuse it. - For "context to respond": each group carries the counterparty's entity
card (summary, recent history, other open threads). Need more, call
context_packwith the counterparty slug. - After the user says they replied/handled something, close the loop
(
loops done) — thread loops also self-close on the next sync when the reply is visible in Gmail.
Continuous ingestion
Google sources sync like any source: autopilot and gbrain sync --all pick
them up automatically. No cron of its own. A bare un-targeted gbrain sync
does NOT reach them — use --source <id> or --all.
Troubleshooting
Every failure has a typed code with the fix attached —
docs/guides/google-connect.md#troubleshooting is the canonical table. The
three the user will actually hit:
- "Google hasn't verified this app" during consent → expected; it's the user's own app: Advanced → Continue. Warn them BEFORE they click the URL.
access_denied_test_user→ they forgot to add themselves as a test user (the error carries the deep link).invalid_grant_testing_expiry(everything silently stopped ~day 7) → their consent screen is still in Testing; publish to Production, thengbrain google connect --reauth <email>.
Cost honesty
Commitment extraction sends recent email text (≤30 days, ≤50 threads/sweep)
to the configured chat provider. Tell the user once during setup; the off
switch is gbrain config set loops.extraction_enabled false. The
unanswered-thread detector is free and unaffected.
Output Format
When relaying gbrain waiting, present per counterparty, most urgent first:
## <Counterparty> (<N> open)
- [<loop_type>] <what's owed> (<age>) — due <date if any>
> "<evidence quote>"
<Gmail deep link>
The trusted-local --json result already carries this as a paste-ready
text field — prefer relaying it over re-rendering. For setup commands,
relay [SHOW USER] blocks verbatim and error.problem + error.fix on
failures; never dump raw JSON envelopes at the user.
Anti-Patterns
- Paraphrasing a
[SHOW USER]block. The checklists carry load-bearing detail ("Desktop app, NOT Web application", the test-user step). Relay verbatim, one message per block. - Asking the user for client_id/client_secret as chat text. Take the
downloaded JSON as a 0600 file (
--client-json <path>) or env vars; secrets in argv/chat are the last resort, never the default. - Answering "who is waiting on me" from
query/search. The open-loop record isopen_loops/gbrain waiting— search results have no loop-state semantics and will happily surface answered threads. - Bypassing the staleness refusal with
--stale-oksilently. Run the namedgbrain sync --source <id>first; only pass--stale-okwhen the user explicitly accepts possibly-outdated loops. - Marking loops done for the user. Close (
gbrain loops done <id>) only after the user says it's handled; thread loops self-close on the next sync when the reply is visible in Gmail.