Two card variants
Every connector response carries aroute 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
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)
Feature flag
The handoff preview card is gated by theinbox.gbAgentHandoffPreview feature flag.
- Default: OFF in all workspaces.
- Enable per-deployment: set the env var
NEXT_PUBLIC_FLAG_INBOX_GB_AGENT_HANDOFF_PREVIEW=1on the Vercelapps/appproject. 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.
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 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.
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.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) show a live
BotMessageSquarestatus 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:
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-streamdraft-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 adraftWithMe block — omitted entirely when no run has happened (status is idle), so conversations that were never drafted for stay clean:
finalDraftTextand lifecycle (status/phase, whether the deterministic fallback was used)handoffReasonwhen the run routed to human reviewtraceEvents(labels, decision/event type, ok/warn status) — the same reasoning trace shown in the expanded barsessionIdandinboundMessageIdfor correlating with server-side logslastError(message,recoverableflag, and — since v0.18.34 — a content-free upstreamdetailsuch asAI_APICallError status=429 cause=RateLimitError, bounded to 200 characters) when the run’s terminal state was a failure
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", andpayload.flowResponseRedactedpresent. - Campaign-origin conversations usually carry a
campaign/Campaignlabel, but the Flow trigger itself should key onpayload.replyKind = "interactive_flow"; thesentkeyword is a fallback for degraded payloads, not the primary proof.
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_filtersis case-insensitive;campaignmatches labels such asCampaign. Leave it empty to allow any Flow completion, including Meta ad/organic Flow completions.proactive_draft_auto_send_firstis deliberately narrow: only the firstflow_completion/keywordproactivedraft_replyin a conversation may be accepted automatically. Legacyall_inboundjobs,human_reviewdrafts, 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_dispatchRPC as the operator Send button. That preserves idempotency, audit, consent/service-window guards, and the normalmessage.dispatchoutbox path.
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_modifieddraft’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 byagent_id.
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:- 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. - Tool-surface token — stored as a workspace secret in Supabase Vault, never returned in any API response. Rotate via the Vault UI.
- Test connection — the Settings page performs a safe authenticated
GETto the configured endpoint and expects the connector to report adraft_runcapability before showing success.
Triage states
When the inbox trigger is disabled, the badge tells you why:See also
- Operations → Runbook — gb-agent triage block
- Operations → Feature Flags — flag catalogue
- Security → Vault Architecture — workspace secrets
- Platform → Data Model —
message_draftsandresponder_runsshape - Features → Advanced Inbox Features — connection banner and conversation tabs that surface Draft with me status