> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tied.com/llms.txt
> Use this file to discover all available pages before exploring further.

# How the agent picks a message

> Intent goes in, a validated channel payload comes out. The model never writes the JSON, and your flows never name a channel.

Tied's channels are not equally expressive. Apple Messages for Business has list pickers, forms, time pickers and Apple Pay. WhatsApp has buttons and lists. SMS has text.

An agent that composed channel payloads directly would have to know all of that, get it right every time, and be re-taught whenever a channel changed. It does not. It says what it is trying to do, and a deterministic builder renders that correctly for wherever the conversation happens to be.

## Intent in, payload out

The agent emits an intent — *offer these six options*, *collect a date*, *take a payment*, *show this link with a preview* — and the builder produces the real platform payload.

<img src="https://mintcdn.com/tied/EKg57bTFCkV_E_Je/images/diagrams/message-builder-light.svg?fit=max&auto=format&n=EKg57bTFCkV_E_Je&q=85&s=64b7775feb58c95e1b237243ffcfd34f" alt="One intent rendered as a different primitive per channel, and the two kinds of reply a tapped option can send back." className="block dark:hidden w-full my-8" noZoom width="900" height="424" data-path="images/diagrams/message-builder-light.svg" />

<img src="https://mintcdn.com/tied/EKg57bTFCkV_E_Je/images/diagrams/message-builder-dark.svg?fit=max&auto=format&n=EKg57bTFCkV_E_Je&q=85&s=511f5bb0a116acac7cfd9a452426f6d7" alt="One intent rendered as a different primitive per channel, and the two kinds of reply a tapped option can send back." className="hidden dark:block w-full my-8" noZoom width="900" height="424" data-path="images/diagrams/message-builder-dark.svg" />

<Info>
  **The model never writes channel JSON.** Every message schema rejects fields it does not recognise,
  so a model improvising an attribute produces a validation failure rather than a malformed message
  reaching your customer. The failure is caught before send, not after.
</Info>

## Choosing the cheapest sufficient primitive

The builder picks by the **shape of the information**, not by what looks impressive.

| What the agent needs | What it sends |
| - | - |
| To say something | Plain text |
| A choice between a handful and a couple of hundred options | A list picker |
| A date or a time | A time picker |
| Several fields at once | A form |
| Money | A payment request |
| Permission to reach a system on the customer's behalf | An authorization prompt |
| To share a link worth previewing | A rich link |

And some hard blocks, because the failure modes are worse than the inconvenience:

* **No form for a single field.** Asking a customer to open a form to type one thing is worse than asking.
* **No payment request without a narration turn first.** Money is never the first thing a customer sees.
* **No authorization prompt unless an action genuinely needs the scope.**
* **No primitive at all for a purely informational answer.** If the reply is an answer, it is text.

## Degradation is the builder's job

Channels disagree about what comes back when a customer taps something.

Some return a **stable identifier** — the option you actually defined. Others return only the **label text** the customer saw. That distinction matters enormously the moment a reply drives a branch, because a label can be ambiguous and an identifier cannot.

<Warning>
  **Where a channel discards the identifier, the builder falls back to matching the label** — and the
  menus it generates keep their labels distinct so that matching is unambiguous. This is handled in
  the builder, not by the model, so it behaves the same way every time rather than depending on what
  the model happened to infer.
</Warning>

## One turn, and what it costs

You can send prose and a primitive in the same turn — an explanation followed by the choices it refers to. It reads better than splitting them.

On Apple Messages for Business it is not free. That channel allows one message in flight per conversation at a time, so two messages in a turn is genuinely two sends, and worst case that is a noticeable pause before the customer sees the second. The builder is deliberate about it rather than chatty.

## Richness is a per-inbox setting

Three presets, and per-primitive overrides on top:

<CardGroup cols={3}>
  <Card title="Minimal" icon="minus">
    Text wherever text will do. Use when you want the transcript to read like a person wrote it.
  </Card>

  <Card title="Balanced" icon="equal">
    The default. Rich primitives where they genuinely reduce effort, text everywhere else.
  </Card>

  <Card title="Rich" icon="sparkles">
    Prefer structured primitives. Use where taps beat typing — mobile-heavy audiences, long option
    lists.
  </Card>
</CardGroup>

## Your flows never name a channel

This is the rule that makes the whole layer worth having.

**A flow definition may not name a channel primitive.** No "list picker" node, no "quick reply" node. It says *ask the customer to choose one of these*, and the rendering is decided at send time.

<Check>
  **So a flow you build on web chat today runs on Apple Messages for Business the day your brand is
  approved, with no re-authoring.** Platform onboarding takes four to six weeks of calendar time —
  that is a queue you wait in, not work you do. Building your flows while you wait is the right use
  of it, and the studio's channel preview lets you see exactly what you are waiting for.
</Check>

Re-authoring a flow because the channel changed would be a product failure, not a migration.

## Next

<CardGroup cols={2}>
  <Card title="Rich Feature Messages" icon="sparkles" href="/channels/apple-messages/rich-feature-messages">
    What the primitives look like on Apple Messages for Business.
  </Card>

  <Card title="Channel nodes" icon="workflow" href="/chatbot/channel-nodes">
    The same idea in the flow builder.
  </Card>

  <Card title="Training studio" icon="sliders-horizontal" href="/agents/studio">
    Channel preview, and publishing a version.
  </Card>

  <Card title="Channels" icon="radio-tower" href="/channels">
    What each channel can actually carry.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.