Docs / console/agent-hub.md · mirrored from the repo
Agent Hub — BO console surface
Design for the back-office Agent Hub: the registry surface that lists every agent as a card, and a per-agent detail page that is the agent’s home in the console. First agent shipped on it: events-calendar (the calendar invite agent).
Builds on docs/agents/AGENT-KERNEL.md (agent anatomy + manifest), docs/agents/AGENT-PLATFORM.md (registry as a BO management surface), and docs/BACK-OFFICE.md (the BO is the control plane — it triggers runs and reads observability; the agent worker stays the system of record for its own runs).
1. Why
Today apps/console has a single hardcoded Agents.tsx page wired to events-calendar.
That doesn’t scale: as the agent workforce grows (Creative Head, Catalog Steward, …) we
need a hub that lists all agents and a per-agent page that generalises the run
console. This doc defines that restructure and the minimal API to back it. No new datastore
— the static registry is enriched with live stats from the existing agent_runs_index.
2. Information architecture
Three-level route hierarchy under /agents:
| Route | Page | Purpose |
|---|---|---|
/agents | AgentHub (new) | Grid of agent cards — the workforce at a glance |
/agents/:agentId | AgentDetail (refactor of Agents.tsx) | One agent’s home: overview · trigger · runs · (agent-specific tabs) |
/agents/:agentId/runs/:runId | AgentRun (unchanged) | Run timeline / observability |
Sidebar nav label changes “Agent Registry” → “Agent Hub” (route stays /agents).
AgentRun’s back-link changes from /agents → /agents/:agentId (back to the agent, not
the hub).
All three routes keep the existing requirePermission('agent:view') guard; trigger is
gated by agent:run as today.
3. The registry (API)
The registry stays a small static map keyed by agent_id in console-api, mirroring
each agent’s manifest.yaml, enriched at request time with live run stats from
agent_runs_index. No manifest parsing at runtime (manifests live in the agent repos); the
map is the BO’s view of the workforce and is the one place to add a new agent’s card.
// console-api — registry entry (static fields mirror manifest.yaml)
type AgentRegistryEntry = {
id: string; // 'events-calendar'
name: string; // 'Events Calendar Manager' (manifest: role)
description: string;
icon: string; // lucide icon name, resolved client-side
kind: string; // manifest: kind — 'shared-service'
autonomy: string; // manifest: autonomy — 'proposes'
urlBinding: keyof Env; // which env var holds the worker base URL
};
Endpoints
GET /api/agents (enriched) — list for the hub cards.
Returns each static entry plus, joined from agent_runs_index:
{
"agents": [{
"id": "events-calendar",
"name": "Events Calendar Manager",
"description": "Researches Indian festivals…",
"icon": "CalendarDays",
"kind": "shared-service",
"autonomy": "proposes",
"configured": true, // worker URL binding present
"run_count": 12,
"last_run": { "status": "completed", "started_at": "2026-06-20T09:00:00Z" }
}]
}
Stats come from one grouped query over agent_runs_index (count + most-recent row per
agent_id); agents with no runs report run_count: 0, last_run: null.
GET /api/agents/:agentId (new) — single entry, same shape as one hub item, so
AgentDetail’s header isn’t hardcoded. 404 for unknown ids.
GET /api/agents/:agentId/events (new, proxy) — passthrough to the agent worker’s
GET /events (supports ?days=&type=&religion=). Backs the Calendar tab. Returns 404
for agents without an events surface / unconfigured worker. (Reuses the existing
agentBaseUrl() proxy helper, like /runs.)
Existing endpoints reused unchanged: GET /api/agents/runs?agent_id= (runs list),
POST /api/agents/:agentId/runs (trigger), GET /api/agents/:agentId/runs/:runId(+/trace).
4. AgentHub page
Responsive card grid (auto-fill, minmax(320px, 1fr)), one AgentCard per
/api/agents entry. Card anatomy:
- Icon (resolved from
iconname via a small lucide map) + name. - Description (clamped to 2–3 lines).
- Chips:
kind,autonomy(e.g.shared-service·proposes). - Status line: last-run badge (
completed/running/failed+ relative time) or “No runs yet”;run_count. A muted “Not configured” state whenconfigured: false. - Whole card is a link to
/agents/:agentId.
Empty state if the registry is empty (won’t happen with events-calendar present, but
handled). Loading skeleton while /api/agents resolves.
5. AgentDetail page
Generalises today’s Agents.tsx, parameterised by :agentId. Sections:
- Header — icon + name + description from
GET /api/agents/:agentId;Refreshand (ifagent:run)Trigger Runactions. The trigger flow (global-strategy checkbox + optional extra strategy context) carries over unchanged. - Tabs —
Runs(default) always present;Calendarshown only when the agent declares an events surface (events-calendar).- Runs tab: the current runs table, fetched from
/api/agents/runs?agent_id=:agentId; rows link to/agents/:agentId/runs/:runId. - Calendar tab: events from
/api/agents/:agentId/eventsgrouped by month, each row showing date, name (+name_hi), type/religion chips, confidence. Day/type filters map to the proxied query params. Read-only in v1 (the agent owns writes).
- Runs tab: the current runs table, fetched from
- Unknown / unconfigured
agentId→ friendly not-found with a link back to the hub.
Agent-specificity (which tabs, which icon) is driven by the registry entry, not branching
on string ids in the component beyond a capability flag (e.g. hasEvents).
6. Files touched
services/console-api/src/index.ts
- Promote the existing inline registry to the typed static map (§3).
- Enrich
GET /api/agentswith run stats. - Add
GET /api/agents/:agentIdandGET /api/agents/:agentId/events.
apps/console/src/
pages/AgentHub.tsx+pages/AgentHub.css(new).pages/Agents.tsx→pages/AgentDetail.tsx(refactor; reuseAgents.css, extend for tabs/calendar).pages/AgentRun.tsx— back-link to/agents/:agentId.router.tsx— three routes (hub / detail / run).components/Sidebar.tsx— label “Agent Hub”, iconLayoutGrid.
No DB migrations. No agent-worker changes (it already exposes /events and /runs).
7. Calendar write path (built)
The Calendar tab is editable. calendar:write lets an operator add/edit/delete events;
calendar:approve flips a proposed event to approved. Events carry a status
(migration agents/events-calendar/migrations/0003_event_status.sql): the agent proposes
(autonomy: proposes), a human approves. Proxied write endpoints on console-api
(POST/PUT/DELETE /api/agents/:id/events, POST …/events/:id/approve) forward to the agent
worker, which is the system of record. Re-running the agent re-proposes changed rows.
8. Manifest-driven registry (built)
scripts/gen-registry.mjs parses every agents/*/manifest.yaml into
services/console-api/src/agent-registry.generated.ts (committed). The manifest carries BO
presentation/capability hints: description, icon (lucide name), and surfaces
(e.g. events → Calendar tab). Run npm run gen:registry after editing a manifest; wire it
into the console-api deploy step to regenerate at deploy time. Only the env-specific worker
URL binding stays in code (AGENT_URL_BINDINGS in console-api).
9. Schedule + mode controls (built)
The BO is the authoritative control plane for each agent’s run mode and schedule, stored in
D1 (services/console-api/migrations/0005_agent_control.sql, table agent_control) — a
pragmatic stand-in for the full Control DO, with every change mirrored into config_audit.
- Mode
auto | manual | paused— set viaPUT /api/agents/:id/control/mode(control:set_mode).pausedrefuses manual triggers (kill-switch-lite). - Schedule cron + enabled —
PUT /api/agents/:id/control/schedule(agent:schedule). - The agent worker has a cron trigger (
triggers.crons, daily heartbeat). Itsscheduled()handler reads control via the secret-guardedGET /internal/agent-control/:idand runs only whenmode === 'auto'and the schedule is enabled — otherwise the tick is a no-op. (Honoring an arbitrary cron expression beyond the wrangler cadence is the remaining refinement.)
The detail page Settings tab drives both.
10. Hub search / filter / grouping (built)
The hub has a search box (name/description), kind filter chips, and groups cards by kind
when more than one kind is present.
11. Still out of scope
- Honoring an arbitrary BO cron expression beyond the wrangler heartbeat cadence.
- A real Control DO (this uses a D1 table as the authoritative store for now).
- Bulk approve / calendar import-export.
- New
calendar:write/calendar:approveperms are seeded foradmin+creative-lead(migration 0005 +packages/rbac).