NetworkDashboard
DocsNetworkDashboard

Dashboard

Give your agent a Home page. One HTML file, no build step.

Key insight: Your agent can own a custom Home page at .co/dashboard.html. The Host reads it and pushes it over the authenticated WebSocket the chat already uses, so there is nothing to serve, fetch, or rebuild.

It starts working on its own

When there is no .co/dashboard.html, ConnectOnion renders the bundled starter fresh — identity, quick actions, recent activity, searchable capabilities, and diagnostics. It writes no file, so upgrades and newly published skills appear automatically.

main.py
from connectonion import Agent from connectonion.network import host host(lambda: Agent("lisa", tools=[...]))
output
The client receives Lisa's current Control Center.

If you create .co/dashboard.html, the file is yours and ConnectOnion never overwrites it.

Your project

my-agent/
├── agent.py
└── .co/
    ├── dashboard.html  ← optional custom Home page
    └── skills/

Live task state belongs to the client

A dashboard snapshot does not receive thinking, approval, input-wait, Stop, failure, or completion frames. O Chat renders that authoritative state outside the sandboxed page. Do not hard-code runtime claims such as “Ready” or “Working” in a custom dashboard.

Editing it

It is a plain HTML file — edit it with any editor. Or ask the agent: the built-in dashboard skill teaches it the file's contract.

In chat

/dashboard put this week's numbers on my home page

Write plain HTML and inline CSS. Two constraints, both enforced by the client's sandbox rather than by convention:

No scripting

<script> tags and inline onclick handlers are blocked by a Content-Security-Policy. Action buttons are the only way to make something happen.

No external URLs

No CDN stylesheets, no remote images, no network fonts. Inline your styles and use data: URIs for images.

No links out

A Home page is one self-contained page. Clients cancel clicks on <a href="https://…">, so such a link renders as dead text — use an action button when you want the user to do something. Same-page anchors (href="#section") work normally.

Keep it under 2MB. The host will not send a larger file, and the Home pane goes blank. Inline images are base64, which is ~33% larger than the source file — compress screenshots before embedding them.

Why so locked down?

The client renders your dashboard.html in a sandboxed iframe under a strict Content-Security-Policy, because from its side the file is untrusted, agent-authored HTML. Everything above follows from that: nothing loads from the network, nothing scripts, and nothing navigates away. A Home page is a glanceable, self-contained page whose one action is running a skill.

Supporting external links later is a deliberate change to that contract, not a setting — it means deciding what a dashboard may navigate to and how (in-sandbox, where the destination still cannot be trusted, or handed to a real browser tab). Until then, treat the page as a closed surface.

Action buttons

A button that runs something declares the skill it runs:

code
<button data-ochat-skill="daily-brief">Build today's brief</button>

Clicking it runs /daily-brief as a visible turn in the chat — the same as typing it. Arguments are optional:

code
<button data-ochat-skill="meeting-prep" data-ochat-args="2pm sync"> Prepare my next meeting </button>

Only project skills work as buttons

Skills in .co/skills/ or .claude/skills/ are published to clients. Your personal skills (~/.co/skills/) and ConnectOnion's builtin skills are not — so a button naming one renders but silently refuses to run. The starter dashboard follows this rule automatically; if you hand-write a button, check the skill's location first.

The client validates every button name against the skills your agent published, so a button can only ever start a skill you actually have.

When it updates

The host sends the file at two moments — nothing is polled, and nothing watches the filesystem.

Delivery

browser                                     agent host
  │──── CONNECT ─────────────────────────────────▶│
  │◀─── CONNECTED ────────────────────────────────│
  │◀─── DASHBOARD_SNAPSHOT ───────────────────────│  Home paints before you type
  │                                               │
  │──── INPUT ───────────────────────────────────▶│
  │◀─── thinking / tool_call / … ─────────────────│
  │◀─── OUTPUT ───────────────────────────────────│
  │◀─── DASHBOARD_SNAPSHOT ───────────────────────│  only if the run changed the file

The post-run send is skipped when the file has not changed since that connection last saw it, so an unchanged Home costs nothing per turn. If you edit dashboard.html by hand while a client is connected, the change shows up after the next run.

An agent with no dashboard.html sends nothing, and clients simply show no Home pane. The wire format is one frame:

code
{ "type": "DASHBOARD_SNAPSHOT", "html": "<!DOCTYPE html>…", "session_id": "550e8400-…" }

If you are writing a client

The HTML is agent-authored, so treat it like any remote document. Two browser-enforced layers, no sanitizer:

  1. sandbox="allow-scripts" without allow-same-origin — an opaque origin, so the frame cannot reach your storage, keys, or parent DOM.
  2. A CSP with a per-render nonce — default-src 'none' plus script-src 'nonce-…' for your own bridge, so the agent's scripts do not run and the page cannot reach the network.

Wrap the HTML — never inject into it

Emit your own <head> first and put the agent's markup in the body. Locating an insertion point by string-matching <head> is defeatable: a <head> inside a comment moves your CSP into that comment and drops the policy entirely, leaving the sandbox as your only layer. Browsers discard a nested <html>/<head>/<body> and keep the children, so a full agent document renders unchanged.

Treat every button click as untrusted intent: verify the source frame, shape-check the skill name, and require it to be in the agent's published skill list — failing closed while that list is still loading. Then run it through your normal send path, so the worst a forged message can do is produce a visible turn the user can see. oo-chat's components/dashboard/ is a reference implementation.

Star us on GitHub

If ConnectOnion saves you time, a ⭐ goes a long way — and earns you a coffee chat with our founder.