---
summary: "Session dashboards: architecture and implementation plan (technical design, pre-GA)"
read_when:
- Implementing or reviewing the session dashboard (boards) feature
- Changing widget hosting, the widget bridge, or board storage
title: "Dashboard Architecture"
---
Technical design document for the session dashboard feature, written before and
during implementation. It is the source of truth for the build-out. When the
feature ships, `/web/dashboard` becomes the user-facing page and this page stays
as the architecture reference.
## Vision
Working with an agent today is a text stream. The dashboard makes it a
workbench: the agent renders live, interactive widgets; the user pins them onto
a persistent surface; chat docks to the side (or hides) and the main content is
the board. You go from "talking to the agent" to "operating a control panel the
agent built for you" without ever leaving the session.
Principles:
- **A board is a face of a session, not a new object.** Every session (thread)
has two faces: the transcript and the board. A session with no pinned widgets
is plain chat. Pin one widget and the board exists. Boards inherit the
session's identity, agent ownership, naming, pinning, and lifecycle. There is
no `dashboard_create`, no board registry, no separate ACL model.
- **Agent parity.** Everything the user can do on a board, the agent can do
with tools: add/update/remove widgets, arrange them, manage tabs, switch the
visible tab, dock or hide the chat.
- **Native, not embedded.** The board is Lit components in the Control UI shell
(the same design system as the rest of the app). Only widget _content_ is
sandboxed in iframes. No URL bar, no browser chrome.
- **Small agent surface.** Widgets are addressed by stable name and updated in
place. Layout is a fluid auto-compacting grid; the agent speaks sizes and
anchors, never pixels or coordinates.
- **Capabilities over trust.** Widget code is arbitrary agent-authored HTML/JS
in a hard sandbox. Reach (gateway data, actions, network) exists only through
a declared, operator-granted capability manifest.
## Concepts
| Concept | Definition |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Session (thread) | Existing gateway session, keyed by stable `sessionKey`. Owned by an agent. |
| Board | The widget face of one session. Exists iff the session has widgets/tabs. Survives `/new`/`/reset` (attached to `sessionKey`, not the transcript). |
| Tab | A presentation page of a board: which widgets, their arrangement, and the chat dock state (`left`/`right`/`bottom`/`hidden`). Boards start with one implicit tab. |
| Widget | Named, sandboxed HTML/JS program owned by the session. Addressed as `sessionKey` + `name`. Updated in place by name. |
| Capability manifest | Per-widget declaration of reach: `data` (read bindings), `actions` (allowlisted verbs), `prompt` (send to session), `net` (allowed origins). |
| Pin (widget) | Moving a transcript widget onto the session's board (user affordance or agent tool arg). Unpin removes it from the board. |
| Pin (session) | Existing sidebar pinning of sessions. A pinned session with a board opens on its board face. |
## UX flows
- **Graduation:** agent calls `show_widget` in any chat → widget renders inline
in the transcript exactly as today → hover shows **Pin to dashboard** → widget
appears on the session's board. The agent can pass `pin: true` to do the same.
- **Board view:** a session with a board gets a face toggle (Chat / Dashboard).
Board view = tab strip (only when >1 tab) + fluid grid + docked chat pane.
Chat dock is resizable, movable (left/right/bottom), and collapsible exactly
like the sidebar. Per-tab dock state is remembered.
- **Drag:** user drags widgets; grid auto-compacts (widgets float up, neighbors
reflow). Resize by handle snaps to size steps. No pixel placement — for
anyone.
- **Reset warning:** `/new` / `/reset` on a board-bearing session asks for
confirmation in the web UI ("context resets, the dashboard stays") and keeps
the board.
- **Sidebar:** pinned sessions render their board face when they have one.
The Home session's board is the default "agent dashboard".
- **Interactions** (three tiers, see below): silent state events, visible
prompt sends, and automation triggers.
## Interaction tiers
1. **State events (default).** Widget UI interactions the model should know
about but not respond to. `bridge.emitState({...})` appends a structured
session notice (same mechanism as group-activity notices). No agent turn is
started; the model sees accumulated notices on its next run.
2. **Prompts (explicit talk).** `bridge.sendPrompt(text)` — requires user
activation; sends a visible user message into the session (the docked chat
shows it). Rate-limited; each send is user-confirmed unless the widget holds
the `prompt` capability grant.
3. **Automation.** `bridge.runAction(name, args)` — fires a manifest-declared
action. Initial verb set: `cron.trigger` (run an existing cron job now) and
`binding.refresh`. Cron jobs already run in visible, isolated run-sessions
and can use a cheaper model: that is the "small model powers the widget"
path. No hidden sessions anywhere.
## Widget model and hosting
Widget HTML/JS is authored by the agent (typically via `show_widget`), wrapped
in the standard document shell (CSP meta, size reporter, bridge bootstrap) and
rendered in `