← Prototype
UI/UX blueprint · v1 · September 2026

FenceOS AI Setup Agent

The screens, states and structure behind “tell us about the business, the agent handles the setup.” This document is the reference; the clickable prototype is the demonstration. No HighLevel integration is specified as built — sections 13 and 14 describe what the UI is shaped to accept later.

00

The organizing idea: three classes of work, not two

Most agent UIs split work into “the agent did it” and “the agent needs permission.” A FenceOS subaccount build has a third class, and designing around it is the difference between a demo and a tool people trust.

Parts of a HighLevel subaccount have no API write path at all. Pushing a snapshot and activating a calendar are agency-UI actions. An agent that pretends otherwise either lies or silently stalls. So the interface names this class explicitly, gives it its own colour, its own node shape in the task rail and its own modal, and treats “I have prepared everything up to this point, here is exactly what you do” as a first-class successful outcome rather than a failure.

Automatic

The agent holds credentials and the action is reversible. It runs, logs the payload, moves on. 11 of 15 tasks.

Needs approval

An API exists, but the action spends money, is irreversible, or submits the client’s legal identity to a third party. 2 of 15.

Human step

No endpoint exists. The agent prepares state, hands over precise instructions, then verifies the result by re-reading. 2 of 15.

Second principle: progressive disclosure by default

A specialist should be able to run a build without knowing what a pipeline ID is. Every activity line has a plain-language surface (“Creating pipeline…”) and a monospace technical layer underneath (the actual request body), revealed by a single global toggle. The toggle is a user preference, not a per-line control — advanced users turn it on once.

01

Application sitemap

Two levels. Everything an operator does daily is one click from the rail; everything about a single build lives under its subaccount.

/                                  Dashboard — agency-wide counters, activity feed, subaccount table
/subaccounts                       Full table: filter, sort, saved views
   /new                           Create wizard
      ├ step 1 · business information
      ├ step 2 · brand & primary contact
      ├ step 3 · FenceOS configuration
      └ step 4 · review
   /:locationId                   Subaccount overview
      ├ /plan                      AI setup plan (approve · edit · ask)
      ├ /run                       Agent workspace — the live build
      │    └ /run/:taskId          Deep link to one task's activity
      ├ /verification              Check dashboard, per-check detail
      ├ /report                    Completed setup report, export
      └ /settings                  Re-run, duplicate configuration, archive
/runs                              All agent runs across the agency
/approvals                         Cross-account approval queue
/audit                             Agency-wide audit log, filter + export
/snapshots                         Snapshot versions and what each one installs
/settings
   ├ /connections                  HighLevel OAuth, Canva, Gmail, Drive
   ├ /templates                    Plan templates and default module sets
   ├ /permissions                  What the agent may do without asking
   └ /team                         Specialists, roles, notification routing

Global, not a route: agent chat drawer · command palette · approval modal · toasts
02

User flow

The happy path is a single rail. Everything that interrupts it hangs off the workspace stage and returns to it — there is no dead end and no branch that leaves the operator without a next action.

DashboardSees a new client needs a build
Wizard4 steps, pre-filled from the branding form
AI plan15 tasks, confidence, dependencies
WorkspaceWatches the agent build
VerificationRe-reads every setting
CompleteReport, open, duplicate

Interrupts during the workspace stage — each resolves back into the run

Automatic taskStreams activity, logs payload, advances. No interruption.
Approval gateModal states the exact payload, reversibility and why. Approve · Reject · Ask.
Human stepNumbered instructions plus what the agent will re-read to confirm. “I've done it → verify.”
FailureNamed cause, the one thing that unblocks it, and three exits: fix · retry · continue without.

Two loops matter. Verification → workspace: “Fix issues with AI” re-enters the run for just the failing tasks rather than starting over. Complete → wizard: “Duplicate configuration” seeds a new build from a finished one, which is how an agency does its second, third and fortieth client.

03

Screen inventory

ScreenJobPrimary actionIn prototype
DashboardIs anything on fire, and what is in flightCreate new subaccountBuilt
Subaccount tableFind one account among hundredsOpen a buildBuilt
Wizard · businessCapture what the agent needsContinueBuilt
Wizard · brandColours, logo, contact, EINContinueBuilt
Wizard · configurationChoose modules, or accept the recommendationContinueBuilt
Wizard · reviewLast look before anything is writtenGenerate setup planBuilt
AI setup planConsent to a specific sequence, not a black boxApprove plan & startBuilt
Agent workspaceWatch, steer, interveneApprove / retry / skipBuilt
Approval modalDecide with the payload in viewApprove & continueBuilt
Handoff modalDo the part the API can’tI’ve done it — verifyBuilt
VerificationTrust the build, or see exactly why notFix issues with AIBuilt
CompletionHand off and move onOpen subaccountBuilt
Audit logProve what changed and whenExpand an actionBuilt
Agent chatAsk instead of huntingAct on a suggested chipBuilt
Approval queueOne place for every waiting decisionBatch approveSpec only
Snapshot registryKnow what v4.2 actually installsDiff versionsSpec only
ConnectionsOAuth health, token expiry, scopesReconnectSpec only
Agent permissionsSet what runs without askingSave policySpec only
04

Wireframes

Layout only — the prototype carries the finished visual design. Regions are named for the components in section 5.

Dashboard

┌──────────┬──────────────────────────────────────────────────────────────────────┐
│ RAIL     │ TOPBAR  breadcrumb ······················· [+ Create subaccount] │
│ brand    ├──────────────────────────────────────────────────────────────────────┤
│          │ ┌────────┐┌────────┐┌────────┐┌────────┐┌────────┐                   │
│ Dashboard│ │ Total  ││In prog.││Complete││ Waiting││ Needs  │  StatTile ×5       │
│ Subaccts │ │   18   ││   4    ││   11   ││ client ││ attn 2 │  last two are the   │
│ Runs     │ │ ▁▃▂▅▄▆ ││        ││        ││   3    ││        │  two blocker classes│
│ Approvals│ └────────┘└────────┘└────────┘└────────┘└────────┘                   │
│ ─────────│ ┌───────────────────────────────────────────┐┌───────────────────┐   │
│ Verify   │ │ SubaccountTable            [filter]    ││ ActivityFeed      │   │
│ Audit    │ │ Business │Stage│ %  │Blocker│Owner│Updated ││ ● agent completed │   │
│ Snapshots│ │ ─────────┼─────┼────┼───────┼─────┼─────── ││ ▲ A2P rejected    │   │
│ Settings │ │ Bee Fen. │Ph.# │44% │client │AB   │Jul 29  ││ ✋ handoff waiting │   │
│          │ │ Family F.│DNS  │56% │client │TW   │Jul 29  ││ ● brand board out │   │
│ ─────────│ │ Jack Fen.│Ph.# │75% │agency │CG   │Jul 29  ││                   │   │
│ user     │ └───────────────────────────────────────────┘└───────────────────┘   │
└──────────┴──────────────────────────────────────────────────────────────────────┘

The Blocker column is the one addition your sheet argues for: at a glance, is this stalled on us, on the client, or on a carrier?

Agent workspace — the primary screen

┌──────────┬──────────────────┬──────────────────────────────┬────────────────────┐
│ RAIL     │ TaskRail         │ AgentActivity                │ AgentInspector     │
│          │           10/15  │                              │      Working ●     │
│          │ ▸ FOUNDATION     │ ┌──────────────────────────┐ │ ────────────────── │
│          │  ✓ Create subacct│ │ 🤖 Setup Agent           │ │ PROGRESS           │
│          │  ✓ Business info │ │ I'm configuring the      │ │   67%  10 of 15    │
│          │  ✋ Install snapsh│ │ FenceOS pipeline.        │ │  ▓▓▓▓▓▓▓░░░        │
│          │ ▸ DATA MODEL     │ │ ··· AI is working        │ │ ────────────────── │
│          │  ✓ Pipeline      │ └──────────────────────────┘ │ ⚠ Phone & IVR failed│
│          │  ✓ Custom fields │ ACTIVITY   [tech detail] [1×]│ ERR_NO_NUMBER      │
│          │  ✓ Custom values │ 10:44 ● Creating pipeline…   │ [Assign] [Retry]   │
│          │ ▸ BRAND + INTAKE │ 10:44 ✓ Stage: New Lead      │ [Continue without] │
│          │  ✓ Brand board   │ 10:45 ✓ Stage: Contacted     │ ────────────────── │
│          │  ✓ Forms         │ 10:45 ✓ Stage: Estimate Sch. │ CURRENT TASK       │
│          │  ✋ Calendars     │      ┌─────────────────────┐ │ Task / Mode / Conf │
│          │ ▸ COMMUNICATIONS │      │ POST /v1/pipelines/ │ │ ────────────────── │
│          │  ✓ SMS & A2P     │      │ {"name":"FenceOS…"} │ │ TARGET             │
│          │  ✗ Phone & IVR   │      └─ tech layer, toggled┘ │ loc_3Ax8Qm         │
│          │  ○ AI reception. │ 10:49 ✗ Could not configure  │ ────────────────── │
│          │ ▸ AUTOMATION     │         the IVR — no phone    │ PERMISSIONS        │
│          │  ○ Workflows     │         number is assigned.  │ ✓ read ✓ write     │
│          │  ○ Follow-up     │                              │ ✋ buy numbers      │
│          │ ▸ VERIFICATION   │                              │ ────────────────── │
│          │  ○ Config checks │                              │ [Pause] [Retry]    │
│          │                  │                              │ [Skip]  [Approve]  │
│          │ ✓done ●run ○wait │                              │ [   Stop setup   ] │
│          │ ✋human ✗failed   │                              │                    │
└──────────┴──────────────────┴──────────────────────────────┴────────────────────┘
   The task rail is drawn as a continuous line with node markers — a fence line.
   Node shape carries meaning independently of colour: filled circle = done,
   pulsing ring = running, hollow = pending, square = human step, ✗ = failed.

Verification

┌──────────┬──────────────────────────────────────────────────────────────────────┐
│ RAIL     │ ┌──────────────────────────────────────────────────────────────────┐ │
│          │ │   ╭────╮   FenceOS setup verification                            │ │
│          │ │   │ 88%│   I created a test lead, drove it through the pipeline  │ │
│          │ │   │READY│  and re-read every setting rather than trusting what   │ │
│          │ │   ╰────╯   I wrote earlier.                                      │ │
│          │ │            9 passing · 2 need review · 1 failing                 │ │
│          │ │            [Run again]  [Fix issues with AI]                     │ │
│          │ └──────────────────────────────────────────────────────────────────┘ │
│          │ ┌──────────────────────────────────────────────────────────────────┐ │
│          │ │ CheckList                                                        │ │
│          │ │ ✓ Subaccount created            loc_3Ax8Qm · 10:02 AM         ›  │ │
│          │ │ ✓ Custom values populated       24 of 24                      ›  │ │
│          │ │ ⚠ Workflows configured          18 active · 6 paused pending A2P ⌄│ │
│          │ │   ┌ The workflows are correct. Six send SMS as their first      │ │
│          │ │   │ action, so they stay paused until the campaign clears.      │ │
│          │ │   │ ┌────────────────────────────────────────────┐              │ │
│          │ │   │ │ Paused: Missed Call Text Back, Reminder 24h │  tech layer  │ │
│          │ │   │ └────────────────────────────────────────────┘              │ │
│          │ │ ✗ Phone number & IVR            No number assigned            ›  │ │
│          │ └──────────────────────────────────────────────────────────────────┘ │
└──────────┴──────────────────────────────────────────────────────────────────────┘

Approval modal & handoff modal

  APPROVAL                                HANDOFF
┌────────────────────────────────┐    ┌────────────────────────────────┐
│ 🛡 Approval required            │    │ ✋ Your turn: Install snapshot  │
│ The agent wants to submit the  │    │ HighLevel exposes no API for   │
│ A2P 10DLC brand and campaign   │    │ this. I've done everything up  │
│ ────────────────────────────── │    │ to it.                         │
│ Task        Configure SMS & A2P│    │ ────────────────────────────── │
│ Subaccount  ABC Fence — FenceOS│    │ 1. Agency view → Snapshots     │
│ Confidence  74%                │    │ 2. Select FenceOS Master v4.2  │
│ Reversible  No — a submitted   │    │ 3. Push to loc_3Ax8Qm          │
│             campaign cannot be │    │ 4. Wait for the report email   │
│             withdrawn          │    │ ────────────────────────────── │
│ ┌────────────────────────────┐ │    │ ┌ WHAT I'LL CHECK WHEN BACK ─┐ │
│ │ Why this needs you          │ │    │ │ Agency UI: Settings →      │ │
│ │ A2P submits the business's │ │    │ │ Snapshots → v4.2 → Push    │ │
│ │ legal name and EIN to the  │ │    │ │ Target: loc_3Ax8Qm         │ │
│ │ carriers. A rejection costs│ │    │ └────────────────────────────┘ │
│ │ days and a re-filing fee.  │ │    │                                │
│ └────────────────────────────┘ │    │                                │
│ ┌ EXACTLY WHAT I WILL SEND ──┐ │    │                                │
│ │ brandId: br_9K2            │ │    │                                │
│ │ Sample 1: "Hi {{first_name │ │    │                                │
│ │ }}… Reply STOP to opt out."│ │    │                                │
│ └────────────────────────────┘ │    │                                │
│ [Reject] [Ask]    [Approve ✓] │    │ [Skip] [Open GHL]  [Done ✓]   │
└────────────────────────────────┘    └────────────────────────────────┘
  Both modals answer the same three questions: what exactly, why me, what happens next.

Create wizard — step 3

┌──────────┬──────────────────────────────────────────────────────────────────────┐
│ RAIL     │   ①─────②─────③─────④   business · brand · configuration · review   │
│          │ ┌──────────────────────────────────────────────────────────────────┐ │
│          │ │ What should the agent set up?                                    │ │
│          │ │ ┌────────────────────────────────────────────────────────[ ●─]─┐ │ │
│          │ │ │ 🤖 Use recommended FenceOS configuration                      │ │ │
│          │ │ │ Residential + commercial contractor, one estimator → 13 of 14 │ │ │
│          │ │ └──────────────────────────────────────────────────────────────┘ │ │
│          │ │ ┌──────────────────────┐┌──────────────────────┐                 │ │
│          │ │ │☑ CRM Pipeline        ││☑ Lead Management     │  FeatureCard grid│ │
│          │ │ │☑ Forms               ││☑ Calendars  ✋HUMAN   │  tags mark which │ │
│          │ │ │☑ SMS                 ││☑ Phone/IVR  ✋HUMAN   │  modules involve a│ │
│          │ │ │☑ AI Receptionist     ││☐ Reporting           │  handoff before   │ │
│          │ │ └──────────────────────┘└──────────────────────┘  the user commits │ │
│          │ └──────────────────────────────────────────────────────────────────┘ │
│          │ [Back]                            Step 3 of 4      [Continue →]      │
└──────────┴──────────────────────────────────────────────────────────────────────┘
05

Component hierarchy

<App>
├─ <AppShell>
│  ├─ <NavRail>              NavItem · NavBadge · UserCard
│  ├─ <TopBar>              Breadcrumb · ContextActions · ThemeToggle · ChatToggle
│  └─ <ViewOutlet>
├─ <DashboardView>
│  ├─ <StatTile> ×5         value · label · sub · Sparkline · severity
│  ├─ <SubaccountTable>     TableRow · BusinessCell · StageBadge · ProgressCell
│  │                        BlockerChip · RowActions
│  └─ <ActivityFeed>        FeedItem(kind, actor, message, time)
├─ <CreateWizard>
│  ├─ <StepIndicator>
│  ├─ <FormField>           label · control · hint · error   ← one validation contract
│  ├─ <ColorField>          swatch + hex, two-way bound
│  ├─ <AssetDropzone>
│  ├─ <RecommendBanner>     Switch · rationale text
│  ├─ <FeatureCard>         checkbox · name · description · HandoffTag
│  └─ <WizardFooter>
├─ <PlanView>
│  ├─ <PlanHero>            agent framing · PlanStats
│  └─ <PlanTask>            index · name · description · ConfidenceMeter
│                           · DependencyRef · ModeBadge
├─ <WorkspaceView>        ← the primary screen
│  ├─ <TaskRail>            PhaseGroup › <TaskNode>(status, shape, label, tail)
│  ├─ <AgentActivity>
│  │   ├─ <AgentStatement>  avatar · current intent · ThinkingIndicator
│  │   ├─ <ActivityToolbar> TechToggle · SpeedControl
│  │   └─ <ActivityLog>     TaskHeader › <LogLine>(time, glyph, message, <TechDetail>)
│  └─ <AgentInspector>
│      ├─ <ProgressBlock>   <AlertBox>  <TargetBlock>
│      ├─ <PermissionList>  PermissionRow(granted | human-only)
│      └─ <ControlGrid>     Pause · Resume · Retry · Skip · Approve · Stop
├─ <VerificationView>
│  ├─ <ReadinessRing>       weighted score · semantic stroke
│  └─ <CheckRow>            status glyph · name · summary · <CheckDetail>
├─ <CompletionView>       Seal · SummaryGrid · HandoffActions
├─ <AuditView>            FilterBar · SearchField · AuditRow › <PayloadDetail>
└─ Global overlays
   ├─ <AgentChat>           MessageList · Message · <ActionChip> · SuggestionChips
   ├─ <ApprovalModal> · <HandoffModal> · <AssignNumberModal> · <FixWithAIModal>
   └─ <Toast>

Shared primitives  Button · Chip/StatusBadge · Meter · Card · KeyValueList
                  Switch · Modal · ScrollArea · EmptyState · Sparkline

<ActionChip> is worth calling out. Every agent answer that identifies a problem ends in buttons that fix it — “Assign number”, “Retry setup”. Chat that only explains is a chatbot; chat that acts is part of the control surface.

07

State & status system

Taken from the live FenceOS Subaccount Onboarding Status Report rather than invented. Four independent layers — conflating them is what makes these dashboards lie.

Layer 1 — Account pipeline stage (one per subaccount, the sheet’s “Pipeline Stage”)

StageBadgeMeaning & exit condition
Subaccount CreationNeutralForm received, location not yet standing. Exits when the location ID exists.
In ReviewActiveAgent building or specialist checking. The working state — most accounts sit here.
Waiting on DNS RecordsClientBlocked on the client’s domain or their web developer. Not an agent failure.
Phone # PendingBlockedThe dominant bottleneck in your data. Number not purchased or not authorised.
A2P ApprovedClearedCarrier registration through. Paused SMS workflows resume automatically here.
Initial Setup CompleteDoneBuild finished and verified. Handover to the client.
On HoldMutedClient disengaged or not proceeding. Excluded from in-progress counts, still searchable.

Layer 2 — Readiness domains (17 per subaccount, one column each in your sheet)

Access · Phone Number · A2P Compliance · Appointment Booking Funnel · Calendars · Google Account Calendar Sync · Sales Opportunity Board · Forms · Workflows · Knowledge Base · AI Voice · AI Conversation · Domain / DNS · Custom Fields · Custom Values · Tags · Integrations

Not StartedPending In ProgressCompleted On Hold— not applicable

These are what the app computes Percentage from, replacing the manual estimate. Completed counts 1, In Progress 0.5, everything else 0, over the domains in scope for the modules that client bought. Reporting a number the operator did not type is only defensible if the operator can click it and see the 17 rows behind it — so the percentage cell opens the domain breakdown.

Layer 3 — Run task status (one per task inside a run)

StatusNode shapeBehaviour
pendingHollow circle, greyDependencies unmet or not reached.
runningFilled circle, pulsing haloStreaming activity. Exactly one task at a time.
awaiting_approvalRing, amber, tail “approve”Run halted. Modal open. Nothing written.
awaiting_humanSquare, violet, tail “you”Instructions issued. Agent re-reads state on return.
completedFilled circle + check, greenWritten and log payload recorded.
failedFilled circle + ✗, redCause named, recovery actions offered. Run halts.
skippedDashed circle, struck labelDeliberately passed. Consequences stated before confirming.

Shape carries status independently of colour, so the rail survives colour-blindness and greyscale printing.

Layer 4 — Blocker class (new — argued for by your “Blockers” column)

Your blocker notes fall into four kinds, and they need different people to act. Rendering them all as one red “Needs Attention” hides who is actually holding the build up.

Agency — us“Phone number purchase pending”. A specialist or an approval unblocks it. Red. Belongs in the approval queue.
Client — them“Anthony is not responding”, “Developers don’t have access to his domain”. Violet. Triggers the follow-up cadence in section 10, not an error state.
Carrier / externalA2P under review, DNS propagating. Amber. Nobody can act; the agent watches and resumes itself.
NoneRunning or done. No blocker chip on the row.
08

AI agent interaction model

The agent is presented as an operator with a task list, not an assistant with opinions. Four rules produce that:

1 · It states intent before acting, in the user’s vocabulary

“I’m configuring the FenceOS pipeline” is the surface. POST /v1/pipelines/ is the layer underneath. The plain sentence is never a euphemism for something broader — if the agent is about to write 24 custom values, it says 24.

2 · Activity streams; it does not report at the end

Log lines append one at a time with timestamps. Long-running tasks show their sub-steps (each pipeline stage as it is created) so the operator can tell “working” from “hung”. A pulsing halo and a “AI is working” indicator mark live state; both stop the instant the agent stops.

3 · Confidence is shown where it changes a decision

On the plan screen, per task, as a five-bar meter plus the number. It is calibrated to the task class: 96% for creating a location, 68% for phone provisioning. Low confidence on a task that also needs approval is the signal to read the payload rather than click through.

4 · Every explanation ends in an action

Chat answers in an operational register — what happened, why, what unblocks it — and renders the unblocking action as a button. “The IVR could not be completed because the subaccount does not currently have an available phone number. I can retry after a number is assigned.” Assign number Retry setup

Controls the operator always has

ControlGuarantee
PauseFinishes the in-flight API call, then stops. Never leaves a half-written task.
ResumeContinues from the exact task and log position. State is server-side.
RetryRe-runs one task. The task’s first step is always the check that failed, so a retry without a fix fails identically and fast.
SkipStates downstream consequences before confirming, then records the skip in the audit log.
StopEnds the run. Nothing is rolled back — the copy says so plainly, because silent rollback of a client’s live subaccount would be worse.
09

Human approval interaction

An approval is a decision, so the interface’s job is to make the decision cheap, not to make the click easy.

What every approval shows

ElementWhy it is there
Task & subaccountApprovals arrive by notification; the operator may not have context.
ConfidenceTells them how hard to look.
ReversibilityThe single most decision-relevant fact. Stated in words, not a risk score: “No — a submitted campaign cannot be withdrawn without re-registering.”
Why this needs youNames the reason class: money, irreversibility, or the client’s legal identity leaving the system.
Exact payloadMonospace, verbatim, scrollable. Not a summary of the payload.
Reject · Ask · ApproveThree exits. Ask opens chat pre-loaded with the question, so “I don’t understand this” is not a dead end that pushes people to approve.

Handoff is not approval

Where approval asks “may I?”, a handoff says “I can’t, and here is precisely what to do.” It shows numbered steps naming the exact screen and target ID, plus what the agent will re-read to confirm — which is what makes “I’ve done it” a verification rather than an honour system. If the re-read shows the change did not land, the agent says so and keeps the handoff open.

Governance

  • Approval policy is configurable per agency, per task class — an agency that trusts phone provisioning can set it to automatic; the class stays visible in the audit log either way.
  • Approvals expire. An unanswered gate ages into the queue with the run paused, not silently auto-approved.
  • Batch approval exists in the queue for identical low-risk gates across accounts, but never for irreversible ones.
  • Every approve, reject and skip records who decided and when. The audit log is the deliverable that makes this defensible to a client.
10

Error & recovery UX

Your sheet is mostly a record of blocked builds, not broken ones. The design follows that: the common case is not “something crashed”, it is “this is waiting on a person or a carrier.”

Error taxonomy

KindExample from your dataTreatment
Hard block“Phone number purchase pending” — ERR_NO_NUMBER_ASSIGNEDRun halts. Named cause, the one prerequisite, three exits: fix now, retry, continue without. Downstream dependents are marked, not silently failed.
Waiting on client“Anthony is not responding”; “Developers don’t have access to his domain”Not an error. Account moves to a client-blocked state and enters the follow-up cadence below. Stays out of the “needs attention” count so real problems stay visible.
External pendingA2P under carrier review; DNS propagatingTask completes as submitted, not done. Dependent workflows are installed but paused, clearly labelled. The agent polls and resumes itself.
Partial success“Google Calendar is not connected to staff account”Task completes with a warning line. Verification surfaces it. Never rounded up to a green check.
Contradiction“Subaccount already exists (contradicts Pipeline Stage)”The agent refuses to write and asks. Reconciling a record it does not understand is how you get duplicate subaccounts.
TransientRate limit, 5xx, token refreshRetried silently with backoff. Only surfaced if it exhausts retries. Recorded in the audit log at debug level.

The follow-up loop — from your second tracker table

Four things you chase clients for, each with first follow-up, second follow-up and a call. Today that lives in a spreadsheet and depends on someone remembering. It is a scheduler, which means the agent should own it.

Request sentForm, DNS, phone auth, or Google connect
Follow-up 1Auto-drafted at +7 days, specialist approves send
Follow-up 2+14 days, escalating copy
Call taskAssigned to the owner — a human, not an email
ReceivedBuild resumes from where it stalled

Two design consequences. The dashboard gets a Waiting on client tile alongside Needs attention, because they route to different work. And the subaccount row shows days in current stage — a build at 44% for six weeks is a different problem from one at 44% since yesterday, and your sheet cannot currently tell them apart.

Recovery affordances

  • Fix issues with AI is honest about its limits. It splits the list into what the agent can resolve given one input from you, and what nobody can resolve yet — and says so rather than spinning on the second kind.
  • Continue without it always exists next to retry. A build blocked on one number should still deliver the other fourteen tasks.
  • Consequences before confirmation. Skipping the phone task states that the receptionist has nothing to answer on and missed-call text back will never fire.
  • Nothing rolls back silently. Stopping a run leaves the subaccount as-is and the copy says so, because a client’s live account is not a transaction to abort.
  • Failures are addressable. Every failed task deep-links, so a notification, an audit row and a chat answer all land on the same place.
11

Responsive & mobile

The realistic mobile job is not building a subaccount — it is answering an approval from a phone so a build is not stalled overnight. The breakpoints follow that.

WidthShellWorkspace
≥ 1180pxRail + contentThree panes side by side. The design target.
900–1180Rail + contentInspector becomes a right drawer over the activity stream; task rail narrows but stays.
760–900Rail + contentDashboard tiles 2-up; wizard fields single column; feature grid single column.
< 760pxRail becomes a sheet behind a menu buttonThree panes become three tabs: Tasks · Activity · Agent. Activity is the default. Tables scroll horizontally inside their own container rather than crushing.
  • Approval and handoff modals are full-height sheets on small screens, with the decision buttons pinned so the payload can scroll without hiding them.
  • The chat drawer goes full-width; the floating button collapses to a circle.
  • Touch targets 44px minimum; the task rail keeps 40px rows so nodes stay tappable.
  • Push notification on approval and on failure, deep-linking to the task.
12

Design system

A control room, not a marketing site. Restrained neutrals, one accent, and semantic colour reserved so it always means something.

Colour

Neutrals are biased slightly toward the accent’s hue rather than pure grey, so panels sit with the accent instead of beside it. The accent is a petrol teal — instrument panel, not SaaS violet. Critically, the accent is never used to mean “good”; green, amber and red are held back for status alone, and violet is spent entirely on the human-step class.

Accent#0F6F77
Human step#5A44A3
Success#256D4A
Warning#95590A
Critical#A02B21
Ink#101617
Ground#F4F6F6

Dark mode is a designed second palette, not an inversion: the accent lifts to #3EB2B6 to hold contrast on a dark ground, and every semantic pair gets its own soft fill and border. Both themes are token-level, and the page respects an explicit user choice over the OS setting.

Typography

Archivo 600/700 — display, headings, numerals, badges Configure phone & IVR
IBM Plex Sans 400/500/600 — interface and prose I’m configuring the FenceOS pipeline. Creating stage: Estimate Scheduled.
IBM Plex Mono 400/500 — the technical layer: IDs, payloads, timestamps POST /v1/pipelines/  loc_3Ax8Qm  10:44 AM

The monospace face is the progressive-disclosure signal. Anything in Plex Mono is machine truth — an identifier, a payload, a timestamp — so operators learn to read past it until they need it. tabular-nums everywhere digits stack.

Structure & motion

Spacing & shape

4px base. Radii 6 / 10 / 14 by role, not decoration. Border, fill and shadow are spent where something genuinely separates — log lines are not cards.

Status form

Chips carry a dot, a border and a fill; task nodes carry shape. Status is never colour alone.

Motion

Only three: log lines slide in as they arrive, the running node pulses, the agent avatar haloes while working. All stop when the agent stops. All respect prefers-reduced-motion.

Density

Comfortable in the wizard, tight in the log and tables. An operator watching a 15-task build wants lines, not cards.

13

Recommended data entities

Modelled so that the status sheet becomes a view rather than a parallel source of truth. Every column in your tracker maps to a field or a derivation below.

agency            id · name · ghl_company_id · oauth_connection_id · timezone
                  approval_policy(jsonb) · snapshot_default_id

subaccount        id · agency_id · business_name · ghl_location_id
                  — from the branding form —
                  website · phone · email · address · city · state · postal_code
                  timezone · ein · ein_document_url · industry · description
                  primary_contact_name/email/phone
                  brand_primary_color · brand_secondary_color · brand_tone · logo_url
                  — tracker fields —
                  owner_user_id            "Owner"
                  access_state             "Access": granted | pending
                  pipeline_stage           enum, section 7 layer 1
                  blocker_class            none | agency | client | external
                  blocker_note             "Blockers" free text
                  detailed_status_url      "Checklist - TFC …" link
                  est_completion_date · stage_entered_at · created_at · updated_at
                  derived: readiness_pct · days_in_stage

readiness_domain  id · subaccount_id
                  domain      enum ×17: phone_number, a2p_compliance,
                              booking_funnel, calendars, google_calendar_sync,
                              opportunity_board, forms, workflows, knowledge_base,
                              ai_voice, ai_conversation, domain_dns, custom_fields,
                              custom_values, tags, integrations, access
                  status      not_started | pending | in_progress | completed | on_hold
                  in_scope(bool) · note · evidence_ref · updated_at · updated_by

setup_run         id · subaccount_id · plan_id · state · started_at · finished_at
                  started_by · speed · config(jsonb: selected modules)

setup_task        id · run_id · key · name · phase · sequence
                  mode        auto | approval | handoff
                  status      section 7 layer 3
                  confidence · depends_on(uuid[]) · domain → readiness_domain
                  started_at · finished_at · error_code · error_message
                  attempt_count · skipped_reason

activity_log      id · run_id · task_id · at · level · message
                  technical(text)         the progressive-disclosure layer
                  request_payload(jsonb) · response_payload(jsonb)
                  ghl_resource_type · ghl_resource_id

approval_request  id · task_id · requested_at · reason_class
                  billing | irreversible | third_party_identity | destructive
                  reversible(bool) · payload_preview(text) · confidence
                  decided_at · decided_by · decision approved|rejected|expired
                  decision_note

handoff_request   id · task_id · instructions(jsonb) · target_screen · target_ref
                  verification_query(text)   what the agent re-reads
                  issued_at · confirmed_at · confirmed_by · verified(bool)
                  verification_result(jsonb)

verification_run  id · subaccount_id · run_at · score · summary
verification_check id · verification_run_id · key · name · status
                  summary · detail · technical · remediable_by_agent(bool)

followup          id · subaccount_id
                  kind        onboarding_form | domain_connection |
                              virtual_phone_number | google_integration
                  requested_at · followup_1_at · followup_2_at · call_at
                  session_scheduled_at · resolved_at · owner_user_id
                  — this is your second tracker table, made actionable

audit_event       id · agency_id · subaccount_id · actor_type agent|user|system
                  actor_id · action · target_type · target_id
                  result · payload(jsonb) · at

connection        id · agency_id · provider highlevel|canva|gmail|drive
                  scopes(text[]) · access_token · refresh_token
                  expires_at · status · last_error

snapshot          id · name · version · manifest(jsonb) · released_at
plan_template     id · agency_id · name · task_definitions(jsonb) · default_modules

Three notes. readiness_domain is deliberately a row per domain rather than 17 columns, so history and evidence attach per domain and the percentage is computed, not typed. activity_log.technical exists as its own column because progressive disclosure is a data requirement, not a rendering trick. And handoff_request.verification_query is what turns “I’ve done it” into a verified fact.

14

Integration architecture — for later

The UI is built around an agent/task orchestrator, so the integration layer slots underneath without reshaping any screen.

┌──────────────────────────────────────────────────────────────────┐
│  Setup Console UI   React · optimistic UI · SSE subscription     │
└───────────────┬─────────────────────────────────▲────────────────┘
        REST    │                                 │  Server-Sent Events
                ▼                                 │  task.status · log.append
┌──────────────────────────────────────────────────┴───────────────┐
│  API layer   /runs · /tasks · /approvals · /handoffs           │
│                /verification · /audit · /followups               │
└───────────────┬──────────────────────────────────────────────────┘
                ▼
┌──────────────────────────────────────────────────────────────────┐
│  Task orchestrator   durable state machine, one task at a time  │
│  · resolves the dependency graph                                 │
│  · classifies each task auto | approval | handoff                │
│  · halts on gates, resumes from persisted position               │
│  · retries transients with backoff, never retries a 4xx blindly  │
│  · writes activity_log + audit_event for every attempt           │
└───┬───────────────┬───────────────┬───────────────┬──────────────┘
    ▼               ▼               ▼               ▼
┌─────────┐  ┌────────────┐  ┌────────────┐  ┌──────────────┐
│HighLevel│  │ Canva      │  │ Gmail      │  │ Scheduler     │
│ adapter │  │ brand board│  │ form email │  │ A2P polling  │
│ OAuth   │  │ 350×180    │  │ snapshot   │  │ follow-ups   │
│         │  │ 300×300    │  │ report     │  │ re-verify    │
│         │  │ 1819×646   │  │            │  │              │
└─────────┘  └────────────┘  └────────────┘  └──────────────┘
      │
      ├─ write endpoints  locations · pipelines · custom fields
      │                    custom values · forms · workflows · contacts
      │                    conversations · media storage · voice agents
      ├─ read endpoints   calendars · opportunities · phone numbers
      ├─ webhooks in      contact created · stage changed · A2P status
      └─ no endpoint      snapshot push · calendar activation
                           user assignment · number purchase
                              └→ these become handoff_request rows.
                                 The capability gap is data, not a
                                 hard-coded exception.

Capability registry

Each task declares the capability it needs. The registry says whether that capability is api, browser or human. When HighLevel ships a snapshot endpoint, one registry row changes and the task silently upgrades from handoff to automatic. No UI work.

Browser automation as a tier

Custom values have no write API but the settings screen is drivable. Treat browser automation as a first-class middle tier — slower, more fragile, flagged as such in the log — rather than pretending it is an API or giving up and calling it human.

Idempotency

Every write carries a key derived from run + task + resource, so a retry after a timeout cannot create a second pipeline. This is what makes Retry safe to put on the screen.

Verification reads, never trusts

Checks re-fetch from HighLevel rather than reading the app’s own write log. A check that passes because we remember writing it is worthless.

Suggested build order

  1. Shell, tokens, status system. Both themes, all four status layers, the chip and node vocabulary. Everything downstream depends on it.
  2. Wizard and the data model. Real capture into subaccount + readiness_domain. Immediately useful even with no agent — it replaces the sheet.
  3. Orchestrator with a mock adapter. The task graph, gates and SSE streaming, wired to a fake HighLevel. Proves the workspace before any credential exists.
  4. HighLevel adapter, read paths first. Verification becomes real before any write does, so the first thing the agent does for real is check existing accounts.
  5. Write paths, in confidence order. Location, pipeline, custom fields, custom values, forms, workflows. Each behind the capability registry.
  6. Handoffs and approvals. Snapshot, calendars, phone, A2P — the parts with real consequences.
  7. Follow-up scheduler and the chase loop. Retire the second tracker table.
  8. Chat, audit export, duplicate configuration. The leverage layer, once the core is trustworthy.

Step 4 is the one worth insisting on. Pointing the verification engine at your eighteen existing subaccounts, read-only, tells you how much of the tracker it can populate on its own — before a single write endpoint is trusted with a client’s account.