LoopX Dashboard
August 22, 2026 · View on GitHub
This is the first product dashboard shell for LoopX. It renders the status data contract with a React/Vite control-plane UI.
Current Status
The dashboard is an experimental operator preview, not the primary LoopX workflow. The CLI, status JSON, run history, and active goal files remain the source of truth for day-to-day work. Use the dashboard for public-safe demos, local inspection, and focused UI experiments until it receives a dedicated product iteration pass.
Fresh Clone Public Preview
No private LoopX state is required for the first dashboard preview. The
app bundles examples/status.example.json as its public-safe example source,
so a fresh checkout can validate and open the UI before starting any local
status server:
cd apps/presentation/dashboard
npm ci
npm run smoke:demo-readiness -- --skip-browser
npm run dev:web
Then open http://127.0.0.1:5173/. Use the bundled example source for a public
demo, or switch to a loopback status URL only after you have started
loopx serve-status locally. Do not commit status.local.json or live
status exports; they can contain local registry/runtime paths and private
project summaries.
npm run dev:web starts only the Vite UI with the bundled example. npm run dev
also starts the loopback status and Chat services and therefore requires a
Python 3.11+ interpreter; see the development section below.
The first read-only channel frontstage lives at /frontstage. It renders a
public-safe goal_channel_projection_v0 fixture as a dense channel board with
decision, quota, user todo, agent todo, active-claim, open-gate, artifact,
timeline, and truth contract lanes. Treat it as the product-path replacement for expanding the
no-dependency static HTML renderer; the Python renderer remains the fallback
demo/diagnostic surface.
The product interaction baseline lives in
docs/product/surfaces/frontstage-dashboard-interaction-baseline.md: showcase mode is
the public case-driven homepage surface, while mode=ops is the dense,
read-only control-plane workspace.
The frontstage first screen is meant to teach the control-plane model before a
developer reads raw status JSON. The top operations strip answers whether the
human gate is explicit, whether agent work is active, how many lanes are
claimed, and whether recent evidence exists. The Role Map then separates the
owner, agent lane, and claim-owner responsibilities so a new contributor can
tell which part of the system is waiting, running, or coordinating side work.
In ops mode, the user/agent todo lanes also have URL-backed search and lane
filters so a developer can reproduce the exact projected candidate slice during
review without changing the underlying LoopX state.
The Efficiency Evidence panel pulls the public-safe self-iteration case from
the showcase catalog so the hosted frontstage can show commit-backed baseline,
actual-window, compression, and evidence-boundary signals without exposing raw
sessions. The Async Work Loop and Showcase Cases panels render the same
catalog as animated narrative lanes and compact case cards, linking back to
public GitHub case pages for deeper reading. Operations lanes are derived from
the read-only projection; showcase panels are derived only from public-safe
showcase metadata. Neither surface is browser write authority.
The default frontstage route is public showcase mode. It ignores statusUrl
and renders only bundled showcase/demo material, so a copied or hosted URL does
not accidentally project local registry state.
examples/fixtures/frontstage-private-status-trap.public.json is the synthetic
negative fixture for that boundary: browser smokes prove its GH_FAKE_* live
status markers stay out of showcase URLs and appear only after an explicit
mode=ops load.
For contributor onboarding, use /frontstage?mode=developer. This is still a
public-safe read-only view: it shows the agent-first start path, quota/status
health checks, peer workspace guard, todo claiming, local server checks,
and writeback boundary without loading live registry data. It is meant to help
new developers understand how to enter LoopX from Codex CLI or another
agent TUI before they open the denser ops board.
The developer extension cockpit lives at /frontstage/developer. It is a
read-only contributor workbench for status-contract exploration, projection
diffing, fixture generation rules, smoke-run checklists, and component examples
so new projection work does not require reverse-engineering the large
dashboard page. It uses static public contracts and fixtures only; live status
feeds, registry files, and browser write APIs stay out of this route.
For live local control-plane inspection, explicitly enter ops mode:
/frontstage?mode=ops&statusUrl=http://127.0.0.1:8766/status.json. The route
then reads attention_queue.items[].goal_channel_projection and stays
read-only; if the feed is missing or has no projection, the bundled demo
fixture remains visible. Ops-mode status sources are limited to relative or
loopback URLs so public frontstage links do not silently pull external/private
feeds. The ops feed is loaded through a TanStack Query-backed local data layer
with schema-version freshness checks, stale-daemon repair copy, and a
local_dashboard_api capability projection. It remains read-only by default:
reward or control-plane write affordances require explicit loopback opt-in,
advertised capability URLs, and preview-locked local APIs. Do not use ops-mode
URLs as public links.
To create a public-safe static bundle for demos, Lark shares, or future GitHub Pages hosting, export the frontstage with the sanitized fixture:
cd apps/presentation/dashboard
npm run export:frontstage-share
The default output is /tmp/loopx-frontstage-share-bundle. It includes a
compiled dashboard, status.frontstage-share.json, a direct /frontstage/
static route, a manifest, and a README with the local serve URL. The exporter
rejects local paths, private registry state, internal document hosts, raw-key
leaks, token assignments, and private key material before reporting success.
The share-bundle smoke also scans generated files for the synthetic GH_FAKE_*
trap markers so public exports cannot accidentally carry a live-status payload.
For repository Pages hosting later, rerun the same exporter with
-- --base /loopx/ --out-dir <artifact-dir> and publish only that
generated site artifact.
Run
From any directory after installing LoopX:
loopx dashboard
This command installs the dashboard's npm dependencies on first run, then
starts the Vite UI together with the loopback status and Chat services. Open
http://127.0.0.1:5173/ after the readiness messages appear.
If a LoopX Chat service is already running on the default port (for example
started by the Tauri desktop shell), loopx dashboard detects it by its exact
capability fingerprint and reuses it instead of failing: it prints the running
URL and opens the browser/PWA route, then exits without starting a second
server. The desktop shell reuses the same services in the opposite order, so
the browser/PWA and native entry points can be started in either order.
The equivalent source-checkout command remains available for dashboard development:
npm ci
npm run build
npm run dev
npm run dev starts the Vite UI together with the loopback status and Chat
services on ports 5173, 8766, and 8767. Use npm run dev:web when those
LoopX services are already running separately. Vite proxies the default
/status.json request to port 8766, so an SSH user only needs to forward port
5173 for the normal development page.
The full-stack launcher needs a Python 3.11+ interpreter for the status and
Chat services. It honors LOOPX_PYTHON first, then the Python recorded by the
LoopX installer in .loopx-python, then the repository .venv,
python3.13/python3.12/python3.11 on PATH, and common Homebrew locations.
If your default python3 is older, point it at an existing interpreter:
LOOPX_PYTHON=/path/to/python3.12 npm run dev
Both the root dashboard and the packaged /chat/ route expose the same
installable PWA manifest and icons. The default loopx dashboard command opens
/chat/; its manifest therefore scopes the installed app to /chat/. This is
an installable standalone surface, not an offline-cache service worker.
The live /status.json route keeps repository-wide public-boundary scanning
out of the first-screen request. Its contract projection reports that scan as
deferred; run loopx check before publishing or pushing public surfaces to
perform the complete boundary audit.
The default screen is the Personal Workspace—LoopX's sole operator-facing frontend. It provides a unified, coherent experience for managing long-running agent Goals:
-
LoopX Manager Overview (
/): Cross-Goal triage answering operator priorities before raw drill-down:- 4-lane overview flow (
需要你/执行中/观察中/已安排); - System Health diagnostics highlighting control-plane and registry status;
- Unified conversation tray supporting global questions, Goal creation drafts, and progress summaries.
- 4-lane overview flow (
-
Goal Workspace (
/?goalId=<id>): Dedicated workspace for an individual Goal:- Chat: Goal-scoped Agent communication, streaming turns, and action previews;
- Tasks: 4-column kanban board (
待确认,待执行 / 进行中,定时与持续,已完成) with quick status updates and one-click conversion of Agent replies to Task drafts; - Files: Repository artifact browser and file inspects;
- Context Drawer: Goal diagnosis, repository bindings, Lark Topic connections, and session health.
-
Action Safety & Control Plane: Durable modifications to Goals, Todos, Heartbeats, monitors, or settings follow the typed preview → explicit user confirmation → verified receipt protocol. The browser never performs unmediated direct writes to control-plane truth. The Goal directory keeps only active Goals in its main list. Use the pause action beside a Goal to preview and confirm a reversible stop; stopped Goals retain their Todos, history, and evidence in a collapsed Stopped Goals section and can be restored from the same section. Stopping a Goal pauses automatic Agent turns; it does not mark the Goal complete or delete state. A stopped Goal leaves active attention and projects zero effective quota, allowing the scheduler to stop host automation such as a Codex App heartbeat while retaining the configured quota. Goal stop and
quota.compute=0share this shutdown path but keep distinct resume authority: only Goal lifecycle resume may reactivate a stopped Goal, while an explicit positive quota update resumes a quota pause. Stop does not force-kill an in-flight tool call; the nextquota should-runpacket tells the host to pause or delete the recurring heartbeat before another automatic turn. The equivalent CLI flow is:loopx goal-lifecycle --goal-id <goal-id> --operation stop loopx goal-lifecycle --goal-id <goal-id> --operation stop --execute loopx goal-lifecycle --goal-id <goal-id> --operation resume --execute loopx quota status --goal-id <goal-id>The first command is a zero-write preview. The executed commands write the authoritative source registry, refresh the shared registry projection, and verify both readbacks. Resume restores scheduling eligibility; quota, Gates, and Todos still decide whether work may run.
-
Public Frontstage (
/frontstage): Public/frontstagecontinues to serve as an unauthenticated, read-only showcase and public-safe presentation surface. Real local operator workflows belong exclusively in the Personal Workspace.
Load Live Status
For the canonical multi-project home, start a global status server. This is the normal operator view for all projects connected into the shared registry:
loopx serve-status --global-registry --port 8766 --limit 80
On macOS, keep the status feed and the Chat service running after login with the user-level LaunchAgent helper:
../../scripts/macos-dashboard-launchagent.sh install
The helper starts:
http://127.0.0.1:8766/status.json
http://127.0.0.1:8767/chat/
Use ../../scripts/macos-dashboard-launchagent.sh restart|stop|uninstall|status
for local service operations. status also probes
http://127.0.0.1:8766/status.json and prints the
status_contract.schema_version; if it is missing or below the expected
dashboard version, run restart before a demo so the live feed is not served by
an older daemon. Logs live under ~/Library/Logs/loopx/.
The status output path is covered without touching real macOS services by
python3 examples/macos-dashboard-launchagent-status-smoke.py.
Then open the packaged personal workspace:
http://127.0.0.1:8767/chat/
Named local and SSH-tunnel sources
The Personal Workspace source switcher keeps a browser-local catalog with one built-in Local source and any number of named SSH-tunnel sources. Start the status server on each remote host, then forward each host to a distinct local port:
# On the remote host
loopx serve-status --global-registry --host 127.0.0.1 --port 8766
# On the operator machine; choose a different local port for every source
ssh -N -L 8876:127.0.0.1:8766 <remote-host>
The add-source panel reads only explicit, shell-safe Host aliases from the
operator machine's OpenSSH config through the current loopback Dashboard
origin. Packaged loopx dashboard serves this endpoint from its Chat runtime,
so a custom Dashboard port works without a fixed discovery port; development
mode proxies the same path to the local Chat service. Select an alias, choose a
local port, copy and run the generated tunnel command, then add the source.
Wildcard hosts, negated patterns, IdentityFile, ProxyCommand, hostnames,
credentials, and config paths are never projected to the browser. The manual
loopback-URL path remains available for custom forwarding setups.
The browser catalog stores only the selected alias label and loopback URL; LoopX does not store SSH credentials or open the tunnel. The active source reports its connection health. Local stays interactive, while every custom SSH-tunnel source is explicitly read-only even though its forwarded URL is loopback.
The switcher intentionally has no synthetic All source. Independent status feeds do not yet share authority, identity, or deduplication semantics, so combining them would imply cross-host coordination that the control plane has not established.
For project-local debugging or a disposable loopx demo, start a local
status server from the project you want to inspect:
loopx serve-status --port 8765
--global-registry is intentionally explicit: it keeps the multi-project home
on the shared registry even when you launch it from inside a project checkout,
while plain serve-status remains useful for project-local debugging.
Keep the dashboard app running and use ?view=ops, the Live source button,
or load this project-local URL from the source control:
http://127.0.0.1:8765/status.json
The status server binds to 127.0.0.1 by default and sends no-store JSON with
local CORS headers for the Vite dashboard.
It also serves POST /reward/dry-run for validating the selected goal/run and
public-safe reward text. To allow direct local dashboard submission, start the
server with the explicit write flag:
loopx serve-status --port 8765 --enable-reward-write-api
The write flag is loopback-only. Without it, the dashboard can validate a reward draft but cannot append feedback.
Load Static Status
Use a local static export:
python3 -m loopx.cli --format json status > apps/presentation/dashboard/public/status.local.json
cd apps/presentation/dashboard
npm run dev
Then load /status.local.json from the dashboard source control.
status.local.json is intentionally git-ignored because live status exports can
contain local registry/runtime paths and private project summaries. Keep it as a
local inspection file only. For public demos, use the sanitized
examples/status.example.json fixture instead of committing a live export.
You can also import a JSON file directly in the browser, or load a local API
URL that returns the same loopx --format json status shape.
Live Single-Page Session Dash
The primary way to watch session task progress is a loopback single-page panel:
loopx dash # serve at http://127.0.0.1:8767/ (auto-refresh every 10s)
loopx dash --goal-id <goal-id> # narrow the panel to one goal
Open the printed URL in any browser and keep it open while the agents work.
The page is a human-focused fleet view: an overview strip of sessions, goals,
active / needs-you / blocked / done buckets, open todos and run statistics,
followed by one card per session with the goals it owns and each goal's
status badge, todo progress bar, waiting reason, and latest run. It refreshes
itself in place every 10 seconds by re-fetching the /panel fragment.
Internal control machinery (decision frames, work-lane contracts, quota slot
math, source warnings) is intentionally not rendered. The panel is
read-only: no write controls, no browser write authority. The server binds
loopback only and exposes no write routes.
A one-shot static snapshot is also available for demos or sharing:
loopx dash generate [--goal-id <goal-id>] --out dash.html
Open dash.html in any browser. The command runs the public/private
boundary scan before reporting success and withholds output on failure.
# print the projection + html as JSON instead
loopx --format json dash generate --goal-id <goal-id>
See the session dash panel design for the layout, data boundary, and validation contract.
Browser Smokes
Dashboard browser smokes are explicit because they start a temporary Vite server. For demo readiness, run the grouped public-safe smoke:
npm run smoke:demo-readiness
That command runs the LaunchAgent status-output smoke, the structured
promotion-gate fresh/warning contract smoke, the source-contract smokes, and
the current Home and Personal Workspace browser smokes. The decision-freshness
and promotion-readiness read models remain covered by focused control-plane
smokes instead of browser tests for the retired legacy Ops view. In CI
environments without Playwright/Chrome,
use:
python3 ../../../examples/dashboard-demo-readiness-smoke.py --skip-browser
The individual browser smokes are still available when you want to debug one surface:
npm run smoke:home-browser
npm run smoke:personal-workspace
npm run smoke:frontstage-share-bundle
node examples/dashboard-throttled-browser-smoke.mjs
node examples/dashboard-operator-gate-browser-smoke.mjs
The home browser smoke protects the canonical control-plane home. It uses a
public-safe four-project fixture, opens the root route without view=share,
checks the Chinese operator copy for user todos, agent priorities, showcase
activity, quota guard state, per-project top-4 todo status, and state
writeback, and rejects raw machine tokens such as single_surface,
focus_wait, or raw internal slot constraints on the first screen. It also captures desktop
and mobile first-screen / decision-frame screenshots under
output/playwright/dashboard-home-visual-acceptance/ and fails on horizontal
overflow so density regressions are visible before calling the frontend broadly
usable. It uses an installed Playwright package or the Codex bundled runtime
when available, and starts Vite through the local vite package rather than
depending on npm / npx being on PATH.
The ops decision-freshness smoke protects the detailed ?view=ops panel with
two public fixtures: a live-like zero-item summary and a stale/rebase-required
decision example. It verifies the rendered Chinese/English operator copy,
counts, top affected goal, and exact-replay wording instead of relying only on
source-string checks.
The promotion-readiness smoke protects the detailed ?view=ops panel with
fresh, stale, and missing readiness fixtures. It verifies the status badges,
readiness/rerun decision, artifact window, age, reason, and source-of-truth copy
for canary promotion readiness. The canonical fixture/browser script is
examples/dashboard-promotion-readiness-browser-smoke.mjs; use the npm script
above instead of calling ad hoc duplicate filenames.
The grouped demo-readiness path also runs examples/promotion-gate-smoke.py
before browser checks, so the structured gate_state, can_promote, and
should_warn contract is covered even when browser smokes are skipped.
The throttled smoke protects the "quiet scheduling state" first screen. The operator-gate smoke protects planned high-complexity goals: they should appear as controller/user actions, not Codex-ready work. Those older browser smokes still use the local Playwright CLI wrapper.