Guide
Wire it to your agents
Herald ships the surface, not the backend. Every component is prop-driven and endpoint-agnostic — it takes generic typed data and injected callbacks, and owns zero assumptions about where that data comes from. That's what lets it drop onto any stack — and it's what an agent wires when it builds you a dashboard. This page is the map from Herald's seams to a live agent gateway.
The mental model
Self-hosted agents — OpenClaw, Hermes, or your own — run as a gateway process: a long-running service that streams replies, reports fleet status, and runs scheduled work. Herald is the control plane on top. You point its callbacks at your gateway's HTTP/SSE endpoints, and map the responses onto Herald's shapes. No SDK, no lock-in.
1. Streaming chat
useStream turns any SSE/fetch stream into live parts[]. Hand it a request(signal) that hits your gateway; it owns the parsing, state, and abort. Feed the result into ChatPanel or InlineAgentChat.
import { useStream } from "@/hooks/use-stream"
const stream = useStream()
async function send(text: string) {
const next = [...messages, { role: "user", content: text }]
setMessages([...next, { role: "assistant", content: "", parts: [] }])
await stream.start((signal) =>
fetch(`${GATEWAY_URL}/agents/${agentId}/chat`, { // ← your gateway
method: "POST",
headers: { Authorization: `Bearer ${GATEWAY_TOKEN}` },
body: JSON.stringify({ messages: next }),
signal,
}),
)
}
<InlineAgentChat messages={messages} draft={draft} onDraftChange={setDraft}
onSend={send} isStreaming={stream.isStreaming} onStop={stream.stop} />The parser is swappable — the default speaks OpenAI-compatible SSE; pass your own for any wire format your gateway emits (including tool-call and reasoning events, which render as tool cards).
2. The fleet
The Dashboard and AgentStatusCard are pure — you feed them your gateway's fleet-status response, mapped to their shapes. Refresh on an interval (or a websocket) and they stay live.
const res = await fetch(`${GATEWAY_URL}/fleet`, {
headers: { Authorization: `Bearer ${GATEWAY_TOKEN}` },
}).then((r) => r.json())
// map your gateway's shape → Herald's
const agents = res.agents.map((a) => ({
id: a.id,
name: a.name,
model: a.model,
status: a.online ? "online" : a.error ? "error" : "idle",
description: a.lastActivity,
stats: [{ label: "Tasks", value: a.taskCount }, { label: "Uptime", value: a.uptime }],
}))
<Dashboard agents={agents} activity={events} stats={kpis} usage={byAgent} />3. Multi-agent rooms
useTurnRunner orchestrates who speaks next; you inject run — one call to your gateway per turn. Render it with RoundtableTranscript.
const room = useTurnRunner({
participants,
run: async ({ speaker, prompt, signal }) => {
const res = await stream.start((sig) =>
fetch(`${GATEWAY_URL}/agents/${speaker.id}/chat`, {
method: "POST",
body: JSON.stringify({ messages: [{ role: "user", content: prompt }] }),
signal: signal ?? sig,
}),
)
return res.acc.text
},
})4. Generation jobs
For non-streaming work — image, render, any submit→poll→result — useGeneration owns the lifecycle. Inject generate, progress, and interrupt.
const gen = useGeneration({
generate: ({ input, signal }) =>
fetch(`${GATEWAY_URL}/txt2img`, { method: "POST", body: JSON.stringify(input), signal }).then((r) => r.json()),
progress: (signal) => fetch(`${GATEWAY_URL}/txt2img/progress`, { signal }).then((r) => r.json()),
interrupt: () => fetch(`${GATEWAY_URL}/txt2img/interrupt`, { method: "POST" }).then(() => undefined),
})5. Config & the rest
The same pattern covers everything else. Each surface takes callbacks; you back them with your own store or gateway endpoints:
- Providers: ProviderManager's
onAdd/onDelete/onSetKey→ your provider store (the key stays write-only). - Personas: PersonaBuilder's
onChange/onSave→ your persona records. - Notifications: NotificationsBell ← your scheduler's cron results (Hermes and OpenClaw both deliver scheduled output you can ingest).
- Deletes: useUndoableDelete's
onCommit→ your DELETE, fired only if the Undo window lapses.
Building with an agent?
Point your coding agent at /llms.txt (the full catalog in one file) or install the Herald Agent Skill from the repo. Then just ask it to build the dashboard you want — it knows how to add the components and wire them to your gateway using the patterns above.