> ## Documentation Index
> Fetch the complete documentation index at: https://docs.switchbord.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# gb-agent Connector

> How the gb-agent customer-agent connector surfaces drafts and human-review handoffs in the Switchbord inbox, and the dry-run boundary that keeps it safe.

The **gb-agent connector** is a Switchbord integration that lets an external customer-agent service (GBCA) propose replies for inbound conversations. The connector posts a structured response into Switchbord, the worker persists it on the message draft row, and the inbox renders it as a card the operator can accept, edit, dismiss, or review — depending on the agent's verdict.

This page is operator-facing. It explains what the connector does, how the inbox surfaces it, and — most importantly — what the connector **cannot** do.

## Two card variants

Every connector response carries a `route` field. The inbox renders one of two card variants based on that route.

### `draft_reply` — proposed message

The agent is confident enough to suggest a ready-to-send reply. The operator sees the existing AI-draft card:

* Header: ✨ AI draft · proposed *time*
* Metadata badges: route, intent, confidence, optional destination/dateWindow/partySize
* Body: the proposed message text
* Actions: **Accept & send**, **Edit**, **Reject**

This card is unchanged from earlier Switchbord releases.

### `human_review` — handoff preview

The agent does not have enough confidence (or the policy disallows autosend) and is handing the conversation back to a human. The operator sees the new amber handoff preview card:

* Header: 🧭 Agent handoff preview · proposed *time*
* Badges: route (Human review), connector (gb-customer-agent), optional handoff type
* Sections (each only shown when populated):
  * **Handoff packet** — type, recommended queue, open tasks, required verification, dry-run boundary
  * **Proposed review actions** — review-only chips (no click handlers, no side effects); each labelled "Review-only"
  * **Evidence references** — up to 5 visible, https-only links, mono-style IDs for non-link evidence
* Actions: **Dismiss** only (no Accept/Send, no create-operator-task, no queue-assignment button)

The card is read-only by design. Everything the operator does next happens through the standard Switchbord inbox controls — assign queue, change status, send a manual reply, escalate. The connector never performs any of those actions on the operator's behalf.

## Feature flag

The handoff preview card is gated by the `inbox.gbAgentHandoffPreview` feature flag.

* **Default**: OFF in all workspaces.
* **Enable per-deployment**: set the env var `NEXT_PUBLIC_FLAG_INBOX_GB_AGENT_HANDOFF_PREVIEW=1` on the Vercel `apps/app` project. Flag flips next deploy.
* **When OFF**: drafts with `route === "human_review"` continue to render the thin "Human review" badge inside the existing draft card. Nothing else changes.
* **When ON**: Variant B renders for those drafts.

See [Operations → Feature Flags](/operations/feature-flags) for the broader flag catalogue.

## The dry-run boundary

By default, the gb-agent connector is restricted to **proposing** — not **acting**. This is enforced in three layers (UI, worker, and the response schema itself) and is the contract you can rely on for manual Draft with me, manually queued connector drafts, `human_review` handoffs, and proactive draft mode when `proactive_draft_auto_send_first=false`:

| The connector **cannot by default**               | What happens instead                                                                 |
| ------------------------------------------------- | ------------------------------------------------------------------------------------ |
| Send a customer-visible message                   | Operator must click Accept & send (draft\_reply) or compose manually (human\_review) |
| Write to CRM / contact records                    | The connector response is read-only metadata; no write paths exist                   |
| Assign a conversation to a queue                  | The operator assigns manually via the existing queue UI                              |
| Mark a conversation read/unread                   | The operator changes status via the existing inbox controls                          |
| Create operator tasks                             | No task-creation surface is exposed; future work tracked separately                  |
| Open external URLs automatically                  | Evidence URLs are https-only with no `target="_blank"` on non-https sources          |
| Expose its endpoint, runId, or tool-surface token | The UI mapper strips all secret-sensitive fields before rendering                    |

The only intentional exception is the explicit workspace opt-in documented below: `proactive_draft_auto_send_first=true` may accept and dispatch the first successful Flow/keyword-triggered proactive `draft_reply` through the same `accept_message_draft_for_dispatch` RPC an operator uses. Outside that narrow path, effects remain manual operator actions inside Switchbord. If you see any other autonomous inbox action as a result of a connector response, that is a bug — please file it.

## "Draft with me" (per-conversation streaming assist)

Separately from the two card variants above, the composer has a **"Draft with me"** trigger — a single violet pill (`SparklesIcon`) on the composer's right edge, gated behind the same `draft_with_me` access mode as the rest of the connector surface (`/api/inbox/gb-agent/access`). Clicking it streams a full-context draft for the open conversation and stages it in a minimized status bar above the composer; nothing is ever auto-sent.

<Info>
  As of v0.18.29, Draft with me is the **only** AI trigger in the composer — the older inline `BotMessageSquare` run-action and its compact duplicate were retired so operators have a single, unambiguous entry point.
</Info>

### Per-conversation, background runs

Since v0.18.30, a Draft with me run is keyed to its **conversation**, not to the composer's mounted component. Run state (status, phase, trace, final draft text, handoff reason) lives in a module-level store (`gb-agent-draft-runs-store`) indexed by conversation id. Practically, this means:

* Switching to a different chat while a draft is generating does **not** cancel it — the run keeps streaming in the background and is still there (finished, handed off, or failed) when the operator switches back.
* Every conversation row, the open chat's header, and the tab strip (see [Advanced Inbox Features](/features/inbox-advanced#persisted-conversation-tabs)) show a live `BotMessageSquare` status badge tinted by that conversation's own run — violet/pulsing while streaming, green when a draft is ready, amber on a human-review handoff, red on failure, and hidden when idle.
* Only the explicit ✕ / Stop control cancels a run. Unmounting the composer (by navigating away) no longer aborts it.
* Starting a new run for the same conversation supersedes whatever was running before.

### Minimized status bar

The panel (v0.18.17) never covers the chat — it renders as a thin bar with a color-coded status dot and a click-to-expand disclosure:

| Dot color       | Meaning                                                  |
| --------------- | -------------------------------------------------------- |
| Violet, pulsing | Drafting in progress                                     |
| Green           | Draft ready — click to expand and **Drop into composer** |
| Amber           | Handed off to human review (no usable draft)             |
| Red             | Generation failed                                        |

Expanding the bar reveals the streaming answer text, a collapsible reasoning/tool trace, and (while active) a **Stop** control plus the always-visible ✕ that dismisses the bar entirely (cancelling a still-streaming run). `panelDismissed` and `panelExpanded` both reset on conversation switch and on every new run, so a stale bar never bleeds across chats. If a new inbound message arrives mid-run, a dismissible amber "stale context" banner warns the operator the in-flight draft may be answering an outdated message — the run is **not** cancelled automatically.

### Fallback chain

Since v0.18.20, a Draft with me run is a chain of attempts, not a single request: **stream → jittered-backoff retry of the stream → deterministic non-stream `draft-run` fallback**. The operator always ends with either a staged, usable draft or an explicit, friendly failure — never a silently empty composer. While the chain is working through retries or the deterministic fallback, the expanded bar shows a "retrying…" / "falling back…" hint so the wait is explained rather than silent.

### Copy chat → draft debug trail

The chat pane's **Copy chat** button (top-right, clipboard icon) writes a structured JSON export to the clipboard containing the full conversation context (v0.18.13). Since v0.18.33, if a Draft with me run has ever executed for that conversation, the export also carries a `draftWithMe` block — omitted entirely when no run has happened (status is `idle`), so conversations that were never drafted for stay clean:

* `finalDraftText` and lifecycle (`status` / `phase`, whether the deterministic fallback was used)
* `handoffReason` when the run routed to human review
* `traceEvents` (labels, decision/event type, ok/warn status) — the same reasoning trace shown in the expanded bar
* `sessionId` and `inboundMessageId` for correlating with server-side logs
* `lastError` (message, `recoverable` flag, and — since v0.18.34 — a content-free upstream `detail` such as `AI_APICallError status=429 cause=RateLimitError`, bounded to 200 characters) when the run's terminal state was a failure

This lets an operator grab a draft plus everything needed to triage an issue without opening devtools.

### Proactive campaign Flow drafts and auto-send-first

Independent of the manual "Draft with me" trigger, a workspace can opt in to **proactive** draft generation (v0.18.12, BORD-818): GB-Agent generates a draft in the background and stages it in the inbox composer before the operator opens the conversation.

For GB campaign Flow forms, the production WhatsApp shape is:

* Meta webhook: `type: "interactive"`, `interactive.type: "nfm_reply"`, `interactive.nfm_reply.name: "flow"`.
* Customer-visible/message body placeholder: `"Sent"`.
* Switchbord normalized message: `body = "Sent"`, `payload.messageType = "interactive"`, `payload.replyKind = "interactive_flow"`, and `payload.flowResponseRedacted` present.
* Campaign-origin conversations usually carry a `campaign`/`Campaign` label, but the Flow trigger itself should key on `payload.replyKind = "interactive_flow"`; the `sent` keyword is a fallback for degraded payloads, not the primary proof.

Recommended toggle sets:

| Outcome                                        | Settings → Integrations → GB-Agent                                                                                                                                                                                                                                                          |
| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Silent on Flow submissions                     | `proactive_draft_enabled = false` (master off). GB-Agent does nothing when `Sent` arrives.                                                                                                                                                                                                  |
| Draft in composer, no automatic customer reply | `enabled = true`, `mode_draft_reply_enabled = true`, `proactive_draft_enabled = true`, `proactive_draft_trigger_mode = flow_completion`, `proactive_draft_trigger_keywords = ['sent']`, optional `proactive_draft_label_filters = ['campaign']`, `proactive_draft_auto_send_first = false`. |
| Auto-send the first completed Flow reply       | Same as draft mode, plus `proactive_draft_auto_send_first = true` and the worker env kill switch must not be `GB_AGENT_PROACTIVE_AUTO_SEND_ENABLED=false`.                                                                                                                                  |

Operational notes:

* `proactive_draft_delay_seconds` (0–300, default 10) waits after the inbound message before generation. A follow-up inbound message cancels a still-pending proactive job and restarts the delay against the newest message.
* `proactive_draft_label_filters` is case-insensitive; `campaign` matches labels such as `Campaign`. Leave it empty to allow any Flow completion, including Meta ad/organic Flow completions.
* `proactive_draft_auto_send_first` is deliberately narrow: only the first `flow_completion`/`keyword` proactive `draft_reply` in a conversation may be accepted automatically. Legacy `all_inbound` jobs, `human_review` drafts, conversations with a prior accepted draft, conversations with a human-authored outbound message, and any RPC send block degrade to draft-only.
* Auto-send still uses the same `accept_message_draft_for_dispatch` RPC as the operator Send button. That preserves idempotency, audit, consent/service-window guards, and the normal `message.dispatch` outbox path.

<Warning>
  Keep `proactive_draft_auto_send_first` off for draft-in-composer review mode. Enable it only when the workspace explicitly wants the first successful Flow-response draft sent automatically; subsequent replies remain draft-only.
</Warning>

## GB-Agent Insights

`/insights` has a **GB-Agent** tab (v0.18.16, BORD-741) — read-only, workspace-scoped rollout and refinement analytics over `responder_runs` (the same telemetry table Draft with me and proactive drafts write to). There is no cost-control gate here; the tab exists purely to make GB-Agent usage visible.

The tab surfaces:

* **Summary tiles** — requests, completions, timeouts, failures, plus completion/timeout/failure rates.
* **Outcome breakdown** — `sent_unmodified`, `sent_modified`, `rejected`, `abandoned`, `timeout`, `failed`, with the derived modification rate (`sent_modified / (sent_unmodified + sent_modified)`) and rejection rate.
* **Edit-distance distribution** — a bucketed histogram (0–10%, 10–25%, 25–50%, 50–75%, 75%+) of how much of a `sent_modified` draft's text an operator changed before sending, plus the average ratio.
* **Time-to-send percentiles** — p50 / p90 / p99 / average operator decision latency (`time_to_action_ms`).
* **By mode** and **by operator/agent** rollups — the same aggregate breakdown grouped by `responder_type` (Response AI vs. campaign) or by `agent_id`.

The tab reads `GET /api/insights/gb-agent`, which accepts `sinceDays` (default 30, clamped 1–365) or explicit `from`/`to` and shares the SSR channel/date-range selectors with the rest of `/insights`. Access is gated the same way as the rest of `/insights` (`requireOperationsAccess`).

## Configuring the connector

Operator setup is done in **Settings → Integrations → gb-agent**. (When this surface ships in full, in-product help will cover the steps in detail; this page is the current source of truth.)

Required configuration:

1. **Endpoint URL** — the full draft-run endpoint where Switchbord posts inbound-message context for evaluation. For GBCA production, use `https://gb-customer-agent-production.up.railway.app/api/agent/draft-run`, not just the service root. Must be https with no userinfo, query string, or fragment.
2. **Tool-surface token** — stored as a workspace secret in Supabase Vault, never returned in any API response. Rotate via the Vault UI.
3. **Test connection** — the Settings page performs a safe authenticated `GET` to the configured endpoint and expects the connector to report a `draft_run` capability before showing success.

## Triage states

When the inbox trigger is disabled, the badge tells you why:

| State                     | Meaning                                                             | What to do                                                 |
| ------------------------- | ------------------------------------------------------------------- | ---------------------------------------------------------- |
| "Endpoint not configured" | No URL or token stored                                              | Configure in Settings → Integrations                       |
| "Endpoint unsafe"         | The configured URL is not https or contains userinfo/query/fragment | Replace with a clean https URL                             |
| "Draft already queued"    | A pending draft from the connector exists                           | Resolve the existing draft first (Accept, Edit, or Reject) |
| "No inbound message"      | No inbound message is available to evaluate                         | Wait for the next customer message                         |

## See also

* [Operations → Runbook](/operations/runbook) — gb-agent triage block
* [Operations → Feature Flags](/operations/feature-flags) — flag catalogue
* [Security → Vault Architecture](/security/vault-architecture) — workspace secrets
* [Platform → Data Model](/platform/data-model) — `message_drafts` and `responder_runs` shape
* [Features → Advanced Inbox Features](/features/inbox-advanced) — connection banner and conversation tabs that surface Draft with me status
