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.
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.
The agent holds credentials and the action is reversible. It runs, logs the payload, moves on. 11 of 15 tasks.
An API exists, but the action spends money, is irreversible, or submits the client’s legal identity to a third party. 2 of 15.
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.
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
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.
Interrupts during the workspace stage — each resolves back into the run
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.
Screen inventory
| Screen | Job | Primary action | In prototype |
|---|---|---|---|
| Dashboard | Is anything on fire, and what is in flight | Create new subaccount | Built |
| Subaccount table | Find one account among hundreds | Open a build | Built |
| Wizard · business | Capture what the agent needs | Continue | Built |
| Wizard · brand | Colours, logo, contact, EIN | Continue | Built |
| Wizard · configuration | Choose modules, or accept the recommendation | Continue | Built |
| Wizard · review | Last look before anything is written | Generate setup plan | Built |
| AI setup plan | Consent to a specific sequence, not a black box | Approve plan & start | Built |
| Agent workspace | Watch, steer, intervene | Approve / retry / skip | Built |
| Approval modal | Decide with the payload in view | Approve & continue | Built |
| Handoff modal | Do the part the API can’t | I’ve done it — verify | Built |
| Verification | Trust the build, or see exactly why not | Fix issues with AI | Built |
| Completion | Hand off and move on | Open subaccount | Built |
| Audit log | Prove what changed and when | Expand an action | Built |
| Agent chat | Ask instead of hunting | Act on a suggested chip | Built |
| Approval queue | One place for every waiting decision | Batch approve | Spec only |
| Snapshot registry | Know what v4.2 actually installs | Diff versions | Spec only |
| Connections | OAuth health, token expiry, scopes | Reconnect | Spec only |
| Agent permissions | Set what runs without asking | Save policy | Spec only |
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 →] │ └──────────┴──────────────────────────────────────────────────────────────────────┘
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.
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”)
| Stage | Badge | Meaning & exit condition |
|---|---|---|
Subaccount Creation | Neutral | Form received, location not yet standing. Exits when the location ID exists. |
In Review | Active | Agent building or specialist checking. The working state — most accounts sit here. |
Waiting on DNS Records | Client | Blocked on the client’s domain or their web developer. Not an agent failure. |
Phone # Pending | Blocked | The dominant bottleneck in your data. Number not purchased or not authorised. |
A2P Approved | Cleared | Carrier registration through. Paused SMS workflows resume automatically here. |
Initial Setup Complete | Done | Build finished and verified. Handover to the client. |
On Hold | Muted | Client 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
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)
| Status | Node shape | Behaviour |
|---|---|---|
pending | Hollow circle, grey | Dependencies unmet or not reached. |
running | Filled circle, pulsing halo | Streaming activity. Exactly one task at a time. |
awaiting_approval | Ring, amber, tail “approve” | Run halted. Modal open. Nothing written. |
awaiting_human | Square, violet, tail “you” | Instructions issued. Agent re-reads state on return. |
completed | Filled circle + check, green | Written and log payload recorded. |
failed | Filled circle + ✗, red | Cause named, recovery actions offered. Run halts. |
skipped | Dashed circle, struck label | Deliberately 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.
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
| Control | Guarantee |
|---|---|
| Pause | Finishes the in-flight API call, then stops. Never leaves a half-written task. |
| Resume | Continues from the exact task and log position. State is server-side. |
| Retry | Re-runs one task. The task’s first step is always the check that failed, so a retry without a fix fails identically and fast. |
| Skip | States downstream consequences before confirming, then records the skip in the audit log. |
| Stop | Ends the run. Nothing is rolled back — the copy says so plainly, because silent rollback of a client’s live subaccount would be worse. |
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
| Element | Why it is there |
|---|---|
| Task & subaccount | Approvals arrive by notification; the operator may not have context. |
| Confidence | Tells them how hard to look. |
| Reversibility | The 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 you | Names the reason class: money, irreversibility, or the client’s legal identity leaving the system. |
| Exact payload | Monospace, verbatim, scrollable. Not a summary of the payload. |
| Reject · Ask · Approve | Three 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.
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
| Kind | Example from your data | Treatment |
|---|---|---|
| Hard block | “Phone number purchase pending” — ERR_NO_NUMBER_ASSIGNED | Run 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 pending | A2P under carrier review; DNS propagating | Task 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. |
| Transient | Rate limit, 5xx, token refresh | Retried 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.
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.
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.
| Width | Shell | Workspace |
|---|---|---|
≥ 1180px | Rail + content | Three panes side by side. The design target. |
900–1180 | Rail + content | Inspector becomes a right drawer over the activity stream; task rail narrows but stays. |
760–900 | Rail + content | Dashboard tiles 2-up; wizard fields single column; feature grid single column. |
< 760px | Rail becomes a sheet behind a menu button | Three 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.
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.
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
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.
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.
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
- Shell, tokens, status system. Both themes, all four status layers, the chip and node vocabulary. Everything downstream depends on it.
- Wizard and the data model. Real capture into
subaccount+readiness_domain. Immediately useful even with no agent — it replaces the sheet. - Orchestrator with a mock adapter. The task graph, gates and SSE streaming, wired to a fake HighLevel. Proves the workspace before any credential exists.
- 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.
- Write paths, in confidence order. Location, pipeline, custom fields, custom values, forms, workflows. Each behind the capability registry.
- Handoffs and approvals. Snapshot, calendars, phone, A2P — the parts with real consequences.
- Follow-up scheduler and the chase loop. Retire the second tracker table.
- 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.