Skip to main content

Ruby Copilot — WhatsApp staff-assist redesign

Date: 2026-06-03 Status: Approved (design), pending implementation plan

Problem

Ruby currently auto-sends WhatsApp replies to patients (autopilot). This produced inconsistent behaviour: transient model/DB hiccups latched conversations off permanently, Meta re-deliveries caused doubles, and staff couldn’t predict or control what Ruby said. Even after reliability fixes, an autopilot that texts patients directly is hard to trust.

Decision

Pivot Ruby from autopilot (auto-sends to patient) to copilot (suggests to staff, staff approve + send). A wrong/late/duplicate message becomes structurally impossible in the default mode because Ruby never sends — staff do.

Goals

  • Ruby produces a suggested reply visible to staff only, never to the patient.
  • One click puts the suggestion into the composer, ready to edit/send. No auto-send.
  • Suggestions are on-demand (staff click a button) — cost-conscious, no DeepSeek call per inbound.
  • A conversation can be marked Ruby on/off.
  • Authorized roles: reception, admin, doctor.

Non-goals

  • No change to the Ruby knowledge assembler or the askRubyWhatsApp agent contract.
  • No auto-suggest-on-inbound (explicitly on-demand only).
  • No removal of the autopilot code path — it stays, gated behind opt-in.

Design

1. Clinic mode

Replace the whatsapp_assistant module’s boolean enabled with:
mode: 'off' | 'copilot' | 'autopilot'   // default 'copilot'
  • off — Ruby disabled clinic-wide.
  • copilot (default) — suggestions only; Ruby never sends.
  • autopilot — legacy auto-send path may fire (opt-in).
Migration: existing config enabled: truecopilot; enabled: falseoff. Stored in the same clinic_modules.config JSON (key mode); parseAssistantConfig back-fills mode from the old enabled boolean for any unmigrated row (no DB migration required — JSON config). This silences the current auto-sends immediately, including on the ssh & Associates test tenant.

2. On-demand suggest endpoint

POST /whatsapp/conversations/:id/suggest
  • Auth: staff with WhatsApp access (reception/admin/doctor); not patients.
  • Loads the conversation transcript (last ~8 messages, same window the dispatcher uses) + clinic knowledge block, calls askRubyWhatsApp, returns { reply, intent }. Never calls sendTextMessage.
  • Reuses the logic already proven in POST /assistant/preview (which “calls Ruby agent, never sends”), generalised to take a conversation id and be reachable by the three staff roles rather than admin-only.
  • Returns 409/empty gracefully if mode === 'off'.

3. Suggestion UI

  • Trigger: a Ruby “Suggest reply” button in the composer toolbar (visible to reception/admin/doctor when clinic mode !== 'off').
  • Render: on success, a staff-only card directly above the composer input, clearly labelled “Ruby suggestion — only your team can see this” with the Ruby branding. Buttons: Use and Dismiss. Loading + error states inline.
  • Use: dispatches a new chat-v2:suggest-use window event carrying the text; the Composer listens and calls setText(text) (mirrors the existing chat-v2:attach / chat-v2:toggle-internal-note event pattern). Staff edit and send manually. The card dismisses on Use.
  • The card is purely client-side ephemeral state — suggestions are not persisted as messages and never enter the messages table.

4. Per-conversation Ruby on/off

  • A toggle in the thread header, default OFF.
  • Backed by a single conversation label ruby-on: present = on, absent = off (so default-off = no label, which is the natural state of every existing thread). Toggling on adds ruby-on; toggling off (and any staff manual send — durable takeover) removes it. This retires the old ruby-paused label entirely; the ruby-resume endpoint folds into the toggle’s “on” direction.
  • Meaningful only in autopilot mode, where the auto-send dispatch fires for a thread only when ruby-on is present. In copilot mode there is no automation, so the toggle is hidden and the on-demand Suggest button is always available.

5. Auto-send gating

runRubyForInbound (the inline dispatch) gains a single early guard: run only when clinic mode === 'autopilot' AND the conversation has the ruby-on label. In copilot (default) it returns immediately at the top. All existing reliability fixes (no-latch-on-transient-error, wamid dedup, send retry) remain for the autopilot path.

Data model

  • No schema migration. mode lives in clinic_modules.config JSON. Per-conversation state stays in conversations.labels (text[]).

API changes

  • POST /whatsapp/conversations/:id/suggest — new (suggest, never send).
  • GET/PUT /whatsapp/assistant/configmode field replaces enabled (with back-compat parsing).
  • POST /whatsapp/conversations/:id/ruby-toggle — body { on: boolean }, adds/removes the ruby-on label (subsumes the old ruby-resume endpoint).
  • Update docs/api-reference.md per the API-documentation rule.

Files (anticipated)

  • server/src/lib/whatsapp-assistant-config.tsmode type + back-compat parse.
  • server/src/routes/whatsapp.ts — suggest endpoint, toggle endpoint, config mode.
  • server/src/lib/whatsapp-assistant-dispatch.ts — mode/toggle guard at top of runRubyForInbound.
  • server/src/lib/whatsapp-assistant-state.tsruby-off label helpers.
  • ui/src/components/chat-v2/composer/Composer.tsx — Suggest button + chat-v2:suggest-use listener.
  • ui/src/components/chat-v2/thread/ — suggestion card component + header Ruby toggle.
  • ui/src/lib/serverComm.tssuggestRubyReply, setRubyConversationMode clients.
  • Superadmin parity: surface clinic mode in the superadmin WhatsApp view (per the superadmin-parity rule).

Roles / permissions

Suggest button + toggle visible to reception, admin, doctor (existing WhatsApp inbox access). Patients never see suggestions. Clinic mode editing stays admin/superadmin.

Testing

  • Unit: parseAssistantConfig back-compat (enabledmode); the dispatch guard (copilot → no send; autopilot + ruby-off → no send; autopilot + on → sends).
  • The suggest endpoint returns text and never writes a message row.
  • Manual/visual: button → card → Use → composer populated; no patient-visible send.

Rollout

Default copilot takes effect on deploy → auto-sends stop immediately. Autopilot is re-enabled per clinic by an admin choosing the mode. No data migration to run.