--- 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 `