Qontak | Chatbot | AI Agent — Qontak Action — Send WhatsApp Flow Action
HEADER BLOCK
| Field | Value |
|---|---|
| PM | Dimas Fauzi Hidayat |
| PRD Version | 1.1 |
| Status | DRAFT |
| PRD Type | PHASE |
| Epic | TBD — create once RFC is agreed |
| Squad | Hadiningbot Squad |
| Product Module | Bot, AI, and Automation |
| RFC Link | N/A — pending |
| Figma Master | N/A — pending. No qontak-designer prototype exists yet for this action (unlike Send Attachment's SendAttachmentDrawer.vue reference) — tracked as a design dependency in Section 15. |
| Anchor | Qontak Action — ANCHOR |
| Labels | epic:qontak-chatbot | module:ai-agent | feature:send-whatsapp-flow-action |
| Last Updated | 2026-07-09 |
Status values:
DRAFT→READY→BUILD→SHIPPEDREADY gate: Epic cannot move to In Progress in Jira without PRD Link + RFC Link populated.
Table of Contents
- HEADER BLOCK
- 2. CONDITIONAL BLOCK: PHASE CONTEXT
- 3. One-liner + Problem
- 4. Target Users + Persona Context
- 5. Non-Goals
- 6. Constraints
- 7. Feature Changes
- 8. New Features
- 9. API & Webhook Behavior
- 10. System Flow + User Stories + ACs
- 11. Rollout
- 12. Observability
- 13. Success Metrics
- 14. Launch Plan & Stage Gates
- 15. Dependencies
- 16. Key Decisions + Alternatives Rejected
- 17. Open Questions
- PRD CHANGELOG
2. CONDITIONAL BLOCK: PHASE CONTEXT
Anchor PRD: Qontak | Chatbot | AI Agent — Qontak Action — ANCHOR
(../qontak-action-anchor.md)
Initiative: Qontak Action — first-party actions in Qontak's own features, invoked via
function-calling, authenticated with the company's Qontak token. No
cross-product credential / HMAC / OAuth approval required.
Catalog item: Send WhatsApp Flow action (one action in the Qontak Action catalog, not a
sequential phase).
Phase Goal: Enable a Qontak chatbot builder to give their AI Agent a native "Send
WhatsApp Flow" action so the agent can autonomously push a structured,
validated form (booking, order, lead capture, appointment) into a WhatsApp
conversation — and act on the customer's answers — without a human-built
flow-tree detour.
Sibling actions: Qontak CRM actions, Qontak Chat actions, Send Attachment — same
initiative, same action framework, company token.
This action: A NATIVE AI Agent action, "Send WhatsApp Flow", configured in the AI
Agent → Actions section (same drawer family as Send Attachment). The
builder selects ONE already-published WhatsApp Flow (created via Flow
Builder), writes a "when to use" trigger description, and configures a
next-action ROUTER (Resolve / Send via API / Assign to agent) that fires
once the customer submits the flow. At runtime the agent FUNCTION-CALLS
the action to deliver the flow message; delivery is non-blocking, so the
conversation stays live while the flow is open.
Deferred to next: Multi-flow selection inside one action (this action pins ONE flow;
"which flow" across different use cases is resolved by configuring
multiple Send WhatsApp Flow actions). Agent-decided (LLM-chosen) next-
action outcome. A dedicated flow-authoring/publishing UI (this action only
references flows already published via Flow Builder).
Cross deps: Shared AI Agent action-configuration surface (`AiAgentAction` /
`AiAgentTool`, the action drawer) — registered and enabled through the
same flow used by Send Attachment and the Qontak CRM/Chat actions. Also
depends on the EXISTING WhatsApp Flow send + completion-capture plumbing
(`IntentWhatsappFlow`, `SendMessageWhatsappFlow`,
`ProcessIncomingMessageWithResolve#find_response`) — this phase re-routes
the completion path for AI-Agent-owned rooms; it does not replace the
Flow Builder path for Flow-Builder-owned rooms.
Reconciliation note (CB-P.3): Send WhatsApp Flow is added as a new row in the Qontak Action anchor's action catalog (not a sequential phase) — see the anchor's Action / Phase Index. It has no credential dependency (no HMAC / no SSO approval), so it ships independently of the Mekari Action credential work. It depends on the shared action-configuration surface and on re-routing an existing webhook path — see Section 17, Open Question 1 (downgraded 2026-07-09: a reusable room-ownership signal already exists in the code; the remaining risk moved to Open Questions 2-3, the
process_async_replycontract extension and the V2closed_reasonwiring point).
3. One-liner + Problem
One-liner: A native action that lets a Qontak AI Agent send a WhatsApp Flow (a structured, validated form) and act on the customer's answers, instead of only collecting information through free-text conversation.
Problem:
Today the AI Agent can only collect structured information (a booking date, an order, a lead's details) by asking for it conversationally and having the AI-service model extract it from free text — a slow, extraction-error-prone path with no input validation. Qontak already ships a fully working WhatsApp Flow send + completion-capture pipeline for Flow Builder (IntentWhatsappFlow, SendMessageWhatsappFlow, ProcessIncomingMessageWithResolve#find_response → SaveFormToEntity), but it is wired only to Flow-Builder intents — the AI Agent cannot call it. Any conversation that needs a structured input step forces a drop to the flow tree or a human handoff, even though the underlying send/capture plumbing the agent would need already exists and is production-proven.
4. Target Users + Persona Context
Personas inherited from the Qontak Action ANCHOR and the Autonomous AI Agent PRD (chatbot/autonomous-ai-agent/prds/phase-1-new-engine-migration.md) — see those for full persona background.
| Persona | Role | Goal | Pain | Workaround |
|---|---|---|---|---|
| Primary — CS Ops Lead / Bot Manager | Customer-side bot owner who configures the AI Agent in Qontak Chatbot | Let the agent collect structured, validated information (booking, order, lead, appointment) via a native form instead of free-text extraction | Conversational slot-filling is slow and error-prone for structured steps; the only escape is dropping to a rigid Flow Builder tree or a human | Builds a Flow Builder flow-tree branch for the structured step and routes the conversation there, breaking the agent's natural-language handling mid-conversation |
| Secondary — Customer (end recipient) (fills the flow) | The end customer chatting over WhatsApp | Give the requested information once, through a native form, without re-typing it across several chat turns | Gets asked the same structured questions one at a time in free text, with no validation until the agent (mis)understands the answer | Answers conversationally, sometimes triggering re-asks when the agent misparses an answer |
(Full persona background: see ANCHOR PRD, Section 2. See Section 6 for plan availability and feature flag scope.)
Design constraint inherited from the ANCHOR (Persona A): Configuration must be operable end-to-end by a non-technical builder — picking an already-published flow, writing a plain-language trigger description, and choosing what happens next. No knowledge of Meta Flow JSON, webhook payloads, or form_response schemas required.
5. Non-Goals
- Multi-flow selection inside one action — the agent does not choose among several attached flows at runtime. Each action pins exactly ONE published flow; supporting several use cases means configuring multiple Send WhatsApp Flow actions, each self-describing via its own trigger description (Option A, see Section 16).
- Agent-decided (LLM-chosen) next-action outcome — the post-completion router (Resolve / Send via API / Assign to agent) is a deterministic, builder-configured setting, not something the agent reasons about at runtime.
- A new flow-authoring or publishing UI — this action only references a WhatsApp Flow that has already been created and published via Flow Builder (
IntentWhatsappFlow,version_status: PUBLISHED). It does not add flow-building, editing, or publishing capability. - Consuming completion data through the
OrganizationEntity/Redis mapping layer — flow completion (form_response.data) is injected into the agent's context as raw JSON. It is explicitly NOT mapped into Flow Builder's entity/Redis system. - Blocking/synchronous flow delivery — the conversation does not pause waiting for the flow to be filled. The agent keeps answering normally; only a real
form_responsesubmission fires the consume-and-route path. - New idle/abandonment logic — if the customer never submits the flow and goes idle, this action relies entirely on the account's existing idle-action configuration. No new timers, reprompt cadence, or abandonment states are introduced.
- Non-WhatsApp channels — the underlying send path (
CreateMessageByBot#send_whatsapp_flow_message→ Hub/api/core/v1/messages/whatsapp/interactive_message/bot) is WhatsApp-specific (Meta Flows). No Instagram/Telegram/Email equivalent exists or is in scope. - A dedicated flow-field-to-API-payload mapping UI — for the "Send via API" router outcome, v1 passes the raw
form_response.dataobject as the API action's payload verbatim. Field-level remapping is deferred (see Section 17, Open Question 4).
6. Constraints
Platform: Configuration — Qontak web app (AI Agent action config drawer, net-new).
Runtime delivery — WhatsApp ONLY. The existing send path is WhatsApp-
specific by construction: CreateMessageByBot#send_whatsapp_flow_message
posts to Hub route "/api/core/v1/messages/whatsapp/interactive_message/bot"
(lib/hub/chat_service/messages/create_message_by_bot.rb:139-162) — there is
no equivalent route for other channels.
Performance: Action dispatch (agent decision → flow message enqueued) ≤ 2s, consistent
with the Send Attachment action's dispatch budget.
Flow-completion routing (customer submits → data available to the agent
loop) inherits the standard incoming-message processing SLA — no
additional latency budget is introduced by this action.
Published-flow
dependency: The action can only reference a WhatsApp Flow that already exists and is
PUBLISHED via Flow Builder (IntentWhatsappFlow.version_status ==
'PUBLISHED' — confirmed uppercase, a string enum with a DB check
constraint, app/models/intent_whatsapp_flow.rb:18). This action does not
create, edit, or publish flow templates — see Section 16, Decision 3.
GROUNDED (2026-07-09): the picker that lists an org's published flows
across ALL intents is NET-NEW query/endpoint work — no existing
repository or API route lists IntentWhatsappFlow org-wide independent of
intent_id; every current query is intent-scoped, and the flow API has no
GET/index route at all (only POST/PATCH, both intent-scoped). This item
was previously mis-classified as reused in Section 7 — corrected below.
Plan scope: Inherit existing AI Agent entitlement — available to whatever plans
already have the AI Agent enabled. No new tier gating in this phase.
Feature flag: ai_agent_send_whatsapp_flow_action | default: OFF. GROUNDED (2026-07-09):
confirmed zero existing hits for this name + 3 variants — the name is
safe to use. However, this codebase has no Flipper-style flag primitive;
the actual gating mechanism is ambiguous between two real precedents: (a)
a system_preferences DB row read via Rollout::Base.enabled? (requires
manual per-environment seeding — no seed file manages these rows today),
or (b) the node_registries.enabled column (the likely precedent for
gating other qontak_* action types). RFC must confirm which mechanism
"Send Attachment" actually uses and mirror it — see Section 17, Open
Question 4.
Read/write: Configure (pick flow, set trigger, set next-action router, enable action)
— builder roles that can already edit AI Agent configuration (Admin / Bot
Manager). Trigger + consume at runtime — the AI Agent only. Submit the
flow — the end WhatsApp customer only; they cannot configure or trigger
the action itself.
6.7 Data Lifecycle
| Artifact Type | Retention Period | Cleanup Trigger | User-Visible Effect |
|---|---|---|---|
Flow reference on the action (external_id/template_id/flow_name stored on AiAgentAction.parameters) | Persisted for the life of the action | Builder changes/removes the flow reference, or deletes the action | Agent can no longer send that flow; an action with no flow reference is invalid and cannot fire |
Flow-completion payload (form_response.data) consumed into agent context | NOT separately persisted — no new storage layer. Flows into the same conversation message / AI Agent activity-log persistence as any other turn (existing ai_activity_logs datamart infrastructure) | Standard conversation/message retention policy (unchanged) | None — appears in the agent's reasoning/response for that turn like any other input |
| Next-action router outcome (resolve / api-call / assign) | Same retention as any conversation event of that type (closed_reason, assignment record, or API action's own outcome log) | Standard retention for that outcome type (unchanged — these are existing primitives) | None new |
7. Feature Changes
Placement & approach: Configured in the same AI Agent → Actions section as Send Attachment (
qontak-designerfeat/ai-agent-actionsreference; production build target is themodules/ai-agentaction forms inchatbot-fe). "Send WhatsApp Flow" is one action, function-called by the agent (no exit conditions) — same invocation model as every other Qontak Action.
Net-new vs Reused (for Engineering)
| Area | Status | Evidence |
|---|---|---|
| Send WhatsApp Flow action + Actions-section config UI (flow picker + next-action router) | 🆕 Net-new in production | No equivalent entry exists in chatbot-fe ACTION_LIST (modules/ai-agent/constants/ai-agent-actions-constants.ts); no qontak-designer prototype exists yet for this action (unlike Send Attachment's SendAttachmentDrawer.vue) |
| Function-call binding: expose the action as an AI-Agent tool | 🆕 Net-new wiring | AiAgentTool model exists; no qontak_whatsapp_flow-equivalent executor/tool exists today |
ai_agent_send_whatsapp_flow_action feature flag | 🆕 Net-new flag | Not yet grep-verified — confirm at RFC time |
| Routing flow completion into the autonomous agent loop instead of the Flow Builder resolver | 🆕 Net-new backend logic — corrected citation + de-risked (2026-07-09) | The unconditional SaveFormToEntity call is at app/core/use_cases/system/hub/process_incoming_message_with_resolve.rb:398-401 (not 323-334 as originally cited — that guards an unrelated fallback-intent check). A reusable room-ownership signal already EXISTS and is already used twice in this same file: prev_intent&.content&.code == I18n.t('model.content_type.ai_agent.code') (lines 670-671, 1212-1215) — no new column or detection mechanism is needed, only a new branch at the call site using this existing check. Downgraded from "critical unknown" to "confirmed low-risk pattern, RFC validates branch placement." Separately, the consuming side (process_async_reply.rb) genuinely needs new work — see the next row. |
Extending process_async_reply's contract to accept flow-completion data | 🆕 Net-new contract change — not a drop-in | app/api/internal_service/v1/ai_agent/use_cases/process_async_reply.rb:38-51 has a fixed, narrow Grape params + Dry contract (output_text/references/exit/token_usage) with no generic context-passthrough slot; unknown params are stripped by Grape before reaching the use case. Consuming form_response.data requires adding a new field to this contract AND new branching logic in process_success/process_fallback (lines 156-192), which today only understand the text/exit/references trio. |
Action-type handling in the production action form components (modules/ai-agent components/forms) | ✏️ Change to existing | Add handling for the new action type, following the same pattern added for Send Attachment |
Action picker / "Add action" list (ACTION_LIST) | ✏️ Change to existing | Add a "Send WhatsApp Flow" entry |
| WhatsApp Flow outbound send (message construction + Hub post) | ♻️ Reused as-is | SendMessageWhatsappFlow (app/core/use_cases/system/hub/send_message_whatsapp_flow.rb:32-78) → SendMessageInteractive#send_whatsapp_flow_message (app/core/repositories/chat_service/send_message_interactive.rb:86-117) → CreateMessageByBot#send_whatsapp_flow_message (lib/hub/chat_service/messages/create_message_by_bot.rb:139-162) |
Flow-completion detection (recognizing a form_response webhook arrived) | ♻️ Reused detection, 🆕 new routing branch | Detection logic in ProcessIncomingMessageWithResolve#find_response is reused; what's new is branching to the agent loop instead of always calling SaveFormToEntity |
Published-flow catalog (list of IntentWhatsappFlow where version_status = 'PUBLISHED') | 🆕 Net-new query + endpoint — re-classified 2026-07-09 | The IntentWhatsappFlow table/model (app/models/intent_whatsapp_flow.rb) is reused, but the ORG-WIDE, cross-intent query the picker needs does not exist: all 21 existing query call sites are intent-scoped or embedded in Path publish/delete internals, and the flow API has no GET/index route at all (only intent-scoped POST/PATCH). This item was previously marked reused — it requires a new repository method + a new controller endpoint. |
| Resolve / Assign-to-agent outcomes | ⚠️ Reuse UNCONFIRMED for the V2 engine — flagged 2026-07-09 | closed_reason values RESOLVE_AI/ASSIGN_AGENT_AI are real strings (config/locales/en.yml:345-350, plain string column, no DB/model enum) and assign_channel_agent_id exists on Room (db/schema.rb:1780) — but both closed_reason values are confirmed used ONLY in the older Ask-Airene/AI-assist path (send_message_with_resolve.rb:253, get_answer.rb:201,224), never in process_async_reply.rb (the V2 autonomous engine this PRD builds on). The wiring point for these outcomes inside V2 is unconfirmed — see Section 17, Open Question 6. |
| Send-via-API outcome | ♻️ Reused as-is (v1) | Existing api node/executor (ActionExecutorFactory, app/core/repositories/node_executions/action_executor_factory.rb:1-42), fed the raw form_response.data as payload |
Change ID: CHG-001 — "Send WhatsApp Flow" appears in the action list
Change Type: Modified component (action picker / "Add action" list)
Surface: AI Agent config → Actions → Add action
Intent: Builder browses available actions to add to the agent.
Before:
- The action list offers API Integration, Qontak CRM/Chat actions, and (pending) Send
Attachment. No native "send a structured form" action exists.
After:
- A new "Send WhatsApp Flow" entry appears. Selecting it opens the new config drawer
(Section 8).
| Element | Before | After |
|---|---|---|
| Action picker list | No Send WhatsApp Flow entry | + "Send WhatsApp Flow" entry |
Action interface (store/ai-agent/interface.ts) | No flow-reference or next-action-router fields | + flow reference (external_id/template_id/flow_name) + next-action router config |
Change ID: CHG-002 — production action form components render the new action type
Change Type: Modified component (action config surface)
Surface: AI Agent config → action drawer/forms (production: chatbot-fe modules/ai-agent
components/forms + ACTION_LIST)
Intent: Render the correct config form for the selected action type.
Before:
- No Send WhatsApp Flow action type exists in the production action forms or ACTION_LIST.
After:
- The production action form components recognise the new action_type and render the
Send WhatsApp Flow config form (Section 8). Existing action paths unchanged.
Figma: pending — net-new UI; no existing prototype reference (tracked as a design
dependency in Section 15).
8. New Features
All items here are net-new in production. The flow-completion routing (8.2) is the single riskiest piece of this PRD — flagged again in Section 17 as a Risk-type Open Question requiring chatbot BE engineering input before RFC.
8.1 — Send WhatsApp Flow action config drawer (Frontend, net-new)
Placement: AI Agent → Actions section → Add action → "Send WhatsApp Flow"
Access: Builder roles that can edit AI Agent configuration (Admin / Bot Manager).
Status: Net-new in chatbot-fe; no qontak-designer prototype exists yet for this
action (design dependency, see Section 15).
Component Tree:
SendWhatsappFlowDrawer (extends ActionConfigBase, same shared shell as SendAttachmentDrawer)
├── Action name — text, default "Send WhatsApp Flow" [from ActionConfigBase]
├── When and how to trigger — textarea (parameters.trigger), [from ActionConfigBase]
│ default: "When the conversation needs structured input — a booking, order, lead
│ detail, or appointment — that's better collected as a form than in
│ free text." (builder editable)
├── Flow picker (net-new) — searchable select listing this org's PUBLISHED WhatsApp
│ Flow templates (IntentWhatsappFlow, version_status = 'PUBLISHED'); shows
│ template_name / header_text as the label; stores external_id/template_id on save.
│ Builders who have not yet published a flow are prompted to do so in Flow Builder
│ first (empty state below).
│ GROUNDED (2026-07-09, design pass — P0): no comparable component pattern exists in
│ qontak-designer. The only "select" pattern in the action-drawer family
│ (ActionFieldRow type=select) is a static-options popover list — no search, no
│ async fetch, no loading/empty states. This must be composed from verified Pixel 3
│ primitives (MpPopover + MpInput, or MpInputTag's suggestion/is-loading mode), not
│ extended from an existing drop-in component.
└── Next-action router (net-new) — single-select, exactly one outcome:
├── Resolve conversation — no further config
├── Send data via API — picks an existing `api` action to chain to;
│ form_response.data passed as its payload verbatim
└── Assign to agent — picks an assignment target per the existing
assign-to-agent action config
GROUNDED (2026-07-09, design pass — P0): more novel than it reads. No 3-way outcome
selector exists anywhere in qontak-designer — ResolveConversationDrawer.vue and
AssignAgentDrawer.vue are separate single-outcome drawers, never composed as
branches inside one drawer. The closest precedent (AssignAgentDrawer's assignment-
type → division/agent conditional field) is single-branch-deep with STATIC options;
here the "Send via API" branch's picker must be sourced from the agent's OWN
already-configured `api` actions — a cross-action data dependency with zero
precedent in this repo.
UI States:
Empty: No published WhatsApp Flow exists for this org yet. "Please publish a WhatsApp
Flow in Flow Builder before configuring this action." Flow picker disabled
until ≥1 published flow exists.
Loading: Flow list fetching; router option dependent-picker (e.g. which `api` action)
fetching.
Error: Selected flow was since unpublished/deleted → picker shows an inline error and
requires re-selection before Save.
Success: A published flow is selected, trigger description is present, and a next-action
router outcome is fully configured → Save enabled.
Figma: pending — no reference design exists yet.
📊 UI State Diagram — Send WhatsApp Flow config drawer
stateDiagram-v2
[*] --> Empty: Builder opens drawer, org has 0 published flows
Empty --> Loading: Builder publishes a flow in Flow Builder, reopens drawer
Loading --> Success: ≥1 published flow available, builder selects one + router
Loading --> Error: Previously selected flow was unpublished/deleted
Error --> Loading: Builder re-selects a valid published flow
Success --> [*]: Save (action persisted)
8.2 — Function-call binding + flow-completion routing (Backend, net-new — critical risk)
What: Expose Send WhatsApp Flow as a function-callable tool (AiAgentTool, following
the pattern used for other qontak_* actions), and re-route flow-completion
webhooks for AI-Agent-owned rooms into the autonomous agent loop instead of the
Flow Builder resolver.
Send path (reused): On invocation, resolve the action's configured flow reference +
conversation channel, then dispatch through the EXISTING send path:
SendMessageWhatsappFlow → SendMessageInteractive#send_whatsapp_flow_message →
CreateMessageByBot#send_whatsapp_flow_message → Hub
/api/core/v1/messages/whatsapp/interactive_message/bot.
Completion path (net-new routing, GROUNDED 2026-07-09): ProcessIncomingMessageWithResolve
#find_response currently always routes a form_response to SaveFormToEntity (the
Flow Builder path) at process_incoming_message_with_resolve.rb:398-401 (corrected
from an earlier mis-citation of 323-334). For an AI-Agent-owned room, this must
instead route the raw form_response.data into process_async_reply. The branch
condition is LOW-RISK: it reuses the exact room-ownership check already live twice
in this file (prev_intent&.content&.code == 'ai_agent', lines 670-671/1212-1215).
What is genuinely net-new and higher-effort: process_async_reply's Grape params +
Dry contract (process_async_reply.rb:38-51) is a fixed envelope (output_text/exit/
references/token_usage) with no generic context slot — a new field must be added
to that contract, plus new branching in process_success/process_fallback (lines
156-192) to do anything with structured form data — before the tenant-configured
next-action router (resolve / send-via-api / assign) can fire once the agent's
turn completes.
Fallbacks (reused, zero new logic): (a) customer replies in free text instead of
submitting the flow → the agent answers normally per its existing behavior; the
flow stays open, no special-casing needed. (b) customer goes idle → the
account's EXISTING idle-action configuration fires unchanged (no new timers).
Engineering owns the exact routing implementation (how an AI-Agent-owned room is distinguished from a Flow-Builder-owned one at the point find_response handles a form_response); see the proposed API sequence in Section 9 and Open Question 1 in Section 17.
9. API & Webhook Behavior
| # | Behavior | Entity Affected | Triggered By | Expected Behavior | Failure Behavior |
|---|---|---|---|---|---|
| 1 | Save Send WhatsApp Flow action | AiAgentAction, AiAgentTool (node_type qontak_whatsapp_flow — Engineering to confirm final key) | Builder clicks Save in the config drawer | Persist the action with its trigger description, selected flow reference (external_id/template_id/flow_name from an existing PUBLISHED IntentWhatsappFlow), and next-action router config. On next agent train/sync, registered as a callable tool with its description/trigger. | • No published flow selected → block save. • Next-action router incomplete (e.g. "Send via API" chosen but no target api action picked) → block save.• Selected flow later unpublished/deleted → action becomes invalid; surfaced on next edit. |
| 2 | Agent invokes Send WhatsApp Flow at runtime | Outbound conversation message (type: flow) | AI Agent selects and calls the tool during a conversation (function-calling), based on the trigger description + conversation context | Resolve the action's flow reference → dispatch via the reused send path → customer receives the WhatsApp Flow message. Conversation remains live and non-blocking. | • Hub/ChatService failure → surfaces per the existing SendMessageInteractive retry behavior (max 2 attempts, per current code) → log ai_agent_whatsapp_flow_send_failed.• Flow reference invalid/unpublished at send time → no send; log failure with reason flow_unavailable. |
| 3 | Flow completion received (customer submits) | Conversation context for that turn (no new persisted entity) | ChatService webhook delivers form_response for a room owned by an AI Agent with this action | Route form_response.data (raw JSON) into process_async_reply for that room — NOT SaveFormToEntity/OrganizationEntity — so the agent can reason over the submitted answers and generate its next reply. | • Room's action can no longer be resolved (deleted/disabled since send) → fall back to the account's default handling for an unexpected form_response (Engineering to confirm — see Open Question 1).• Malformed/partial form_response.data → agent proceeds with whatever fields are present; no hard failure. |
| 4 | Next-action router fires (post-completion) | rooms.closed_reason / assignment / chained api action invocation | Agent's turn (from row 3) completes after consuming the flow data | Fire exactly the ONE configured outcome: Resolve (closed_reason = RESOLVE_AI), Send via API (invoke the configured api action with form_response.data as payload), or Assign to agent (ASSIGN_AGENT_AI / assign_channel_agent_id). | • Configured api action invocation fails → surfaces via that action's own existing failure/log behavior (Section 9 row reused from the api node); router does not silently retry a different outcome. |
[Claude to resolve during RFC: HTTP method/path for the new tool registration + action
CRUD, request/response JSON schema, NodeRegistry node definition (inputs/outputs/
parameters), the function-calling parameter schema exposed to AI Service, and the exact
mechanism for distinguishing an AI-Agent-owned room from a Flow-Builder-owned room inside
ProcessIncomingMessageWithResolve#find_response.]
📊 API Sequence — send, completion, and routing (PROPOSAL — Engineering owns final design)
sequenceDiagram
participant C as Customer
participant AIS as AI Service (function-calling)
participant BE as Chatbot BE
participant Hub as ChatService / Hub
AIS->>BE: invoke qontak_whatsapp_flow(action_id)
BE->>Hub: SendMessageWhatsappFlow (reused send path)
Hub-->>C: WhatsApp Flow message delivered
alt Customer submits the flow
C->>Hub: form_response webhook
Hub->>BE: ProcessIncomingMessageWithResolve#find_response
Note over BE: NEW routing: room is AI-Agent-owned →<br/>route to process_async_reply (NOT SaveFormToEntity)
BE->>AIS: form_response.data injected into agent context
AIS-->>BE: agent's reply for this turn
BE->>BE: fire configured next-action router
Note over BE: Resolve / Send via API / Assign to agent
else Customer replies in text instead
C->>BE: free-text message
BE->>AIS: standard turn (flow stays open, reused behavior)
else Customer goes idle
Note over BE: existing idle-action configuration fires (reused, unchanged)
end
10. System Flow + User Stories + ACs
10.1 System Flow
Flow: AI Agent sends a WhatsApp Flow and acts on the customer's answers
Type: User Journey + API Sequence
CONFIG (build time)
1. Builder opens AI Agent config → Actions → Add action → "Send WhatsApp Flow".
2. Builder selects an already-PUBLISHED WhatsApp Flow (from Flow Builder), edits the
trigger description, and configures the next-action router (Resolve / Send via API /
Assign to agent).
3. Builder saves → action persisted; on agent train/sync it is registered as a callable
tool (AiAgentTool).
RUNTIME (conversation)
4. Customer message arrives; AI Agent evaluates the conversation.
5. Agent's function-calling decides the action's trigger condition is met → calls the
tool → the configured WhatsApp Flow message is delivered. Delivery is non-blocking;
the conversation stays live.
6. BRANCH — customer replies in free text instead of filling the flow → the agent
answers normally (reused behavior); the flow remains open for later submission.
7. BRANCH — customer goes idle → the account's existing idle-action configuration fires
unchanged (reused behavior).
8. BRANCH — customer submits the flow → ChatService delivers a form_response webhook →
backend routes the raw submitted data into the autonomous agent loop (net-new
routing, NOT the Flow Builder entity path) → the agent's turn consumes the data and
generates a reply → the tenant-configured next-action router fires exactly one
outcome: Resolve, Send via API, or Assign to agent.
9. FAILURE BRANCH — If the flow send fails, or the configured router action itself
fails, each surfaces via its own existing failure/log behavior; the conversation is
not blocked.
📊 System Flow — AI Agent Send WhatsApp Flow
sequenceDiagram
participant C as Customer
participant A as AI Agent (function-calling)
participant BE as Chatbot Backend
C->>A: Message signaling a structured-input need
A->>A: Evaluate trigger descriptions, select Send WhatsApp Flow action
A->>BE: call qontak_whatsapp_flow(action_id)
BE-->>C: WhatsApp Flow message delivered (non-blocking)
alt Customer submits the flow
C->>BE: form_response webhook
BE->>BE: route to agent loop (net-new), NOT Flow Builder entity path
BE->>A: form_response.data available in context
A-->>C: agent's reply for this turn
BE->>BE: fire next-action router (resolve / api / assign)
else Customer replies in text
C->>A: free-text reply
A-->>C: normal agent answer (flow stays open)
else Customer goes idle
Note over BE: existing idle-action configuration fires unchanged
end
10.2 User Stories
| User Story | Importance | Mockup / Technical Notes | Acceptance Criteria |
|---|---|---|---|
| [WAFLOW-S01] — Builder configures a Send WhatsApp Flow action As a CS Ops Lead / Bot Manager, I want to add a "Send WhatsApp Flow" action to my AI Agent, picking an already-published flow, a trigger description, and what happens after the customer submits it, so that the agent can collect structured input natively instead of by free-text extraction. | Must Have | Figma: N/A — no reference design exists yet (Section 15 dependency) Data Fields: • name (string, required) — User input• parameters.trigger (string, required) — User input• flow_reference (external_id/template_id/flow_name, required) — User selection from published IntentWhatsappFlow rows• next_action (enum: resolve|send_api|assign, required) — User selection• next_action_target (api action id, or assignment target — required if next_action != resolve) — User selectionBefore-After Behavior: Before: no native action exists to send a WhatsApp Flow; builders rely on a Flow Builder flow-tree branch or human handoff for structured input. After: builders configure a reusable Send WhatsApp Flow action the agent can call autonomously, with a deterministic outcome on completion. | — Happy Path — • AC-1: Given the AI Agent action list, when the builder selects "Send WhatsApp Flow", then the config drawer opens showing this org's published WhatsApp Flow templates in a searchable picker. • AC-2: Given the drawer, when the builder selects a published flow, sets a trigger description, configures exactly one next-action router outcome, and clicks Save, then the action is persisted and listed in the agent's actions. • AC-3: Given "Send via API" is chosen as the router outcome, when the builder picks an existing api action as the target and saves, then that target is stored and will receive form_response.data as its payload at completion time.— Error / Unhappy Path — • ERR-1: Given the org has zero published WhatsApp Flow templates, when the builder opens the drawer, then the flow picker is disabled and the builder is prompted to publish a flow in Flow Builder first. • ERR-2: Given "Send via API" or "Assign to agent" is selected without a target chosen, when the builder clicks Save, then save is blocked with a required-field error on the router target. • ERR-3: Given a previously selected flow has since been unpublished or deleted, when the builder reopens the action, then an inline error prompts re-selection before Save is re-enabled. — Permission Model — • CAN: Admin, Bot Manager (roles that can edit AI Agent config) • CANNOT: Agent/viewer roles without AI Agent edit rights • Unauthorized: "Add action" not rendered. — UI States — • Loading: flow list / router-target list fetching • Empty: zero published flows in org • Error: selected flow invalid/unpublished • Success: valid flow + trigger + complete router config → Save enabled |
| [WAFLOW-S02] — Agent sends the WhatsApp Flow non-blockingly As an AI Agent (on behalf of the builder), I want to call the Send WhatsApp Flow action and deliver the configured flow to the customer without pausing the conversation, so that the customer can either fill the form or keep chatting normally. | Must Have | Figma: N/A (runtime, no builder UI) Data Fields: • action_id (string, required) — Agent function-call arg• channel (derived) — from the conversation room (WhatsApp only)• flow reference (derived) — from the action configBefore-After Behavior: Before: the agent can only collect structured input via free-text turns. After: the agent sends a native WhatsApp Flow message via the existing send path, and the conversation stays fully interactive while it is open. | — Happy Path — • AC-1: Given an enabled Send WhatsApp Flow action whose trigger condition is met, when the agent calls the tool, then the configured flow message is delivered to the customer on WhatsApp via the existing SendMessageWhatsappFlow path.• AC-2: Given the flow has been delivered, when the customer sends a normal text reply instead of submitting the flow, then the agent answers that message normally and the flow remains open for later submission. • AC-3: Given the flow has been delivered, when the customer goes idle, then the account's existing idle-action configuration fires unchanged — no new idle logic is introduced by this action. — Error / Unhappy Path — • ERR-1: Given the Hub/ChatService send fails, when the agent attempts delivery, then it is retried per the existing SendMessageInteractive behavior; if still failing, ai_agent_whatsapp_flow_send_failed is logged and the conversation continues.• ERR-2: Given the action's flow reference is invalid or unpublished at send time, when the agent attempts to call the tool, then no message is sent and ai_agent_whatsapp_flow_send_failed is logged with reason flow_unavailable.— Permission Model — • CAN: the AI Agent runtime only • CANNOT: human agents/customers cannot trigger this action directly • Unauthorized: action not exposed as a tool if disabled or behind the OFF feature flag. — UI States — • Loading: N/A (async send) • Empty: N/A • Error: failure logged; conversation unblocked • Success: flow message appears in the conversation like any interactive message |
| [WAFLOW-S03] — Agent consumes the flow submission and the next-action router fires As an AI Agent (on behalf of the builder), I want to receive the customer's flow submission directly in my reasoning context and then trigger the configured next action, so that the conversation resolves, escalates via API, or hands off exactly as the builder intended — without a human re-entering the data. | Must Have | Figma: N/A (runtime, no builder UI) Data Fields: • form_response.data (JSON object, required) — from the ChatService webhook, injected raw into agent context• next_action + next_action_target (from action config) — used to fire the routerBefore-After Behavior: Before: a form_response for any room is always mapped into Flow Builder's OrganizationEntity/Redis system via SaveFormToEntity — the AI Agent never sees it. After: for an AI-Agent-owned room, the raw submission is routed into process_async_reply, the agent reasons over it, and the configured router fires. | — Happy Path — • AC-1: Given a customer submits the configured WhatsApp Flow, when the completion webhook ( form_response) arrives for an AI-Agent-owned room, then the raw form_response.data is injected into that room's agent turn — NOT into OrganizationEntity/Redis.• AC-2: Given the agent has consumed the submission, when it finishes generating its reply for that turn, then the tenant-configured next-action router fires exactly one outcome (Resolve / Send via API / Assign to agent). • AC-3: Given "Resolve" is configured, when the router fires, then rooms.closed_reason is set to RESOLVE_AI.• AC-4: Given "Send via API" is configured, when the router fires, then the target api action is invoked with form_response.data as its payload verbatim.• AC-5: Given "Assign to agent" is configured, when the router fires, then the room is assigned per ASSIGN_AGENT_AI / assign_channel_agent_id, matching existing assign-to-agent behavior.— Error / Unhappy Path — • ERR-1: Given the form_response arrives for a room whose Send WhatsApp Flow action was deleted or disabled since the flow was sent, when the webhook is processed, then the system falls back to the account's default handling for an unexpected form_response (Engineering to confirm exact fallback — Open Question 1).• ERR-2: Given form_response.data is malformed or only partially filled, when the agent consumes it, then the agent proceeds with whatever fields are present rather than hard-failing the turn.• ERR-3: Given the "Send via API" target action itself fails, when the router invokes it, then the failure surfaces via that api action's own existing failure/log behavior — the router does not silently substitute a different outcome.— Permission Model — • CAN: the AI Agent runtime only triggers the router • CANNOT: customers cannot choose the router outcome; a fired router outcome cannot be undone by the customer • Unauthorized: N/A — no human triggers this path. — UI States — • Loading: N/A (async) • Empty: N/A • Error: fallback per ERR-1; logged • Success: conversation shows the resolved/escalated/assigned state matching the configured router |
| [WAFLOW-S04] — Builder enables/disables the action and rollout gating As a CS Ops Lead / Bot Manager, I want to turn the Send WhatsApp Flow action on or off for my agent (subject to the feature flag), so that I control whether the agent can send this flow and can stop it quickly if needed. | Should Have | Figma: N/A — reuse existing action enable/disable + agent train flow Data Fields: • enabled (bool) — User input• ai_agent_send_whatsapp_flow_action (flag, default OFF) — Account configBefore-After Behavior: Before: no such action exists to toggle. After: the action can be enabled per agent and is gated by the account feature flag during rollout. | — Happy Path — • AC-1: Given the feature flag is ON for the account, when the builder adds and enables the action, then on train it becomes a callable tool for the agent. • AC-2: Given an enabled action, when the builder disables (or removes) it and re-trains, then the agent can no longer call it. — Error / Unhappy Path — • ERR-1: Given the feature flag is OFF for the account, when the builder opens the action list, then "Send WhatsApp Flow" is not available (not rendered). — Permission Model — • CAN: Admin, Bot Manager • CANNOT: roles without AI Agent edit rights • Unauthorized: toggle not rendered. — UI States — • Loading: save/train in progress • Empty: N/A • Error: save failure toast • Success: action shows enabled/disabled state |
Dependencies: S02 and S03 depend on S01 (action must exist and be fully configured, including the router). S04 gates S01–S03 via feature flag.
NEGATIVE SCENARIOS (from Section 5 Non-Goals)
NEG-1: Given a Send WhatsApp Flow action pinned to one flow,
When the builder wants the agent to choose among several flows at runtime,
Then no multi-flow selection occurs — configure multiple actions instead. [Non-Goal 1]
NEG-2: Given a completed router outcome is configured deterministically,
When the agent might otherwise reason about a different outcome,
Then the agent does not override the tenant-configured router. [Non-Goal 2]
NEG-3: Given a builder wants to create a brand-new flow from this action,
When they open the Send WhatsApp Flow drawer,
Then no flow-authoring/publishing capability is offered — only a picker over
already-published Flow Builder flows. [Non-Goal 3]
NEG-4: Given a customer submits the flow,
When the completion is processed for an AI-Agent-owned room,
Then the data is NOT written into OrganizationEntity/Redis — it goes to the agent
context only. [Non-Goal 4]
NEG-5: Given the flow has been sent and is still open,
When the customer sends any other message,
Then the conversation is not blocked waiting for the flow submission. [Non-Goal 5]
NEG-6: Given a customer never submits the flow and goes idle,
When idle time is reached,
Then no new abandonment logic fires — only the account's existing idle-action
configuration. [Non-Goal 6]
NEG-7: Given a conversation on a non-WhatsApp channel,
When a Send WhatsApp Flow action would fire,
Then no flow is sent — this action is WhatsApp-only. [Non-Goal 7]
NEG-8: Given "Send via API" is configured,
When the router fires,
Then form_response.data is passed to the target API verbatim — no field-level
remapping UI is offered in v1. [Non-Goal 8]
🧪 Test Coverage Matrix — [WAFLOW-S01]
| Dimension | Coverage | Notes |
|---|---|---|
| Boundary values | ⚠️ partial | ERR-1 covers zero published flows. ⚠️ QA: org with a very large number of published flows — picker pagination/search performance |
| State transitions | ✅ defined | AC-1→AC-2 drawer open→save; ERR-2/3 block-on-invalid |
| Data validation | ⚠️ TBD | ⚠️ QA: special characters in action name/trigger; flow reference tampering (id for a flow belonging to another org) |
| Concurrency | ⚠️ TBD | ⚠️ QA: two builders editing the same agent's actions simultaneously |
| Network/timeout | ⚠️ TBD | ⚠️ QA: published-flow list fetch times out mid-drawer-open |
🧪 Test Coverage Matrix — [WAFLOW-S02]
| Dimension | Coverage | Notes |
|---|---|---|
| Boundary values | ⚠️ TBD | ⚠️ QA: extremely long trigger description; very large flow payload (many screens/fields) |
| State transitions | ✅ defined | Trigger-met → send → text-reply/idle/submit branches (AC-2, AC-3) |
| Data validation | ✅ defined | ERR-2 flow_unavailable |
| Concurrency | ⚠️ TBD | ⚠️ QA: agent fires the action twice in rapid turns; duplicate-flow-send guard |
| Network/timeout | ✅ defined | ERR-1 covers Hub/ChatService send retry + logged failure |
🧪 Test Coverage Matrix — [WAFLOW-S03]
| Dimension | Coverage | Notes |
|---|---|---|
| Boundary values | ⚠️ TBD | ⚠️ QA: form_response.data with 0 fields, 1 field, and a very large field set |
| State transitions | ⚠️ partial | AC-1/AC-2 cover the happy submit→consume→route sequence; ERR-1 covers the deleted-action case. ⚠️ QA: room reassigned to a different agent between send and submission |
| Data validation | ⚠️ partial | ERR-2 covers malformed/partial data. ⚠️ QA: unexpected/extra fields in form_response.data not matching the flow's declared schema |
| Concurrency | ⚠️ TBD | ⚠️ QA: customer submits the flow twice (duplicate form_response webhook) |
| Network/timeout | ⚠️ TBD | ⚠️ QA: webhook delivery delayed/out-of-order relative to a subsequent customer text message |
11. Rollout
Feature flag: ai_agent_send_whatsapp_flow_action (see Section 6 — OFF by default)
Rollout: Stage 1 → Internal QA (Qontak internal test accounts)
Stage 2 → Closed beta: 3–5 design-partner AI Agent customers with an
existing published WhatsApp Flow
Stage 3 → Open beta: opt-in for all active AI Agent accounts
GA → On by default for all plans with AI Agent enabled
Backward compat: Yes — additive. Existing Flow Builder WhatsApp Flow send/capture behavior
for Flow-Builder-owned rooms is unaffected; the new routing branch only
applies to AI-Agent-owned rooms.
Migration: No migration of EXISTING data. CORRECTED (2026-07-09): this is not a
config-only addition, though — registering the new action type requires
(1) a new node_registries DB row (node_type, name, inputs/outputs/
properties/settings schema, enabled) created via data migration or admin
tooling (no seed file manages these rows today), (2) a new executor class
under app/core/repositories/node_executions/nodes/, and (3) a hardcoded
edit to NodeTypeRegistry::GROUP (node_type_registry.rb:18-24) mapping the
new type to its executor. No existing rows are migrated, but this is a
real code + data deploy, not a pure config toggle.
12. Observability
Key Events:
| Event Name | Trigger | Properties |
|---|---|---|
ai_agent_whatsapp_flow_action_created | Builder saves a new Send WhatsApp Flow action | account_id, agent_id, action_id, flow_external_id, next_action, timestamp |
ai_agent_whatsapp_flow_sent | Agent successfully sends the flow message | account_id, agent_id, action_id, channel, conversation_id, timestamp |
ai_agent_whatsapp_flow_send_failed | Send fails after retries / pre-send guard | account_id, agent_id, action_id, reason (hub_error/flow_unavailable), timestamp |
ai_agent_whatsapp_flow_completed | Customer submits the flow and it is routed into the agent loop | account_id, agent_id, action_id, conversation_id, field_count, timestamp |
ai_agent_whatsapp_flow_router_fired | Next-action router fires post-completion | account_id, agent_id, action_id, next_action (resolve/send_api/assign), outcome (success/failed), timestamp |
Dashboard owner: Hadiningbot Squad (PM: Dimas Fauzi Hidayat)
Alerts:
- whatsapp_flow_send_success_rate < 90% over a rolling 1h window → alert Hadiningbot on-call (Slack)
- ai_agent_whatsapp_flow_router_fired with outcome=failed > 20 in 15 min → alert Eng on-call
12.6 Post-Launch Monitoring Cadence
Review cadence: Weekly for the first 4 weeks post-GA, then monthly.
Owner: Hadiningbot Squad (PM: Dimas Fauzi Hidayat)
Review scope: flow send success rate, completion rate, router outcome mix, failure
reasons.
Trigger thresholds:
- whatsapp_flow_send_success_rate drops > 5 points week-over-week → investigate within 48h.
- flow completion rate (submitted / sent) < 20% sustained for 2 weeks → revisit trigger-
description guidance and flow UX with design partners.
Rollback consideration:
If whatsapp_flow_send_success_rate < 80% sustained for 24h and unresolved, PM disables
the ai_agent_send_whatsapp_flow_action flag for affected accounts while root-causing.
13. Success Metrics
Quality & Reliability:
⭐ Primary KPI: WhatsApp Flow send success rate
Definition: ai_agent_whatsapp_flow_sent / (ai_agent_whatsapp_flow_sent +
ai_agent_whatsapp_flow_send_failed) × 100
Baseline: N/A — new capability
Target: ≥ 95% within 30 days of GA
Adoption & Usage:
- Action adoption
Definition: % of active AI Agents with ≥1 enabled Send WhatsApp Flow action
Baseline: 0% — feature does not exist today
Target: ≥ 20% of active AI Agents within 60 days of GA
- Flow completion rate
Definition: ai_agent_whatsapp_flow_completed / ai_agent_whatsapp_flow_sent × 100
Baseline: N/A — new capability
Target: ≥ 40% within 60 days of GA (customers who receive the flow actually submit it)
Efficiency & Impact:
- Structured-input deflection vs. conversational slot-filling
Definition: % of structured-input conversations (booking/order/lead capture) resolved
via a WhatsApp Flow submission WITHOUT a flow-tree or human handoff, vs.
baseline handoff rate for the same conversation types
Baseline: To be measured during closed beta (Assumption — see Section 17)
Target: ≥ 25% reduction in flow-tree/human handoffs on structured-input
conversations within 60 days of GA for adopting accounts
14. Launch Plan & Stage Gates
| Stage | Audience | Duration | Success Gate to Advance | Owner |
|---|---|---|---|---|
| Internal Alpha | Qontak internal test accounts (with a published test flow) | 1–2 weeks | All WAFLOW-S01–S04 ACs pass QA; whatsapp_flow_send_success_rate ≥ 95% in test | PM + QA |
| Closed Beta | 3–5 design-partner AI Agent customers with an existing published flow | 2–3 weeks | ≥ 3 customers configure & fire the action; completion rate baseline measured; no Sev-1/2 defects | PM + CSM |
| Open Beta | Opt-in, all active AI Agent accounts | 2–4 weeks | Adoption trending up; send success rate sustained ≥ 95% for 2 weeks; router-outcome mix understood | Eng Lead |
| GA | All plans with AI Agent enabled (flag ON by default) | Ongoing | All Open Beta gates sustained 2 weeks; PMM approved; monitoring + alerts live | PM + PMM |
15. Dependencies
| Dependency | Owning Team | Deliverable Needed | Blocking? |
|---|---|---|---|
Shared AI Agent action-configuration surface (AiAgentAction, AiAgentTool, action drawer) | Hadiningbot Squad | Stable surface to register & enable the new action type | YES |
Existing WhatsApp Flow send path (SendMessageWhatsappFlow → SendMessageInteractive → CreateMessageByBot) | Hadiningbot / ChatService (Core) | Reused as-is for outbound delivery | YES |
Existing WhatsApp Flow completion detection (ProcessIncomingMessageWithResolve#find_response) | Hadiningbot / Core | Net-new routing branch for AI-Agent-owned rooms (does not replace the Flow Builder branch) — de-risked 2026-07-09: reuses the existing prev_intent.content.code == 'ai_agent' check already live in this file; see Open Question 1 | YES |
NET-NEW org-wide published-flow catalog query + endpoint (IntentWhatsappFlow, version_status = 'PUBLISHED') | Hadiningbot Squad | GROUNDED (2026-07-09): no existing repository/route lists flows org-wide across intents — new repository method + controller endpoint required, not read-only reuse | YES |
| AI Service function-calling / tool execution | AI Service team | Execute the new tool + accept form_response.data as turn input | YES |
NET-NEW node_registries DB row + new executor class + NodeTypeRegistry::GROUP hash edit | Hadiningbot Squad | GROUNDED (2026-07-09): no seed file manages node_registries rows — a data migration or admin-tooling step is required in addition to the executor class and registry-hash edit | YES |
process_async_reply contract extension (new field + new process_success/process_fallback branching for form data) | Hadiningbot Squad | GROUNDED (2026-07-09): the current Grape params + Dry contract is fixed/narrow with no generic context slot — this is a scoped backend contract change, not a routing tweak | YES |
v2 AI Agent capability/routing payload + CapabilityRefPresence validator | Hadiningbot Squad / AI Agent v2 | Schema must accept the new action under capabilities[].actions[] | YES |
Existing api node/executor (for "Send via API" router outcome) | Hadiningbot Squad | Confirmed programmatically invocable outside the agent's own function-calling path (NodeExecutionExecute proves the pattern); still needs a new lookup layer resolving the router's configured target into credential/parameters | YES |
closed_reason (RESOLVE_AI) / ASSIGN_AGENT_AI wiring point inside the V2 autonomous engine | Hadiningbot Squad | GROUNDED (2026-07-09): confirmed used only in the older Ask-Airene path (send_message_with_resolve.rb, get_answer.rb), never in process_async_reply.rb — the V2 integration point is unconfirmed, not reused | YES |
Feature-flag mechanism decision (system_preferences rollout row vs. node_registries.enabled) | Hadiningbot Squad | Confirm which mechanism gates other qontak_* actions (e.g. Send Attachment) and mirror it | YES |
| Design (Figma) frames for the config drawer | UI/UX | GROUNDED (2026-07-09, design pass): P0 blocker, not just "no prototype" — no comparable component pattern exists in qontak-designer for either the flow picker (searchable/async combobox) or the next-action router (3-way select with a cross-action-sourced dependent picker); both need net-new composition, not extension of an existing pattern | YES |
📊 Dependency Graph (PROPOSAL — Engineering to validate blocking status in RFC)
graph LR
F[Send WhatsApp Flow Action - this PRD]
F -->|BLOCKING| ACS[AI Agent action-config surface]
F -->|BLOCKING, low-risk pattern exists| ROUTE[Completion-routing branch]
F -->|BLOCKING, net-new| CONTRACT[process_async_reply contract extension]
F -->|BLOCKING, net-new| CATALOG[Org-wide published-flow catalog query]
F -->|BLOCKING, net-new| NODEREG[node_registries row + executor class]
F -->|BLOCKING, unconfirmed| V2WIRE[closed_reason wiring in V2 engine]
F -->|BLOCKING| AIS[AI Service function-calling]
F -->|BLOCKING, P0 design gap| DESIGN[Figma frames - no comparable component pattern]
F -->|reused| SEND[WhatsApp Flow send path]
F -->|reused, invocable| API[Existing api node - send-via-api outcome]
16. Key Decisions + Alternatives Rejected
16a — Decisions Made
| Date | Decision | Rationale |
|---|---|---|
| 2026-07-09 | One flow per action; multiple use cases = multiple self-describing actions (Option A) | Reuses the agent's existing tool_name/description-based selection; avoids building a new runtime flow-picker/selection mechanism |
| 2026-07-09 | Send + CONSUME v1 — flow completion is routed into the autonomous agent loop with raw form_response.data, skipping OrganizationEntity/Redis | The agent needs the submitted data in its own reasoning context to generate the next reply and drive the router; the Flow Builder entity system is a different consumer with a different lifecycle |
| 2026-07-09 | Delivery is non-blocking; both fallbacks (text reply, idle) reuse existing behavior with zero new logic | Keeps the conversation natural — the agent should not stop responding just because a flow is open; reusing the idle-action config avoids a second abandonment mechanism |
| 2026-07-09 | AI Service (not a static template) writes the message that follows a flow submission | Consistent with how the autonomous agent generates every other reply; a static template would be a special case |
| 2026-07-09 | Post-completion outcome is a tenant-configured, deterministic router (Resolve / Send via API / Assign to agent), not agent-decided | Predictability for the builder; the three outcomes already exist as primitives, so the router is thin to build and easy to reason about |
| 2026-07-09 | This action only references already-published Flow Builder flows; it does not add flow-authoring capability | Avoids duplicating flow-building UI; reuses a production-proven authoring path |
| 2026-07-09 | "Send via API" passes form_response.data verbatim as the target API action's payload in v1 (no field-mapping UI) | Keeps v1 build small; field-level remapping is deferred pending real usage evidence (Open Question 4) |
16b — Alternatives Rejected
| Alternative | Why Rejected | Date |
|---|---|---|
| One action holding multiple flows, with the agent choosing which to send at runtime (Option B) | Forces the agent to reason over a flow catalog and pass a selector argument; more to build and more to get wrong than reusing existing per-action tool selection | 2026-07-09 |
Consuming flow completion via the existing OrganizationEntity/Redis mapping (reusing the Flow Builder path as-is) | That system is scoped to Flow Builder's entity model and lifecycle; forcing the agent to read from it adds an indirection layer the agent doesn't need | 2026-07-09 |
| Agent-decided next-action outcome (LLM chooses resolve/API/assign at runtime) | Less predictable for the builder; the three outcomes have very different business consequences (closing a conversation vs. escalating) that shouldn't be left to model judgment in v1 | 2026-07-09 |
| Blocking the conversation until the flow is submitted or times out | Breaks the natural-language experience the AI Agent is built around; customers who prefer to type an answer would be stuck | 2026-07-09 |
| Building a new flow-authoring UI inside the AI Agent action drawer | Duplicates Flow Builder's proven authoring path for no clear benefit; this action is a consumer of published flows, not an author of them | 2026-07-09 |
17. Open Questions
| # | Type | Question | Owner | Deadline |
|---|---|---|---|---|
| 1 | Risk | DOWNGRADED (2026-07-09, code-grounded). ProcessIncomingMessageWithResolve#find_response currently always routes a form_response to SaveFormToEntity at process_incoming_message_with_resolve.rb:398-401 (corrected citation). Mitigation (confirmed, not just proposed): reuse the room-ownership check already live twice in this file — prev_intent&.content&.code == I18n.t('model.content_type.ai_agent.code') (lines 670-671, 1212-1215) — as the new branch condition. No new detection mechanism or schema change is needed; RFC only needs to validate exact branch placement. | Eng Lead (Hadiningbot) | Validate at RFC |
| 2 | Risk | NET-NEW (2026-07-09, code-grounded). process_async_reply's Grape params + Dry contract (process_async_reply.rb:38-51) is a fixed envelope (output_text/exit/references/token_usage) with no generic context slot — unknown params are stripped before reaching the use case. Consuming form_response.data requires adding a new field to this contract plus new branching in process_success/process_fallback (lines 156-192). Mitigation: scope this contract change explicitly in the RFC; decide whether the new field lands on the existing async-reply endpoint or a new dedicated one. | Eng Lead (Hadiningbot) | Before RFC |
| 3 | Risk | NET-NEW (2026-07-09, code-grounded). closed_reason values RESOLVE_AI/ASSIGN_AGENT_AI are confirmed used only in the older Ask-Airene/AI-assist path (send_message_with_resolve.rb:253, get_answer.rb:201,224) — never in process_async_reply.rb (the V2 engine this PRD builds on). The "Resolve"/"Assign to agent" router outcomes may need a new integration point into V2's room-closing logic, not a reuse of an existing autonomous-agent code path. Mitigation: confirm with engineering whether V2 sets closed_reason via a path not yet surfaced (e.g. a worker), or whether new wiring is required. | Eng Lead | Before RFC |
| 4 | Open Question | For the "Send via API" router outcome, does v1 truly pass form_response.data verbatim as the target api action's payload, or does real usage during closed beta show a field-mapping surface (flow field names → API request fields) is needed sooner than expected? | PM + Eng | Closed beta |
| 5 | Risk | UPGRADED from Open Question (2026-07-09, design-grounded — P0). No comparable component pattern exists in qontak-designer for the flow picker (searchable/async combobox — the only existing "select" pattern is static-options-only) or the next-action router (a 3-way outcome selector with a cross-action-sourced dependent picker — no precedent anywhere in the repo). This is a real build item, not just "no prototype yet." Mitigation: design composes the picker from verified Pixel 3 primitives (MpPopover/MpInput, or MpInputTag's suggestion/loading mode) and the router from MpRadio + a conditional ActionFieldRow-style dependent field, sourcing "Send via API" options from the agent's own configured api actions; scope both as net-new design + build work before RFC, not a Send-Attachment-drawer extension. | Design (UI/UX) | Before RFC |
| 6 | Assumption | Reusing the existing published-flow catalog (IntentWhatsappFlow, version_status = 'PUBLISHED') as the picker's data source, scoped org-wide regardless of which Flow Builder intent originally created the flow, is acceptable — i.e., a flow doesn't need to stay conceptually "owned" by its original intent to be picked here. Confirmed (2026-07-09): this query does not exist today and must be built net-new regardless (see Section 15) — this question is about the scoping rule, not whether the query exists. | PM + Eng | Before RFC |
| 7 | Open Question | Feature-flag mechanism: system_preferences rollout row (manual per-environment seeding, Rollout::Base.enabled?) vs. node_registries.enabled (the likely precedent for gating other qontak_* actions, e.g. Send Attachment). Confirmed (2026-07-09): the proposed flag name has zero existing hits, so naming is safe either way — but the mechanism itself must be decided. | Eng Lead | Before RFC |
| 8 | Open Question | NET-NEW (2026-07-09, code-grounded). Registering the new action type requires a node_registries DB row (no seed file manages these today) — via data migration or admin tooling? Decide the mechanism and reflect it in the RFC's migration plan. | Eng Lead | Before RFC |
| 9 | Assumption | The agent's function-calling reliably distinguishes between multiple Send WhatsApp Flow actions (each pinned to a different flow) using their trigger descriptions. Mitigation: validate during closed beta with multi-action configs; provide trigger-writing guidance (same mitigation pattern as Send Attachment). | PM + AI Service | Closed beta |
| 10 | Risk | A form_response arrives for a room whose Send WhatsApp Flow action was deleted or disabled between send and submission (ERR-1 in WAFLOW-S03). What is the exact fallback behavior — silently drop, log-and-ignore, or attempt a best-effort default routing? Mitigation: must be explicitly decided at RFC time; left undefined risks a silently dropped customer submission. | Eng Lead | Before RFC |
| 11 | Risk | Baseline handoff rate for structured-input conversations is not yet measured, so the deflection KPI target (Section 13) cannot be validated pre-launch. Mitigation: instrument and measure during closed beta before committing the GA target. | Data + PM | Closed beta |
PRD CHANGELOG
| Version | Date | By | Section | Type | Summary |
|---|---|---|---|---|---|
| 1.0 | 2026-07-09 | Claude | All | CREATED | PRD created from a product-ideation session grounded in chatbot (BE) — WhatsApp Flow send/capture plumbing (IntentWhatsappFlow, SendMessageWhatsappFlow, ProcessIncomingMessageWithResolve) and the AI Agent action framework (ActionExecutorFactory, NodeRegistry). Classified as a PHASE PRD in the Qontak Action catalog (corrected from an initial misclassification under Mekari Action). Locked design decisions: one-flow-per-action, send+consume v1 skipping the OrganizationEntity/Redis mapping layer, non-blocking delivery reusing both existing fallbacks, AI-Service-authored replies, and a tenant-configured (not agent-decided) next-action router. |
| 1.1 | 2026-07-09 | Claude | S6,S7,S8,S9,S11,S15,S17,Dependency Graph | MODIFIED | Applied confirmed findings from a ground-prd-code pass (chatbot repo) + ground-prd-design pass (qontak-designer repo). Code: downgraded Open Question 1 from critical risk to confirmed-low-risk (corrected find_response citation from lines 323-334 to 398-401; the room-ownership signal prev_intent.content.code == 'ai_agent' already exists at lines 670-671/1212-1215) — the real remaining risk moved to a net-new process_async_reply contract extension (new OQ2) and an unconfirmed V2 closed_reason wiring point (new OQ3, since RESOLVE_AI/ASSIGN_AGENT_AI are only used in the older Ask-Airene path). Re-classified the published-flow catalog query and the new node_registries row/executor class from "reused"/"none required" to net-new build work (S7, S11, S15). Added the feature-flag-mechanism ambiguity (system_preferences vs node_registries.enabled, new OQ7) and fixed version_status casing ('PUBLISHED', not 'published'). Design: upgraded the flow-picker and next-action-router UI gaps from "no prototype yet" to P0 blockers (new OQ5) — no comparable searchable-combobox or multi-branch-router component pattern exists anywhere in qontak-designer; annotated Section 8.1's component tree accordingly. |