Namaste Ji by Ayushman Dash

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:

RoutePagePurpose
/agentsAgentHub (new)Grid of agent cards — the workforce at a glance
/agents/:agentIdAgentDetail (refactor of Agents.tsx)One agent’s home: overview · trigger · runs · (agent-specific tabs)
/agents/:agentId/runs/:runIdAgentRun (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 icon name 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 when configured: 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:

  1. Header — icon + name + description from GET /api/agents/:agentId; Refresh and (if agent:run) Trigger Run actions. The trigger flow (global-strategy checkbox + optional extra strategy context) carries over unchanged.
  2. TabsRuns (default) always present; Calendar shown 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/events grouped 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).
  3. 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/agents with run stats.
  • Add GET /api/agents/:agentId and GET /api/agents/:agentId/events.

apps/console/src/

  • pages/AgentHub.tsx + pages/AgentHub.css (new).
  • pages/Agents.tsxpages/AgentDetail.tsx (refactor; reuse Agents.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”, icon LayoutGrid.

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 via PUT /api/agents/:id/control/mode (control:set_mode). paused refuses 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). Its scheduled() handler reads control via the secret-guarded GET /internal/agent-control/:id and runs only when mode === '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:approve perms are seeded for admin + creative-lead (migration 0005 + packages/rbac).