Agent task console
Status
Proposed — not started (2026-06-21). A thin, opinionated web UI for the
IRC-based agent task system (see irc-ai-agents). It builds on the existing
dashboard-backend (already joins #firehose and streams SSE) and the task
state machine documented in
vibes/irc/agent-controller/docs/task-states.{md,d2}. It complements The
Lounge — it is explicitly not a general IRC client and does not replace one.
This plan is the design; nothing here is built yet.
Summary
A purpose-built console that presents the agent task system as tasks, not as
raw IRC channels: a status board (pending(draft) → ready → in_progress → for_review → done), a per-task view with the agent transcript + live tool
activity, and a small set of controller-mediated actions (create / ready /
complete / reopen / archive, edit description).
It is a task dashboard that happens to use IRC as transport. The read model
is the #firehose event stream plus channel CHATHISTORY; writes are the task
commands the agent-controller already validates. There is no new system of
record — ergo remains canonical (irc-ai-agents — a claim since retired by agent-dispatch-unification).
Guiding principle: stay thin by staying read-mostly. The expensive part of any web IRC client is per-user identity (auth → a per-user bouncer session). This console defers that: all writes route through one privileged backend identity, attributing the acting human in-band. Real per-user IRC identity is an explicit, optional later phase — and the reason The Lounge stays around.
Goals
- One screen that answers “what is the agent system doing right now” far better than scanning channels in The Lounge.
- Present the lifecycle natively: status columns, draft-prep, the review gate,
and capacity at a glance (active vs
MAX_ACTIVE_TOTAL, ready-queue depth). - Per-task detail: description, live agent transcript (human/agent messages +
→ / ✓ / ✗tool calls), a status timeline (from<status>_atMETADATA + firehose), and the controller actions as buttons. - Reuse what exists:
dashboard-backend’s firehose read model + SSE, ergo’sCHATHISTORY/METADATA, the controller as the sole write authority. Minimal new code. - Decoupled and swappable: the firehose vocabulary + the controller’s commands are the contract, exactly like every other client.
Non-goals (what keeps it thin)
- Not a general IRC client. No arbitrary channel join, no DCC, no user/channel modes, no multi-network, no nick management. One network (ergo); channels = tasks; nothing else is shown.
- Not a replacement for The Lounge. The Lounge stays as the “chat as yourself / free-form” escape hatch so the console never has to grow a full bouncer.
- No per-user IRC identity in v1 (see “Identity — the cost fork”).
- No mobile push, message editing/redaction, or offline support in v1.
- Not a kanban/PM tool. Status columns, not swimlanes-and-sprints.
Identity — the cost fork
The single decision that sets the whole effort:
- v1 — controller-mediated, one backend identity. The backend connects to
ergo as a single nick (e.g.
console) and issues task commands / posts clarifications on behalf of the web user, carrying the human’s name in-band (<alice> use the staging branch). The worker already treats any non-self, non-controller PRIVMSG as a human clarification turn, so this “just works”; the attribution lives in the text. Cheap, no bouncer. - Phase 4 (optional, ~10× the cost) — real per-user identity. SSO → a
per-user ergo SASL session held by the backend (bouncer-lite): true
attribution, presence, per-user ACLs. Only build this if “humans converse as
themselves from the web UI” becomes a hard requirement. It is the prerequisite
for ever enabling
require-saslon ergo (ties intokanidm).
Requester attribution is the one wrinkle v1 must answer: !new records the
sender as requester, so console-created tasks would all read requester = console. Options (decide in Phase 2): (a) accept console and show the real
user only in the console’s own audit; (b) a small controller extension — a
trusted console nick may prefix !new/actions with [as <nick>] and the
controller uses that as the requester. Lean (b)-lite for honest attribution.
Architecture
┌────────────────────────┐ ┌──────────────────────────────┐│ Browser SPA │ │ The Lounge (unchanged) ││ • status board │ │ free-form chat / "as you" ││ • task detail + xcript │ │ escape hatch ││ • action buttons │ └───────────────┬──────────────┘└──────────┬─────────────┘ │ IRC │ HTTP + SSE (or WS) ▼ ▼ ┌─────────────┐┌────────────────────────────┐ IRC (read │ ergo ││ console-backend │ + write) │ channels, ││ (extends dashboard-backend)│◀────────────▶│ CHATHISTORY││ • joins #firehose (read) │ │ METADATA ││ • CHATHISTORY proxy │ └──────┬──────┘│ • command/message proxy │ │ IRC│ as `console` identity │ ▼│ • in-memory task state │ ┌──────────────────┐└────────────────────────────┘ │ agent-controller │ (sole │ write authority │ state owner) └──────────────────┘The console-backend is a read-model + command-proxy, the shape
dashboard-backend already is — not a bouncer. The only new privilege is that it
can write (post task commands), which changes its trust posture (see Security).
Backend (extend dashboard-backend → console-backend)
The read half exists. Add:
GET /api/tasks— snapshot of current task state (from the firehose in-memory view + METADATA). Already largely present.GET /api/events— SSE live stream (already present; the board subscribes).GET /api/tasks/:id/history— CHATHISTORY proxy for#task-<id>: returns the conversation (human/agent messages,→/✓/✗tool lines,[PERM]), paginated. New.POST /api/tasks{desc, as}→!newin#tasks(creates a draft). New.POST /api/tasks/:id/actions{action: ready|complete|reopen|archive|kill, as}→ the matching!-command in#task-<id>. New.PUT /api/tasks/:id/description{text, as}→!desc <text>(while draft).POST /api/tasks/:id/messages{text, as}→ PRIVMSG#task-<id>as<as> text(clarification). New.
Writes require a registered ergo identity for the backend (unlike the
anonymous read-only dashboard) and auth in front (see Security). Keep it
small and in Go to match agent-controller/dashboard-backend.
Data model / state
Entirely derived — no store of its own:
- Board state ← the firehose in-memory view (tasks keyed by id, each with
status,title,requester,assignee, timestamps). Live viastatus/task_*events. - Per-task transcript ←
CHATHISTORYof#task-<id>on open, then live PRIVMSGs + firehosetool_call_*/statusmerged into one timeline. - Timeline / cycle time ← the
<status>_atMETADATA markers + firehosestatusevents. - The mapping is exactly
docs/task-states.md— that file is the contract; this console renders it.
Frontend
Opinionated, small. Suggested stack: Svelte or SolidJS (tiny, fast, no heavy runtime); plain CSS with the palette already drafted for the IRC theme. Component tree:
- Board — columns by status (
ready,in_progress,for_review,blocked;pendingdrafts and recentdone/archivedcollapsible). A capacity header:N/MAX_ACTIVE_TOTAL active · M ready— the one number no current view shows. - Task card — title, status pill (colors from the state spec), assignee
(friendly worker nick), age, a
for_reviewbadge that begs a review. - Task detail (drawer/route) — description (with
!descedit while draft), transcript with tool-call rows rendered distinctly, status timeline, and the action buttons gated by status (!readyon a draft,!complete/!reopenonfor_review,!killonin_progress,!archive). - Draft-prep flow —
!newopens a draft card with the readiness checklist inline;!readyis the prominent CTA once a description exists. - Composer — posts clarifications to the task channel (v1: as the console identity, prefixed with the web user).
Optimistic UI optional: actions can render immediately and reconcile when the
authoritative firehose status event arrives (the controller remains the truth).
Security / trust
- The backend gains write power (can create tasks, drive lifecycle, post as
humans), so it must sit behind auth — the platform
internalGateway (Envoy) with forward-auth/OIDC, or app-level auth. (The Lounge is already behind that Gateway atirc.rrchnm.internal; the console gets its own HTTPRoute, e.g.tasks.rrchnm.internal— the public route already noted as a follow-up inplan-irc-ai-agents.) - The backend needs a registered ergo account (
console) materialized via ESO, like the controller’s. It is not anonymous (the read-only dashboard is). - The
#firehose+m/ sole-publisher ACL hardening (still open inplan-irc-ai-agents) matters more here: the console trusts firehose integrity. - Decide whether read and write share one service (simpler, but couples the
read-only dashboard’s trust with write power) or split into a read-only
dashboard-backend+ a separate privilegedconsole-backend. Lean: one service, auth-gated writes, read endpoints unauthenticated within the cluster.
Deployment
- Extend
vibes/irc/dashboard-backend/(or fork toconsole-backend); Go image toghcr.io/chnm/systems/vibes/irc/<name>,:main, mirrored to Zot. - k8s Deployment in
vibes; HTTPRoute on theinternalGateway; SAN + hostname wiring like The Lounge (routes.yaml, internal TLS cert,k0s-gateways.yaml). netpol:vibes-isolationalready allows same-namespace → ergo. - Frontend can ship as static assets served by the same Go binary.
Open questions
- Requester attribution for console-created tasks (see Identity) — accept
console, or add the trusted[as <nick>]controller extension. - Auth mechanism — Gateway forward-auth/OIDC vs. app-level; ties to
kanidmand is the on-ramp to Phase 4 per-user identity. - One service or two (read-only dashboard vs. privileged console-backend).
- Optimistic vs. server-confirmed UI for actions.
- CHATHISTORY limits/pagination for long task transcripts; lazy-load older.
- Notifications — in-app only in v1; push (
for_reviewready to review,blocked) reuses the deferred push-bridge fromplan-irc-ai-agents.
Implementation order
- Read-only board. Modern SPA on the existing snapshot + SSE: status columns + capacity header. No writes. Biggest UX win per unit effort; validates the firehose-as-UI-state model. (~days)
- Task detail + transcript.
CHATHISTORYproxy endpoint; per-task view merging history + live events; status timeline. (~days) - Controller-mediated actions. Auth in front; backend gains a
consoleidentity; buttons for!new/!desc/!ready/!complete/!reopen/!archive/!kill; settle requester attribution. (~1 week) - In-app clarifications / chat. Composer posts to the task channel as the
console identity (prefixed with the web user); optimistic send. For this to be
real “chat about the work” and not just answering
[[BLOCKED]]prompts, the agent must stay reachable / remember — see the “Agent conversation” section ofplan-irc-ai-agents. (~days) - (Optional, expensive) Real per-user identity. SSO → per-user ergo SASL sessions; true attribution + presence. Only if needed. (~weeks)
- (Optional) Polish. Push notifications, search, mobile/PWA, the “animated workspace” flourish.
A read-only board + task detail (steps 0–1) is a compelling MVP on its own and is mostly frontend on top of code that already exists.
Relationship to irc-ai-agents
- Subsumes that plan’s dashboard frontend evolution (its step 13, “timeline → animated/Godot”) with something more useful and task-native.
- Reuses its already-built read backend (steps 9–10) and the frozen firehose vocabulary + METADATA — no controller or event-contract changes for steps 0–1.
- Inherits its open items that this console leans on:
#firehose+mhardening (integrity), the public HTTPRoute, and per-user identity (its “ephemeral identities can’t be IaC’d” wrinkle is exactly Phase 4 here).