← Cycles
Qmap · c28

live-qcontrol-stream

archivedcreated 2026-05-13· last activity 2026-08-15

Live qcontrol event stream → qmap visualization via SSE bridge

New in this cycle: Live qcontrol stream — SSE bridge, MCP actors, tool-call flows from the extended reducer

Why

QControl now emits MCP protocol events (request/response/error/notification/session lifecycle) alongside the IO events (file/exec/connection/http) that c22 already handles. These MCP events are fully implemented and documented but invisible to qmap — the c22 reducer ignores them. Meanwhile, the c22 bridge serves reduced sessions via HTTP polling only, meaning the experiment must repeatedly fetch to see updates.

This cycle proves the live pipeline: qcontrol discover --json → bridge → SSE → qmap, with the reducer extended to surface MCP server actors and tool-call flows alongside the existing IO actors.

Adjacent cycles:

  • c22.runtime-session-map — proved the bridge + reducer pattern for IO events (file, exec, connection, http)
  • c24.synthetic-live-session-replay — proved the replay/subscribe UX with synthetic data

Objective

One runnable experiment that:

  • Connects to a live qcontrol session bridge via SSE (Server-Sent Events)
  • Renders MCP servers, tools, files, external hosts, and processes as they appear in real time
  • Uses an incremental session accumulator (O(1) per event) instead of full re-reduction
  • Supports fixture mode for offline testing without a running bridge
  • Supports polling fallback when SSE is unavailable

Scope

  • dev/experiments/c28.live-qcontrol-stream/ — experiment surface, reducer, SSE source, bridge
  • cycles/c28.live-qcontrol-stream/ — cycle metadata

Architecture

Accumulator pattern

The c22 reducer replays all accumulated events on every call — O(n) per event. c28 restructures the same logic into createSessionAccumulator():

  • ingest(event) — O(1) per event (map lookup + update)
  • snapshot() — O(n actors) on demand, only when the bridge needs to broadcast

The pure function reduceQControlSession(events) is preserved as a convenience wrapper for fixtures and tests.

Event flow

qcontrol discover --json (JSONL on stdin)
  → bridge.mjs
    → accumulator.ingest(event)           O(1) per event
    → 250ms debounce fires:
      → accumulator.snapshot()            O(n actors)
      → broadcast via SSE to all clients
  → browser EventSource
    → QMapAgentSession re-renders

Event coverage

Event typeActor typeEntity typeNotes
mcp.requestmcp-serverMCP Server
mcp.request (tools/call)skillTool
mcp.response(existing server)—
mcp.error(existing server)—Notable event
mcp.notification(existing server)—Light volume
mcp.session_close(existing server)—Tags closed
mcp.diagnostic(existing server)—Notable event
mcp.oauth(existing server)—Notable on error
llm.requestvendorLLM ProviderNew
llm.response(existing provider)—Token/cost stats, notable on error
llm.usage(existing provider)—Context window metadata
llm.rate_limit(existing provider)—Notable when actively limited
agent.tool_call (mcp)skillToolBridges to MCP tool tracking
agent.tool_call (skill)skillSkillNew actor
agent.tool_call (builtin)——Stats on root actor
agent.tool_decision——Notable on denial
agent.tool_result——Notable on failure
process.snapshot——Bootstraps root actor

IO events (file.read/write, exec.spawn, connection.*, http.request) carry forward from c22 unchanged.

Non-goals

  • agent.message — conversation turns (no actor/flow mapping; future timeline feature)
  • file.open/close, connection.close, sse.*, websocket.* — lifecycle bookends already covered by higher-level events
  • Production-grade error recovery or reconnection UX
  • Promoting anything to src/ — this stays experiment-local
  • Real-time session switching (single-session focus is fine)
  • Diff-aware SSE (full snapshots are fine at current scale)

Progress

2026-05-13: Pipeline working, MCP events flowing

Bridge + discover pipeline:

  • Fixed event format mismatch: discover outputs { type, payload, process } (no event wrapper field). Updated both bridge.mjs ingestEvent() and accumulator ingest() to accept both legacy (event.event === "process.log") and current (bare type) formats.
  • Fixed SSE session flicker: bridge was re-evaluating "newest session" on every broadcast, causing actors to appear/disappear as events alternated between PIDs. Fixed by locking SSE clients to a session at connect time.
  • Upgraded to multi-session SSE: bridge now broadcasts all sessions (event: "sessions") to unfiltered clients, single session (event: "snapshot") to ?pid= clients. Frontend decides how to merge/group.

MCP event normalization:

  • MCP payload schema changed: old format nested under data.mcp, new format puts fields directly under payload with session: { host, transport } sub-object. Added normalizer in ingest() that flattens both shapes into a single mcp object.
  • Required --plugins core-tools (from qcontrol plugin-core-tools branch) plus inject_mcp_server = true in ~/.config/qcontrol/run.toml.

Root actor labeling:

  • classifyRoot() now recognizes ~/.local/share/claude/versions/<ver> path pattern → label becomes "Claude Code (2.1.107)" instead of raw version number.

QMapLiveStream wrapper component:

  • New src/components/QMapLiveStream.vue — takes sessions array, merges actors/flows internally, deduplicates by ID, sums flow volumes.
  • QMapLiveStreamDefaults.js — all dataflows highlighted, solid lines, distributed arrows (3 count, 2.5 size, 0.5 speed, 0.9 opacity).
  • Supports groupBy: { level1: "session" } to rug actors by their agent session.

View modes:

  • Entity Type: all sessions merged, grouped by entity type (Agent, MCP Server, External Host)
  • Session: each agent session gets its own rug with entity type subdivisions
  • Flat: no grouping
  • Defaults to Live SSE + Session view.

qcontrol setup (for reference)

# Build from plugin-core-tools branch
cd ~/web/qpoint/qcontrol
git checkout plugin-core-tools
make

# Configure
# ~/.config/qcontrol/run.toml:
#   [features]
#   inject_mcp_server = true

# Claude shim (~/.local/bin/claude) points to local build:
#   __qc_qctrl=/Users/mark/web/qpoint/qcontrol/bin/qcontrol
#   exec "$__qc_qctrl" run --plugins io-events,core-tools -- "$__qc_target" "$@"

# Pipeline
~/web/qpoint/qcontrol/bin/qcontrol discover --socket --json 2>/dev/null | node dev/experiments/c28.live-qcontrol-stream/bridge/bridge.mjs

Success Criteria

  • Live pipeline works end-to-end: qcontrol discover --json | node bridge.mjs → browser shows actors appearing in real time
  • MCP server and tool actors appear with correct types, labels, and flows
  • Fixture mode loads and renders without a running bridge
  • Bridge SSE endpoint delivers snapshot-on-connect + debounced updates
  • Code is generously commented for cross-project readability

Visual record

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

Fixture session, session grouping
Fixture session, session grouping
Entity-type grouping
Entity-type grouping
Flat — the ungrouped event field
Flat — the ungrouped event field
Dataflows on — sliding arrows and mountain pulses
Dataflows on — sliding arrows and mountain pulses
Event markers over the flows
Event markers over the flows
Event Focus — attention narrowed to the active edge
Event Focus — attention narrowed to the active edge
The stream from above
The stream from above
Dramatic lighting on the session
Dramatic lighting on the session
c28 hero

Artifacts

/experiments/c28.live-qcontrol-stream/ (local dev)

Qpoint Brand Style Guide