---
title: 'Tutorial: Customer support flow'
path: tutorials/customer-support-flow
status: published
---

# Tutorial: Customer support flow

Build a flow that classifies an inbound customer message, retrieves relevant knowledge-base docs, generates a response, and routes low-confidence responses to a human reviewer.

## The shape

```mermaid
flowchart LR
  Entry["API Entry"]
  Classify["Classify Intent<br/>(Flexible)"]
  KB["KB Search<br/>(Plugin Call)"]
  Respond["Generate Response<br/>(Guarded)"]
  Branch{"confidence > 0.7?"}
  Reply(["respond"])
  HITL["HITL Review"]

  Entry --> Classify --> KB --> Respond --> Branch
  Branch -->|yes| Reply
  Branch -->|else| HITL
```

## Steps

### 1. Start from the example

In the toolbar, click **Catalog…** → **Examples** tab → **Customer Support** → **Open**. A pre-built flow is loaded with a fresh id. Use this as the base; below is what each node is for.

### 2. Inspect the API Entry

`out_amount` port (yes, an example field) wires into the classifier. The entry's `config` is `{method: POST, path: /}`. Invocations look like `POST .../invoke` with `{"input": {"message": "..."}}`.

### 3. Configure the classifier

`node_classify` is a Flexible Prompt. Its config:

- **Goal**: `Classify the customer's intent and urgency. Return JSON with intent, urgency, confidence, and entities.`
- **Output schema**:
  ```jsonc
  {
    "intent": "enum[question, complaint, request, feedback]",
    "urgency": "enum[low, medium, high]",
    "confidence": "float",
    "entities": "map<string, string>"
  }
  ```
- **Model role**: `fast` (cheap classification model from the registry).

### 4. KB retrieval

`node_kb_search` is a Tool Plugin call:

- **Plugin**: `scaidrive`
- **Method**: `search`
- **Args**:
  ```jsonc
  { "path": "/knowledge/support/", "query": "{{message.text}}" }
  ```

The compiler auto-adds `scaidrive` to the flow's plugin list. The result is bound to a variable for the next step to reference.

### 5. Generate the response

`node_respond` is a Guarded Prompt:

- **Goal**: `Generate a response grounded in the retrieved docs. Cite sources.`
- **Guard**: `kb_results.length > 0` — don't hallucinate when there are no docs.
- **Validate**: `result.citations.length > 0` — refuse responses without citations.
- **Model role**: `primary` (the smart model).

### 6. Branch on confidence

`node_decision` is a Decision node, style `if`:

- **Subject hint**: `confidence` (optional, fills in placeholder text on the branch conditions).
- Two outgoing conditional edges:
  - `confidence > 0.7` → respond directly (sequential edge to wherever you want the response handed back).
  - `else` (typically a sequential fallthrough) → `node_review` (HITL Review).

### 7. The HITL Review

`node_review` is an HITL Review node. The fields you set:

- **Scope**: leave blank — inherits `support` from the flow's ScaiQueue topology.
- **Queue**: `review`.
- **Sections** (via the Form Builder):
  - Context section showing the original customer message + intent classification.
  - Input section with the AI-generated response (editable).
  - Decision section with `approve`, `reject`, `edit_and_approve` options.

### 8. Flow-level config

Confirm in the Flow properties panel:

- **Models**: two roles — `primary` (smart) and `fast` (cheap). Picker reads from your tenant's ScaiGrid catalog.
- **ScaiQueue**:
  ```jsonc
  { "scope": "support",
    "queues": [
      { "slug": "review", "purpose": "review", "consumer_mode": "fifo" }
    ] }
  ```
- **Plugins**: `scaidrive` (auto-added by the compiler).

### 9. Deploy

Click **Deploy**. Two things happen:

1. The pre-deploy provisioning step ensures the `support` scope + `review` queue exist in ScaiQueue (idempotent).
2. ScaiGrid creates the Core.

### 10. Test

```bash
curl -X POST "https://scaigrid.scailabs.ai/v1/modules/scaicore/cores/${CORE_ID}/invoke" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"input": {"message": "I have not received my refund for order 42"}}'
```

For a low-confidence response, the invocation will pause at the HITL Review. Check the Live Runs panel: a "Pending breakpoints" bar shows the paused execution. Resolve via the ScaiQueue review UI (or `POST /v1/scaicore/checkpoints/{id}/resolve`).

## What you learned

- Plugins integrate via `tool_plugin` — pure ScaiCore plugin_call, no special node kind per integration.
- The `Guarded Prompt` pattern of `guard` + `validate` makes the response step refuse-rather-than-guess.
- HITL Review compiles to a single `@checkpoint` with `hitl_target` — no correlation thread, no daemon.
- ScaiQueue topology declared once on the flow; per-node `scope` inherits.

## Next

- **[Expense approval with HITL](./expense-approval-with-hitl)** — adds ScaiBunker for sandboxed policy checks.
- **[Multi-model flow](./multi-model-flow)** — fast vs. smart model selection in depth.
