# Swarm UI The swarm's own web surface and the operator's day-to-day view: served by the gateway on the **swarm apex** (`services.hyperhive.swarm.domain`), readable only by operators. The per-hive dashboard, on each hive's own domain, covers host-level detail for one hive. ## What it shows | route | what | | ---------------------------------------------- | --------------------------------------------------------------------------------------------------- | | `/` | the hive directory, each hive with its last reported status | | `/agents` | every agent: status, config PR, wanted state; create agents, link forge, matrix and GitHub accounts | | `/agents//terminal` | one agent's live terminal | | `/agents//subagents//terminal` | one of that agent's subagents' live terminal | | `/jobs` | the controller's job graph — where agent creation and credential mints show progress | | `/schedules` | the scheduled prompts: add, edit, pause, resume and remove | | `/issues` | a cross-repo issue report | Everything it shows comes from [`swarm-controller`](../../swarm-controller/README.md). An agent created here or with `swarmctl agent create` starts `paused`; set it `up` from its card. ### Agent and subagent terminals An agent's detail panel on `/agents` shows a small live preview of its terminal, and below it the agent's **subagents**: every subagent the swarm queue holds terminal rows for from the last 24 hours. Picking one shows its terminal in the same preview, and **expand** opens it in its own tab, as it does for the agent. The list refreshes every 10 seconds, so a subagent the agent spawns shows up once it writes output. A preview shows rows published while it's open. A subagent terminal has no turn-state badges. ### Scheduled prompts `/schedules` has one card per scheduled prompt. The **name** is the card's title, with the **agents** it fires for, the message, how often it repeats and when it fires next. **edit** turns the card into its own form; **pause** and **resume** stop and restart it; **remove** deletes it after a confirmation. **+ schedule** opens an empty form above the list, and the filters narrow the list by name, agent or state (`active`, `paused`, or `past` for a one-shot whose time has passed). `past` is the controller's clock against the start time, not a record that an agent fired, so you can still pause a `past` schedule. A schedule repeats **every** some number of seconds, minutes, hours or days, or fires **once**. Its start time is the moment a one-shot fires, and the origin of the fire times of a repeating one. The form previews the next three fire times. Changing the start or the repeat moves them; editing only the name, message or agents doesn't, and the form warns when an edit would. A start in the past fires the latest missed time once, right away, in every agent chosen, and the form says so. Only an operator can change a schedule here or with `swarmctl schedule`; an agent has no way to add one. The page needs the controller's `/api/schedules` routes. ### Sending a message to an agent Under an agent's terminal, in the preview and in its own tab, is a **message** box. **send** hands the text to the agent's harness, and the agent takes it as an inbox message from your authelia username. **sent** means the harness was listening and holds the message. If the harness isn't running, or isn't connected to the swarm queue, the box shows an error and the agent gets nothing. Subagents have no message box. The message doesn't go through the agent's hive, so the hive dashboard's message history doesn't list it. The agent's answer shows in its terminal; an agent that calls `send` to your username gets an unknown-recipient error. A harness that restarts before taking its turn loses the message.
How a message reaches the agent The gateway sets `Remote-User` from authelia's answer on every `/api/` request, replacing any the browser sent. `POST /api/agents//input` takes that header as the sender and sends `{from, body}` as a NATS request on `$SWARM.input.`. swarm-controller is the only queue client allowed to publish there. The agent's harness subscribes to its own subject and queues each message for its turn loop with its reminders. Only the agent's own queue credential may subscribe there. The harness doesn't reply, so swarm-controller answers 202 when the request times out after two seconds, and 503 when the queue reports no subscriber.
Where the subagent list and rows come from The agent's subagent daemon publishes each subagent's terminal rows on `$SWARM.term..sub.` with the agent's own queue credential, into a stream named `term-sub-` that swarm-controller creates for every declared agent. The stream keeps rows for 24 hours. swarm-controller serves the list as `GET /api/agents//subagents`, the subagents named by the subjects in that stream, each with `last_row_unix`, the stream's publish time of its newest row, or `null` when that row isn't read within two seconds. The lookups run concurrently, sixteen at a time, so the list waits at most two seconds for each sixteen subagents. swarm-controller also relays one subagent's rows as SSE on `GET /api/agents//subagents//term/stream`. An agent with no store identity, or no queue address, publishes no subagent terminals.
### Linking external accounts Each agent on `/agents` opens three dialogs that write a credential for it into the swarm secret store through swarm-controller. All three are blind set actions: no route hands a token back. swarm-controller refuses to link a name that already holds an account; delete that account first to replace it. The agent's detail panel lists its linked accounts under **accounts**, one row per account: kind, name and host. Opening an agent makes one request, `GET /api/hives/{hive}/agents/{agent}/linked-accounts`, which returns every account of that agent as names and hosts and never a credential. | kind | one row per | name | host | | ------- | ------------------------------------------- | ----------- | ------------------------ | | matrix | `swarm/agents//matrix/` | the account | its `homeserver`, if set | | forgejo | `swarm/agents//forge/