Qontak | AI Agent | Meta Business Agent Integration — Phase 1: MBA-Aware Foundation
HEADER BLOCK
| Field | Value |
|---|---|
| PM | Dimas Fauzi Hidayat |
| PRD Version | 1.7 |
| Status | DRAFT |
| PRD Type | NEW (Phase 1 of 3) |
| Epic | TBD — add once Epic is created |
| Squad | Chatbot (BOT) |
| RFC Link | N/A — RFC to follow via rfc-starter after PRD is READY |
| Figma Master | Pending — low-fi wireframe embedded in §6 (inbox control states); Figma TBD |
| Anchor | Yes — Qontak | AI Agent | Meta Business Agent Integration — ANCHOR |
| Labels | epic:qontak-chatbot | module:ai-agent | feature:meta-business-agent |
| Last Updated | 2026-07-18 |
Table of Contents
- HEADER BLOCK
- CONDITIONAL BLOCK: PHASE CONTEXT
- One-liner + Problem
- Target Users + Persona Context
- Non-Goals
- Scope Changes
- Constraints
- New Features
- API & Webhook Behavior
- System Flow + User Stories
- Rollout
- Observability
- Success Metrics
- Launch Plan & Stage Gates
- Dependencies
- Key Decisions + Alternatives Rejected
- Open Questions
- PRD CHANGELOG
CONDITIONAL BLOCK: PHASE CONTEXT
| Field | Value |
|---|---|
| Anchor PRD | Qontak | AI Agent | Meta Business Agent Integration — ANCHOR |
| Phase | Phase 1 of 3 |
| Phase Goal (verbatim from Anchor Phase Index) | Make the Qontak inbox fully aware of MBA-run conversations — persist standby copies, track who controls each room, and let agents take over and hand back. |
| Prior phases | None — this is the first phase. |
| Deferred to later phases | Phase 2: MBA onboarding/config UI on the channel page, MBA Graph management endpoints (eligibility/onboarding/allowlist/settings), SyncToMetaMba knowledge/persona sync, Resources/Actions sync columns. Phase 3: connector gateway (MBA executes Qontak-hosted actions), MBA agent test/eval integration. |
| Cross-phase dependencies | Phase 2 and 3 both consume this phase's room control-state model and standby persistence — the data foundation must be stable before management surfaces build on it. |
Evidence note (research grounding): sources: [] — no direct user research exists on MBA
adoption intent (product announced by Meta 2 Jul 2026). The problem is grounded in platform
mechanics (Meta developer docs) and code grounding of hub-core/chatbot (2026-07-07), not
customer interviews. Declared as a deliberate evidence gap; validate adoption intent with 3–5
design-partner conversations during the beta stage.
2. One-liner + Problem
One-liner: Make the Qontak inbox fully aware of Meta Business Agent conversations — every message persisted, control state visible, take-over and hand-back working.
Problem:
When Meta Business Agent is enabled on a WhatsApp number, consumer messages and MBA's replies arrive on the standby webhook field and control changes arrive on messaging_handovers — and hub-core's webhook consumer routes neither, so those conversations are invisible or half-visible in the Qontak inbox. An agent who replies implicitly takes control on Meta's side, but Qontak has no Thread Control client, so the conversation can never be returned to MBA — the room is permanently human-owned after one reply. Any Qontak customer with an eligible WABA can switch MBA on today in WhatsApp Manager without Qontak's involvement, which means the first adopter turns their paid Qontak inbox into a partially blind console with no warning.
3. Target Users + Persona Context
| Persona | Role | Goal | Pain | Workaround |
|---|---|---|---|---|
| Primary — CS Agent | Agent handling WhatsApp conversations in the Qontak web inbox | Read the full conversation (including MBA's replies), know who is driving, take over cleanly, and hand back when done | MBA-run rooms would show only fragments; no indicator of who controls the room; after one reply the agent owns the room forever | None — asks the customer to repeat context; room never returns to the AI |
| Secondary — CS Supervisor | Supervisor monitoring team workload and AI performance on WhatsApp | Spot which rooms MBA is driving vs humans, and trust the transcript for review/QA | Cannot distinguish MBA rooms in the room list; transcript gaps make after-the-fact review unreliable | None — cross-references WhatsApp Business app manually |
(See Constraints for plan availability and feature flag scope.)
4. Non-Goals
- No MBA onboarding or configuration UI — eligibility check, onboarding, allowlist, settings, and the channel-page "Meta Business Agent" section are Phase 2.
- No knowledge/persona sync to Meta —
SyncToMetaMba, the Resources "Meta sync" column, and the Actions "Meta connector" column are Phase 2. - No connector gateway — MBA executing Qontak-hosted actions is Phase 3.
- No MBA test/eval integration — running
AiAgentTestCaseagainst Meta's agent test/eval APIs is Phase 3. - No changes to Qontak's own AI agent pipeline — the existing bot flow / AI agent behavior on non-MBA rooms is untouched; Qontak's AI must never respond on rooms MBA controls.
- No mobile app changes — web inbox only in this phase.
- No reporting/analytics changes — MBA containment reporting consumes this phase's data but ships under the AI ROI report initiative, not here.
Scope Changes
Engineering surfaces this PRD touches (controlled vocab). Kept in sync with the scope_changes frontmatter above.
- Backend —
hub-core: routestandbyandmessaging_handoversevents in the Kafka webhook consumer (app/core/events/kafka_consumers/webhook_outboxes/incoming_consumer.rbchannel_event_type switch); persist standby consumer messages + MBA echoes as first-class messages; new room control-state field sourced from handover events; new Thread Control (release) method on a new MBA API client (baseapi.facebook.com, versioned viaX-API-Versionheader — sibling of, not extension to, thewa_cloudGraph client); suppress Qontak bot/AI responses on MBA-controlled rooms — suppression point is an RFC choice (ground-prd 2026-07-19): hub-core's message-interaction fan-out (repositories/messages/creates/customer.rb:80-91— note it serves ALL webhook subscribers, not only the bot) vs the chatbot dispatch entry (ProcessIncomingMessageWorker→process_incoming_message_with_resolve.rb).chatbot: no changes this phase unless the RFC lands suppression chatbot-side. - Frontend —
hub-chat(inbox): conversation control banner with Take over action, "Meta Business Agent" sender label on echo messages, handover timeline markers, "Return to Meta agent" composer action, "Meta AI" room-list tag on the room list — ground-prd 2026-07-19:ChatListhas notagprop; it renders tags fromroom.tagsdata via theroomItemTagscomputed (ChatList.vue:253-278, 294), so the tag ships as a new prop or a data-side tag injection (RFC choice). - Design — Figma for the four inbox elements (field-accurate low-fi wireframes exist; Figma frames TBD — flagged in Header).
5. Constraints
| Field | Value |
|---|---|
| Platform | Web inbox only (hub-chat). Mobile deferred (Non-Goal 6). |
| Performance | Standby message persisted and visible in inbox ≤ 5s from webhook receipt (p95). Control-state banner reflects a messaging_handovers event ≤ 2s after consumption (p95). Thread Control release round-trip feedback to the agent ≤ 3s (p95). |
| Data limits | Standby echoes stored as normal messages — same retention as all room messages. Handover events stored as room events, same retention as room history. No new retention class. |
| Plan scope | All plans with a WhatsApp Cloud channel. This phase is a safety/visibility layer, not a monetized feature — gating it would recreate the blind-inbox problem for lower tiers. |
| Feature flag | mba_aware_inbox | default: OFF. Enabled per organization. Flag OFF = current behavior (standby events consumed but only logged, no UI). Flag flips ON per org at beta, default ON at GA. |
| Tenancy | Standby/handover events must resolve to the owning organization via the existing ChannelIntegration webhook lookup — zero cross-tenant leakage tolerated (hard gate, see §12). |
| Data lifecycle | Standby echoes and handover room-events follow existing message/room retention and deletion flows — deleted when the room is deleted; no orphaned artifacts. Unroutable standby events (no matching channel integration) go to the existing dead-letter path with a 14-day retention, then purged. |
6. New Features
Surface: existing inbox conversation view (/inbox) — four additive elements, no new pages.
Inbox conversation (existing)
├── ControlBanner (NEW) — shown only when room has MBA control history
│ ├── State: agent-driving → "Meta Business Agent is handling this conversation" + [Take over]
│ └── State: app-driving → "You're in control" + [Return to Meta agent]
├── Message bubble (existing) — NEW sender type label: "Meta Business Agent" on standby echoes
├── Timeline marker (NEW) — centered pill on each control change:
│ "You took over — control passed to Qontak · {time} · {actor}" /
│ "Returned to Meta Business Agent · {time} · thread control pass"
└── Composer (existing) — NEW inline action "Return to Meta agent" (only when app holds control)
Room list (existing ChatList component)
└── Tag slot (existing prop) — NEW tag value "Meta AI" on rooms where MBA holds control
UI states:
- Loading: banner and markers render with the room history load — no separate spinner.
- Empty: rooms with no MBA history render exactly as today (zero visual change when flag ON but MBA unused).
- Error: Thread Control
releasefailure → banner unchanged (app still in control) + error toast "Couldn't return the conversation to Meta Business Agent. Try again." + retry affordance. - Success: control returned → banner switches to agent-driving state + timeline marker appended.
Wireframe — inbox control states (existing vs. new):
Everything in solid grey is the current inbox (room-list panel, message bubbles, composer — the existing ChatList component and its room-tags rendering). The four dashed-blue elements are the entire Phase 1 FE build: the control banner, the "Meta Business Agent" sender type on standby echoes, the two timeline markers on control changes, and the "Return to Meta agent" composer action. Figma frames TBD (Dependency 4).
7. API & Webhook Behavior
Written in plain language per PRD convention — HTTP/JSON specifics belong to the RFC. Meta docs v2.0.0 (2026-07-18): Thread Control is
POST /business/whatsapp/phone_numbers/{phone_number_id}/thread_controlonapi.facebook.com;actionenum ispass|release, and onlyreleasefunctions today ("relinquishes thread control and hands the conversation back to Meta Business Agent") —passis reserved for future use. All hand-back behavior in this PRD usesrelease.
| # | Behavior | Entity Affected | Triggered By | Expected Behavior | Failure Behavior |
|---|---|---|---|---|---|
| 1 | Consume standby webhook — consumer message | Room, Message | Meta delivers a consumer message while MBA holds control | Resolve org/channel via existing webhook lookup; create the room if it doesn't exist (MBA answering a brand-new customer — room starts unassigned, MBA-controlled); persist the consumer message on the room timeline | Unroutable event → dead-letter path + mba_standby_unroutable log; consumer retries per existing Kafka semantics — no message dropped silently |
| 2 | Consume standby webhook — MBA echo | Message | Meta delivers a copy of MBA's outbound reply | Persist as a message with sender type "Meta Business Agent" so the transcript is complete; no notification to agents (observing, not acting) | Same dead-letter + retry semantics as #1; duplicate echo (redelivery) is idempotent on the external message id |
| 3 | Consume standby delivery/read receipts | Message status | Meta delivers receipts for MBA's messages | Update message status so the inbox shows delivered/read consistently with normal rooms | Receipt for unknown message → logged, skipped (no crash) |
| 4 | Consume messaging_handovers | Room control state, Room event | Control of the conversation changes (either direction) | Set the room's control state to the event's new controller (meta_agent or app); append a room event that renders as the timeline marker; this webhook is the source of truth for control state | Out-of-order/duplicate events reconciled by event timestamp — latest wins; unroutable → dead-letter + log |
| 5 | Agent sends a message on an MBA-controlled room | Room control state, Message | Agent replies in the inbox (normal send) | Send via existing outbound path (Meta auto-transfers control to the app on send); optimistically set control state to app pending confirmation by the next messaging_handovers event | Send failure → existing send-error handling; control state NOT flipped (no handover happened) |
| 6 | Return control to MBA (Thread Control release) | Room control state, Room event | Agent clicks "Return to Meta agent" | Call the new Thread Control method on the Graph client; on success set control state to meta_agent + append room event; confirm against the messaging_handovers event that follows | Graph error → control state unchanged, error toast to agent, mba_release_control_failed log with Meta error code; retryable |
| 7 | Suppress Qontak automation on MBA-controlled rooms | Bot/AI dispatch | Inbound message on a room whose control state is meta_agent | Qontak bot flow / AI agent must NOT fire (MBA is the responder; double-response is a customer-facing bug and violates one-responder-per-number) | If control state is unknown (no handover event yet), default to suppression on rooms with any MBA history + mba_suppression_ambiguous log |
Meta API surface & build map — this phase's slice (docs v2.0.0, fetched 2026-07-18):
| Meta surface | Endpoints & key facts (v2.0.0) | Qontak builds | Reuses | Where |
|---|---|---|---|---|
| Webhooks | Subscribe via POST /{WABA_ID}/subscribed_apps — both existing subscribe calls send no field list (wa_embedded_signup/services/apis.rb:94-104, wa_cloud/services/apis.rb:63-70): the webhook field set (messages, standby, messaging_handovers) is Meta app-level configuration, so enabling the new fields takes effect for ALL WABAs at once and the consumer must tolerate them globally from day one (flag-OFF orgs log-only, per S06/AC-1) | Route standby + messaging_handovers in the Kafka consumer; persist standby messages/echoes; room control_state from handover events | wa_cloud incoming_consumer.rb switch, existing message persistence, embedded-signup subscription flow | S01–S03 |
| Thread Control | POST /business/whatsapp/phone_numbers/{phone_number_id}/thread_control on api.facebook.com; action ∈ {pass, release} — only release functions (hand back to MBA; pass reserved); caller must hold control; to = consumer number | release client method + optimistic control-state flip confirmed by the follow-up handover event | new MBA API client (below) | S04–S05 |
| MBA API client (cross-cutting, born this phase) | Base api.facebook.com (not graph.facebook.com), versioned via X-API-Version header; Bearer auth = system-user/BISU token with whatsapp_business_messaging, or the bizai_wa_enterprise_api_3p_access tech-provider capability (Anchor OQ-2) | New client as a sibling of, not extension to, the wa_cloud Graph client; Phases 2–3 add their endpoint groups onto it | system-user token pattern (FACEBOOK_SYSTEM_USER_ACCESS_TOKEN / per-org override — matches Meta's BISU model; LOCKBOX keeps holding channel secrets), existing client retry/error patterns | Scope Changes |
8. System Flow + User Stories
System Flow
- Customer sends a WhatsApp message to a number where MBA is enabled; Meta delivers it to MBA (primary responder) and to Qontak on the
standbyfield. - Qontak resolves the org/channel, creates the room if new, persists the consumer message; room control state =
meta_agent; room list shows the "Meta AI" tag. - MBA replies; Meta delivers a copy on
standby; Qontak persists it labeled "Meta Business Agent"; receipts update its status. - Agent opens the room: control banner reads "Meta Business Agent is handling this conversation"; transcript is complete.
- Customer raises something needing a human; agent clicks Take over (focuses composer) and sends a reply through the normal outbound path.
- Meta transfers control to the app; the
messaging_handoversevent confirms; Qontak sets control state =app, appends a "You took over" timeline marker; subsequent consumer messages now arrive on themessagesfield (existing path). - Failure branch: if the send fails, control state does not flip and the existing send-error UX applies.
- Agent resolves the issue and clicks Return to Meta agent; Qontak calls Thread Control
release. - On success: control state =
meta_agent, "Returned to Meta Business Agent" marker appended, banner reverts; MBA answers the next customer message. - Failure branch: Thread Control error → toast + retry; the room stays app-controlled;
mba_release_control_failedlogged. - Throughout: Qontak's own bot flow / AI agent never fires on rooms MBA controls (suppression check on every inbound dispatch).
System-flow diagram (User Journey — mirrors the numbered steps above, both failure branches included):
sequenceDiagram
autonumber
actor C as Customer (WhatsApp)
participant M as Meta / WhatsApp Cloud
participant MBA as Meta Business Agent
participant Q as Qontak (hub-core + inbox)
actor A as CS Agent
C->>M: Sends message
M-->>Q: standby field (consumer message)
Q->>Q: Resolve org, create room if new, persist — control = meta_agent — "Meta AI" tag
M->>MBA: Deliver to primary responder
MBA-->>M: Reply
M-->>Q: standby copy (MBA echo) + delivery/read receipts
Q->>Q: Persist echo labeled "Meta Business Agent"
A->>Q: Opens room (banner: "Meta Business Agent is handling this")
Note over A,Q: Customer raises something needing a human
A->>M: Take over — send reply (existing outbound path)
alt Send succeeds
M-->>Q: messaging_handovers (control = app)
Q->>Q: control = app — append "You took over" marker
C->>M: Follow-up message
M-->>Q: messages field (existing inbound path)
else Send fails
Q-->>A: Existing send-error UX — control NOT flipped
end
A->>Q: Click "Return to Meta agent"
Q->>M: Thread Control (release)
alt Pass succeeds
M-->>Q: messaging_handovers (control = meta_agent)
Q->>Q: control = meta_agent — append "Returned" marker
C->>MBA: Next message → MBA resumes
else Pass fails
Q-->>A: Error toast + retry — room stays app-controlled — log mba_release_control_failed
end
Note over Q: Suppression — Qontak bot/AI never fires while control = meta_agent
User Stories
| User Story | Importance | Mockup | Technical Notes | Acceptance Criteria |
|---|---|---|---|---|
| [MBA-S01] — Persist standby conversations As a CS agent, I want every message in an MBA-run conversation stored in the room, so that the transcript I see is complete. | Must Have | — (backend) | Data Fields: • external_message_id (string, required) — source: Meta webhook payload (idempotency key)• sender_type (enum, required: customer | meta_business_agent) — source: standby payload direction• channel_integration_id (fk, required) — source: existing webhook lookupBefore-After Behavior: Before: standby events are not routed — MBA conversations are invisible or fragmentary. After: consumer messages, MBA echoes, and receipts persist as first-class messages on the room. | — Happy Path — • AC-1: Given MBA holds control of a conversation, when a consumer message arrives on the standby field, then it is persisted on the correct org's room within 5s (p95) and appears in the inbox timeline.• AC-2: Given MBA sends a reply, when its echo arrives on standby, then it is persisted with sender type "Meta Business Agent" and its delivery/read receipts update its status.• AC-3: Given no room exists for the consumer (MBA answering a first-contact customer), when the first standby event arrives, then a room is created unassigned with control state meta_agent and no agent notification fires.• AC-4: Given Meta redelivers an event, when the same external_message_id arrives twice, then exactly one message exists (idempotent).— Error / Unhappy Path — • ERR-1: Given a standby event whose webhook resolves to no channel integration, when it is consumed, then it goes to the dead-letter path (14-day retention) and mba_standby_unroutable is logged with the WABA id — never silently dropped.— Permission Model — • CAN: system (webhook consumer) • CANNOT: n/a — no user-triggered path — UI States — • Success: messages render in timeline order with existing bubble components. |
| [MBA-S02] — Track who controls the room As a CS agent, I want the room to know whether Meta's agent or Qontak holds the conversation, so that I always know who is driving. | Must Have | — (backend) | Data Fields: • control_state (enum, required: meta_agent | app) — source: messaging_handovers payload• handover_event room event (actor, direction, timestamp) — source: webhookBefore-After Behavior: Before: no control-state concept exists on rooms; messaging_handovers is unhandled. After: every control change updates the room's control state and appends a room event; the webhook is the source of truth. | — Happy Path — • AC-1: Given a messaging_handovers event transferring control to the app, when it is consumed, then the room's control state is app within 2s (p95) and a room event is appended.• AC-2: Given a handover event transferring control back to Meta, when it is consumed, then control state is meta_agent and a room event is appended.• AC-3: Given duplicate or out-of-order handover events, when consumed, then the event with the latest timestamp determines control state (no flapping). — Error / Unhappy Path — • ERR-1: Given a handover event for an unknown room, when consumed, then it is dead-lettered and mba_handover_unroutable is logged.— Permission Model — • CAN: system (webhook consumer) • CANNOT: agents/admins cannot edit control state directly — it only changes via webhook or the S05 pass action. |
| [MBA-S03] — See MBA state in the inbox As a CS agent, I want a control banner, sender labels, timeline markers, and a room-list tag on MBA conversations, so that I can read the room at a glance. | Must Have | §6 wireframe — Figma TBD | Data Fields: • control_state (from room, S02)• sender_type (from messages, S01)• handover room events (S02) Before-After Behavior: Before: MBA rooms are indistinguishable from normal rooms with missing messages. After: banner shows who is driving, MBA messages are labeled, control changes render as timeline markers, and the room list shows a "Meta AI" tag rendered through ChatList's existing tags row (new prop or room-tags data injection — no tag prop exists today). | — Happy Path — • AC-1: Given a room with control state meta_agent, when the agent opens it, then the banner reads "Meta Business Agent is handling this conversation" with a Take over action, and the room list shows the "Meta AI" tag.• AC-2: Given a room with control state app, when opened, then the banner shows "You're in control" with a Return to Meta agent action, and the tag is absent.• AC-3: Given handover room events exist, when the timeline renders, then each control change appears as a centered marker with actor and timestamp. — Error / Unhappy Path — • ERR-1: Given the control state cannot be determined (no handover event yet on a room with MBA history), when the room opens, then the banner shows the observing state (not "You're in control") and no automation fires (per S7 #7). — Permission Model — • CAN: any agent/supervisor with access to the room (read) • CANNOT: roles without room access — elements not rendered — UI States — • Loading: renders with room history load • Empty: rooms with no MBA history are visually unchanged • Error: banner falls back to observing state • Success: banner + labels + markers + tag consistent with control state |
| [MBA-S04] — Take over from MBA As a CS agent, I want replying to take the conversation from Meta's agent, so that escalation is one action, not a procedure. | Must Have | §6 wireframe — Figma TBD | Data Fields: • outbound message via existing send path • optimistic control_state = app pending webhook confirm (S02)Before-After Behavior: Before: an agent reply on an MBA room behaves like any send, but Qontak has no record that control moved. After: send triggers optimistic control-state flip, confirmed by the messaging_handovers event; banner and markers update. | — Happy Path — • AC-1: Given MBA holds control, when the agent sends a reply, then the message is delivered via the existing outbound path and the banner switches to "You're in control" optimistically. • AC-2: Given the subsequent messaging_handovers event arrives, when consumed, then the optimistic state is confirmed and the "took over" timeline marker is appended with the agent's name.• AC-3: Given the customer sends follow-ups after takeover, when they arrive on the messages field, then they flow through the existing (non-standby) inbound path unchanged.— Error / Unhappy Path — • ERR-1: Given the send fails, when the error returns, then control state does NOT flip, the existing send-error UX shows, and no marker is appended. — Permission Model — • CAN: agents assigned/with access to the room • CANNOT: read-only roles — composer disabled as today |
| [MBA-S05] — Return the conversation to MBA As a CS agent, I want to hand the conversation back to Meta's agent when I'm done, so that the AI resumes and my queue stays clean. | Must Have | §6 wireframe — Figma TBD | Data Fields: • Thread Control release call on the new MBA API client (new method)• control_state = meta_agent on success + room eventBefore-After Behavior: Before: no Thread Control client exists — control can never be returned; one reply owns the room forever. After: a Return action calls release; on success the room reverts to MBA control with a timeline marker. | — Happy Path — • AC-1: Given the app holds control, when the agent clicks "Return to Meta agent", then Thread Control release is called and on success the banner reverts within 3s (p95) and a "Returned" marker is appended.• AC-2: Given control was returned, when the customer's next message arrives, then it arrives on standby and MBA answers (verified in beta with a live MBA number).— Error / Unhappy Path — • ERR-1: Given the Graph call fails, when the error returns, then control state stays app, an error toast with retry shows, and mba_release_control_failed is logged with Meta's error code.• ERR-2: Given repeated failures (3 attempts), when the agent retries, then the toast suggests checking MBA status on the number and the failure count is visible in observability (§10 alert). — Permission Model — • CAN: agents with access to the room; supervisors • CANNOT: read-only roles — action not rendered — UI States — • Loading: action shows pending state during the call • Error: toast + unchanged banner • Success: banner reverts + marker appended |
| [MBA-S06] — Feature flag + org rollout control As a Qontak platform admin, I want the MBA-aware layer behind a per-org flag, so that rollout is controlled and reversible. | Should Have | — | Data Fields: • mba_aware_inbox flag (per org, default OFF)Before-After Behavior: Before: n/a — capability doesn't exist. After: flag OFF = events consumed and logged only (no UI, no persistence side effects beyond logging); flag ON = full S01–S05 behavior. | — Happy Path — • AC-1: Given mba_aware_inbox is OFF for an org, when standby/handover events arrive, then they are logged for observability but no messages/room events are persisted and no UI elements render.• AC-2: Given the flag flips ON, when the next event arrives, then persistence and UI behavior activate without deploy or migration. — Error / Unhappy Path — • ERR-1: Given the flag flips OFF after use, when agents open previously-persisted MBA rooms, then existing messages/markers remain visible (no data deletion) but new events stop persisting. |
| [MBA-S01-NEG] — No Qontak automation on MBA rooms (Guard Rail — from Non-Goal 5) As Qontak's bot flow / AI agent, when a message arrives on a room MBA controls, then I do not respond. | Guard Rail | — | — | NEG-1: Given a room with control state meta_agent, when an inbound message arrives, then the Qontak bot flow / AI agent does not fire and no bot reply is sent (verified: zero double-responses in beta). |
| [MBA-S02-NEG] — No MBA configuration from Qontak (Guard Rail — from Non-Goals 1–2) As an org admin, when I look for MBA settings in Qontak this phase, then no configuration surface exists. | Guard Rail | — | — | NEG-1: Given Phase 1 is live, when an admin opens the WhatsApp channel Account details page, then no Meta Business Agent section renders (Phase 2 scope) and no sync columns appear in Bot automation. |
Dependencies: MBA-S03 depends on S01+S02; S04 depends on S02; S05 depends on S02+S04. MBA-S01/S02 have no intra-PRD dependencies.
9. Rollout
| Stage | Audience | Duration | Exit condition |
|---|---|---|---|
| Stage 0 — Internal QA | Qontak test org on a Meta-allowlisted test WABA with MBA enabled (test consumer numbers via MBA allowlist) | 2 weeks | All §12 Gate 1 criteria pass on live MBA traffic |
| Stage 1 — Closed beta | 2–3 design-partner orgs with MBA pilot access, flag ON per org | 4 weeks | Gate 2 criteria pass; design-partner interviews validate the adoption-intent evidence gap (§CB) |
| Stage 2 — GA | Flag default ON for all orgs with WhatsApp Cloud channels | — | Gate 3 |
No data migration is involved — the feature only affects events arriving after the flag is ON; historical MBA conversations (from any pre-flag period) are not backfilled, and rooms with partial history render whatever was captured (transition behavior: the banner appears from the first post-flag handover event onward).
10. Observability
| Event | Trigger | Key properties |
|---|---|---|
mba_standby_message_persisted | S7 #1/#2 success | org_id, room_id, sender_type, lag_ms |
mba_standby_unroutable | S7 #1 failure | waba_id, reason |
mba_handover_event | S7 #4 consumed | org_id, room_id, new_controller, source_timestamp |
mba_take_over | S7 #5 optimistic flip | org_id, room_id, agent_id |
mba_release_control_succeeded / mba_release_control_failed | S7 #6 | org_id, room_id, agent_id, meta_error_code (failure) |
mba_suppression_fired / mba_suppression_ambiguous | S7 #7 | org_id, room_id |
Dashboard owner: Chatbot squad (including the hub-core consumer widgets — squad owns the feature end-to-end).
Alert thresholds:
mba_release_control_failedrate > 5% over 1h → page Chatbot on-call.- Standby persistence lag p95 > 60s over 15m → page Chatbot on-call (consumer backlog).
- Any
mba_suppression_ambiguous> 10/day → investigate control-state coverage.
Post-Launch Monitoring Cadence
Weekly review of the dashboard for the first 4 weeks post-GA (owner: Dimas F + Chatbot EM). Trigger for immediate investigation: transcript completeness (< 99%) or any confirmed double-response (Qontak automation firing on an MBA-controlled room — target: zero).
11. Success Metrics
Adoption & Usage:
| Metric | Definition | Baseline | Target |
|---|---|---|---|
| MBA rooms observed | # rooms with ≥1 standby-persisted message, per org | 0 (capability doesn't exist) | Tracking live for 100% of flag-ON orgs from Stage 1 |
Quality & Accuracy:
| Metric | Definition | Baseline | Target |
|---|---|---|---|
| ⭐ Transcript completeness | Messages persisted ÷ messages delivered on standby+messages webhooks for MBA rooms | 0% (standby unprocessed) | ≥99% within 30 days of GA |
| Control-state accuracy | Rooms whose banner matches the latest messaging_handovers event (sampled audit) | N/A | 100% in beta, sustained at GA |
| Double-response incidents | Qontak automation replies on MBA-controlled rooms | N/A | 0 — hard guard rail (MBA-S01-NEG) |
Efficiency & Impact:
| Metric | Definition | Baseline | Target |
|---|---|---|---|
| Hand-back usage | % of taken-over MBA rooms returned via Thread Control release | N/A — capability doesn't exist | ≥50% of taken-over rooms within 60 days of GA (validates the hand-back loop is real, not one-way) |
12. Launch Plan & Stage Gates
| Gate | Stage | Criteria (measurable) |
|---|---|---|
| Gate 1 — QA exit | Stage 0 → 1 | 100% of test-matrix standby/handover payloads persisted correctly; Thread Control release verified round-trip on the live test number; zero cross-tenant events in a multi-org test; suppression verified (zero bot replies on MBA rooms) |
| Gate 2 — Beta exit | Stage 1 → 2 | Transcript completeness ≥99% across beta orgs; control-state accuracy 100% on sampled audit; mba_release_control_failed < 5%; no Sev-2+ incident attributable to the feature for 2 consecutive weeks |
| Gate 3 — GA health | Post-GA | §10 monitoring cadence green for 4 weeks; flag remains the kill switch (flipping OFF reverts to logged-only behavior with no data loss) |
13. Dependencies
| # | Dependency | Owner | Blocking? | Notes |
|---|---|---|---|---|
| 1 | Chatbot squad BE capacity for the hub-core changes — webhook consumer cases, control-state model, Thread Control client | Chatbot squad EM | YES | Chatbot squad owns this end-to-end (AI Agent feature); the work lands in hub-core, so it is cross-repo but NOT cross-squad. Needs one committed BE engineer before the Epic moves to In Progress. |
| 1a | hub-core PR review by the repo's maintaining squad | Chat squad reviewers | NO | Code-review courtesy on their repo, not a delivery dependency. |
| 2 | Meta MBA pilot/allowlist access on a Qontak-owned test WABA | Dimas F via Meta partner manager | YES | Gate 1 requires live MBA traffic. Mitigation while waiting: build against Meta's documented payloads + contract tests, so only Stage 0 verification (not development) blocks on access. |
| 3 | Webhook field subscription — standby + messaging_handovers added to the app's subscribed fields in the Meta Developer Portal | Platform ops | YES | One-time config; without it no events arrive at all. |
| 4 | Figma frames for the four inbox elements | Design (Wulan) | NO | Low-fi wireframes exist; FE can build from them, Figma follows for polish. |
| 5 | Design-partner orgs with MBA pilot access for Stage 1 | Dimas F + CS | NO (blocks Stage 1 timing, not build) | 2–3 orgs; also serves the declared evidence gap. |
14. Key Decisions + Alternatives Rejected
14a — Decisions Made
| Date | Decision | Rationale |
|---|---|---|
| 2026-07-08 | Consume standby/messaging_handovers via the existing Kafka webhook consumer routing (new cases in the existing channel_event_type switch) | Grounded in code: the consumer's event-type dispatch is the established pattern for every WhatsApp webhook field; no new service |
| 2026-07-08 | Persist MBA echoes as first-class messages (not a shadow table) | Inbox, search, retention, and future reporting all work for free; sender type distinguishes them |
| 2026-07-08 | messaging_handovers is the source of truth for control state; agent-send flips state only optimistically | Meta owns the handover; local guesses must reconcile to the webhook (S8 AC coverage) |
| 2026-07-08 | Room-list indicator reuses the existing ChatList tag rendering with a new "Meta AI" value (corrected 2026-07-19: no tag prop exists — tags come from room.tags data; the indicator is a new prop or data-side injection) | Zero new components in the room list; IA-alignment rule |
| 2026-07-08 | Flag default OFF at beta, ON at GA, and the flag doubles as the kill switch (logged-only mode) | Reversible rollout without deploys; observability keeps flowing even when OFF |
14b — Alternatives Rejected
| Alternative | Why Rejected | Date |
|---|---|---|
Poll the Graph API for conversation state instead of consuming messaging_handovers | Polling is rate-limited, laggy, and inverts Meta's push model; the webhook is authoritative | 2026-07-08 |
| A separate "MBA inbox" view for observed conversations | Splits the agent's workflow; the whole point is one inbox with complete rooms | 2026-07-08 |
| Defer standby persistence to Phase 2 and ship only control actions now | Take-over without a complete transcript is unusable; persistence IS the foundation | 2026-07-08 |
| Block MBA-controlled rooms from agent replies until an explicit "Take over" click | Adds friction to escalation; Meta's model already treats send-as-takeover, we mirror it | 2026-07-08 |
15. Open Questions
| # | Type | Question | Owner | Deadline |
|---|---|---|---|---|
| 1 | Assumption | The "Meta Business Agent" sender is modeled on the existing bot-participant mechanism (vs a new participant type) — final call in the RFC | Chatbot squad tech lead | RFC review |
| 2 | Open Question | Do MBA replies count toward agent SLA / first-response metrics, or are MBA rooms excluded from SLA until takeover? | Dimas F + Reporting PM | 2026-08-15 |
| 3 | Open Question | Placement of "Return to Meta agent" — composer inline action vs conversation-header action (wireframe flags both) | Design (Wulan) | Figma delivery |
| 4 | Risk | Meta's pilot-stage payloads may differ at GA — Mitigation: contract tests against documented payloads + Stage 0 verification on a live allowlisted number before any org-facing rollout | Chatbot squad | Stage 0 |
| 5 | Risk | Test WABA / MBA allowlist access is denied or slow — Mitigation: development proceeds on documented payloads; escalate via Meta partner manager; Stage 0 timeline (not build) absorbs the delay | Dimas F | 2026-08-01 |
PRD CHANGELOG
| Version | Date | By | Section | Type | Summary |
|---|---|---|---|---|---|
| 1.0 | 2026-07-08 | Claude | All | CREATED | Initial Phase 1 PRD created from the MBA strategic-response grounding (hub-core + chatbot code grounding and IA-aligned wireframes, 2026-07-07). |
| 1.1 | 2026-07-08 | Claude | 10, 13, 15 | MODIFIED | Ownership corrected per PM: Chatbot squad owns end-to-end including hub-core changes (cross-repo, not cross-squad); Chat squad reduced to PR review; observability + alert ownership and open-question owners updated. |
| 1.2 | 2026-07-08 | Claude | Header, 6, 8 | MODIFIED | Embedded the inbox control-states wireframe (wireframes/phase-1-inbox-control-states.svg) in §6; Header Figma line and story Mockup cells now link it. |
| 1.3 | 2026-07-08 | Claude | 8 | MODIFIED | Added the System-flow sequence diagram (mermaid) to §8, transcribing the numbered flow incl. both failure branches (per score-prd 14a.5). |
| 1.4 | 2026-07-18 | Claude | Header, Scope, 5, 6, 7, 8, 10, 11 | MODIFIED | API-grounding vs Meta MBA docs v2.0.0 (§7 Meta API surface & build map): Thread Control hand-back verb corrected pass → release throughout (docs: only release functions; pass reserved), events renamed mba_release_control_*, client re-anchored to the new api.facebook.com MBA API client. |
| 1.5 | 2026-07-18 | Claude | Header, 7 | MODIFIED | Standalone API-inventory doc dissolved into the Phase PRDs per PM instruction: §7 gains this phase's "Meta API surface & build map" table (Webhooks, Thread Control, MBA API client). |
| 1.6 | 2026-07-19 | Claude | Header, Scope, 7, 8, 14 | MODIFIED | ground-prd fixes: ChatList tag mechanism corrected (no tag prop — room.tags data; new prop or injection, RFC choice); webhook field subscription documented as app-level (neither subscribe call sends subscribed_fields); MBA client re-anchored on the system-user/BISU token pattern (not per-channel LOCKBOX); consumer full path named; suppression-point RFC choice recorded (hub-core fan-out serves all subscribers vs chatbot dispatch). |
| 1.7 | 2026-07-19 | Claude | 8 | MODIFIED | Mermaid parse fix: semicolons in sequence-diagram message text broke rendering (replaced with em dashes); diagram now validates with mmdc. |