The operator-facing surface used to configure an agent — declarative query / condition rules, source-to-target field mapping, connection test affordances, version switching, and the structured document editor where prompts and policies are authored. Six components compose the full configuration vocabulary.
18.1 QueryBuilder
The flat AND/OR condition builder that anchors every audience filter, ask-when rule, agent action condition, and webhook event filter across the platform. The operator builds the rule top-down: each row is a field · operator · value triple; the conjunction toggle picks AND or OR; an Add condition button drops a fresh row at the bottom. The value editor swaps shape on the field’s type — single select, chip toggles, duration count + unit, or plain text — without changing the row chrome.
QueryBuilder
.qb
Three conditions stacked with the AND/OR conjunction toggle on top — a select field with one value, a multiselect with two chips, and a duration with count + unit. The WHERE label sits to the left of the first row; subsequent rows show the conjunction label. The remove affordance lives at the end of each row.
WHERE
AND
AND
<!-- One .qb wraps the conjunction toggle, the .qb-rows stack, and -->
<!-- the + Add condition button. Each .qb-row is a fixed-grid -->
<!-- conj · field · op · value · remove triple. -->
<div class="qb">
<div class="qb-conj-toggle" role="group" aria-label="Match">
<button type="button" class="qb-conj-btn is-on">Match all (AND)</button>
<button type="button" class="qb-conj-btn">Match any (OR)</button>
</div>
<div class="qb-rows">
<div class="qb-row">
<span class="qb-conj">WHERE</span>
<select class="qb-field">…</select>
<select class="qb-op">…</select>
<select class="qb-value qb-value-select">…</select>
<button class="qb-remove" aria-label="Remove condition">…</button>
</div>
<!-- …more rows… -->
</div>
<button class="qb-add">+ Add condition</button>
</div>
The atom of the QueryBuilder. One row carries a single rule: pick a field, pick an operator, supply a value (if the operator takes one). The operator vocabulary and the value editor are derived from the field’s declared type — text fields offer equals / contains / starts-with; number fields offer comparison operators; multiselects collapse to chip toggles; is-known / is-unknown drop the value editor altogether.
ConditionRow
.qb-row
Six rows in a stack, each demonstrating a different operator variant against the same underlying field shape — equals (single text value), does not equal, contains, in-last (duration count + unit), is one of (multiselect chips), and is known (no value editor).
WHERE
AND
AND
AND
AND
AND
<!-- One .qb-row per condition. The conj span renders WHERE / AND / -->
<!-- OR; the value column swaps editor shape on operator + field -->
<!-- type. is-known / is-unknown render with no value editor at all. -->
<div class="qb-row">
<span class="qb-conj">WHERE</span>
<select class="qb-field">…</select>
<select class="qb-op">…</select>
<input type="text" class="qb-value qb-value-text" value="Jay Stockwell">
<button class="qb-remove" aria-label="Remove condition">…</button>
</div>
<!-- in-last operator → duration value editor (count + unit) -->
<div class="qb-row">
<span class="qb-conj">AND</span>
<select class="qb-field">…</select>
<select class="qb-op">…</select>
<div class="qb-value qb-value-duration">
<input type="number" value="24">
<select><option>Hours</option></select>
</div>
<button class="qb-remove">…</button>
</div>
<!-- is-known → no value editor; the remove button moves up -->
<div class="qb-row">
<span class="qb-conj">AND</span>
<select class="qb-field"><option>Email</option></select>
<select class="qb-op"><option>is known</option></select>
<button class="qb-remove">…</button>
</div>
import { ConditionRow } from '@magicblocksai/ui';
import type { QueryCondition, QueryField } from '@magicblocksai/ui';
declare const fields: QueryField[];
declare const condition: QueryCondition;
// Standalone — drop a single ConditionRow into your own surface
// without a QueryBuilder wrapper. Wire conjunctionLabel, onChange,
// onRemove yourself.
<ConditionRow
fields={fields}
condition={condition}
conjunctionLabel="WHERE"
onChange={(next) => updateRule(next)}
onRemove={() => removeRule(condition.id)}
/>
// Operator drives editor shape — equals on a text field is a text
// input; in-last on a duration is a count + unit; is-known has no
// value editor at all. The operator vocabulary is derived from the
// field's `type`. Override per-field with `operators: QueryOperator[]`.
18.3 FieldMapper
Two-column source-to-target mapper. The left column picks a MagicBlocks data field (a contact attribute, a key fact, a CRM property); the right column picks the destination on an external system (a HubSpot property, a Calendar field, a CSV column). The arrow between is decorative — the data direction is conveyed by the column headers. The same primitive backs HubSpot deal-creation mapping, Calendar field auto-population, and CSV column-to-CRM import.
FieldMapper
.field-mapper
Three source-to-target mappings drawn between a MagicBlocks contact + key-fact source list on the left and a HubSpot property list on the right. The header row labels the two columns and the directional arrow; each row repeats the same shape so the operator can scan top-to-bottom for a particular field.
MagicBlocks field
→
HubSpot property
→
→
→
<!-- A .field-mapper wraps the column-label header, the .field-mapper- -->
<!-- rows stack, and the + Add row button. Each .field-mapper-row is -->
<!-- a fixed-grid source · arrow · target · remove quadruple. -->
<div class="field-mapper">
<div class="field-mapper-head">
<div class="field-mapper-col-label">MagicBlocks field</div>
<div class="field-mapper-arrow">→</div>
<div class="field-mapper-col-label">HubSpot property</div>
<div></div>
</div>
<div class="field-mapper-rows">
<div class="field-mapper-row">
<select class="field-mapper-select">…</select>
<span class="field-mapper-arrow">→</span>
<select class="field-mapper-select">…</select>
<button class="field-mapper-remove" aria-label="Remove mapping">…</button>
</div>
<!-- …more rows… -->
</div>
<button class="field-mapper-add">+ Add property</button>
</div>
A button with a built-in pending / ok / error result chip. Wraps an async probe — MCP Discover Tools, Webhook Test, HubSpot Test, Form Test — and shows the outcome inline without forcing the consumer to manage three state variables. The button auto-disables while pending, swaps to the pending label and spinner, then renders a tonal chip on resolve. The chip auto-clears after resetAfterMs (4s default; pass 0 to keep it sticky).
TestConnectionButton
.test-connection
Four side-by-side states — untested (idle), testing (pending with spinner + busy label), success (ok chip with check + caption), and error (warning chip with cross + message). The chip sits to the right of the button on wide screens and wraps below on narrow ones.
import { TestConnectionButton } from '@magicblocksai/ui';
// Async wrapper — resolve with { message } for the ok chip caption,
// throw for the error chip. The button manages all three states.
<TestConnectionButton
onTest={async () => {
const r = await fetch('/api/webhook/test', { method: 'POST' });
if (!r.ok) throw new Error(`HTTP ${r.status}`);
return { message: '200 OK in 142ms' };
}}
>
Test webhook
</TestConnectionButton>
// Controlled — drive state explicitly when you need to coordinate
// with other surfaces. `resetAfterMs={0}` keeps the chip sticky.
import { useState } from 'react';
import type { TestConnectionState } from '@magicblocksai/ui';
function Controlled() {
const [state, setState] = useState<TestConnectionState>({ status: 'idle' });
return (
<TestConnectionButton
state={state}
onStateChange={setState}
onTest={async () => { /* … */ return { message: 'OK' }; }}
resetAfterMs={0}
/>
);
}
18.5 VersionSwitcher
A dropdown that swaps between historical revisions of a versioned entity — agents, personas, and tasks are the platform’s three versioned entity types. The trigger renders the current version label + an optional status pill; the open menu lists every prior version with a timestamp and an optional status badge. Optional footer actions add Compare + Create new version entry points.
VersionSwitcher
.version-switcher
The trigger and a statically-rendered open menu showing four versions — v4 (current, live), v3 (draft), v2, and v1 — each with semver-style label, timestamp, and where relevant a status badge (Live / Draft). The footer carries Compare and Create-new-version actions.
Sectioned document editor for Sales Playbooks, structured Q&A docs, change logs, RFC drafts — any “named sections with rich body per section” surface. Each section has a numbered heading, an editable title, an optional kind chip, and a body. A sticky TOC rail on the left jumps between sections; required-marked sections suppress the per-section remove affordance. The body defaults to an autogrowing textarea; pass renderBody to swap in a richer editor.
StructuredDocEditor
.structured-doc
A sales-playbook editor with the TOC rail on the left and three sections in the body — Introduction (required, locked title), Qualification (editable, with a draft caption), and Close (required, with a Critical kind chip and a saved caption). The + Add section button at the bottom is enabled because allowAdd is on.
01
Locked · required for every playbook
02
Draft · edited 3h ago
03
Critical
Saved · required for every playbook
<!-- .structured-doc wraps the optional TOC nav + the body stack. -->
<!-- .has-toc and .has-toc-left / -right toggle the grid layout. -->
<div class="structured-doc has-toc has-toc-left">
<nav class="structured-doc-toc" aria-label="On this page">
<div class="structured-doc-toc-title">On this page</div>
<ol class="structured-doc-toc-list">
<li class="structured-doc-toc-item">
<button class="structured-doc-toc-link">
<span class="structured-doc-toc-num">1.</span>
<span class="structured-doc-toc-label">Introduction</span>
</button>
</li>
<!-- …more items… -->
</ol>
</nav>
<div class="structured-doc-body">
<!-- One <section.structured-doc-section> per chapter section. -->
<!-- .is-required suppresses the remove button. -->
<section class="structured-doc-section is-required">
<header class="structured-doc-section-head">
<div class="structured-doc-section-num">01</div>
<div class="structured-doc-section-title-block">
<div class="structured-doc-section-title-row">
<input class="structured-doc-section-title-input"
value="Introduction" disabled>
</div>
<div class="structured-doc-section-caption">
Locked · required for every playbook
</div>
</div>
<div class="structured-doc-section-actions"></div>
</header>
<div class="structured-doc-section-body">
<textarea class="structured-doc-section-textarea" rows="3">…</textarea>
</div>
</section>
<!-- …more sections… -->
<button class="structured-doc-add">+ Add section</button>
</div>
</div>
import { useState } from 'react';
import { StructuredDocEditor } from '@magicblocksai/ui';
import type { StructuredSection } from '@magicblocksai/ui';
const initial: StructuredSection[] = [
{ id: 'intro', title: 'Introduction',
body: 'Set the scene. Greet the contact…',
caption: 'Locked · required for every playbook',
lockedTitle: true, required: true },
{ id: 'qual', title: 'Qualification',
body: 'Discover budget, timeline, and decision-maker…',
caption: 'Draft · edited 3h ago' },
{ id: 'close', title: 'Close',
body: 'Ask for the meeting. Confirm the next step…',
kind: 'Critical',
caption: 'Saved · required for every playbook',
lockedTitle: true, required: true },
];
function Example() {
const [sections, setSections] = useState<StructuredSection[]>(initial);
return (
<StructuredDocEditor
value={sections}
onValueChange={setSections}
showToc
tocPosition="left"
tocTitle="On this page"
allowAdd
allowRemove
/>
);
}
18.7 Agent builder shell
The host shell behind every section of agent configuration — the same IA operators have today (left rail with nested sections under General and the block list under Journey, plus single main edit pane), redesigned in the new visual language. The right side of the viewport is reserved for the live testing chat bar (a separate concern that lives outside this composition), so the shell is rail + pane only — never rail + pane + inspector.
Decoupled from the app shell. The agent builder is the .ab-builder (rail + pane) — it does not own the outer workspace chrome, so it’s shown standalone below. In a real app it mounts inside a <WorkspaceShell> main column (the canonical NextGen shell — see 15.27) as one inner section among many (dashboard, sessions, contacts …). Compose it as <WorkspaceShell><AgentBuilderShell…/></WorkspaceShell>.
Block editor — Journey · Want Secret Deals? · Jobs to Do
.ab-builder · .ab-pane
The canonical state of the builder while editing a Journey block: the rail shows the active section path (Journey, expanded, with the selected block highlighted), the pane shows the block’s tabs (Jobs to Do active, Actions count visible, Advanced reachable), and the body lists the block’s job cards — General job (free-text instruction) and Collect Key Fact (named slot the agent fills mid-conversation). Basic/Advanced lives in the rail footer; the per-block Planner toggle sits at the bottom of the pane.
Winery Example
OmnichannelVersion 3 ▾Unsaved changes
JS
◆
Want Secret Deals?
Advanced
Manage block-specific settings and actions to customise this part of the conversation.
More on Journey Blocks ↗
The person we are outreaching to is a previous customer. You should ask them how
they enjoyed their last wine purchase (mention the wine they purchased). Ask them
if they need help with any pairing suggestions or recipes.
Collect Key Fact
Wants info on secret specials
Ask the user if they want to know about any secret deals on the wines they have
enjoyed drinking.
/* The shell is a two-column grid — left rail + main pane. No
third column: the right side of the viewport is reserved for the
live testing chat bar, which lives outside the builder shell. */
.ab-builder {
display: grid;
grid-template-columns: 280px 1fr;
min-height: 780px;
}
/* Rail groups expand / collapse via [data-open]. The body uses
display:none rather than max-height so nothing flashes on first
paint. */
.ab-rail-group[data-open="false"] .ab-rail-group-body { display: none; }
.ab-rail-group[data-open="true"] .ab-rail-group-chev { transform: rotate(180deg); }
/* The active state of a block in the rail mirrors the selected
item in the pane head — accent-soft background, accent text. */
.ab-rail-block.is-active {
background: var(--accent-soft);
color: var(--accent);
}
/* Job-card tags telegraph job kind: General job (green dot) reads as
"instruction", Collect Key Fact (pink dot) reads as "named slot to
fill". Same anatomy underneath, different role. */
.ab-job-tag.is-general { background: rgba(15,128,98,0.12); color: #0F8062; }
.ab-job-tag.is-collect { background: var(--accent-soft); color: var(--accent); }
/* Mode toggle in the rail foot — the only non-section control that
lives in the rail. Two segments, ink-on-paper for the active. */
.ab-mode-toggle button.is-active {
background: var(--segmented-active-bg); color: var(--segmented-active-fg);
}
// Three small handlers, all idempotent and defensive:
// 1. rail-group chevrons (expand / collapse a group),
// 2. mode toggle (Basic ↔ Advanced),
// 3. single switch (Planner).
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('[data-rail-group-toggle]').forEach((btn) => {
btn.addEventListener('click', () => {
const group = btn.closest('.ab-rail-group');
if (!group) return;
const open = group.getAttribute('data-open') === 'true';
group.setAttribute('data-open', open ? 'false' : 'true');
btn.setAttribute('aria-expanded', open ? 'false' : 'true');
});
});
document.querySelectorAll('[data-mode-toggle]').forEach((root) => {
root.querySelectorAll('button').forEach((btn) => {
btn.addEventListener('click', () => {
root.querySelectorAll('button').forEach((b) =>
b.classList.toggle('is-active', b === btn)
);
});
});
});
document.querySelectorAll('[data-toggle]').forEach((btn) => {
btn.addEventListener('click', () => {
const on = btn.getAttribute('aria-pressed') === 'true';
btn.setAttribute('aria-pressed', on ? 'false' : 'true');
});
});
});
The agent-builder topbar — back · name + type chip · version + unsaved meta · trailing Share / Test / Save pills. Spans the full shell width (pass it to AgentBuilderShell’s head slot).
/* Ships in @magicblocksai/css (components/_shared.css, @surface: operator).
No chapter-private CSS — every .ab-* selector is in the package. */
// No wiring needed — the React component owns its interactivity
// (RailGroup collapse, PaneTabs roving tabindex, the shell's drag-resize).
// This static demo is a rendered snapshot.
The left rail — a scroll container for nested RailGroups, leaf RailItems, and the sortable Journey RailBlockItems (wrap them in the shipped SortableList for drag-reorder). Optional sticky foot — here a Basic / Advanced mode toggle.
/* Ships in @magicblocksai/css (components/_shared.css, @surface: operator).
No chapter-private CSS — every .ab-* selector is in the package. */
// No wiring needed — the React component owns its interactivity
// (RailGroup collapse, PaneTabs roving tabindex, the shell's drag-resize).
// This static demo is a rendered snapshot.
The main edit pane — a PaneHeader (glyph · title · badge · description · PaneTabs) over a scrollable body of JobCards, with a sticky Planner footer. Exposed as the shell’s compound .Pane.
◆Want Secret Deals?Advanced
Manage block-specific settings and actions to customise this part of the conversation. More on Journey Blocks ↗
General job
The person we are outreaching to is a previous customer. You should ask them how they enjoyed their last wine purchase.
Collect Key Fact
Wants info on secret specials
Ask the user if they want to know about any secret deals on the wines they have enjoyed drinking.
<main class="ab-pane"><header class="ab-pane-head"><button type="button" class="ab-pane-collapse" aria-label="Collapse rail">«</button><h2 class="ab-pane-title"><span class="ab-pane-title-glyph">◆</span>Want Secret Deals?<span class="chip chip-pink">Advanced</span></h2><p class="ab-pane-desc">Manage block-specific settings and actions to customise this part of the conversation. <a href="#">More on Journey Blocks ↗</a></p><div role="tablist" class="ab-pane-tabs"><button type="button" role="tab" aria-selected="true" tabindex="0" class="ab-pane-tab is-active"><span aria-hidden="true">≡</span>Jobs to Do</button><button type="button" role="tab" aria-selected="false" tabindex="-1" class="ab-pane-tab"><span aria-hidden="true">⚡</span>Actions<span class="ab-pane-tab-count">1</span></button><button type="button" role="tab" aria-selected="false" tabindex="-1" class="ab-pane-tab"><span aria-hidden="true">✦</span>Advanced</button></div></header><div class="ab-pane-body"><article class="ab-job"><span class="ab-job-handle" aria-hidden="true"></span><div class="ab-job-body"><span class="ab-job-tag is-general">General job</span><p class="ab-job-text">The person we are outreaching to is a previous customer. You should ask them how they enjoyed their last wine purchase.</p></div><button type="button" class="ab-job-menu" aria-label="Job menu">⋯</button></article><article class="ab-job"><span class="ab-job-handle" aria-hidden="true"></span><div class="ab-job-body"><span class="ab-job-tag is-collect">Collect Key Fact</span><h4 class="ab-job-title">Wants info on secret specials</h4><p class="ab-job-text">Ask the user if they want to know about any secret deals on the wines they have enjoyed drinking.</p></div><button type="button" class="ab-job-menu" aria-label="Job menu">⋯</button></article></div><footer class="ab-pane-foot"><label class="switch" aria-label="Planner"><input type="checkbox"/><span class="switch-track"><span class="switch-thumb"></span></span></label><span>Planner</span></footer></main>
/* Ships in @magicblocksai/css (components/_shared.css, @surface: operator).
No chapter-private CSS — every .ab-* selector is in the package. */
// No wiring needed — the React component owns its interactivity
// (RailGroup collapse, PaneTabs roving tabindex, the shell's drag-resize).
// This static demo is a rendered snapshot.
import { AgentBuilderShell, PaneHeader, PaneTabs, PaneTab, JobCard, SortableList, Switch } from '@magicblocksai/ui';
<AgentBuilderShell.Pane
head={
<PaneHeader glyph="◆" title="Want Secret Deals?"
badge={<span className="chip chip-pink">Advanced</span>}
description={<>Manage block-specific settings and actions. <a href="#">More on Journey Blocks ↗</a></>}
onCollapse={collapseRail}
tabs={
<PaneTabs defaultValue="jobs">
<PaneTab value="jobs" icon="≡">Jobs to Do</PaneTab>
<PaneTab value="actions" icon="⚡" count={1}>Actions</PaneTab>
<PaneTab value="advanced" icon="✦">Advanced</PaneTab>
</PaneTabs>
}
/>
}
foot={<><Switch aria-label="Planner" /> Planner</>}
>
{/* Jobs reorder within the tab — whole-row drag via the shipped SortableList. */}
<SortableList items={jobs} rowId={(j) => j.id} onReorder={setJobs}
renderRow={(j) => (
<JobCard kind={j.kind} title={j.title}>{j.text}</JobCard>
)} />
</AgentBuilderShell.Pane>
18.8 Agents list composition
The agents HQ — the list that sits before any single-agent build session (18.7). + New agent opens the template gallery (18.21). Header carries a 4-tile KPI strip (conversations, qualified leads, meetings booked, average reply time). Below: status filter tabs, then a stack of rich .ag-card rows. Each card surfaces avatar, title + tag, role copy, version + last-update meta, channels icon strip, a per-agent sparkline + counts (last 7 days), status pill (Live / Paused / Unpublished / Draft), and trailing actions (chat / share / Open).
Agents HQ — 5 agents · 1,038 conversations across the live ones
.list-screen.ag-list · .ag-card
A workspace at a glance. The KPI strip shows performance across all live agents. The filter row lets the operator drill into a status (All / Live / Draft / Paused / Unpublished). Each agent card is dense by design: in one row an operator can read the agent's identity, its channels, its last-7-days throughput, its current status, and jump straight in. Built on the same list-screen shell as 19.7 sessions and 14.15 contacts; the cards are chapter-private (.ag-*) and reuse .kpi-delta-tile (7.27) for the top strip and .badge + .dot (7.4) for the status pills.
Agents
Your AI sales reps. Each one replies in under a minute — across chat, SMS, email, DMs, and CRM.
The pane that replaces Try my Agent as the first state an operator lands on when opening an agent. Covers the same onboarding ground (run a test chat · connect a channel · take a 2-min tour) but inside a richer dashboard that grows up with the agent — KPI strips that fill in as data arrives, goal trackers, channel-connect rail, conversations-over-time chart, a live conversations strip, plus guardrails + missing-knowledge surfaces. Empty by design on day one (every number is 0, every chart is a frame), so the same composition reads from minute one through year one without a separate empty-state screen.
Agent overview — first state after creation
.ab-overview
All numbers zero, all charts blank, all conversations empty. The operator's next move is signposted by the three hero pills (Run a test chat / Connect a channel / 2-min tour) and the rail of channels each showing a Connect button. Hero headline uses the kit's display-with-serif-italic-emphasis pattern (same as .hero at 9.2). KPI tiles use the kit's mono caption + display value + delta caption shape (chapter-private chrome here because the 5+4 split-strip needs its own grid). Every section panel is a single primitive (.ab-ov-card) so consumers building their own subsets compose horizontally.
New agent — v1 draft
Your new agent is ready to meet your first lead.
Three things to do before you publish. Each takes a minute.
Visit0Web Chat only−100% vs last week
Outreach0First message sent−100% vs last week
Engaged0Replied at least once−100% vs last week
Worked lead0Engaged + qualified—
Lead handover0Handed to a human—
% Worked lead rate—vs engaged
% Lead handover—vs worked
Opt-out—per week
Fail / Error—Bounces, blocked, …
Goals
· last 7 days
Qualify 500 leads0 / 5000%
Book 80 meetings / week0 / 800%
< 60s avg reply time——
< 5% opt-out rate——
Where this agent is live
Web Chatapp.thebusiness.com.au
SMS+61 480 022 801
Emailsales@thebusiness.com.au
API / WebhookHighLevel · Pipedrive
WhatsAppWhatsApp Business
Instagram DM@thebusiness · 79.2k
VoiceVoice · private beta
Conversations over time
· daily volume by channel · last 14 days
WebSMSEmailAPI
Waiting for first conversation…
Will appear here as soon as your agent starts talking.
No conversations yet. Run a test chat or connect a channel.
Guardrails
✓ All clear
On-messageStays on persona & playbook
No promisesNo prices, dates or guarantees we can't keep
TCPA quiet hours9am — 9pm AEST
Brand safeNo banned words, no rivals named
Missing knowledge
Questions your agent couldn't answer.
Nothing missing yet — your agent hasn't taken a real conversation.
<!-- The overview composes 6 chapter-private blocks under one
.ab-overview wrapper. Every block is independently
reorderable / removable — the only structural rule is the
hero comes first. -->
<div class="ab-overview">
<header class="ab-ov-hero">
<span class="ab-ov-hero-eyebrow">New agent — v1 draft</span>
<h2 class="ab-ov-hero-title">
Your new agent is <em>ready to meet</em> your first lead.
</h2>
<p class="ab-ov-hero-sub">Three things to do before you publish.</p>
<div class="ab-ov-hero-actions">
<button class="ab-ov-hero-pill is-primary">Run a test chat</button>
<button class="ab-ov-hero-pill">Connect a channel</button>
<button class="ab-ov-hero-pill">2-min tour</button>
</div>
</header>
<div class="ab-ov-kpis ab-ov-kpis-primary">
<div class="ab-ov-kpi">
<span class="ab-ov-kpi-label">Visit</span>
<span class="ab-ov-kpi-value">0</span>
<span class="ab-ov-kpi-sub">Web Chat only</span>
<span class="ab-ov-kpi-delta is-down">−100% vs last week</span>
</div>
…4 more tiles
</div>
<div class="ab-ov-kpis ab-ov-kpis-secondary">…4 rate tiles…</div>
<div class="ab-ov-row-2">
<section class="ab-ov-card">…Goals…</section>
<section class="ab-ov-card">…Where this agent is live…</section>
</div>
<section class="ab-ov-card">…Conversations chart…</section>
<section class="ab-ov-card">…Conversations live + empty state…</section>
<div class="ab-ov-row-2">
<section class="ab-ov-card">…Guardrails…</section>
<section class="ab-ov-card">…Missing knowledge…</section>
</div>
</div>
/* All Overview chrome is chapter-private (.ab-ov-*) — promote to
_shared.css once a second consumer needs the same shape.
Hero: gradient wash + display headline w/ serif italic emphasis
on a span (.ab-ov-hero-title em). Three pills; .is-primary uses
--success-text (the "go live" colour) not --accent.
KPI strips: 5 / 4 columns desktop, 2 / 2 columns ≤ 980px.
Tiles separated by 1px hair-soft vertical rule (becomes
horizontal on mobile). Empty state uses 0 / — for values.
Cards: .ab-ov-card is the surface primitive — paper bg, hair
border, r-lg radius, gap-3 flex column. Used 4 times. */
/* PROVISIONAL — composes existing kit primitives + the chapter-
private .ab-ov-* chrome. A future wrapper
could codify this shape; today it lives inline so the design
can flex per agent type (Website vs Omnichannel changes which
channels are listed; SMS-only changes the KPI labels).
Existing kit exports used: StatTile (KPI tiles), Button. */
import {
Button,
AgentChannelIcon,
MessageSquareIcon,
SmartphoneIcon,
MailIcon,
PhoneIcon,
GlobeIcon,
} from "@magicblocksai/ui";
export function AgentOverview({ agent, kpis, goals, channels }) {
return (
<div className="ab-overview">
<header className="ab-ov-hero">
<span className="ab-ov-hero-eyebrow">
{agent.name} — v{agent.version} {agent.status}
</span>
<h2 className="ab-ov-hero-title">
Your new agent is <em>ready to meet</em> your first lead.
</h2>
<p className="ab-ov-hero-sub">
Three things to do before you publish. Each takes a minute.
</p>
<div className="ab-ov-hero-actions">
<button className="ab-ov-hero-pill is-primary">Run a test chat</button>
<button className="ab-ov-hero-pill">Connect a channel</button>
<button className="ab-ov-hero-pill">2-min tour</button>
</div>
</header>
<KpiStripPrimary kpis={kpis.primary} />
<KpiStripSecondary kpis={kpis.secondary} />
<div className="ab-ov-row-2">
<GoalsCard goals={goals} />
<ChannelsCard channels={channels} />
</div>
<ConversationsChartCard />
<ConversationsLiveCard />
<div className="ab-ov-row-2">
<GuardrailsCard />
<MissingKnowledgeCard />
</div>
</div>
);
}
Overview — the live agent’s home
.ab-overview · .golive-card · .recent-changes
The same composition once the agent is live, redesigned as the agent’s home: a status band that answers “is it okay?” in one sentence with a SetupProgressChip, the GoLiveCard deep-linking into the Channels tab, the identity card (persona + role, with full editing one level up on the Personas shelf), a four-tile KPI strip, the “Questions it couldn’t answer” queue with Teach it, the Guardrails glance, and RecentChangesList where Sage edits carry an Undo. Live agents open here by default; unpublished agents open on Journey.
Live · website agent
Live on crefco.com — 38 conversations this week.
Greeting → Discovery → Handoff · 5 blocks · last published 2 days ago
Setup 5 of 6 — connect a phone number
Where this agent talks
Website widgetLive on crefco.com
Phone & SMSNot connected yet
Sounds like
Battery Persona · v3 · creativity 0.6
“Warm, concise, and professional — ends every message with a cheer.”
Role: B2B lead qualifier for inbound website visitors.
Conversations
312
↑38 this week
Leads captured
47
↑15% conversion
Goal value
$1,940
↑vs $1,210 prev. week
Handed to a human
6%
18 conversations
Questions it couldn’t answer
3 new
“Do you do jumbo loans?”Asked twice this week
“What are your closing costs?”Asked yesterday
Guardrails
✓ Monitor on
CREFCO policy+ 2 extra rules on this agent
Recent changes
You added the key fact budget to Discoveryyesterday
The first screen under General — where the agent gets its identity, role copy, and voice. Two states demoed: the page form (the canonical edit surface), and the edit dialog the operator opens via the pencil to manage the underlying persona record (which is independently versioned and can be reused across agents). Both states share the same .ab-form-* primitives so a future operator-form library could promote them together.
Persona page form
.ab-pane-form
Composed inside the builder pane (the shell + rail context is covered at 18.7). Three fields: agent name (required, short), role description (textarea with character count, optional), and the persona picker (select of saved personas + Create-new link + edit pencil to open the dialog). The picker is followed by a read-only summary card showing the active persona's version, description, and a fade-truncated prompt preview.
Persona
Basic?
158 / 700
Version 1 · 08 Mar 2026Creativity 0.3
Created from website https://www.tyrnells.com.au by the MagicBlocks Wizard.
You work for Tyrnell, the latest community family-owned winery in the Hunter Valley. Your role is to guide customers in selecting and purchasing premium wines while promoting the exclusive membership benefits we offer. You should respond with a friendly and direct tone, ensuring every interaction conveys trustworthiness and warmth. Use key phrases like ‘Experience the legacy’ and ‘Join the wine journey’ naturally within your responses to encourage engagement. Aim to evoke a warm and inviting atmosphere, suggesting wines that bring back family memories and create a sense of belonging…
<div class="ab-pane-form" role="region" aria-label="Persona configuration">
<div class="ab-form-title-row">
<h2>Persona</h2>
<span class="ab-mode-chip">Basic</span>
<span class="ab-form-help" title="What this screen does">?</span>
</div>
<div class="ab-form-field">
<label class="ab-form-label" for="persona-name">
Agent name <span class="ab-form-req">*</span>
</label>
<input id="persona-name" class="ab-form-input" type="text"
value="Charlie">
</div>
<div class="ab-form-field">
<label class="ab-form-label">Describe the role of the agent in the company</label>
<div class="ab-form-input-row">
<textarea class="ab-form-input" rows="3">…</textarea>
<span class="ab-form-count">158 / 700</span>
</div>
</div>
<div class="ab-form-field">
<label class="ab-form-label">Choose agent persona</label>
<div class="ab-set-picker">
<select class="ab-form-input">…personas</select>
<button class="ab-set-create">+ Create new</button>
<button class="ab-set-edit">…pencil SVG</button>
</div>
<div class="ab-set-card">
<div class="ab-set-card-meta">Version 1 · 08 Mar 2026 · Creativity 0.3</div>
<p class="ab-set-card-desc">Created from website…</p>
<p class="ab-set-card-prompt">You work for Tyrnell…</p>
</div>
</div>
</div>
/* Pane form — bounded, paper bg, top-aligned. Three field shapes:
.ab-form-field vertical stack (label + control + count)
.ab-set-picker 3-col grid (select + create + edit)
(shared library-picker family — same shape
backs Guardrails 18.13, Knowledge 18.14,
Design & Go Live appearance 18.17)
.ab-set-card read-only summary surface (warm bg)
.ab-set-card-prompt fade-truncated long-text preview (chapter-
private to 18.10's persona-prompt slot)
Field input: hair-bordered, accent-soft focus ring (kit standard).
Character count: absolutely-positioned bottom-right of the .ab-form-
input-row wrapper so it overlays the textarea/input without
reflowing.
The select gets a custom dropdown caret via background-image (two
triangles forming a downward chevron) because native select arrows
vary wildly across OS chrome. */
import { Input, Textarea, Select, Button, PenIcon } from "@magicblocksai/ui";
/* PROVISIONAL — the agent-builder form layout chrome lives chapter-
private (.ab-pane-form / .ab-form-*) because the field set is
stable but the shell wrapper varies per screen (Persona, Key Facts,
Actions all reuse this layout). Promote to kit once a second
consumer needs the same form-pane primitive. */
export function PersonaPage({ agent, personas, onEdit, onCreate }) {
return (
<div className="ab-pane-form">
<header className="ab-form-title-row">
<h2>Persona</h2>
<span className="ab-mode-chip">Basic</span>
</header>
<div className="ab-form-field">
<label htmlFor="persona-name">Agent name *</label>
<Input id="persona-name" defaultValue={agent.name} />
</div>
<div className="ab-form-field">
<label>Describe the role of the agent in the company</label>
<Textarea
defaultValue={agent.role}
maxLength={700}
showCount
/>
</div>
<div className="ab-form-field">
<label>Choose agent persona</label>
<div className="ab-set-picker">
<Select value={agent.personaId} options={personas} />
<Button variant="link" onClick={onCreate}>+ Create new</Button>
<Button variant="ghost" icon onClick={onEdit}><PenIcon /></Button>
</div>
<PersonaSummaryCard persona={agent.persona} />
</div>
</div>
);
}
Persona edit dialog — New custom persona / Edit
.ab-dialog
The modal an operator opens via the pencil on the picker. Personas are versioned + reusable, so the dialog manages the underlying record — the agent above just references it by id. Two-column top row (name + description), version dropdown + creativity slider, tag chip-input, then the long-form persona prompt. Demoed overlaid on the parent pane to read the actual chrome relationship.
/* The dialog overlay is absolutely-positioned inside its
demo-stage so the modal frames the parent pane (you can see
the dimmed page behind it). In real consumer code, the
overlay would mount via the kit's <Modal> portal — which
pins to body, traps focus, and locks scroll. The CSS shape
is identical; only the mount point differs. */
The second screen under General — where the operator declares which details the agent should extract from each conversation (name, preferences, budget, contact info). In the redesigned studio each fact’s asked-vs-listened behaviour reads as a sentence — see KeyFactBehaviourLine (18.20). Three states demoed: the list page with sortable fact cards + Privacy advisory, the “Create a new key fact” chooser dialog (template vs custom), and the Edit key fact dialog. Cards reuse the chapter's shared initSortableList() drag-and-drop helper (also used by journey blocks at 18.7). The dialog's Tab 2 — the Ask-when condition builder — ships with 18.12.
Key Facts list — sortable cards + Privacy advisory
.ab-pane-form.is-list · .ab-fact-card
Operator scans the list, drags cards to reorder (try Cmd/Ctrl + Up/Down on a focused card for keyboard reorder), clicks the 3-dot menu for inline actions, and uses “+ New” for the chooser dialog below. The yellow Privacy & Safety advisory at the bottom is a permanent guard against operators wiring up sensitive-data extraction.
Key Facts
Basic
Key facts are details you collect from users — like their name, preferences, or contact info. These can be saved to user profiles, sent to your CRM, or used to guide the conversation. More on key facts ›
Shared key facts listTotal: 2
Wine PreferencesAsk about their preferred type of wine.
Wants Info on Secret SpecialsAsk the user if they want to know about any secret deals on the wines they have enjoyed drinking.
For Privacy & Safety, avoid requesting sensitive information like passwords, bank details, health records, PINs, or other private data.
/* List page chrome (chapter-private):
.ab-pane-form.is-list widens the form pane to 880px
.ab-list-toolbar 3-col grid (search / filter / new)
.ab-list-search rounded pill input with leading icon
.ab-list-new accent-green primary button
.ab-list-count total counter strip (mono caption)
.ab-fact-list vertical stack of sortable cards
.ab-fact-card handle / glyph / body / menu
.ab-fact-card.is-dragging / .is-drop-before / .is-drop-after
sortable visual states — matches the
same contract used by journey blocks
so initSortableList() is reusable.
.ab-privacy-advisory warning-soft pinned strip at bottom. */
// Sortable list is wired generically by the chapter's
// initSortableList(list, { itemSelector }) helper. Same helper
// powers journey blocks at 18.7 — second consumer of the contract
// confirms the pattern is reusable across the builder.
document.querySelectorAll('.ab-fact-list').forEach((list) => {
initSortableList(list, { itemSelector: '.ab-fact-card' });
});
// Drag-and-drop: click + hold the card, drop above/below
// a target — accent rail indicates landing
// Keyboard reorder: focus a card, Cmd/Ctrl + ArrowUp/Down
// moves it one position
// Visual state classes: .is-dragging / .is-drop-before /
// .is-drop-after (applied by the helper,
// styled by the chapter CSS).
import {
SortableList,
Input,
Button,
Alert,
PlusIcon,
FilterIcon,
SearchIcon,
} from "@magicblocksai/ui";
export function KeyFactsPage({ facts, onReorder, onCreate }) {
return (
<div className="ab-pane-form is-list">
<header className="ab-form-title-row">
<h2>Key Facts</h2>
<span className="ab-mode-chip">Basic</span>
</header>
<p>Key facts are details you collect from users…</p>
<div className="ab-list-toolbar">
<Input
type="search"
placeholder="Search key facts by name only"
icon={<SearchIcon />}
shape="pill"
/>
<Button variant="ghost" icon aria-label="Filter"><FilterIcon /></Button>
<Button tone="success" icon={<PlusIcon />} onClick={onCreate}>
New
</Button>
</div>
{/* SortableList uses the kit's useSortable hook (chapter
1.3.0) — same shape as the inline init helper above. */}
<SortableList items={facts} onReorder={onReorder} className="ab-fact-list">
{(fact, dragHandleProps) => (
<FactCard fact={fact} dragHandleProps={dragHandleProps} />
)}
</SortableList>
<Alert tone="warning" className="ab-privacy-advisory">
<strong>For Privacy & Safety,</strong> avoid requesting sensitive
information like passwords, bank details, health records, PINs,
or other private data.
</Alert>
</div>
);
}
Create a new key fact — template or custom
.ab-chooser-grid
Opens via the “+ New” button. Two illustrated paths: pick a prebuilt template (name / email / phone / budget) or start a custom fact from scratch. The “Recently used templates” section below is empty on day one, populates as the operator drops in templates. Picking either path opens the Edit dialog (Demo 3).
Create a new key fact
Choose how you'd like to create your Key Fact. Select from a template, start fresh with a custom one, or modify a recently created fact.
Recently used templates (0)
No recently used templates yet
Start creating Key Facts, and the templates you use will appear here for quick access.
<div class="ab-dialog-overlay" role="dialog">
<div class="ab-dialog" style="max-width: 540px;">
<header class="ab-dialog-head">
<span class="ab-dialog-title">Create a new key fact</span>
<button class="ab-dialog-close">×</button>
</header>
<div class="ab-dialog-body">
<p>Choose how you'd like to create your Key Fact…</p>
<div class="ab-chooser-grid">
<button class="ab-chooser-card">
<span class="ab-chooser-illus"><svg>…magnifier+books</svg></span>
<span class="ab-chooser-title">Choose from template</span>
<span class="ab-chooser-sub">Use a predefined format…</span>
</button>
<button class="ab-chooser-card">
<span class="ab-chooser-illus"><svg>…book</svg></span>
<span class="ab-chooser-title">Create custom key fact</span>
<span class="ab-chooser-sub">Start from scratch…</span>
</button>
</div>
<div class="ab-chooser-recent">
<h4>Recently used templates (0)</h4>
<div class="ab-chooser-recent-empty">
<svg>…empty-state stacked books</svg>
<p>No recently used templates yet</p>
<span>Start creating Key Facts…</span>
</div>
</div>
</div>
</div>
</div>
/* Chooser dialog uses the same .ab-dialog primitives as the Persona
edit dialog at 18.10. The body content is bespoke:
.ab-chooser-grid 2-col grid of large illustrated cards
.ab-chooser-card clickable surface; hover → accent-soft fill
.ab-chooser-illus 72×72 SVG slot (success-text tinted)
.ab-chooser-recent-empty centered "no recent" illustration block.
Card padding stays loose to communicate the "this is a meaningful
choice" weight — these are the entry points for everything that
follows in the agent's key-fact configuration. */
import { Modal, Button } from "@magicblocksai/ui";
export function CreateKeyFactChooser({ open, onClose, onPick, recent }) {
return (
<Modal open={open} onOpenChange={onClose} title="Create a new key fact">
<p>Choose how you'd like to create your Key Fact…</p>
<div className="ab-chooser-grid">
<button className="ab-chooser-card" onClick={() => onPick('template')}>
<span className="ab-chooser-illus"><ChooserMagnifierBooks /></span>
<span className="ab-chooser-title">Choose from template</span>
<span className="ab-chooser-sub">Use a predefined format and customize as needed.</span>
</button>
<button className="ab-chooser-card" onClick={() => onPick('custom')}>
<span className="ab-chooser-illus"><ChooserBook /></span>
<span className="ab-chooser-title">Create custom key fact</span>
<span className="ab-chooser-sub">Start from scratch and define your own key fact.</span>
</button>
</div>
<RecentTemplatesSection items={recent} />
</Modal>
);
}
The dialog the operator opens to configure a single fact. Tab 1 (shown) holds the structural setup — type / source / name / token / ask-text / listen-text / option values. Tab 2 (Ask when) wires the conditional logic that decides whether the agent should ask this fact in a given conversation; that tab ships with 18.12 because it shares the Conditions editor with global Actions.
Edit key fact
Updates will take effect across this agentChanges will reflect and override all Journey Blocks using this key fact, including the current one in the shared list.
16 / 30
39 / 400
161 / 400
<div class="ab-dialog-overlay" role="dialog">
<div class="ab-dialog">
<header class="ab-dialog-head">
<span class="ab-dialog-title">Edit key fact</span>
<button class="ab-dialog-close">×</button>
</header>
<div class="ab-dialog-body">
<div class="ab-warning-banner">
<svg>…triangle</svg>
<div>
<b>Updates will take effect across this agent</b>
<span>Changes will reflect…</span>
</div>
</div>
<div class="ab-dialog-row-2">
<div class="ab-form-field">Type of information</div>
<div class="ab-form-field">Who am I listening to</div>
</div>
<div class="ab-dialog-row-2">
<div class="ab-form-field">Fact name + count</div>
<div class="ab-form-field">Token + copy button</div>
</div>
<label class="ab-form-check">
<input type="checkbox" checked> Listen across all blocks
</label>
<div class="ab-form-field">Ask textarea + count</div>
<div class="ab-form-field">Listen textarea + count</div>
</div>
<footer class="ab-dialog-foot">
<button class="ab-h-pill">Cancel</button>
<button class="ab-h-pill is-primary">Update Fact</button>
</footer>
</div>
</div>
/* Same .ab-dialog / .ab-dialog-row-2 / .ab-form-* primitives as
the Persona dialog (18.10). New chrome for this screen:
.ab-warning-banner yellow advisory at top — operator changes
cascade across every journey block
.ab-token-input input + copy-button grid — for the
machine-readable token slot
.ab-form-check inline checkbox row.
When Tab 2 (Ask-when) ships with 18.12, the dialog grows tabs
at the top of the body; the form chrome below stays identical. */
import {
Modal,
Tabs,
Select,
Input,
Textarea,
Checkbox,
Alert,
Button,
CopyButton,
} from "@magicblocksai/ui";
export function EditKeyFactDialog({ fact, open, onClose, onSave }) {
const [draft, setDraft] = useState(fact);
return (
<Modal open={open} onOpenChange={onClose} title="Edit key fact">
<Tabs defaultValue="setup" items={[
{ id: 'setup', label: 'Key fact setup' },
{ id: 'ask-when', label: 'Ask when condition' },
]} />
<Alert tone="warning">
<b>Updates will take effect across this agent.</b>
Changes will reflect and override all Journey Blocks using
this key fact.
</Alert>
<div className="ab-dialog-row-2">
<Field label="Type of information to collect *">
<Select value={draft.type} options={TYPE_OPTIONS} />
</Field>
<Field label="Who am I listening to for the key fact? *">
<Select value={draft.source} options={SOURCE_OPTIONS} />
</Field>
</div>
<div className="ab-dialog-row-2">
<Field label="Fact name *">
<Input value={draft.name} maxLength={30} showCount />
</Field>
<Field label="Key fact token *">
<Input value={draft.token} mono trailing={
<CopyButton value={draft.token} icon />
} />
</Field>
</div>
<Checkbox checked={draft.listenAcrossBlocks}
label="Listen for this fact across all journey blocks" />
<Field label="Ask the user *">
<Textarea value={draft.ask} maxLength={400} showCount rows={3} />
</Field>
<Field label="Listen for key fact *">
<Textarea value={draft.listen} maxLength={400} showCount rows={4} />
</Field>
<Modal.Foot>
<Button onClick={onClose}>Cancel</Button>
<Button tone="accent" onClick={() => onSave(draft)}>Update Fact</Button>
</Modal.Foot>
</Modal>
);
}
18.12 Actions
The third screen under General — the conditions+effects engine that drives every conversation. In the redesigned studio the agent-level set surfaces as Anytime actions — rules that watch every block (18.20). An action is a thing the agent does (switch journey block, send a message, add a button, hand off to a human, … fifteen types total). A condition is the trigger that decides when to fire (key fact known, user said X, AI sentiment was negative, no message for N minutes, … fifteen types total). The pane composes both inside a 3-step wizard: Action · Conditions · Goal.
Design tweaks vs the current product: action types render as a 4-col card grid (vs the radio list) so all fifteen read at once with icons; the step indicator is numbered with check glyphs for completed steps; conditions use the kit's already-shipped .qb shape (18.1 QueryBuilder) so the operator vocabulary stays identical across every conditions surface in the platform (Ask-when, agent actions, webhook event filters, audience filters).
Action wizard — Step 1 (Action + config)
.action-wizard · .action-picker · .action-config
Picking an action type now reveals its config panel below the grid — driven by the actionRegistry. Here Send Message is selected, showing the snippet-or-custom <MessageField>. Each type declares its config as data (a fields list) or points to a custom panel, so adding a new action is a single registry entry. All 15 types render as cards; the four Website-only types (Add Buttons / Add Forms / Add Calendar / Embed) show disabled with a “Website only” caption on this Omnichannel agent.
Only match once
Select an action
Send MessageSend a message to the end user — a saved snippet or custom text.
/* Promoted to @magicblocksai/css (operator surface) in v1.67.0:
.action-wizard 2-col shell (.action-list / .wizard-pane)
.wizard-steps numbered step indicator (.is-active / .is-done)
.action-picker registry-driven grid of .action-tile cards
(.is-selected accent fill · .is-disabled "Website only")
.action-detail selected-action description card
.action-config per-action config surface — stacks .field-row rows
.message-field Select | Custom snippet-or-custom editor
Full rules ship in _shared.css. */
import { useState } from "react";
import {
ActionWizard, ActionList, WizardSteps, ActionPicker,
ActionConfigPanel, ActionField, MessageField, Switch,
} from "@magicblocksai/ui";
// `actionRegistry` + the `Action` / `ActionFieldSpec` types are also kit exports.
// `actions` is the block's actions; `selectedId` the one being edited.
export function ActionStep({ actions, selectedId, onChange, onSelect, onAdd }) {
const action = actions.find((a) => a.id === selectedId)!;
const [collapsed] = useState(false);
return (
<ActionWizard
actionsList={
<ActionList
items={actions.map((a) => ({
id: a.id,
name: a.name,
scope: KIND_LABELS[a.type], // kind chip
meta: a.conditions.length === 0 // NEW — trigger summary
? "Fires every turn"
: `${a.conditions.length} condition${a.conditions.length === 1 ? "" : "s"}`,
}))}
selectedId={selectedId} onSelect={onSelect} onAddAction={onAdd} />
}
steps={
<WizardSteps onStepChange={() => {}} steps={[
{ id: "action", label: "Action", status: "active" },
{ id: "conditions", label: "Conditions", count: 1, status: "todo" },
{ id: "goal", label: "Goal", status: "todo" },
]} />
}
toolbar={<label className="ab-only-once">Only match once <Switch defaultChecked /></label>}
>
{/* Pick a type → the registry seeds a new action of that type */}
<ActionPicker value={action.type} channel="chat"
onChange={(type) => onChange(actionRegistry[type].makeDefault(action.id))} />
{/* …and its config reveals below — declarative fields (rendered via
<ActionField> → <MessageField> etc.) or a custom panel. */}
<ActionConfigPanel action={action} onChange={onChange}
context={{ snippets: savedSnippets }} />
</ActionWizard>
);
}
Add Calendar — custom config panel
.calendar-config · <FieldMapper>
The complex exemplar. When Add Calendar is picked, the registry points to a custom <CalendarActionConfig> panel (the escape hatch) rather than declarative fields. It composes <Select> (provider + embed type), <Input> (account / event), and <FieldMapper> for the MagicBlocks→Calendly form-field auto-population — with Name / Email always mapped (locked rows).
Calendar form auto-population
Quickly prefilled into your calendar’s form fields.
Name→Name
Email→Email
Phone (keyfact)→[form_field_3]
<div class="calendar-config">
<div class="field-row">
<label class="field-label">Select a calendar</label>
<select class="...">Calendly / Google / HighLevel / HubSpot</select>
</div>
<!-- Calendly: embed type · account · event ID -->
<div class="calendar-autofill">
<p class="field-label">Calendar form auto-population</p>
<div class="calendar-autofill-locked">Name → Name</div> <!-- locked -->
<div class="calendar-autofill-locked">Email → Email</div> <!-- locked -->
<!-- <FieldMapper> renders the editable MagicBlocks→Calendly rows -->
</div>
</div>
/* operator surface (v1.67.0):
.calendar-config stacked field rows
.calendar-autofill the form-field mapper section
.calendar-autofill-locked Name/Email always-mapped rows
Editable rows are <FieldMapper> (chapter 18.3 / .field-mapper). */
import { CalendarActionConfig } from "@magicblocksai/ui";
// Registered as the Add Calendar action's custom `Panel`;
// renders it automatically when `action.type === "add_calendar"`.
export function CalendarStep({ action, onChange, contactFields, calendlyFields }) {
return (
<CalendarActionConfig
action={action}
onChange={onChange}
context={{
calendarSourceFields: contactFields, // MagicBlocks fields (left)
calendlyFields, // [form_field_N] slots (right)
}}
/>
);
}
Switch Journey & Human Takeover — conditional panels
.action-config-stack · .action-config-section
Two more custom Panels whose fields cascade. Switch Journey picks a target block (or “Current Block”), optionally switches channel — revealing a channel and, for SMS, a sender picker — and shows a follow-up action only while staying on the current block. Human Takeover reveals a Slack workspace + channel cascade when its follow-up is set to Slack, then gates an in-chat transfer message and an out-of-hours message behind a staff-availability profile.
Switch Journey
Follow-up action
Runs after the switch when staying on the current block.
Human Takeover
Transferred to Staff Message
Outside Staff Availability Message
<!-- Switch Journey — block/channel cascade + conditional follow-up -->
<div class="action-config-stack">
<div class="field-row"><label class="field-label">Select Journey Block</label> <select>…</select></div>
<div class="field-row field-row-switch"><label class="switch">…</label> Switch Channel</div>
<!-- channel + sender selects appear when Switch Channel is on -->
<div class="action-config-section"> <!-- follow-up: only on Current Block -->
<select>Send message / Auto</select>
<div class="message-field">…</div>
</div>
</div>
/* operator surface (v1.68.0):
.action-config-stack vertical field stack (shared by every Phase-2 panel)
.action-config-section grouped sub-cascade (follow-up / Slack block) */
import { SwitchJourneyActionConfig, HumanTakeoverActionConfig } from "@magicblocksai/ui";
// Both are registered as their action type's custom `Panel`; <ActionConfigPanel>
// renders them automatically. Shown standalone here for clarity.
export function FlowPanels({ action, onChange, ctx }) {
return action.type === "switch_journey" ? (
<SwitchJourneyActionConfig
action={action}
onChange={onChange}
context={{ journeyBlocks: ctx.blocks, switchChannels: ctx.channels, smsSenders: ctx.senders, snippets: ctx.snippets }}
/>
) : (
<HumanTakeoverActionConfig
action={action}
onChange={onChange}
context={{ slackConnections: ctx.slack, availabilityProfiles: ctx.profiles, snippets: ctx.snippets }}
/>
);
}
Add Buttons, Add Forms & declarative actions
.action-button-rows · <ActionConfigPanel>
Add Buttons brackets a message with quick-reply buttons (label rows you add / remove). Add Forms picks a saved form and wires its before / submit / privacy messages. The simplest three — End Chat, Opt-Out and Embed — need no custom component: they declare fields in the registry and render through <ActionField> (here, Embed’s URL + height + two messages). Run Task graduated to its own panel in Phase 3 (next).
Add Buttons
Message before
Buttons
Quick-reply buttons shown with the message. Each can trigger its own action.
/* operator surface (v1.68.0):
.action-button-rows the button list
.action-button-row one row — label <input> (flex:1) + remove .icon-btn
Declarative actions (Embed/End Chat/Opt-Out/Run Task) reuse .field-row. */
import { ButtonsActionConfig, FormsActionConfig, ActionConfigPanel } from "@magicblocksai/ui";
// Buttons + Forms are custom panels; End Chat / Opt-Out / Run Task / Embed are
// declarative — ActionConfigPanel renders their `fields` with no bespoke code.
export function ActionConfig({ action, onChange, ctx }) {
if (action.type === "add_buttons")
return <ButtonsActionConfig action={action} onChange={onChange} context={{ snippets: ctx.snippets }} />;
if (action.type === "add_forms")
return <FormsActionConfig action={action} onChange={onChange} context={{ forms: ctx.forms, snippets: ctx.snippets }} />;
// Embed / End Chat / Opt-Out → declarative fields; Run Task → its own panel:
return <ActionConfigPanel action={action} onChange={onChange} context={{ snippets: ctx.snippets }} />;
}
Nested sub-actions & Run Task — recursion + prompt config
<SubActionField> · <RunTaskActionConfig>
The framework recurses: a quick-reply button (or a form submit) triggers its own action, edited by a restricted <ActionPicker> + a nested <ActionConfigPanel> — the allow-list excludes Add Buttons, so it always terminates. Run Task graduates to a panel: a saved-prompt / custom-instructions toggle plus a functions multi-select.
Add Buttons — a button with a nested action
Run Task
Instructions
Functions
Tools the task may call.
lookup_ordercrm_search
<!-- A button row that expands to a recursive sub-action editor -->
<div class="action-button-item">
<div class="action-button-row"><input value="Book a tasting"> <button>Edit action</button> <button class="icon-btn">🗑</button></div>
<div class="action-button-subaction">
<div class="sub-action-field">
<div class="action-picker">… restricted tiles (no Add Buttons) …</div>
<div class="sub-action-config"><!-- nested <ActionConfigPanel> -->…</div>
</div>
</div>
</div>
import { SubActionField, RunTaskActionConfig } from "@magicblocksai/ui";
// A button's nested action — SubActionField fronts a restricted ActionPicker
// with a recursive ActionConfigPanel (allow-list excludes add_buttons → terminates).
export function ButtonAction({ button, onChange, ctx }) {
return (
<SubActionField
value={button.action}
onChange={(action) => onChange({ ...button, action })}
idPrefix={button.id}
context={{ snippets: ctx.snippets }}
/>
);
}
// Run Task — saved prompt | custom instructions + a functions multi-select.
export function TaskStep({ action, onChange, ctx }) {
return (
<RunTaskActionConfig
action={action}
onChange={onChange}
context={{ prompts: ctx.prompts, functions: ctx.functions }}
/>
);
}
Action type picker — searchable dropdown
.action-opt · <ActionPicker layout="dropdown">
For a long (and growing) action set, <ActionPicker layout="dropdown"> swaps the 4-col grid for a searchable <Combobox> — type to filter, and each option reads as icon · name · one-line description (pulled from the action registry). The grid stays the default; off-channel types are omitted from the dropdown (the grid shows them disabled). Below: the descriptive option rows the popover renders.
Send MessageSend a message to the end user — a saved snippet or custom text.Switch JourneyMove the conversation to another journey block.Run TaskRun a saved prompt or call functions to do work mid-chat.Human TakeoverHand off to a human agent and pause the bot.
<div class="action-opt-demo" role="listbox" aria-label="Action type">
<span class="action-opt"><span class="action-opt-glyph" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"><circle cx="8" cy="8" r="5"></circle></svg></span><span class="action-opt-text"><span class="action-opt-name">Send Message</span><span class="action-opt-desc">Send a message to the end user — a saved snippet or custom text.</span></span></span>
<span class="action-opt"><span class="action-opt-glyph" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"><circle cx="8" cy="8" r="5"></circle></svg></span><span class="action-opt-text"><span class="action-opt-name">Switch Journey</span><span class="action-opt-desc">Move the conversation to another journey block.</span></span></span>
<span class="action-opt"><span class="action-opt-glyph" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"><circle cx="8" cy="8" r="5"></circle></svg></span><span class="action-opt-text"><span class="action-opt-name">Run Task</span><span class="action-opt-desc">Run a saved prompt or call functions to do work mid-chat.</span></span></span>
<span class="action-opt"><span class="action-opt-glyph" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"><circle cx="8" cy="8" r="5"></circle></svg></span><span class="action-opt-text"><span class="action-opt-name">Human Takeover</span><span class="action-opt-desc">Hand off to a human agent and pause the bot.</span></span></span>
</div>
/* The .action-opt option rows ship in @magicblocksai/css (@surface: operator).
<ActionPicker layout="dropdown"> renders them inside <Combobox>'s popover via
its renderOption — searchable, keyboard-navigable, viewport-aware. */
// No wiring — <ActionPicker layout="dropdown"> composes the kit's <Combobox>
// (search + roving keyboard nav + portal popover) out of the box.
import { useState } from 'react';
import { ActionPicker } from '@magicblocksai/ui';
// layout="dropdown" → a searchable <Combobox> instead of the 4-col grid.
// Each option reads icon · name · one-line description (from the registry);
// type to filter. The grid stays the default elsewhere.
const [type, setType] = useState('send_message');
<ActionPicker
layout="dropdown"
value={type}
onChange={setType}
channel="chat"
/>
Action wizard — Step 2 of 3 (Conditions)
.ab-cond-builder · uses .qb shape
Two condition rows ANDed together. The value editor changes shape by condition type: All Key Facts Found (Active Block) uses the Yes / No toggle (boolean comparator), No message received for uses the kit's number + unit duration shape from the 18.1 QueryBuilder. Step 1 carries a green tick in the indicator now that it's complete; Step 3 stays neutral. Every comparator listed at agents.md per condition-type is reachable from the same operator dropdown the kit's QueryBuilder already vocabularises — consistent surface across actions / ask-when / webhook filters.
Only match once
When should this action fire?
All conditions must be true (AND).
<div class="ab-wiz-body">
<div class="ab-cond-builder">
<!-- Each row: type select / comparator select / value editor / remove -->
<div class="ab-cond-row">
<select>All Key Facts Found (Active Block)</select>
<select>Equals</select>
<div class="ab-cond-row-yesno">
<button>No</button>
<button class="is-active">Yes</button>
</div>
<button class="ab-cond-row-remove">×</button>
</div>
<!-- Value editor swaps shape on type: number + unit for "No message
received for", text input for "User Message Contents", chips for
"One of", etc. Same shape vocabulary as the 18.1 QueryBuilder. -->
<div class="ab-cond-row">
<select>No message received from user for</select>
<select>in the last</select>
<div style="display: grid; grid-template-columns: 80px 1fr; gap: 6px;">
<input type="number" value="2">
<select><option>Minutes</option>…</select>
</div>
<button class="ab-cond-row-remove">×</button>
</div>
<button class="ab-cond-add">+ Add condition</button>
</div>
</div>
/* Conditions builder reuses the .ab-form-input chrome for selects +
inputs (same focus ring, same border treatment) and adds:
.ab-cond-builder gap-3 vertical stack
.ab-cond-row 4-col grid (type / comparator / value / remove)
140px on comparator (fits "does not equal")
minmax(0, 1fr) on type + value so they shrink
gracefully on narrow viewports
.ab-cond-row-yesno inline segmented control for boolean values
(Yes activates with success-text fill)
.ab-cond-row-remove ghost button → error-soft on hover
.ab-cond-add dashed-border accent button, fills on hover.
The 15 condition types vocabularise different value editors but the
row chrome is identical — same contract as the kit's QueryBuilder
at 18.1 so a single component eventually backs both. */
import { QueryBuilder } from "@magicblocksai/ui";
// The 15 condition types map to fields the kit's QueryBuilder already
// understands. Each declares its type (boolean, number, text, etc.)
// which drives the operator vocabulary + value editor shape.
const CONDITION_FIELDS = [
{ id: 'all_facts_found', label: 'All Key Facts Found (Active Block)',
type: 'boolean' },
{ id: 'key_fact', label: 'Key Fact',
type: 'enum', subFields: keyFactsForThisAgent },
{ id: 'user_msg_count', label: 'User message count (Active Block)',
type: 'number' },
{ id: 'user_msg_content', label: 'User Message Contents',
type: 'text' },
{ id: 'wants_human', label: 'Wants To Talk To Human',
type: 'boolean' },
{ id: 'sentiment', label: 'Sentiment',
type: 'enum', options: ['Positive','Negative','Neutral','Unknown'] },
{ id: 'no_msg_for', label: 'No message received from user for',
type: 'duration' },
{ id: 'custom', label: 'Custom Condition',
type: 'custom-prompt' },
// …7 more.
];
export function ConditionsStep({ conditions, onChange }) {
return (
<QueryBuilder
fields={CONDITION_FIELDS}
value={conditions}
onChange={onChange}
conjunction="AND"
addLabel="+ Add condition"
/>
);
}
// Same component also backs the Ask-when tab on Key Fact edit (18.11
// Tab 2), the webhook event-filter on Channels (18.16), and audience
// filters on Campaigns (14.17). One conditions vocabulary, one
// operator set, four consumers.
Action wizard — Step 3 of 3 (Goal)
<GoalStep>
The wizard’s final step: pick the goal this director works toward, and — once chosen — its monetary target. Step 2 (Conditions) is the kit’s <QueryBuilder> from 18.1, shown above — no new component needed.
The goal this director works toward.
$
<div class="action-config-stack">
<div class="field-row"><label class="field-label">Select a goal</label> <select>…</select></div>
<!-- value shows once a goal is picked -->
<div class="field-row"><label class="field-label">Goal value</label> <div class="input-group"><span class="input-affix">$</span><input type="number"></div></div>
</div>
/* Reuses the shared .action-config-stack + .field-row families.
The $ prefix is the kit's <Input prefix> (.input-group / .input-affix). */
import { GoalStep } from "@magicblocksai/ui";
// Step 3 of the agent-builder wizard. Conditions (Step 2) is <QueryBuilder>.
export function GoalStepDemo({ goal, setGoal, ctx }) {
return <GoalStep value={goal} onChange={setGoal} goals={ctx.goals} />;
}
Reference — all 15 action types: Auto · Switch Journey · Change stage · Change segment · End Chat · Send Message · Run Task · Add Buttons (Website) · Add Forms (Website) · Add Calendar (Website) · Add Link · Opt-Out · Embed (Website) · Human Takeover · Do not respond. All 15 condition types: Key Fact · All Key Facts Found (Active Block) · User message count (Active Block) · AI message count (Active Block) · User Message Contents · AI Message Contents · Wants To Talk To Human · User Asked Question · Slash Command · Form Field · Page URL · Sentiment · Custom Condition · User Intent — Wants to Opt-Out · No message received from user for.
18.13 Guardrails
The fourth screen under General. Per Jay's direction this screen moves to a library item — a GuardrailSet is now a reusable bundle of rules an operator can load into multiple agents (same pattern as Persona at 18.10). The agent page picks a set + layers five agent-level runtime controls on top — PII collection, Redaction, Rules monitor, Jailbreak prevention, Moderation. The rules themselves live inside the set and get edited via the dialog. Per-block Advanced overrides can later override any subset for a specific phase of the conversation.
Library separation: rules = library content (versioned, reusable, edited inside the set). Runtime controls = agent-scoped (configure how the agent enforces those rules at runtime). One conceptually clear edge between “what we won't do” (set) and “how we make sure of that” (controls).
Picker loads a saved GuardrailSet (currently Winery House Rules — Standard, reused by 3 agents). The summary card surfaces the set's shape without unrolling every rule. Below sits the agent-level controls list — the five safety layers that run at conversation time. PII + Rules monitor + Jailbreak are on by default for this agent; Redaction is off; Moderation uses the kit's built-in fallback.
Guardrails
Basic?
Pick a guardrail set from your library, or create a new one for this agent. The five controls below configure how the agent enforces those rules at runtime — reach for them when you need to tighten or relax safety per agent. More on guardrails ›
Version v3 · 12 May 202612 / 15 rules used↻ Shared with 3 agents
A 12-rule baseline for every Tyrnell-family agent — covers on-message constraints, responsible service of alcohol, no-promises pricing, and the “no recommending other wineries” brand boundary.
Runtime layers that filter or rewrite responses. Each one is agent-scoped — tighten or relax independently of the set above.
PII collection controlStop the agent from asking for sensitive info even if a Key Fact would otherwise prompt it.
RedactionStrip sensitive content from user messages before storage + downstream propagation.
Off
Rules monitorOutput verification layer — check every generated message against the set's rules; auto-rewrite or block violations.
On
Jailbreak preventionDetect prompt-injection / jailbreak attempts; respond with the configured fallback instead of complying.
ModerationWhen the moderation classifier flags a user message, respond with this fallback rather than generating a reply.
/* New chapter-private chrome for the library-loaded screen:
.ab-set-picker 3-col grid: select + Create-new + edit pencil
(shared library-picker family — same shape
backs Persona 18.10, Knowledge 18.14, and
Design & Go Live appearance 18.17).
.ab-set-card warm summary card under the picker
.is-shared meta highlights cross-agent reuse
.ab-control-list paper card with horizontal hair dividers
.ab-control-row 32px glyph / body / action grid
.ab-control-row.is-on glyph adopts accent fill
.ab-control-row-status pill (Off / On)
.ab-pill-toggle 42×24 segmented toggle (cousin of .ab-only-once-
toggle but bigger, label-pairing-friendly). */
import {
Input,
Select,
Button,
Switch,
PenIcon,
ShieldIcon,
} from "@magicblocksai/ui";
export function GuardrailsPage({ agent, guardrailSets, onEditSet }) {
return (
<div className="ab-pane-form">
<header className="ab-form-title-row">
<h2>Guardrails</h2>
<span className="ab-mode-chip">Basic</span>
</header>
<p>Pick a guardrail set from your library…</p>
{/* Library picker — same shape as Persona picker (18.10) */}
<Field label="Choose guardrail set">
<div className="ab-set-picker">
<Select value={agent.guardrailSetId} options={guardrailSets} />
<Button variant="link" onClick={onCreateSet}>+ Create new</Button>
<Button variant="ghost" icon onClick={onEditSet}><PenIcon /></Button>
</div>
<GuardrailSetSummaryCard set={agent.guardrailSet} />
</Field>
{/* Agent-level runtime controls — 5 rows, each with its own
configured value alongside an On/Off pill toggle. */}
<Field label="Agent-level controls">
<ControlList>
<ControlRow icon={<LockIcon />} name="PII collection control"
sub="Stop the agent from asking for sensitive info">
<Select value={agent.piiBlocked} options={PII_OPTIONS} />
</ControlRow>
<ControlRow icon={<BlackoutIcon />} name="Redaction"
sub="Strip sensitive content before storage">
<Switch checked={agent.redactionEnabled} />
</ControlRow>
<ControlRow on icon={<ShieldIcon />} name="Rules monitor"
sub="Output verification layer">
<Switch checked={agent.rulesMonitorEnabled} />
</ControlRow>
{/* …Jailbreak / Moderation rows… */}
</ControlList>
</Field>
</div>
);
}
Edit guardrail set — library record (sortable rules)
.ab-dialog · .ab-rule-list (sortable)
The dialog the operator opens via the pencil. This edits the library record — changes here flow to every agent that uses this set. Rules drag-reorder via the same shared initSortableList() helper (third consumer now — journey blocks, fact cards, rules — the helper's shape is locked). Try Cmd/Ctrl + Up/Down on a focused rule for keyboard reorder.
Edit guardrail set
This set is used by 3 agentsChanges will roll out to every agent using Winery House Rules — Standard the next time they reload.
winery RSA brand
Don't recommend other wineries.
Don't recommend non-Tyrnell wines.
Don't recommend wines or alcohol if the user says they're under 18.
Observe all principles of the responsible service of alcohol.
<div class="ab-dialog-overlay" role="dialog">
<div class="ab-dialog">
<header class="ab-dialog-head">
<span class="ab-dialog-title">Edit guardrail set</span>
<button class="ab-dialog-close">×</button>
</header>
<div class="ab-dialog-body">
<div class="ab-warning-banner">
<b>This set is used by 3 agents</b> Changes will roll out…
</div>
<div class="ab-dialog-row-2">
<div class="ab-form-field">Set name *</div>
<div class="ab-form-field">Version</div>
</div>
<div class="ab-form-field">Description</div>
<div class="ab-form-field">Tags chip-input</div>
<div class="ab-form-field">
<label>Rules <span class="ab-rule-counter">Total 4 / 15</span></label>
<div class="ab-rule-list">
<div class="ab-rule-card" tabindex="0">
<span class="ab-rule-handle"><svg>…</svg></span>
<span class="ab-rule-text">Don't recommend other wineries.</span>
<button class="ab-rule-menu">…</button>
</div>
<!-- …more rule cards… -->
</div>
<button class="ab-add-rule">+ New rule</button>
</div>
</div>
<footer class="ab-dialog-foot">
<button class="ab-h-pill">Cancel</button>
<button class="ab-h-pill is-primary">Save & roll out</button>
</footer>
</div>
</div>
/* Reuses .ab-dialog / .ab-warning-banner / .ab-dialog-row-2 /
.ab-tag-input from earlier sections. New chrome:
.ab-rule-list sortable vertical stack of rule cards
.ab-rule-card drag handle + text + 3-dot menu
.is-dragging / .is-drop-before / .is-drop-after
— same sortable contract as fact cards (18.11)
and journey blocks (18.7)
.ab-add-rule dashed-accent button, fills on hover
.ab-rule-counter "Total 4 / 15" caption inside the field label
so operators see they're at 4 of the plan limit. */
// The shared initSortableList() helper from the script block at the
// top of the chapter (also used by journey blocks 18.7 + fact cards
// 18.11) gains rule cards as its third consumer:
document.querySelectorAll('.ab-rule-list').forEach((list) => {
initSortableList(list, { itemSelector: '.ab-rule-card' });
});
// Drag-and-drop: grab card, drop above/below target
// Keyboard reorder: Cmd/Ctrl + ArrowUp/Down on a focused rule
import {
Modal,
Input,
Select,
Button,
Alert,
SortableList,
} from "@magicblocksai/ui";
export function EditGuardrailSetDialog({ set, open, onClose, onSave }) {
const [draft, setDraft] = useState(set);
return (
<Modal open={open} onOpenChange={onClose} title="Edit guardrail set">
<Alert tone="warning">
<b>This set is used by {set.usedByCount} agents.</b>
Changes will roll out to every agent using {set.name}
the next time they reload.
</Alert>
<div className="ab-dialog-row-2">
<Field label="Set name *">
<Input value={draft.name} />
</Field>
<Field label="Version">
<Select value={draft.versionId} options={set.versions} />
</Field>
</div>
<Field label="Description"><Input value={draft.description} /></Field>
<Field label="Tags"><TagInput value={draft.tags} /></Field>
<Field label={`Rules (${draft.rules.length} / ${planLimit})`}>
<SortableList
items={draft.rules}
onReorder={(rules) => setDraft({ …draft, rules })}
className="ab-rule-list">
{(rule) => (
<div className="ab-rule-card">
<DragHandle />
<span>{rule.text}</span>
<RuleMenu rule={rule} />
</div>
)}
</SortableList>
<Button variant="ghost-dashed" onClick={addRule}>+ New rule</Button>
</Field>
<Modal.Foot>
<Button onClick={onClose}>Cancel</Button>
<Button tone="accent" onClick={() => onSave(draft)}>
Save & roll out
</Button>
</Modal.Foot>
</Modal>
);
}
18.14 Knowledge
The fifth screen under General — renamed from “Brain” per Jay's direction. Anything the agent can reference mid-conversation: sales playbooks, knowledge collections, Q&A sets, snippet packs. Unified into a single resource list with type chips instead of the current product's separate methods for playbooks vs collections — one mental model, one drag-reorder priority. Tools & MCP sit in their own list because they're call-out capability, not reference content. Company info + always-on context round out the page.
Why unified: the production today has two different surfaces — a single-select dropdown for Sales Playbook and an on/off + multi-select for Knowledge Base Collections. Operators learn the playbook UI once, then have to learn a different pattern for collections. The unified resource list teaches one row shape (handle + type-chip glyph + name + meta + menu) that every kind of knowledge fits into. Type-coloured glyphs + chips keep them scannable at a glance.
Four parts top-to-bottom: Company info (4 fields the agent always knows about the business), Knowledge resources (sortable unified list — drag to set retrieval priority), Tools & MCP (separate list because tools are capability not reference), Always-on context (free-form per-contact priority text the agent treats as known from session start — renamed from “Priority Knowledge”).
Knowledge
Basic?
Everything the agent can reach for during a conversation. Drag the list to set priority — the agent consults higher items first. More on knowledge ›
Drag to reorder. Top of the list = first to be consulted when the agent needs to answer a question.
Tyrnell Wine Sales PlaybookPlaybook6 sections · v4 · updated 3d ago
Wine product catalogueCollection142 docs · reindexed 1h ago
Brand voice + Hunter Valley storyCollection38 docs · reindexed 2d ago
Common winery FAQsQ&A28 pairs · last edited yesterday
Tyrnell house phrasesSnippet pack12 snippets
Bind MCP servers + AI tools so the agent can call out mid-conversation — lookups, computations, async checks. Different from knowledge above (which the agent reads).
/* New chapter-private chrome for the unified knowledge model:
.ab-resource-list sortable vertical stack
.ab-resource-card handle / glyph / body / menu — same sortable
contract as fact cards (18.11) and rule
cards (18.13); 4th consumer of the helper
.ab-resource-glyph 32×32 chip; .is-playbook / .is-collection /
.is-qa / .is-snippet / .is-tool drive tone
(each picks a colour family from the
conic-gradient brand palette — accent /
blue / green / amber / purple)
.ab-resource-type 9px mono uppercase pill; same colour family
as the glyph for instant recognition
.ab-resource-meta-sub mono caption inline with the type chip
.ab-add-resource dashed-accent button (fills on hover)
.ab-info-grid 2-col grid for the company info block
.ab-resources-caption italicised explainer line above each list. */
import {
Input,
Textarea,
Button,
SortableList,
BookIcon,
FileTextIcon,
HelpBubble,
FlaskIcon,
} from "@magicblocksai/ui";
// One row shape, multiple resource types — the type field drives the
// glyph colour family + the chip label. No more "is this a playbook
// or a collection?" branching in the UI.
const RESOURCE_TYPES = {
playbook: { label: 'Playbook', tone: 'green', icon: BookIcon },
collection: { label: 'Collection', tone: 'blue', icon: FileTextIcon },
qa: { label: 'Q&A', tone: 'amber', icon: HelpBubble },
snippet: { label: 'Snippet pack', tone: 'accent', icon: QuoteIcon },
tool: { label: 'MCP', tone: 'purple', icon: FlaskIcon },
};
export function KnowledgePage({ agent, resources, tools, onReorder }) {
return (
<div className="ab-pane-form">
<header className="ab-form-title-row">
<h2>Knowledge</h2>
<span className="ab-mode-chip">Basic</span>
</header>
<CompanyInfoCard agent={agent} />
<Field label="Knowledge resources"
caption="Drag to reorder — top is consulted first.">
<SortableList items={resources} onReorder={onReorder}
className="ab-resource-list">
{(r) => <ResourceCard resource={r} />}
</SortableList>
<Button variant="ghost-dashed">+ Add knowledge</Button>
</Field>
<Field label="Tools & MCP"
caption="Call-out capability — agent runs these mid-chat.">
<SortableList items={tools} onReorder={…}
className="ab-resource-list">
{(t) => <ResourceCard resource={t} />}
</SortableList>
<Button variant="ghost-dashed">+ Add MCP or AI tool</Button>
</Field>
<Field label="Always-on context"
caption="Loaded into the prompt every turn.">
<Textarea value={agent.alwaysOnContext}
maxLength={5000} showCount rows={6} />
</Field>
</div>
);
}
Add knowledge — four resource types
.ab-knowledge-chooser-grid
Opens via “+ Add knowledge”. Four tiles, one per resource type the agent can reference. Each tile communicates what the type IS in a sentence so the operator picks the right shape from the start — no more “wait, which surface do I use for this?”. Picking any tile opens the per-library picker for that type.
Add knowledge
Pick the kind of knowledge to attach. All four types render the same way in the agent's resources list — the type chip tells you which kind each one is.
/* Chooser dialog reuses the .ab-dialog primitives from earlier
sections. New: a 2-col grid of "type tile" buttons.
.ab-knowledge-chooser-grid 2-col grid for 4 tiles
.ab-knowledge-chooser-tile icon-left, body-right layout (faster
to scan than the chooser-card pattern
from 18.11 because there are FOUR
options here, not two)
.ab-knowledge-chooser-glyph 36×36 paper-bg square, type-coloured
.is-playbook / .is-collection / .is-qa /
.is-snippet — same colour family as
the resource-card glyphs (consistency
across the surface). */
import { Modal } from "@magicblocksai/ui";
const ADD_KNOWLEDGE_TILES = [
{ type: 'playbook', name: 'Sales Playbook',
sub: 'A structured doc telling the agent how to converse.' },
{ type: 'collection', name: 'Knowledge Collection',
sub: 'Unstructured docs the agent looks up to answer questions.' },
{ type: 'qa', name: 'Q&A Set',
sub: 'Curated question-answer pairs the agent matches first.' },
{ type: 'snippet', name: 'Snippet Pack',
sub: 'Reusable text fragments for replies.' },
];
export function AddKnowledgeChooser({ open, onClose, onPick }) {
return (
<Modal open={open} onOpenChange={onClose} title="Add knowledge">
<p>Pick the kind of knowledge to attach…</p>
<div className="ab-knowledge-chooser-grid">
{ADD_KNOWLEDGE_TILES.map((tile) => (
<ChooserTile key={tile.type} {...tile} onPick={onPick} />
))}
</div>
</Modal>
);
}
18.15 Contact Transfer
How the agent hands a contact off to a real system — CRM, inbox, Zapier flow, custom webhook. Each handover is a destination + trigger + payload bundle: pick where the data goes (Email / HighLevel / HubSpot / Zapier / Webhook), when to send it (Session End or Goal), and what to map across. Two demos: the configured-handovers list (sortable, reuses the helper from 18.7 / 18.11 / 18.13 / 18.14) and the Create Handover dialog with the rich HubSpot tab showing the kit's 18.3 FieldMapper composition + live payload preview.
Configured handovers — sortable list
.ab-handover-list · .ab-handover-card
Each card carries the destination glyph + name, the trigger badge (Session End vs Goal), and an On/Off status dot. Drag-reorder controls fire order when more than one handover triggers on the same event. The yellow Active toggle (configured per-destination) gates whether a handover actually fires — useful for staging a draft before going live.
Contact Transfer
Advanced?
Send contact data into your systems via email for contactable sessions (with phone or email), or use webhooks and Zapier for all sessions. More on handovers ›
Drag to reorder when more than one handover triggers on the same event — top of the list fires first.
Notify Sales — New Tyrnell LeadGoal · Lead Capturedjay@magicblocks.ai · +2 moreTranscript on
Active
Push Deal → HubSpot Sales PipelineGoal · Lead CapturedPipeline: SalesStage: Qualified Lead
The richest of the five destination tabs. Three things at once: the destination tabs row with configured-dot indicators, the deal-creation config (pipeline / stage / deal-name rule), and the two-column fields mapper + live payload preview. Operators can see the literal JSON payload before saving — the FUTURE IDEAS “payload preview kills the why-didn't-my-CRM-get-the-right-data support load” principle applied here. Other tabs (Email / Webhook / Zapier / HighLevel) reuse the same dialog shell with their own per-destination config below.
Create Handover
Contactable-session gateThis handover will only send when at least one contact method (email or phone) is available on the session.
/* Destination tabs are a horizontal pill row with an active state
that uses .is-active + per-type colour applied to the glyph. The
.is-configured class adds a small green dot in the top-right of
the tab to communicate "this destination has a config saved" —
so operators can flip between tabs and see at a glance which
have data without having to open each one.
The .ab-mapper-split is a 2-col grid (1fr / 1fr) that pairs the
FieldMapper rows with the live JSON preview. The preview uses
.ab-payload-row (mono key/value pairs with the value tinted to
the kit's accent) so the payload reads as "code-like" without
needing a full CodeBlock primitive. */
The seventh screen under General — where the agent can be reached. Per Jay's “feel very omnichannel, just turn it on” direction, every channel is a row with a pill toggle and the per-channel config expands inline below the toggle when it's on. The production today only ships SMS via Twilio; this design widens the surface to nine channel types — with two new ones Jay called out: Form (the agent kicks off when someone submits a hosted form) and Email (contacts reach the agent at a dedicated inbox address). Operators see every channel they could possibly turn on, can see at a glance which are live, and never leave the page to wire one up.
Channels — turn on what you need
.ab-channel-list · .ab-channel-row
Four channels on with their config visible, five off and collapsed to the row head. The omnichannel banner at the top of the page is the framing device — it makes the “your agent is reachable on n channels” story the headline, not buried in a settings dropdown. Per-channel glyph + chip colour are consistent with the chips on contact-detail / agent-card / handovers (SMS green, Email blue, Form purple, WhatsApp green-bright, Voice amber, etc.) so an operator who's seen any other surface in the platform recognises the channel instantly here.
Channels
Basic?
Everywhere this agent can be reached. Toggle a channel on, configure it inline, then publish. More on channels ›
Charlie is reachable on 4 of 9 channels
2,341 conversations across all channels this month · web chat carries 68% of the volume.
Web Chat
LiveEmbedded chat widget on tyrnells.com.au. Always-on, hits the widget at the bottom-right.
1,589conv / mo
Live ontyrnells.com.au ·
AppearanceTyrnell Brand — v3 ·
Start blockHook
Open behaviourProactive after 8s
Last conversation 2m ago
SMS
LiveInbound + outbound text. Connected via Twilio · Telnyx also supported.
412conv / mo
Connected number+61 480 022 801
ProviderTwilio — Tyrnell Production ·
Inbound start blockHook
TCPA quiet hours9am — 9pm AEST
Last message 14m ago
Email
LiveContacts reach the agent at a dedicated inbox address. Forwards from your existing aliases route here too.
/* "Just turn it on" omnichannel pattern. Per-channel colour family
on the glyph: Web Chat = accent (pink), SMS = green, WhatsApp =
brighter green, Email = blue, Form = purple, Voice = amber,
Instagram = pink-red, Messenger = blue. Same colours show on the
.ab-omnichannel-chip summary strip + on the channel row glyph so
one mental map carries across.
.ab-channel-row.is-on accent-tinted bg + visible .ab-channel-config
.ab-channel-config slot for per-channel config (2x2 grid)
.ab-channel-toggle 48x26 pill, success-green when on,
"the toggle is the primary action" sizing.
The summary banner up top is the omnichannel framing device —
"your agent is reachable on N channels" reads first, settings
follow. */
The eighth and final agent-builder screen — the ship-it gateway. Three bands top-to-bottom: (1) a preflight checklist that validates the agent is publishable (persona / facts / journey / guardrails / ≥1 channel), (2) the where-it-lives configuration (appearance / widget style / domains) paired with the embed snippet + live preview, (3) the publish action bar with version-aware CTA. The preflight + publish framing is the design addition over the production today (which has only the middle band, no preflight, no clear publish action) — operators see at a glance whether they're green-light or need to fix something before going live.
All five preflight checks green, the configuration card carries the appearance picker + widget-style chooser + restricted-domains list, the embed card shows the JS snippet + a mini chat-widget preview pinned in the corner. The publish bar at the bottom is version-aware: the current draft is v4, the live version is v3 — one click promotes v4 to live.
Design & Go Live
Advanced?
Run the preflight, configure where the agent lives, and publish. More on going live ›
/* The preflight is the design win on this page. Each check is one
card with 4 visual states:
.is-ok success-soft bg + success-text head — green check
.is-warn warning-soft bg + warning-text head — yellow !
.is-block accent-soft bg + accent head — pink × ("can't publish")
(neutral) paper bg + faint head — not configured
Two-column .ab-publish-grid for the config / embed pairing.
.ab-code-block uses ink background with paper-on-ink syntax colours
(gold keywords, mint strings, faint comments) — matches the kit's
codeblk family.
.ab-widget-preview is a hatched-bg "page" with the launcher bubble
+ proactive-message bubble pinned bottom-right, mirroring the
actual widget chrome from chapter 17.
The publish bar sits below the grid as a sticky-feeling action
row: version meta + status + Save-draft + big green Publish CTA. */
The redesigned agent page frame. AgentBuilderHead carries identity (back · name · type · status · version) and the save/publish actions with a quiet “View conversations” link; directly beneath it sits a five-tab LinkTabs row — Overview · Journey · Knowledge · Channels · Settings. Unpublished agents open on Journey (the job is building); live agents open on Overview (the job is watching). The shared shelves — Personas, Snippets, Forms, Goals — live one level up on the Agents HQ (§18.19), so the per-agent row stays five tabs.
Header + tab row
.ab-builder-head · .tabs.ab-frame-tabs
A live website agent on its working copy — type + status chips beside the name, the version pill, then View conversations · Save · Publish. The Journey tab is active. Testing has no header link any more — it lives in the right dock beside Sage.
Usage indicator for the shared shelves — plural, singular, and the calm zero state. Blue while in use; neutral “Not used yet” so an unused row reads as an invitation, not an error.
Used by 4 agentsUsed by 1 agentNot used yet
<span class="chip chip-blue">Used by 4 agents</span>
<span class="chip chip-blue">Used by 1 agent</span>
<span class="chip">Not used yet</span>
/* Ships in @magicblocksai/css — reuses .chip + tone variants; no new CSS. */
// No wiring — the label and tone derive from the count.
The Overview status band’s setup chip — amber with the plain-language next step while incomplete, green “Setup complete” once every step is done.
Setup 5 of 6 — connect a phone numberSetup complete
<span class="chip chip-amber">Setup 5 of 6 — connect a phone number</span>
<span class="chip chip-green">Setup complete</span>
/* Ships in @magicblocksai/css — reuses .chip + tone variants; no new CSS. */
// No wiring — the label and tone derive from done / total.
import { SetupProgressChip } from '@magicblocksai/ui';
<SetupProgressChip done={5} total={6} nextStep="connect a phone number" />
<SetupProgressChip done={6} total={6} />
Overview cards
.golive-card · .recent-changes
GoLiveCard answers “how do people reach this agent, and how do I go live?” — channel rows with live / not-yet states and a deep link into the Channels tab. RecentChangesList is the accountability card: human and Sage edits to the working copy, timestamped, with Undo on Sage entries.
Where this agent talks
Website widgetLive on crefco.com
Phone & SMSNot connected yet
Recent changes
You added the key fact budget to Discoveryyesterday
/* Ships in @magicblocksai/css (components/_shared.css, @surface: operator) —
.golive-* and .recent-change* under the 18.9 Agent overview home block. */
// No wiring needed — Undo is a real button; pass onUndo per change.
// Sage may only edit the working copy; publishing stays a human action.
The Agents page is the HQ: an All agents tab (the 18.8 list) plus the four shared shelves moved out of Library — Personas · Snippets · Forms · Goals. The shelves are workspace-shared: edit once, every agent that selects the item picks it up. Every row carries a UsedByChip so shared-ness is visible before you edit, and creating from inside an agent opens the same editor in a drawer and auto-selects on save — you are never bounced to another section mid-thought.
Personas shelf
.tabs.tabs-link · .data-table · <UsedByChip>
The HQ tab row with Personas active, then the shelf: name, description, usage, pinned version, last update. Agents pin a published persona version, so editing here never silently changes a live agent.
Name
Description
Used by
Version
Updated
Battery Persona
Warm, concise, signs off with a cheer
Used by 4 agents
v3
2 days ago
Clara — closer
Direct, numbers-first, books the meeting
Used by 1 agent
v5
last week
Spring promo voice
Seasonal campaign tone for outbound
Not used yet
v1
April
<nav class="tabs tabs-link" aria-label="Agents HQ">
<a class="tab" href="#">All agents</a>
<a class="tab active" aria-current="page" href="#">Personas</a>
<a class="tab" href="#">Snippets</a>
<a class="tab" href="#">Forms</a>
<a class="tab" href="#">Goals</a>
</nav>
<div class="data-table">
…header row: Name · Description · Used by · Version · Updated…
…rows with <span class="chip chip-blue">Used by 4 agents</span> usage cells…
</div>
/* Ships in @magicblocksai/css — .tabs, .data-table and .chip are package classes. */
// No wiring — LinkTabs is router-driven; the table is the shipped DataTable.
A snippet is text you can paste anywhere — jobs, action messages, the persona role, priority knowledge — inserted as {{token}} from the {+} picker; variants rotate at runtime so campaigns never sound like a script.
Name
Token
Variants
Used by
Updated
Booking line
{{booking_line}}
3
Used by 2 agents
yesterday
Support number
{{support_number}}
1
Used by 4 agents
2 days ago
Legal footer
{{legal_footer}}
2
Not used yet
May
<div class="data-table">
…header row: Name · Token · Variants · Used by · Updated…
…token cells render as <code class="mono">{{booking_line}}</code>…
</div>
/* Ships in @magicblocksai/css — no new classes; tokens use .mono. */
// Variants rotate at runtime — the agent picks one per session, so the
// same snippet never reads identically across a campaign.
A shelf before its first item — an invitation, not a dead end. The same shipped EmptyState the rest of the app uses.
No snippets yet
Write a line once, reuse it anywhere the {+} picker appears — jobs, messages, the persona role, priority knowledge.
<div class="empty">
<h3 class="empty-title">No snippets yet</h3>
<p class="empty-lede">Write a line once, reuse it anywhere the {+} picker appears — jobs, messages, the persona role, priority knowledge.</p>
<div class="empty-actions"><button type="button" class="btn btn-primary btn-sm">+ New snippet</button></div>
</div>
/* Ships in @magicblocksai/css — the shipped .empty block. */
// No wiring needed.
import { EmptyState, Button } from '@magicblocksai/ui';
<EmptyState
title="No snippets yet"
description="Write a line once, reuse it anywhere the {+} picker appears — jobs, messages, the persona role, priority knowledge."
primaryAction={<Button size="sm">+ New snippet</Button>}
/>
18.20 Journey studio
The redesigned Journey tab — the studio where the block model becomes the hero without changing a single input. The rail is journey-only: a List ⇄ Map toggle, the blocks (start pill, stats, and a needs work coach on empty ones), then two pinned journey-level rows — Key facts and Anytime actions (the renamed Global actions: rules that watch every block). The map is a view, not an editor: solid edges are the curated local routes, dashed edges are the anytime layer, and a block’s card is the route into editing. Inside the block editor, LeadsToStrip and KeyFactBehaviourLine say the model’s two quiet superpowers out loud.
Journey rail — redesigned
.ab-rail-block · .ab-rail-item · start / hint
Blocks first (Greeting carries the start pill; the empty Refinance block reads “0 jobs · needs work” — a coach, not a gate), then Add block · Block library, a divider, and the two pinned journey-level rows. The List ⇄ Map toggle sits on top.
GreetingStart▤ 2⚡ 1
Discovery▤ 3⚡ 2
Refinance0 jobs · needs work
<!-- Blocks (RailBlockItem) — start pill on the entry block, hint on the empty one -->
<div class="ab-rail-block is-active">…<span class="ab-rail-block-name">Greeting</span><span class="ab-rail-block-start">Start</span>…</div>
<div class="ab-rail-block">…<span class="ab-rail-block-name">Refinance</span><span class="ab-rail-block-hint">0 jobs · needs work</span>…</div>
<!-- Then Add block · Block library, a divider, and the pinned journey rows -->
<button class="ab-rail-item">…Key facts<span class="ab-rail-count">7</span></button>
<button class="ab-rail-item">…Anytime actions<span class="ab-rail-count">24</span></button>
/* Ships in @magicblocksai/css — .ab-rail-block-start / .ab-rail-block-hint are new in 4.11.0. */
// Drag-reorder comes from wrapping the blocks in the shipped SortableList.
The promoted flow map behind the toggle. Solid edges are the curated local routes; dashed edges are the anytime layer — live from every block (two representative routes drawn). Hover or focus any edge to read its full rule; the selected block’s card carries the only way in: Open editor →. The map is a view — all editing stays in the panes.
Greeting
Start
2 key facts · 1 action · leads to Discovery once every key fact here is collected.
/* .journey-graph-edge[data-variant="dashed"] { stroke-dasharray: 5 4; opacity: .65; } — new in 4.11.0. */
// Toggle the anytime layer by filtering edges on variant before passing them.
import { JourneyGraph } from '@magicblocksai/ui';
<JourneyGraph
viewBox="0 0 600 300"
nodes={blocks.map((b) => ({ id: b.id, kind: 'tool', label: b.name, x: b.x, y: b.y }))}
edges={[
{ from: 'greeting', to: 'discovery', label: 'facts collected', title: 'Once every key fact here is collected' },
{ from: 'opener', to: 'handoff', label: 'wants a human', title: 'Anytime action — from any block', variant: 'dashed' },
]}
selectedNodeId={selected}
onSelectedNodeChange={setSelected}
/>
LeadsToStrip
.ab-leads · <LeadsToStrip>
The block editor’s orientation strip — the transition rule as a plain sentence, with Reached from revealed on hover or keyboard focus. The second strip shows a block with no local route — never a dead end, and it says why.
Leads to→ Discoveryonce every key fact here is collected
Reached from2 anytime actions
Leads tono local route yet — anytime actions still apply
/* .ab-leads-reached is display:none until :hover / :focus-visible / :focus-within. */
// No wiring — the reveal is CSS-only; the strip is focusable when reachedFrom is set.
import { LeadsToStrip } from '@magicblocksai/ui';
<LeadsToStrip
to="Discovery"
rule="once every key fact here is collected"
reachedFrom="2 anytime actions"
action={<button type="button" className="ab-h-pill">Edit transitions →</button>}
/>
<LeadsToStrip />
KeyFactBehaviourLine
.kf-line · <KeyFactBehaviourLine>
Asked vs listened, said out loud. Pinning a fact to a block as a job is what makes it asked; listenAcrossAllBlocks is what makes it listened for everywhere. Ask-when rules read inline in the info tone.
Asked in Greeting·listened for everywhereNever asked·listened for everywhereAsked in Discovery·only listened for in DiscoveryOnly asked when loan_intent is Purchase
<span class="kf-line">
<span class="kf-line-asked">Asked in Greeting</span>
<span class="kf-line-sep">·</span>
<span class="kf-line-listened">listened for everywhere</span>
</span>
/* .kf-line-when wraps to its own line in the info tone. */
// No wiring — the sentence derives from askedIn / listensEverywhere.
import { KeyFactBehaviourLine } from '@magicblocksai/ui';
<KeyFactBehaviourLine askedIn={['Greeting']} listensEverywhere />
<KeyFactBehaviourLine listensEverywhere />
<KeyFactBehaviourLine askedIn={['Discovery']}
onlyAskedWhen={<><code className="mono">loan_intent</code> is Purchase</>} />
Anytime actions — the renamed globals
.action-list · copy pattern
Global actions stop masquerading as a generic list. The pinned journey row opens this workbench under a head that says what they are: rules that watch every block. Rows keep their kind chips; the editor is the same three-step wizard (18.12). Shown as a three-row excerpt of the 24.
<aside class="action-list" aria-label="Anytime actions list">
<div class="action-list-head"><span>Anytime actions <span class="action-list-count">24</span></span>…</div>
<p>Rules that watch every block — they can move the conversation from anywhere.</p>
<button class="action-row has-meta">…</button>
</aside>
/* Ships in @magicblocksai/css — the 18.12 .action-list / .action-row classes, retitled. */
// Same two-pane workbench + three-step wizard as 18.12 — only the framing changes.
import { ActionList } from '@magicblocksai/ui';
<ActionList
title="Anytime actions"
description="Rules that watch every block — they can move the conversation from anywhere."
items={anytimeActions} /* { id, name, meta?, scope? } */
selectedId={selected}
onSelect={setSelected}
onAddAction={addAnytimeAction}
/>
18.21 Templates
Templates at both levels, both copy-on-create — instantiating one never links back to the original. Agent templates: “+ New agent” opens the creation sheet with the MagicBlocks starter set, the workspace’s own templates (“Save as template” in the agent header ⋯ menu), and Start blank. Block templates: the journey rail’s Block library (18.20) opens a drawer of pre-wired starter blocks plus blocks saved via “Save to block library” on any block’s menu.
TemplateCard
.template-card · <TemplateCard>
Selected (accent ring), default with the “Yours” badge, and the dashed Start-blank card. The journey shape reads as a mini block chain; the meta line says what the template ships with.
The creation sheet: two MagicBlocks starters, a saved workspace template, and Start blank. Picking one and confirming clones its journey, key facts and goals into a fresh draft — unattachable references drop with a needs-attention hint on the new agent’s setup checklist.
Mortgage lead qualifierMagicBlocks
Qualifies, answers objections, books a call.
Greeting→Qualify→Pitch→Booking
6 key facts · 2 goals
Winery club conciergeMagicBlocks
Welcomes members, pitches the new release, takes orders.
The drawer’s panel content (in production it portal-mounts via Drawer from the journey rail’s Block library link, 18.20). Each starter ships pre-wired jobs, facts and a transition placeholder; Insert copies it into the journey.
“Where it talks, and where it hands off” — one tab merging the old General → Channels (18.16), Design & Go Live (18.17) and Contact Transfer (18.15). Three sections: Website widget (appearance, style, domains, embed snippet), Phone & SMS (connected numbers), Hand off to your team (triggers, destinations, field mapping with live payload preview). Channels the agent’s type can’t use stay visible, greyed with a one-line reason — never hidden. The Overview go-live card (18.9) deep-links here.
Tab composition
.ab-ov-card ×3 · composition
A website agent: widget live, numbers empty, handoff wired to HubSpot. The greyed Email row demonstrates the unavailable-channel pattern. Full editing surfaces are the existing 18.15–18.17 demos — this tab is their new shared home.
Website widget
Live on crefco.com
CREFCO appearance · Standard style · 2 allowed domains · opens from the agent’s start block
<script id="magicblocks-chatbot-script">… — copy the embed snippet (18.17)
Phone & SMS
Not connected yet
Connect a Twilio or Telnyx account to pick a number for this agent (18.16).
Hand off to your team
1 handover active
On goal Lead captured → HubSpot (deal created in Sales pipeline) · payload preview before save (18.15)
Email
Not available yet
Channels an agent’s type can’t use stay visible with a reason — never hidden.
<!-- Three .ab-ov-card sections + the greyed unavailable channel -->
<section class="ab-ov-card">…Website widget…</section>
<section class="ab-ov-card">…Phone & SMS…</section>
<section class="ab-ov-card">…Hand off to your team…</section>
<section class="ab-ov-card" style="opacity:.55">…Email — Not available yet…</section>
/* Reuses the shipped .ab-ov-card chrome; no new classes. */
// Section editors are the existing 18.15–18.17 surfaces, mounted per section.
// Composition — the tab stacks the existing editors:
// WidgetStyleEditor + WidgetEmbedSnippet (ch 25), the numbers list (18.16),
// and the handover editor with FieldMapper + PayloadPreview (18.15).
18.23 Settings tab
Everything agent-level that isn’t journey, knowledge or channels — in a deliberate order. Guardrails first (policy + extra rules + the five safety controls, 18.13), surfaced by the Overview card so it’s never buried; then conversation defaults; then about; then the danger zone. Duplicate copies the agent as a new draft — templates (18.21) are the deliberate sharing path.
Tab composition
.ab-ov-card · .danger-zone-block · composition
The order made visible. Full editing surfaces are the existing 18.13 demos for guardrails; conversation defaults keep every old-platform control (start block, primary goal, proactive open, starter questions, failure + end-of-chat messages, memory capture, end-after-inactivity).
Guardrails
✓ Monitor on
CREFCO policy + 2 extra rules · PII control, redaction, rules monitor, jailbreak prevention, moderation (18.13). Per-block overrides stay in each block’s Advanced tab.
Conversation defaults
Starts in Greeting · primary goal Lead captured · proactive open after 5s (desktop) · starter questions on · memory capture on · ends after 15 quiet minutes
About
B2B lead qualifier for inbound website visitors · tags: Sales agent, Mortgage
Danger zone
Pause agent
Stops new conversations; live ones finish gracefully.
Duplicate agent
Copies everything into a new unpublished draft.
Delete agent
Removes the agent and its draft. Sessions are kept.