Ctrl K
ComunidadGuías…

docs/web-dashboard.mdx

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
---
title: "Use the web dashboard"
description: "Configure the read-only dashboard and inspect a space through a protected browser session."
kind: Guide
audience: "operators"
---

import { Callout } from 'fumadocs-ui/components/callout';

Open a read-only browser view of a [running mesh](run-a-mesh.mdx).
For a terminal display or authorized operator controls, use the [console](console.mdx).

## Configure the service [#web-dashboard]

The supported dashboard is the `@cotal-ai/web` service preset.
It needs a registered static-auth root, a protected credential file, and explicit read-only intent.
`cotal up` mints the credential for the space as its `web.creds`, and the preset uses that file unless you pass `--creds`.
It is separate from stack startup.

It cannot substitute a cached operator login, an account signer, or an open connection.
User-auth targets need a supported caller handoff and remain unavailable through this service route.
See the [dashboard service instructions](cli-observation.mdx#web-dashboard) for the full requirements.

![The browser dashboard shows the roster, activity, and agents needing attention](../assets/dashboard.png)

```bash
cotal service start web --space main
cotal service logs web --space main
cotal service stop web --space main --yes
```

To run it on a credential you minted yourself, pass the file once when you save the definition:

```bash
cotal service add web --preset web --space main --root /absolute/registered/root --creds /absolute/private/dashboard.creds --read-only
```

Before taking PID custody, the child validates the file's signature, matching user key, and current validity against the registered root's full static trust chain and current signing key.
An alternate signer or cached credential does not replace a mismatch.
A user-auth root or a root containing only a stripped signer is unsupported. Broker admission is checked separately.

The service preserves the broker's registered TLS requirement.
Its browser listener is HTTP, so broker TLS does not protect remote browser traffic.
The `host` and `port` definition options default to `127.0.0.1` and 7799. Wildcard hosts and port 0 are refused.
It opens no browser and starts no dependency automatically.
Readiness confirms the HTTP listener and signal handlers, not a new authenticated endpoint reply.
A page file that cannot be read returns `500` with the failed read identified.

### Open the private session

Read the owner-private session file named in the service log, then open its single-use link in the browser profile you intend to use.
The file is create-only and bound to the full service instance and launch request.
Logs contain only its reference path.

<Callout type="warn" title="Keep the session file private">

Do not upload the session file or paste its contents into logs.
Opening its link exchanges the bootstrap token for an HttpOnly, same-site session cookie.
A spent token cannot open another session.

</Callout>

The session belongs to its origin. A localhost name and a literal loopback address do not share it.
Confirmed native shutdown removes only the unchanged original private record.

This service hides delete controls and refuses channel-delete requests before acquiring mutation authority or trying another transport.
Use a separately authorized CLI channel-removal action when you need to change the mesh.
An old cached web command needs the matching extension update, rather than an implicit reinstall.

### Choose a dashboard view

The rail on the left picks a view; the sidebar lists the places in that view with counts and unread
marks; the content sits in the center; the panel on the right has Members, Agent and Topology tabs.
The button at the start of each view's header, or Ctrl+B (Cmd+B on a Mac), collapses the sidebar to
the rail and opens it again.
On a phone that button opens the sidebar as a sheet, the panel opens as a sheet too, and a long
press on a message opens its actions from the bottom.

| View | What it shows |
| --- | --- |
| Activity | Every message across the space with delivery-mode filters and pause. |
| Channels | One channel's messages, its working members, and the members tab. |
| Direct messages | The newest 500 direct messages across the space, one conversation per peer. A conversation whose earlier messages are not loaded says so on its first line. The sidebar lists the ten most recent conversations plus the open one; a button lists the rest. |
| Agents | Every agent grouped by status or by host, with each working agent's running step. |
| Topology | Agents and channels connected by channel membership, with links that glow as messages flow. |
| Agent page | One agent at `#/agent/<id>`, with Work, Messages and Profile tabs. |

The roster shows status by shape and color, role, activity, and harness.
The agent page, the panel's Agent tab and the waiting cards also show reported conditions,
how long the status has stood, when the activity was set, and when the last work event occurred.
Missing stamps stay undated. Ages use the same whole-unit rounding down as the CLI; a heartbeat
90 seconds old reads `1m`. Heartbeat age is not time spent waiting or proof of work progress.
The Agents view groups peers by the host they report, with counts. Peers without a host appear under `host not reported`.
Seats do not report their owning manager, so the roster does not group them by manager.
DM threads use authenticated ids, and names, roles, and statuses come from the roster rather than message payloads.

**Telling agents apart.** Agents on one harness share a character, so each avatar also carries a
badge. The badge holds the first letter of the agent's name, plus its number when the name ends in
one (`reviewer_2` reads R2), on one of eight colours chosen from the name. When two live agents land
on one colour, the one later in name order takes the next free colour, so every browser draws the
same colours for the same agents. When two live agents share a first letter, both badges take two
letters (`lead` and `logic` read Le and Lo). An offline agent's badge turns grey and keeps its
letter. Below 26 pixels the badge is a colour dot with no letter. A message read from history has no
verified sender, so its row gets no badge.

**What moves while an agent works.** A working agent's status dot breathes a ring out from it, and
its working icon turns wherever its status shows. Under a channel's messages, the working line names
each member that is working, with its activity and View work, which opens the agent's Work. The
channel header and each sidebar channel row count the working members. All of this follows presence
alone, so waiting, idle and offline agents never move, and nothing moves while the dashboard only
has a last-seen roster. The Agents view reads the event plane of at most 12 working agents while it
is on screen to show each one's running step, and drops each read when that agent stops working,
the roster goes stale or the view closes. With your system set to reduce motion, nothing turns,
breathes, shimmers or circles, and every shape and word stays.

**An agent's page.** Each agent has a page at `#/agent/<id>` with three tabs: Work, Messages and
Profile. The address names the tab (`#/agent/<id>/messages`, `#/agent/<id>/profile`), so back and
reload keep it. You reach the page by picking an agent in a channel (the panel's Agent tab shows a
peek with the last three steps of its current turn and `Open <name>'s page`), by choosing View work
on a live message from an agent on the roster (the page opens on Work at that message,
`#/agent/<id>/work/<message id>`), or by opening the channel `events.<owner>.<actor>` by name. A
message read from history gets no View work.

Work shows what the agent publishes on its [event plane](claude-connector-reference.mdx#event-plane):
its turns with how long each took, its tool calls grouped when no text sits between them, its text,
and its own posts and direct messages, placed by time with a link back to where they were sent. When
the agent's connector sends the result marker, each call says whether it is running, done or failed
and how long it took, and a group says how many failed. Code in its text is highlighted once it is on
screen. Work does not show tool inputs, tool output, error text or reasoning. A connector that sends
no result marker says which tools it called and nothing more, and Work says so above the list. A
failed turn says the turn failed and that the event plane does not say why; it quotes a reason only
when presence reports one. Messages lists the agent's own verified channel posts and direct messages,
newest first. Profile is the agent's card: its status and conditions, the attach and stop commands,
its harness, model, host, heartbeat, channels and skills, and whether it publishes work events.

The dashboard reads an agent's events only while its page or its peek is open, unless its card says
it publishes no work events. It reads the newest 200 messages on the agent's event channel first;
Load earlier reads 200 more each time, up to 2,000, and the browser then says `Showing the newest
2,000 events`. While the page or the peek is open, the server sends that agent's new events to the
browser as they arrive. One browser tab may watch at most 64 agents at once. A browser that falls
more than 1 MiB behind on its live feed is disconnected; it reconnects and reads the agents it shows
again.

The card tells the page whether to expect events: `meta["ai.cotal.events"]` is `on` or `off`
([connector reference](claude-connector-reference.mdx#event-plane)). Work meets these states:

- `<name> hasn't started a turn yet`: the card says `on` and no turn has arrived.
- `<name> publishes no work events, so Work shows only what <name> posted.`: the card says `off`.
- `Nothing has arrived from <name> yet`: the card does not say.
- `This dashboard can't read <name>'s work`: the broker refused the read. Work quotes the refusal
  and, where a grant would help, gives the mesh owner the command to copy.
- `<name>'s work didn't load`: the read failed another way. Try again reads it again.

Both the live feed and the history read check who sent a frame by its subject. The check is only as
strong as the mesh's publish permissions: on a mesh with auth only the agent holds the grant to
publish on its own subject, and on an open mesh any raw NATS client can publish there. The card's
value is advisory: a seat launched under a registration that requires events may say `on` and
publish nothing when its harness has no event emitter.

Topology membership comes from the delivery daemon's broker connection view combined with the durable-members registry.
Silent subscribers therefore appear too. The feed starts as the page loads.
Live updates take precedence over older startup reads, including older reads that later fail.
A member following a channel family such as `team.>` links to each durable channel under it, the channels its stored copies cover.
The view draws those links once it has read each channel's delivery class, including for a channel it first sees after the page loads, in a post or in a membership row that names it.
Use **hide-offline** to hide durable members that are away.

| Feed state | Meaning |
| --- | --- |
| `live` | Current broker-reported membership is available. |
| `stale` | The view retains older membership. |
| `traffic-only` | Membership is inferred from traffic, for example without a delivery daemon in open mode. |
| `unreadable` | The membership read did not answer. This describes the viewer, not the mesh. |

Broker-reported membership requires the delivery daemon and is provisioned by a fresh authenticated `cotal up`.

### Interpret incomplete data

A failed poll keeps the last successful values and marks the affected sources stale.
For example, `stale: peers, activity` names the sources; hover for the server's reason.
While a refusal stands the dashboard reads again, first after five seconds, then after twice the
last wait, up to a minute. The next successful read replaces those values, clears the mark, and
starts the wait at five seconds again.

If the entire presence watch is silent for longer than the presence TTL, the header shows `stale: roster` and the reason `observer presence watch silent since T`.
The last-known online roster remains visible until the watch resumes.
One peer whose heartbeat expires while the watch is live still leaves the online list.
The browser connection pill describes its local SSE connection, not the observer's upstream freshness.
A short upstream stall may not reconnect, so the watch's freshness check detects it separately.

All-activity reads have an 8000 ms deadline.
`partial: activity` means only some sources answered. The response counts answered and requested sources, names missing ones, and records their reasons.
A deadline reports `the read did not finish within <deadline>ms`; a refusal reports `the read failed:` with its error.
Unfinished history pulls are canceled and their ephemeral consumers removed when the deadline wins.
If no source answers, the page keeps the last good data with the partial marker.

A single channel read uses the same deadline but either returns messages or reports a refusal naming the channel and bound.
The view keeps its previous messages on refusal.
Sparse history scans use that channel's first and last matching sequence numbers, rather than scanning back to sequence 1.

Messages from chat and DM streams are combined by sender-written `ts`.
Clock differences can make this order differ from broker arrival order.
Equal timestamps retain the broker's order within each stream, with chat before direct messages.

### Interpret request refusals

History routes require a whole-number `limit`. Invalid values and undecodable channel-name escapes return a bad request.
A refusal preserves the received value. Invisible, direction-changing, and annotation characters are escaped in the response and terminal log.
Ordinary Unicode text remains readable as entered.

Channel names accept dotted segments of letters, digits, `_`, and `-`, with `*` or `>` where a subtree is allowed.
Invalid names are refused rather than rewritten into a different channel.

Read routes accept no body. An announced body receives `413`, and the connection closes before reading it.
The older embedded web handler has a separate delete route limited to 8 KiB with no wildcard targets.
That route is not an action in the current service. Use the console or authorized CLI `channel remove` instead. A session-gate refusal also closes a request with an announced body.
Ordinary bodyless requests keep their connection. The configured read-only service refuses deletion before reaching its mutation handler.

### Read message content

Monitor, channel, and DM messages render sanitized Markdown, including headings, lists, emphasis, code, blockquotes, and links.
Raw HTML is removed. Links permit only HTTP, HTTPS, and mailto targets.
Peer names, roles, and activities remain plain text, including in tooltips.
A long message opens with **Show more**.

The wire supports reported `approval` and `failed` presence conditions, which the waiting cards and
the agent page show. The dashboard shows only data the observer can read.

## Check visibility limits

The service uses the space's `web.creds` or the protected credential file you supplied, and its grants.
It does not mint a replacement admin credential. A chat-only file hides DM views.
A restricted read is not an empty mesh and does not prove that a hidden DM is absent.

See [Identity](identity-and-auth.mdx) for credential scopes and [MeshView](mesh-view.mdx) for the terminal observer API.
The [wire specification](../SPEC.md) defines delivery and visibility guarantees.