Dashboard: agents CRUD — list, create, edit, delete instances #53

Closed
opened 2026-04-18 15:01:25 +00:00 by claude-desktop · 0 comments
Collaborator

User story

As an operator, I want an /agents page on the dashboard where I can see every instance, its type, its overrides, and its live state (idle / busy / queued), and where I can create / edit / delete instances without touching JSON or restarting the service.

Context

All the plumbing is landed by A1 (SQLite), A2 (pool scheduler state), A3 (match_labels), A4 (prompt appendix), A5 (container reconcile). A6 exposes it as a web form.

Acceptance criteria

API

  • GET /agents → returns every SQLite row + type defaults + live state (idle/busy/queue-depth) merged.
  • POST /agents body {name, type, model?, prompt_appendix?, match_labels?, notes?} → insert, reconcile container, return the full row. 400 on duplicate name, invalid type, malformed match_labels JSON.
  • PATCH /agents/:name body of any subset of {model, prompt_appendix, match_labels, notes} → update, reconcile container if image-relevant fields changed, return the updated row.
  • DELETE /agents/:name → remove from SQLite, stop + remove container, drop in-memory Worker, release worktrees, drop sessions keyed on any (type, repo, issueOrPr) this instance was mid-processing. Volume survives by default (see A5).
  • DELETE /agents/:name?wipe=true → additionally remove the state volume. Require a second confirmation query param (?wipe=true&confirm=<name>) to avoid fat-finger.
  • All endpoints return JSON. No HTML responses from the API.

UI

  • /agents page listing instances in a table: name, type, model (resolved, showing override if any), match_labels, queue depth, status pill (idle / busy / stopped).
  • "New agent" button → modal with: type dropdown (populated from agents.json type keys), name input (validated: lowercase, hyphens, no collision), model dropdown (from a static list of Claude model IDs), prompt appendix textarea, match_labels tag-input, notes textarea.
  • Row click → edit view with the same form, pre-filled. Save = PATCH.
  • Delete button → confirm modal showing what will be cleaned up (container, worktrees, session count). "Also wipe volume" checkbox drives the ?wipe=true flag.
  • Skill viewer (read-only) — a collapsible section per skill (implement, review, etc.) showing the file content from disk. Next to it, a textarea for the instance's prompt_appendix with live char count. This is the "edit skill through UI" surface the milestone intentionally limits — base skills are immutable here, only the appendix is writable.

Live state

  • The existing SSE stream on /events already carries queue depth and task transitions. The /agents page subscribes and updates rows in place — no full reload.
  • Status pill polls the backing worker on render; cheap because it's in-memory.

Cleanup on delete

  • If the instance is mid-task: cancel the running task (same path as POST /cancel), wait up to 15 s, then force-remove.
  • Drop SQLite row last, so a partial failure leaves the row (visible in the UI as "stopped") rather than orphan containers with no record.

Tests

  • API: happy-path CRUD, duplicate name, invalid type, PATCH with no changes, DELETE mid-task, DELETE with ?wipe=true&confirm=<wrong> → 400.
  • UI: smoke test that list renders, create form submits, delete triggers the confirm modal.

Out of scope

  • RBAC / auth on the admin endpoints — inherits whatever the dashboard has today (none).
  • Bulk operations.
  • Audit log of config changes (future story if we need it).
  • Git-committing dashboard-edited config back to the repo — SQLite is the source of truth for overrides; config/agents.json is the type-defaults contract in git.

References

  • Tracking issue: #47.
  • Existing dashboard: src/dashboard.html.
  • Current /health and /events endpoints.

Dependencies

  • Blocked by: A1, A2, A4, A5.
  • Blocks: nothing (A7 doesn't need the UI).
  • Branch off: main (after A5 lands).
## User story As an **operator**, I want an `/agents` page on the dashboard where I can see every instance, its type, its overrides, and its live state (idle / busy / queued), and where I can create / edit / delete instances without touching JSON or restarting the service. ## Context All the plumbing is landed by A1 (SQLite), A2 (pool scheduler state), A3 (match_labels), A4 (prompt appendix), A5 (container reconcile). A6 exposes it as a web form. ## Acceptance criteria ### API - [ ] `GET /agents` → returns every SQLite row + type defaults + live state (idle/busy/queue-depth) merged. - [ ] `POST /agents` body `{name, type, model?, prompt_appendix?, match_labels?, notes?}` → insert, reconcile container, return the full row. 400 on duplicate name, invalid type, malformed `match_labels` JSON. - [ ] `PATCH /agents/:name` body of any subset of `{model, prompt_appendix, match_labels, notes}` → update, reconcile container if image-relevant fields changed, return the updated row. - [ ] `DELETE /agents/:name` → remove from SQLite, stop + remove container, drop in-memory `Worker`, release worktrees, drop sessions keyed on any (type, repo, issueOrPr) this instance was mid-processing. Volume survives by default (see A5). - [ ] `DELETE /agents/:name?wipe=true` → additionally remove the state volume. Require a second confirmation query param (`?wipe=true&confirm=<name>`) to avoid fat-finger. - [ ] All endpoints return JSON. No HTML responses from the API. ### UI - [ ] `/agents` page listing instances in a table: name, type, model (resolved, showing override if any), `match_labels`, queue depth, status pill (idle / busy / stopped). - [ ] "New agent" button → modal with: type dropdown (populated from `agents.json` type keys), name input (validated: lowercase, hyphens, no collision), model dropdown (from a static list of Claude model IDs), prompt appendix textarea, match_labels tag-input, notes textarea. - [ ] Row click → edit view with the same form, pre-filled. Save = PATCH. - [ ] Delete button → confirm modal showing what will be cleaned up (container, worktrees, session count). "Also wipe volume" checkbox drives the `?wipe=true` flag. - [ ] **Skill viewer (read-only)** — a collapsible section per skill (`implement`, `review`, etc.) showing the file content from disk. Next to it, a textarea for the instance's `prompt_appendix` with live char count. This is the "edit skill through UI" surface the milestone intentionally limits — base skills are immutable here, only the appendix is writable. ### Live state - [ ] The existing SSE stream on `/events` already carries queue depth and task transitions. The `/agents` page subscribes and updates rows in place — no full reload. - [ ] Status pill polls the backing worker on render; cheap because it's in-memory. ### Cleanup on delete - [ ] If the instance is mid-task: cancel the running task (same path as `POST /cancel`), wait up to 15 s, then force-remove. - [ ] Drop SQLite row last, so a partial failure leaves the row (visible in the UI as "stopped") rather than orphan containers with no record. ### Tests - [ ] API: happy-path CRUD, duplicate name, invalid type, PATCH with no changes, DELETE mid-task, DELETE with `?wipe=true&confirm=<wrong>` → 400. - [ ] UI: smoke test that list renders, create form submits, delete triggers the confirm modal. ## Out of scope - RBAC / auth on the admin endpoints — inherits whatever the dashboard has today (none). - Bulk operations. - Audit log of config changes (future story if we need it). - Git-committing dashboard-edited config back to the repo — SQLite is the source of truth for overrides; `config/agents.json` is the type-defaults contract in git. ## References - Tracking issue: #47. - Existing dashboard: `src/dashboard.html`. - Current `/health` and `/events` endpoints. ## Dependencies - **Blocked by:** A1, A2, A4, A5. - **Blocks:** nothing (A7 doesn't need the UI). - **Branch off:** `main` (after A5 lands).
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks#53
No description provided.