---
title: Agent Inbox
---
```js
import {API, apiFetch, pollDelay, waitForVisible} from "./components/config.js";
```
```js
// Live poll: messages list
const inboxState = (async function*() {
let failures = 0;
while (true) {
let messages = [], notices = [], ok = false;
try {
const resp = await apiFetch("/messages/?limit=100");
ok = resp.ok;
if (ok) messages = await resp.json();
const nresp = await apiFetch("/messages/notices");
if (nresp.ok) notices = await nresp.json();
} catch {}
failures = ok ? 0 : failures + 1;
yield {messages, notices, ok, ts: new Date()};
await waitForVisible(pollDelay({ok, failures}));
}
})();
```
```js
const messages = inboxState.messages ?? [];
const notices = inboxState.notices ?? [];
const _ok = inboxState.ok ?? false;
const _ts = inboxState.ts;
// Broadcast read state is per reader (STATE-WP-0093): the dashboard has no
// reader identity, so live broadcasts get their own section.
const isBc = m => m.to_agent === "broadcast";
const unread = messages.filter(m => !isBc(m) && !m.read_at && !m.archived_at);
const read = messages.filter(m => !isBc(m) && m.read_at && !m.archived_at);
const broadcasts = messages.filter(m => isBc(m) && !m.archived_at);
const archived = messages.filter(m => m.archived_at);
// Group unread by agent for KPI
const agentCounts = {};
for (const m of unread) {
agentCounts[m.to_agent] = (agentCounts[m.to_agent] ?? 0) + 1;
}
const topAgents = Object.entries(agentCounts).sort((a, b) => b[1] - a[1]).slice(0, 4);
```
# Agent Inbox
```js
import {injectTocTop} from "./components/toc-sidebar.js";
import {withDocHelp} from "./components/doc-overlay.js";
const _kpiBox = html`
Inbox
${topAgents.map(([agent, count]) => html`
`)}
`;
const _liveEl = html`
●
${_ok
? `Live · updated ${_ts?.toLocaleTimeString()}`
: html`Offline — run: make api`}
`;
injectTocTop("inbox-kpi-box", _kpiBox);
injectTocTop("live-indicator", _liveEl);
```
Inter-agent coordination messages. Agents send messages via `send_message()` MCP tool and read them via `get_messages()`.
---
## Standing notices
Live `standing` broadcasts stay in every agent's inbox until that agent
acknowledges them (`POST /messages/{id}/ack`). Repositories listed as not yet
acknowledged are the ones still out of date.
```js
function repoList(label, slugs, color) {
if (!slugs.length) return "";
return html`${label} (${slugs.length})
${slugs.join(", ")}
`;
}
if (notices.length === 0) {
display(html`No live standing notices.
`);
} else {
display(html`${notices.map(n => html`
${n.subject}
${n.acked_count} acknowledged · ${n.delivered_only_count} delivered, not acknowledged · ${n.unreached_count} not reached
${repoList("Not yet acknowledged — delivered", n.delivered_only, "#d97706")}
${repoList("Not yet acknowledged — never reached", n.unreached, "#dc2626")}
${repoList("Acknowledged", n.acknowledged, "#059669")}
`)}
`);
}
```
---
## Unread
```js
function fmtDate(s) {
if (!s) return "—";
const d = new Date(s);
return d.toLocaleDateString() + " " + d.toLocaleTimeString([], {hour: "2-digit", minute: "2-digit"});
}
function renderMessage(m, showMarkRead = false) {
const isBroadcast = m.to_agent === "broadcast";
const borderColor = isBroadcast ? "#7c3aed" : (!m.read_at ? "#d97706" : "#6b7280");
async function onMarkRead() {
await fetch(`${API}/messages/${m.id}/read`, {method: "PATCH"});
}
async function onArchive() {
await fetch(`${API}/messages/${m.id}/archive`, {method: "PATCH"});
}
return html`
${m.subject}
body
${m.body}
${m.thread_id ? html`
thread: ${m.thread_id}
` : ""}
`;
}
if (unread.length === 0) {
display(html`No unread messages.
`);
} else {
display(html`${unread.map(m => renderMessage(m, true))}
`);
}
```
---
## Broadcasts
```js
if (broadcasts.length === 0) {
display(html`No live broadcasts.
`);
} else {
display(html`${broadcasts.map(m => renderMessage(m, false))}
`);
}
```
---
## Read
```js
if (read.length === 0) {
display(html`No read messages.
`);
} else {
display(html`${read.map(m => renderMessage(m, false))}
`);
}
```
---
## Archived
```js
if (archived.length === 0) {
display(html`No archived messages.
`);
} else {
display(html`${archived.map(m => renderMessage(m, false))}
`);
}
```