Skip to content

Building a chatbot agent

PortalAPIIntermediate~25 min

Build a chatbot that holds a structured conversation, such as onboarding, intake or a diagnosis. It works through its questions in stages, saves what each user tells it, and picks up where it left off the next time. You can do all of it in the portal, with no code. If you later want the conversation inside your own product, the same agent serves it there through the Chat API.

If you have never built one, do the getting-started tutorial first. It creates a chatbot with the wizard's defaults and has a conversation with it in about twenty minutes. This page picks up from there: designing your own conversations, stages, prompts, and the data each stage collects.

Before you start

  • Owner or Admin of an organization. The chatbot is created from the organization's Agents page, and only an Owner or Admin can give it an LLM key.
  • A model for it to use. The chatbot calls an LLM through an AI config: a provider, a model and a key. See Configure an LLM provider. The tutorial's Step 3 is the shortest route.

How a chatbot is put together

  • A conversation is one flow, such as onboarding or find-mentor.
  • A conversation has stages, in order. Each stage has a prompt, which tells the model what to do at that point, and optionally an extraction spec, which lists the facts to collect from the user there.
  • Routing rules hand a chat from one conversation to another.

Conversations, stages, prompts and partials are stored as nodes in the agent's system memory, and routing rules as links between those nodes. Every agent gets its system memory automatically when it is created, so there's nothing to set up.

The model runs one turn at a time. On each turn it replies to the user, reports any facts it learned, and says whether to move to another stage. Hadron saves the facts, moves the chat along, and builds the next turn's prompt.

Step 1: Create the chatbot

  1. Open your organization's Agents page and choose New Chatbot.
  2. Work through the Create Chatbot Agent wizard: Agent, System Prompt, Memories and Review. On Memories the system memory is Auto-created. Tick Knowledge if the chatbot should have a memory of reference material to draw on.
  3. Choose Create Chatbot Agent.

The wizard creates the agent and its system memory, plus a starting design:

  • a setup conversation with one stage, onboard, which asks the user's name;
  • a fallback conversation for questions the chatbot can't help with;
  • the prompts for both, and a shared partial that tells the model how to answer.

The tutorial's Step 2 goes through the wizard's fields one by one.

Step 2: Add your own conversation

A new chat from the portal starts in a conversation that is not marked setup or fallback. If there are several, which one it picks isn't fixed. If there is none, as with only the wizard's two conversations, it starts in one of those two, and again which one isn't fixed. So your own conversation is the first thing to add, and while you are designing, keep one ordinary conversation that every chat should begin in. (Through the Chat API, startChat can name the conversation to start in.)

  1. Open the agent. On its Profile tab, next to Conversations, choose Conversation Editor →.
  2. Type a name into New conversation… and choose + Conversation.
  3. Choose the new conversation. The Edit conversation panel opens. Under Stages (in order), type a name into New stage name… and choose + Add stage. Add as many stages as the flow needs, in the order it runs. The first stage is where every chat in this conversation begins.
  4. In the same panel, fill in Goal signals (one per line) with words a user would say when they want this conversation, then choose Save goals. A chat that is in the fallback conversation moves to a conversation whose goal signal appears in the user's message. Failing that, a message of four words or more moves it to one of the ordinary conversations, and which one isn't fixed when there are several.

Step 3: Write each stage's prompt

Choose a stage to open Edit stage. Write the stage's instructions under Prompt and choose Save prompt.

A prompt is plain instructions for the model, plus two kinds of placeholder:

  • {{name}} inserts a fact already saved about this user, so the prompt can use it: "You're talking to {{name}}, who runs a {{industry}} business." Write the fact's plain name, {{name}}. A dotted form such as {{memory.name}} looks for something else and comes out empty.
  • {{> prompts:partials:metadata-spec}} inserts a shared piece of prompt, called a partial. Edit partials under Shared Partials in the editor. The wizard's metadata-spec partial tells the model how to answer, and every stage prompt should include it.

To move to the next stage, tell the model when to, and what to call it:

Ask what industry they work in and how long they've been in business.
When you know both, set next_stage to "goals".

The stage is named by its bare name, goals, not by a path. Nothing moves a chat on by itself: it changes stage when the model says so, or when a routing rule hands it to another conversation (Step 5).

Step 4: Collect facts at a stage

Under Extraction spec in the same panel, list what this stage should learn, then choose Save extraction. Each entry names a field, describes it for the model, and gives its shape, such as string, number or string[]:

[
  { "field": "industry", "description": "The user's business industry", "shape": "string" },
  { "field": "yearsInBusiness", "description": "How long they've been in business", "shape": "number" }
]

When the model learns one of these, Hadron saves it. From then on, every prompt can use it as {{industry}}.

Where it is saved. Facts are saved on the memory the chat lives in. In a private chat, that is the user's own memory, so each user's answers stay separate. In a shared chat, it's the App's memory, which everyone in the App can see. All of an App's shared chats share one set of facts today, so one member's answers can show up in another member's prompts (hadron-server#910). See Chatting with an agent for private and shared chats.

Name fields plainly. A field is saved under the last part of its name, so memory.name is saved as name and read back as {{name}}. Prefixes such as memory. or chat. don't send a fact anywhere different today.

Step 5: Hand off between conversations

Drag from one item to another in the editor to add a routing rule, then set it up in Edit routing rule: Label, Condition, Timing, Behavior and Priority, then Save rule.

Today, a rule only acts when it hands the chat to another conversation, and only with Timing on_complete or always. A rule that points at a stage, or that uses on_enter, is saved but has no effect. Rules are also checked only on turns where the model didn't choose a stage itself. The details, and the fix in progress, are in What the chat engine acts on today. Edge conditions explains when a rule is the right tool and when to leave the choice to the model.

Step 6: Try it, then publish a version

Install the agent as an App with App type set to Chatbot, then chat with it from the App's Chats tab under Agent chat. The tutorial's Step 4 and Step 5 walk through both. To run scripted users through it and get a pass/fail report, use the Tester: Chatbot Control → Overview → Open Tester →. See Test a chatbot with scripted personas.

When a design works, freeze it. In the editor, choose Save revision, then Publish it under Revisions.

A published revision is what users get, until you publish again

Once a revision is published, every chat runs from that saved version. Edits you make in the editor afterwards don't reach users until you save a new revision and publish that. If a change seems to have no effect, check whether a revision is published. Restore brings an older revision back into the editor.

Host it in your own app

The portal's Agent chat calls the model for you, using the agent's AI config. To put the same chatbot inside your own product, your app runs the conversation through the Chat API and calls the model itself:

  1. startChat opens a chat for one of your users. It returns the first stage's compiled prompt and the respond tool schema.
  2. The welcome turn. Send that prompt and tool to your LLM with no user message, and pass its respond call to processChatResponse. The chat opens with the chatbot speaking first.
  3. For each user message, sendChatMessage returns the prompt, the tool schema and the message history. Your app sends those to your LLM.
  4. processChatResponse takes the model's respond tool call. Hadron saves the reply and any facts, moves the chat on, and returns the message to display.

The model answers through the respond tool. The tool schema Hadron gives it requires message and onTrack; data carries the facts, and next_stage names a stage by its bare name. The wizard's metadata-spec partial already asks for all of them. (processChatResponse itself treats a missing onTrack as true.)

Hadron takes one complete respond call per turn; there is no streaming endpoint. Your app can still stream the model's output to the user as it arrives, and call processChatResponse once the tool call is complete.

The full contract, with operations, arguments, errors and a per-turn example, is in the Chat API reference. This path needs code, and there's no agent-tool equivalent: the hadron_chatbot_* MCP tools were removed.

What the editor writes

For builders who edit the system memory directly, through node tools or the API, this is the layout the editor and the engine use:

Node (loc in the system memory) What it holds
conversations:<name> data.stageOrder, the stages in order. data.isSetup or data.isFallback marks the wizard's special conversations.
conversations:<name>:<stage> data.promptRef, the loc of the stage's prompt node, and data.extractionSpec, the list of { field, description, shape }. With no promptRef, the stage's own content is its prompt.
prompts:… Prompt text in content, with {{field}} placeholders and {{> loc}} partials.

Routing rules are graph edges between these nodes, not fields on them: the rule's label is the edge's name, Condition and Priority are edge columns, and Timing and Behavior live in the edge's data. See Conversation routing for the full model.