← Cycles
Qflow · c12

qcontrol-live-view

activecreated 2026-07-29· last activity 2026-08-15

Live qflow visualization of qcontrol agent events — agents pulsing to event-type nodes, fed by qdash SSE, with a growing timeline scrubber

New in this cycle: Live qcontrol events — agents pulsing to typed event nodes, growing timeline

Why

qcontrol now streams every agent event (file/exec/network/http/llm/agent/mcp) through qdash, whose live view runs at http://127.0.0.1:7878/live as tables. qflow's spatial, animated idioms should make this stream immediately legible: which agents are active, what kinds of events they emit, at what rate, and where the errors are.

Objective

A working ExpLiveView.r1.vue experiment that:

  • Subscribes to qdash SSE (/api/events/stream) through a translation layer and renders agents (sources) → event-type nodes (destinations) with one pulse per event
  • Seeds history via /api/events backfill and shows a YouTube-style play bar whose track grows with live events; scrubbing rebuilds state at time t; "jump to live" resumes tailing
  • Filters: everything/curated mode toggle + agents/families/types selection matching qdash /live semantics, initializable from URL params
  • Survives the full firehose (pulse budgets, marker binning, history cap, stale-agent eviction)

Scope

  • dev/experiments/c12.qcontrol-live-view/ — App.vue nav shell, ExpLiveView.r1.vue, PlayBar.vue, lib/ (QdashFeed, backfill, adapter, filterModel, classifier, history, mockFeed), helpers copied from c11 (LiveFeed, NodeRegistry, TrailPulse, trail-texture, shapes/)
  • dev/vite.config.js — /api/events proxy → http://127.0.0.1:7878 (qdash has no CORS + a local-host check; EventSource can't set headers)
  • dev/App.vue — experiment list entry

Key protocol/reference files (qcontrol repo): crates/qdash/ui/app/lib/live/sse-protocol.ts, backfill.ts, event-table-projections.ts (filter semantics — ported, not imported), schemas/ts/qcontrol-run.ts (RunRecord types). qmap c28 notableEventClassifier.js is the severity/category basis for scrubber markers.

Design decisions:

  • sourceAddress = run.id (always present; one node per run/session); display label agent_id · pid; filtering by canonical agent_id to match qdash's agents axis
  • destinationAddress = event.type; family = type.split('.')[0]; per-family palette, severity overrides pulse tint
  • qdash /live filter is page-local (not URL-encoded) → c12 defines the URL contract (?agents=&families=&types=); a qdash-side "Open in qflow" link is deferred to r2

Plan: /Users/mark/.claude/plans/create-a-plan-for-curried-clarke.md

Key Changes

  • r1 shipped: dev/experiments/c12.qcontrol-live-view/ — nav shell App.vue, ExpLiveView.r1.vue (c10 two-pass pipeline: agents → event-type nodes, trail pulses, growth-throttled relayout, fit-to-content), PlayBar.vue
  • Translation layer in lib/: adapter.js (RunRecord → dual-shape flow record, family palette, severity pulse tint), classifier.js (qmap c28 classifier adapted to RunRecord fields + envelope-severity catch-all), mockFeed.js (synthetic wrapper frames through the real adapter for offline dev)
  • Live feed: lib/QdashFeed.js (EventSource client for qdash data-only frames, 200ms micro-batching, status machine) + lib/backfill.js (JS port of qdash's splice client: first-connect newest-N, reconnect gap-fill, id-dedupe). Feed instance retained across pause/resume so reconnects gap-fill instead of re-backfilling (avoids duplicate history)
  • Filters: lib/filterModel.js ports qdash /live semantics (agents axis → types override families), everything/curated modes, agent/family chips with pre-filter counts, URL params (?agents=&families=&types=) read on mount + written via replaceState; filter changes rebuild retroactively from history
  • Timeline: lib/history.js (50k-cap record log, notables subset) + PlayBar.vue (growing timestamp axis, severity-binned marker diamonds ≤150 bins with aggregated hover labels, LIVE button). Scrub → throttled (~120ms) scene rebuild at time t with pulses only in the trailing 2s window; jump-to-live rebuilds and resumes tailing; live batches keep recording to history (scene-gated) while scrubbed back
  • Perf: pulse budget (≤12/batch prioritized notable→curated→rest, ≤40 in-flight), pulse skip when document.hidden, stale-agent eviction via getStaleNodes(5min) every 30s (following only, fade + destroy + relayout)
  • Harness: /api/events Vite proxy (changeOrigin, SSE verified through it), dev/App.vue entry, CYCLES.md entries
  • Agent pawn sprites: agents render as the q-nuxt-layer pixel-pawn SVG (vendored as agent-pawn.svg — the published package doesn't export assets) rasterized nearest-neighbor via lib/agentTexture.js (Image + canvas + Texture.from; Graphics.svg() can't parse its class-based CSS). Blink = white halo circle behind the sprite; falls back to circles if the texture fails. New defaults: pulses isometric, agents circular-packed, event types arc
  • Pawn blink = white-silhouette overlay sprite (second rasterization with source-in composite — the sprite analog of draw-white-then-tint, since tint can't brighten)
  • Replay playback: PlayBar play/pause + cycling speed chip (1–16×). Incremental cursor over history applies crossed records through handleBatch (no per-tick rebuilds); reaching the live edge jumps back to tailing. Play while following replays from the beginning
  • Bug fix: scrub rebuilds destroyed players mid-ease; pixi-ease then broke on the dead targets and stopped ALL later animations, leaving rebuilt players frozen at alpha 0 (pulses unaffected — TrailPulse animates via ticker). Fix: ease.removeAll() + clear pending blink timeouts in clearScene, plus destroyed-guard in blinkPlayer
  • Agent spacing: drawnRadius accounts for the pawn's wider footprint (1.2×), and a new Agent Padding slider threads into grid/packed-grid/circular layouts
  • Label controls: Position (top/bottom/left/right, default right) + Offset slider. Left/right placement is edge-aligned — the wrapper shifts by half the text width so the label's near edge sits flush to the node edge regardless of text length
  • Pulse layering: dedicated pulseHolder as the bottom child of the root holder so trails render behind nodes. Each TrailPulse is its own render group (plain sub-container + per-frame Graphics redraws = the c2 render-group cache gotcha otherwise)

Outcome

In progress (as of 2026-07-29) — r1 functional end-to-end. Working: live qdash SSE feed through the Vite proxy with backfill seeding, mock mode, agents-as-pawns → event-type nodes, filters (modes + agents/families chips + URL params), timeline with scrub/replay/jump-to-live, scene-reviewer pass clean.

Pick up here

  • Visual tuning by eye: pulse density under full firehose (budgets in the Pulses panel), arc angles for the event ring, agent padding default, and whether pulses-behind-nodes needs a brighter destination flash (arrivals are partially hidden under busy pawns now)
  • r2 candidates (from plan, deferred): per-vendor agent logos — q-nuxt-layer has components/agent/registry.js (alias matching) + assets/svgs/ai-agent-logos/; the sprite plumbing in lib/agentTexture.js makes this a texture-map swap keyed on agent_id. qdash-side "Open in qflow" link (serialize /live filter into c12's URL params — qcontrol repo change). Folded file.* frame count-weighting (payload fold count unverified). Play-from-t already shipped; incremental rebuild snapshots only if 50k replays feel slow.
  • Known rough edges: replay cursor re-anchors on history trim (guard in replayTick); backfill limit change restarts the feed (full reset); agent-pawn.svg is vendored — sync manually if the q-nuxt-layer original changes, or add an assets export to the package

Visual record

10 captures — heroes, page slices, and full-page renders. Click to view.

Mock feed ~8s — event types materializing
Mock feed ~8s — event types materializing
~25s — full event-type field, three agents working
~25s — full event-type field, three agents working
Filtered to claude-cli — one agent's signature
Filtered to claude-cli — one agent's signature
Filtered to the http family
Filtered to the http family
Agents in packed grid layout
Agents in packed grid layout
Event types radial around the agents
Event types radial around the agents
Curved pulses
Curved pulses
Labels left — ledger reading
Labels left — ledger reading
The long run — timeline grown, field saturated
The long run — timeline grown, field saturated
c12 hero

Artifacts

/experiments/c12.qcontrol-live-view/ (local dev)

Qpoint Brand Style Guide