Module · Replying and messaging
Response Builder
Lesson 6 of 21 · 5 min
Ask a workflow how the pipeline is doing and a paragraph is the wrong answer. What you want is a heading, a table of the deals, and maybe a chart — the shape a person would put in a slide, not the shape they would put in a sentence.
Response Builder composes that. Instead of one block of text, you stack components: a text block, a data table, a chart, an alert. Each one gets its data from somewhere upstream, they render in the order you set, and the whole thing arrives as a single reply. It is the difference between a chatbot and something worth opening.
It is also the node in this course whose documentation and whose behaviour disagree the most, so read the config section carefully before you build with it.
The node's own fields
| Field | What it holds |
|---|---|
components | Array of component configurations |
output_destination | Where to send the output One of: chat · sse_output · api_response Defaults to chat. |
components
- What it holds
- Array of component configurations
output_destination
- What it holds
- Where to send the output One of:
chat · sse_output · api_responseDefaults tochat.
Two fields, and almost everything lives in the first one. output_destination is the simpler half: chat renders the reply in the chat panel, sse_output pushes it into a named output space on a page, and api_response hands it back to whatever called the workflow. Most of the time it is chat and you never touch it.
What a component actually is
A component is one entry in the components array, and it has five keys. This is the shape the builder writes and the shape the node reads — it is not the shape the config schema describes.
| Key | What it is for |
|---|---|
id | A unique string. Required in practice — the node sorts by looking each component back up by id, and one without an id stops the node. The builder generates it; a hand-written spec must not omit it. |
type | Which component to render. One of eleven in the engine; the palette offers ten. |
order | Where it sits in the stack. Lowest first. |
inputMapping | Per-property data references. Values must be a bare {{alias.field}} and nothing else. |
staticValues | Per-property literal values. Anything you are typing rather than pulling — headings, alert text, page sizes — goes here. |
id
- What it is for
- A unique string. Required in practice — the node sorts by looking each component back up by id, and one without an id stops the node. The builder generates it; a hand-written spec must not omit it.
type
- What it is for
- Which component to render. One of eleven in the engine; the palette offers ten.
order
- What it is for
- Where it sits in the stack. Lowest first.
inputMapping
- What it is for
- Per-property data references. Values must be a bare {{alias.field}} and nothing else.
staticValues
- What it is for
- Per-property literal values. Anything you are typing rather than pulling — headings, alert text, page sizes — goes here.
The split between those last two is the thing to internalise. inputMapping is for data the workflow produced; staticValues is for words you wrote. Putting a literal in inputMapping does not raise — see below.
The components worth knowing first
| Type | What it renders |
|---|---|
text | Markdown. Headings, bold, lists. The workhorse — most replies open with one. |
table | A sortable, paginated data table. Feed it an array of objects and it works out the columns. |
recharts / chart | Two chart components, not one: recharts takes a flat array of objects and is what you want for CRM rows; chart takes a Plotly configuration object. |
alert | A highlighted notice — info, success, warning or error. For the caveat that belongs with the numbers. |
json | A collapsible tree. Almost always a debugging component rather than a customer-facing one. |
text
- What it renders
- Markdown. Headings, bold, lists. The workhorse — most replies open with one.
table
- What it renders
- A sortable, paginated data table. Feed it an array of objects and it works out the columns.
recharts / chart
- What it renders
- Two chart components, not one: recharts takes a flat array of objects and is what you want for CRM rows; chart takes a Plotly configuration object.
alert
- What it renders
- A highlighted notice — info, success, warning or error. For the caveat that belongs with the numbers.
json
- What it renders
- A collapsible tree. Almost always a debugging component rather than a customer-facing one.
A worked example
Someone asks about the pipeline in chat. A CRM search collects the deals, and one Response Builder turns them into a heading, a table and a footnote.
Three components, one reply
Text from staticValues, table data from inputMapping, an alert to carry the caveat.
Scroll for all 3 steps →
- The text component's
contentis instaticValuesbecause it is a sentence I wrote. It contains a full stop, which matters more than it should. - The table's
datais ininputMapping, pointing at{{crmmanager_1.results}}— the array the search returned. Nothing describes the columns; the table reads them off the first row. - Its
pageSizeandexpandedPageSizeare literals, so they are instaticValuestoo. Ten rows show, fifty on expand. - The alert carries the caveat about currency. Caveats belong in the answer, not in a follow-up message nobody reads.
One more thing that the table does quietly: hand it more than 200 rows and it caches the full set for an hour and renders the first 200 with paging controls. You do not configure that, and you do not need to trim the search.
This or Text Response
| Reach for | When |
|---|---|
| The answer is a sentence, or a few. Anything conversational. Anything going somewhere that renders plain text. | |
| The answer is a set of rows, a number worth charting, or a mix of prose and data. Anything a person would want to sort or scroll. |
- When
- The answer is a sentence, or a few. Anything conversational. Anything going somewhere that renders plain text.
- When
- The answer is a set of rows, a number worth charting, or a mix of prose and data. Anything a person would want to sort or scroll.
The tell is whether the user will want to do something with the answer. Reading is Text Response. Sorting, scanning, comparing — that is a table, and a table pasted into a chat message as pipe characters is not a table.
What breaks
A component built from the config schema produces nothing. The schema says each component is {type, props, order}. The node never reads props — it reads inputMapping and staticValues. A component with a props object contributes no data, gets skipped, and if it was the only component the reply that reaches the user is the fallback string Response generated successfully. No error, no warning, and the workflow validates.
A literal with a full stop in inputMapping disappears. Any mapping value containing . or [ is treated as a data path rather than text. So Here is your pipeline renders and Here is your pipeline. resolves to nothing and drops its component. The same applies to a sentence with a variable inside it — mapping values must be a bare reference, and prose belongs in staticValues.
A component without an id stops the node. After building the components the node sorts them, and the sort looks each one back up in your config by its id. A component that never had one is not found and the run fails there. The builder always writes an id, so this only bites specs written by hand or by an agent — and those validate as runnable first, which is exactly how it reaches production.
Try it
- Add a Response Builder after any node that returns rows. Give it one
tablecomponent, mapdatato that node's array, and run it. Columns you never named appear. - Add a
textcomponent above it withorderset lower, and put a markdown heading instaticValues. Reorder them and watch the reply reorder. - Now move that heading from
staticValuesintoinputMappingand run it again. If it ends in a full stop it vanishes, and nothing tells you why. That is the failure worth seeing once. - Add an
alertwithtypeset towarningand a sentence about what the numbers exclude. Every real report has one of these.
Next: Email nodes — the same composing problem when the reply has to leave the product and land in an inbox.

