Design record for the operator console — the downstream non-technical operator's surface. **Not scheduled for current build;** lands after the spearhead (steps 1-6) and the ACD/escalation rungs (capability ladder rungs 1-2). The current build target is slice-2; the next build target after slice-2 is slice-3 (the OpenAI Realtime adapter). An agent that proposes building the console now is wrong — point it at spec §0. Records the visual-brainstorming session's resolved decisions so they don't get re-litigated at implementation time: §1 Principles (load-bearing): - GUI is a pure API client — **green-zone, never an insider** (ADR-0008); holds no privilege a third party couldn't get from the public API. - Config-as-code is the single source of truth; the UI is a reconciled *view* (the k8s model — declarative desired-state in git, the engine reconciles; console + CLI + git are views over it). - Operator persona, not builder. The builder authors flows in **code + an AI pair** (ARCHITECTURE 'DX spine'); this console is NOT the authoring surface for the technical builder. **No drag-drop authoring canvas** — a deliberately-closed decision (vision-revision §8). - Ships in the batteries-included distro and the all-in-one image (the reference GUI is bundled — 'boom' includes a usable console). §2 IA — Ops Console shell: left nav + center panel + persistent live rail. Dense single-screen; for an operator who lives in it. Live rail always visible regardless of the selected section. §3 Dashboards — Live Ops (default landing) + Quality (sibling view). Maps the k8s split: Live Ops = live operational state; Quality = the data-owned eval loop (capability ladder rungs 3→4). §4 Flows editor — master/detail + a generated, navigable preview map. The preview map is a *view* rendered from the config, not a drag-drop authoring canvas. Earns its place by showing branching (the escalation split) the linear stage-list flattens. §5 Declarative flow schema (illustrative YAML; format is open). The config the console edits via the API; the builder edits in git; the engine reconciles. The map is generated from this structure. §6 Other sections (conventional): Agents · Numbers · Queues · People · Recordings · Settings. §7 Architecture: separate app, public API only; never reaches into the FOB. Config writes go through the reconcile path. Live data rides the event stream off Valkey (ADR-0005); never the 20 ms media loop. §8 v1 vs. later scoping. §9 open decisions (flow config format, map rendering depth, quality metrics, live transport, framework). Cross-refs ADR-0002 (fused vertical + composable platform), ADR-0006 (WebRTC ingress / escalation UX), ADR-0007 (rented trunk / Numbers config), ADR-0008 (FOB / green-zone — the UI is green-zone), ARCHITECTURE.md 'DX spine' + 'GUI & extension architecture', PORT_PLAN §5 (contact-center domain).
14 KiB
Default UI — Design (the operator console)
- Status: Design accepted (direction). Not yet scheduled — a later-rung surface.
- Date: 2026-06-29
- Origin: Visual brainstorming session (the deployability + default-UI thread of the 2026-06-29 strategic review).
- Related: ADR-0002 (fused vertical + composable platform), ADR-0006 (WebRTC ingress / escalation UX), ADR-0007 (rented trunk / Numbers config), ADR-0008 (the UI is green-zone), ARCHITECTURE.md "DX spine" + "GUI & extension architecture", PORT_PLAN.md §5 (contact-center domain).
0. Scope & sequencing — READ THIS FIRST (esp. agents)
Do not implement this yet. This is a design record, not an implementation plan. The UI is a later-rung surface that lands after the spearhead (steps 1–6: media → tap → brain → barge-in → rented-transport PSTN → spend cap) and the ACD/escalation rungs (capability ladder rungs 1–2). The current build target is slice-2 (the agent tap). An agent that proposes building the console now is wrong — point it here, at this section.
When the UI is built, it gets its own slice spec + implementation plan at that time. This document records what it should be and why, so those decisions don't get re-litigated.
This UI is also not the authoring surface for the technical builder. The builder authors flows in code + an AI pair (config-as-text, git-versioned — ARCHITECTURE "DX spine"). This console is for the downstream non-technical operator. There is no drag-drop authoring canvas (a deliberately-closed decision — vision-revision §8). These two facts are load-bearing; do not blur them.
1. Principles (load-bearing)
- The GUI is a pure API client — green-zone, never an insider. A separate application, outside the FOB trust boundary (ADR-0008). It holds no privilege a third party couldn't get from the public API. No backdoors, no coupling to internal config files (FreePBX's mistake). This guarantees API completeness — if the console can do it, the API can.
- Config-as-code is the single source of truth; the UI is a reconciled view. The Kubernetes model (ARCHITECTURE "GUI & extension architecture"): declarative desired-state lives in git and is reconciled by the engine; the API/CLI/console are views over it. The operator edits in the console or the builder edits in git — same source of truth, reconciled. The console is the dashboard, never a side-channel.
- Operator persona, not builder. See §0. The console serves the person running the contact center day-to-day, exactly as integrators built operator UIs on top of Asterisk.
- Ships in the batteries-included distro and the all-in-one image (the reference GUI is bundled — "boom" includes a usable console). The ecosystem can build rival GUIs on the same public API.
2. Information architecture — the Ops Console shell
Chosen shell: left nav + center panel + a persistent live rail. Dense, single-screen; for an operator who lives in it.
┌─────────────────────────────────────────────────────────────────────┐
│ rutster ▸ Acme Support ◴ on shift │ top bar (tenant, session)
├───────────┬─────────────────────────────────────────┬───────────────┤
│ Dashboard │ │ LIVE │
│ Flows │ │ 12 active │
│ Agents │ main panel (selected section) │ 87% contained │
│ Numbers │ │ $4.10/hr │
│ Queues │ │ ⚠ 1 escalation│
│ People │ │ │ persistent
│ Recordings│ │ (always in │ live rail
│ Settings │ │ view) │
└───────────┴─────────────────────────────────────────┴───────────────┘
left nav center panel live rail
- Left nav sections: Dashboard · Flows · Agents · Numbers · Queues · People · Recordings · Settings.
- Live rail (right, persistent): active calls, containment %, spend rate, escalation alerts. Fed by the live event stream (§7). Always visible regardless of the selected section.
3. Dashboards
Two dashboards. Live Ops is the default landing; Quality is a sibling view (a nav/tab sibling of the dashboard, one click away). This maps the k8s split: Live Ops = live operational state, Quality = the data-owned eval loop.
3.1 Live Ops (default)
Leads with working the floor:
- Compact KPI strip: containment · calls · AHT · spend · escalations (today).
- Live calls table: caller · flow · who (🤖 agent persona / queue / 🧑 human) · state · duration · actions. Actions include listen and take over — the escalation gesture (capability rung 2), which joins a WebRTC leg via the audiohook/barge primitive (ADR-0006, PORT_PLAN §2 audiohook).
3.2 Quality (sibling)
Leads with watching the AI:
- Containment trend (today / 7d / 30d) on calls you own.
- "Where AI handed off" — escalation reasons ranked ("asked for refund" ×8, "couldn't find order" ×5, "wanted a human" ×4), each drilling into the calls/transcripts. This is the data-owned failure signal that feeds the self-improvement loop (capability rungs 3→4) — the differentiation, made visible. Beware the containment-vs-resolution metric trap: surface resolution, not just deflection.
4. Flows editor — the "call-flow builder"
The console's flagship surface, and the answer to "what is a call-flow builder in the AI era." It is a structured config editor, not a drag-drop IVR canvas. A flow = what happens on an inbound call.
Shape: master/detail editor + a generated, navigable preview map.
┌─ Flows ▸ Acme Support ───────────────────────────────────────────────┐
│ PREVIEW — generated from config · click a node to edit │
│ 📞 Number → 🤖 Agent → ↗ Escalation? ─┬─ ✓ contained → 📝 Post-call │ generated map
│ └─ ✗ stuck → 🧑 Queue → Human │ (read-only, navigable)
├──────────────┬───────────────────────────────────────────────────────┤
│ STAGES │ AGENT — selected │
│ 📞 Inbound │ persona [ Support Bot ▾ ] │ master / detail
│ 🤖 Agent ◂ │ knowledge base [ Acme Docs ▾ ] │ editor
│ 🕐 Hours │ greeting [ "Hi, Acme support…" ] │
│ ↗ Escalation │ [ + tool ] [ + guardrail ] │
│ 📝 Post-call │ │
└──────────────┴───────────────────────────────────────────────────────┘
- Master/detail: the stage list (left) + the selected stage's structured form (right).
- Generated preview map (top): a horizontal pipeline rendered from the config — read-only and navigable (click a node → selects that stage below; edits regenerate the map). It is a view, not an authoring canvas. It earns its place by showing branching the linear stage-list flattens (the escalation split). Map and editor stay in sync; the config is the truth.
- Flow spine (the stages):
Inbound → Agent → Hours → Escalation → Post-call, branching at escalation (contained → Post-call/stuck → Queue → Human). - Agent stage fields: persona (ref) · knowledge base · greeting · tools · guardrails.
Why this is "genuinely new design space, not dialplan 2.0": the conversation half-dissolves into the agent (the model improvises the dialogue), so what's left to author is the routing / escalation / business scaffolding around the agent — which is mostly structured config, not a node graph.
5. The declarative flow schema (the source of truth the console edits)
The console reads/writes this through the public API; the builder edits the same files in git; the engine reconciles. Illustrative (format — YAML/TOML/typed — is an open decision; it must be typed, validated, and LSP-friendly per ARCHITECTURE "DX spine"):
# flows/acme-support.yaml — reconciled desired-state; console edits this via the API
flow: acme-support
inbound:
numbers: ["+15550100", "+18005550111"] # → Numbers section maps these (ADR-0007 rented transport)
agent:
persona: support-bot # ref → agents/support-bot.yaml (reusable)
knowledge_base: acme-docs
greeting: "Hi, Acme support — how can I help?"
tools: [order-lookup, refund-status]
guardrails: [no-pii-readback, max-attempts-3]
hours:
timezone: America/Los_Angeles
open: { mon-fri: "09:00-17:00" }
closed_action: voicemail
escalation:
when: [ai_stuck, caller_requests_human, sentiment_negative]
to: queue:support # → Queues / People (rung 2 takeover)
post_call:
- transcript: { to: crm }
- summary_email: { to: owner }
spend:
cap_per_call_usd: 0.50 # ENFORCED in-boundary (rutster-spend), not advisory
Agent personas (agents/*.yaml), knowledge-base refs, queues, and number inventory are separate
reusable objects referenced by flows. The preview map (§4) is generated from this structure.
6. Other sections (conventional — described, not mocked)
- Agents — reusable AI agent definitions (persona, KB, prompts, tools, guardrails) referenced by flows. The brain itself is green-zone/external; this configures how it's invoked.
- Numbers — phone-number inventory and number→flow mapping; rented-transport provider config (ADR-0007: CPaaS keys / out-of-tree SBC endpoint).
- Queues — ACD config (skills, overflow, SLAs) — PORT_PLAN §5 domain core.
- People — human agents (skills, presence, state); the escalation targets for rung 2.
- Recordings — calls / transcripts / CDR; the drill-down target from Quality. Data the operator owns.
- Settings — trunk/rented-transport, spend caps, brain/tap config, tenancy, RBAC, secrets refs.
7. Architecture & data flow
- Separate app, public API only. The console talks to the rutster control API (REST/gRPC) for config CRUD and to the event stream (WebSocket/SSE) for live data (the rail + Live Ops). It never reaches into the FOB; it has no privileged channel (ADR-0008).
- Config writes go through the reconcile path, not direct mutation — the console PUTs desired-state; the engine reconciles (k8s model). Concurrent git edits and console edits converge on the same store.
- The preview map is generated client-side from the fetched flow config — no separate "map" data model; the config is the model.
- Live data (rail, live-calls table, containment) rides the event stream off Valkey (ADR-0005) — never the 20 ms media loop.
- Auth: SSO + RBAC + per-tenant scoping (this is legitimate server ingress — ADR-0006 "tap ≠ ingress"). Multi-tenant isolation is enforced server-side; the console is just a scoped client.
8. Scope when built — v1 vs. later
v1 (when scheduled): the Ops Console shell; Live Ops dashboard; Flows editor (master/detail + preview map) over the flow schema; Numbers, Agents, Settings enough to stand up a flow. Read-only Recordings.
Later: Quality dashboard depth (trend analytics, eval-set export — rung 3→4); rich Queues/People management; rival-GUI ecosystem docs; theming.
Non-goals (ratified, do not add): a drag-drop authoring canvas; the builder's authoring surface (that is code+AI); any privileged/backdoor channel; coupling to internal config-file formats.
9. Open decisions
- Flow config format — YAML vs TOML vs a typed schema with generated bindings (must be LSP-friendly).
- Map rendering — depth of branching shown; how sub-flows / shared fragments render.
- Quality metrics — exact containment/resolution definitions (avoid the deflection-gaming trap).
- Live transport — WebSocket vs SSE for the event stream to the console.
- Framework — out of scope here; an implementation-time decision for the future slice spec.