Files
fusion/plugins/fusion-plugin-compound-engineering
gsxdsm 0106eee4ff feat(compound-engineering): live agent output, steering, and a real Q&A surface
Users can now watch everything the agent does while a CE stage works, steer
it mid-stage, and read the whole conversation as a proper chat surface.

Live output:
- New host capability: CreateInteractiveAiSessionOptions.onProgress — the
  engine adapter streams thinking/text deltas + tool start/end markers from
  the pi agent hooks (any plugin can use this).
- Orchestrator buffers per-session live activity (merged deltas, discrete
  tool lines, capped), emits throttled progress events over SSE, and
  GET /sessions/:id attaches it as liveActivity for the polling fallback.
- Routes detach turn execution: start/answer/resume return immediately
  (status active) and clients converge via push/poll — the turn is watchable
  instead of hidden inside a blocking POST.
- Turn timeout is now INACTIVITY-based: an actively-working long turn is
  never killed; a quiet one interrupts with its working trace preserved.
- On settle the trace persists into history as a condensed record.

Steering:
- Stage protocol: responses may be a direct answer, {value, comment}
  (answer + guidance), or {feedback} (guidance without answering); the
  system prompt instructs agents to treat steering as first-class input.
- CeFlow: guidance textarea alongside selectable questions — attach to the
  clicked answer, or "Send guidance" on its own.

Q&A UI:
- Transcript no longer hides control records: past questions/answers render
  as chat bubbles (option ids → labels), steering turns marked, working
  traces as collapsible "Agent work" blocks, completion marker.
- Live working pane (pulse + streaming thinking/tool lines) while a turn runs.

Tests: 130 plugin tests green (14 new: live buffer/flush ordering, inactivity
watchdog survives active work, detached convergence, steering payload shapes,
transcript rendering, live pane). Engine seam tests green; plugin/core/
engine/dashboard tsc clean. Core full suite OOMs locally (known orchestrator-
shell issue) — covered by CI shards.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 14:17:40 -07:00
..

Compound Engineering Plugin for Fusion

A dedicated dashboard surface for the compound-engineering (CE) workflow: an artifact hub, interactive in-dashboard ce-* skill sessions, a work→board bridge, and event-driven bidirectional sync between the Fusion board and a plugin-local CE-pipeline state model. It runs alongside Fusion's native pipeline — it does not replace or bypass it.

Install (one-click)

  1. Open Settings → Plugins → Fusion Plugins.
  2. In Bundled Plugins, click Install on Compound Engineering.
  3. Enable the plugin if prompted.

Once installed and enabled, Fusion registers the Compound Engineering dashboard destination automatically and installs the bundled ce-* skills into a plugin-local, discoverable directory.

What it does

Compound engineering normally runs as terminal slash-commands whose artifacts scatter across docs/, with no unified surface and no link between a finished plan and the board work that follows. This plugin surfaces the whole flow inside Fusion while reusing the real skills so the plugin improves as they do.

Artifact hub

The primary dashboard view (viewId: "compound-engineering") discovers and renders CE artifacts from their conventional locations (STRATEGY.md, docs/ideation/, docs/brainstorms/, plan docs, docs/work/, CONCEPTS.md, docs/solutions/) and groups them by stage. Artifacts are read through a plugin route and rendered self-contained (sandboxed preview). The hub renders explicit empty / partial / error states rather than crashing or silently dropping an unreadable artifact.

Artifact HTTP endpoints live under /api/plugins/fusion-plugin-compound-engineering/ and back the hub list/read.

Interactive ce-* sessions

Each pipeline stage maps to a bundled skill via the stage registry (src/session/stage-registry.ts): { stageId, skillId, artifactLocation, icon, label }. Adding a stage is a data entry — no new route, store, or screen.

The launcher lists the registered (and operator-enabled) stages. Launching a stage starts an interactive agent session driven by the host's createInteractiveAiSession seam (a foundational extension added by this plan, because the existing createAiSession is one-shot and cannot pause on a mid-agent question). The session orchestrator (src/session/orchestrator.ts):

  • streams thinking / text turns,
  • surfaces a structured question and pauses in awaiting_input,
  • accepts a structured answer and continues,
  • on complete, writes the artifact to the stage's conventional location.

Lifecycle states are launching → active → awaiting_input → completed, plus error and interrupted. On interrupt or error the orchestrator auto-saves progress and emits an observable event — never silent loss — and an interrupted/error session can be resumed/retried back to its current question.

Multiple sessions

Sessions are independent pipeline runs — the store, routes, and orchestrator all hold many at once (each with its own live agent handle). The dashboard's Sessions panel lists every session with its stage, status, and last activity; from there you can:

  • open any session and keep working on it (an awaiting_input session is flagged "needs your input"),
  • switch between sessions — the panel stays visible while a flow is open, and a session you switch away from keeps running server-side,
  • resume an interrupted/error session from where it stopped,
  • discard a settled (completed/error/interrupted) session via DELETE /sessions/:id, which disposes any live handle before deleting the row (pipeline-link rows are kept — board-task provenance survives).

The list refreshes on any CE push event and falls back to polling GET /sessions while any session has a turn in flight.

Live working output, steering, and the Q&A surface

Turn execution is detached: POST /sessions, /answer, and /resume return as soon as the session row reflects the request, with the agent turn running in the background. While it runs:

  • The engine streams live progress through the seam's onProgress option (thinking/text deltas + tool start/end markers — a host capability any plugin can use). The orchestrator accumulates it per session and GET /sessions/:id attaches it as liveActivity, so the flow renders a live working pane (pulsing indicator, muted thinking, per-tool ✓/✗ lines).
  • The per-turn timeout is inactivity-based: a long but actively-working turn is never killed; only a turn with no progress for turnIntervalMs is interrupted (its working trace is preserved in the transcript).
  • On settle, the working trace is persisted into the conversation history as a condensed collapsible "Agent work" block — the transcript keeps the full story: opening message, every past question and answer (option ids rendered as labels), steering turns, working traces, and completion.

Steering: alongside any selectable question the user can type free-text guidance — attached to their answer as {value, comment}, or sent WITHOUT answering as {feedback}. The stage system prompt instructs the agent to treat both as first-class input (incorporate, adjust course, re-ask or proceed).

Transport

Session updates are pushed over the shared /api/events SSE stream. The orchestrator emits observable events via ctx.emitEvent (turn / question / completed / error / interrupted, plus throttled mid-turn progress); the host forwards them to connected clients as project-scoped plugin:custom events, and the view subscribes through the subscribePluginEvents context capability — refetching the session on each event (no raw EventSource; no deep dashboard import). Client polling of GET /sessions/:id remains as a fallback while a turn is mid-flight, so a missed event still converges. Session identity is project-scoped: the projectId used at start is threaded through every later answer/resume/poll so they resolve the same store and live handle.

Work → board bridge

When a stage reaches its work phase (ce-work, stage id work), its complete payload may carry a derived task list. The orchestrator creates each as a Fusion board task via ctx.taskStore.createTask, tagged CE-originated (source workflow_step with CE markers in sourceMetadata) and recorded as a pipeline-link row. The link row — not task-row JSON — is the authoritative back-reference from a board task to its originating pipeline/stage/artifact (per the FN-5719 pattern). Created tasks then run the normal lifecycle with no plugin interference. Zero derived tasks is a clean no-op.

Bidirectional sync model

Two separate state machines are kept in sync, never merged:

  • Board-task ownership → the task's column. The board is authoritative for task state.
  • CE-pipeline ownership → ce_pipeline_state.{currentStage, status}. The CE flow is authoritative for artifact/pipeline content.

Inbound (board → pipeline). The onTaskMoved / onTaskCompleted lifecycle hooks do the minimum under the 5s hook budget: resolve the link and enqueueSync(...), then return. Heavy advancement is not done inline.

Reconcile (the convergence guarantee). reconcileCePipelines(ctx) is a single on-demand sweep — not a tight interval poll. It (1) drains the queue and (2) independently re-derives transitions by comparing live board state against pipeline state. Step (2) is why a dropped or never-enqueued hook event still converges: the queue is an optimization; the board↔state comparison is the source of truth.

Outbound (pipeline → board). When a pipeline advances to a stage that produces board work, the reconciler creates the next-stage board task via ctx.taskStore.createTask and links it.

Conflict policy. The reconciler only reads the already-terminal board task columns (board-authoritative) and only writes CE-owned fields plus a brand-new board task — the two writers never contend over the same cell.

Bundled-skills isolation model

The ce-* skills are bundled and pinned inside the plugin (src/skills/<skillId>/SKILL.md), declared via PluginSkillContribution with plugin-root-relative skillFiles. On load they are physically installed (cpSync, idempotent skip-if-exists) into a plugin-local, discoverable directory so an agent session can resolve them. The install is guarded to never touch a global ~/.claude/skills path an operator's own compound-engineering install owns — registering the bundled copy can never clobber a global install.

Settings

Operator-facing settings render in Settings → Plugins → Compound Engineering, grouped as follows. Every setting has a real consumption point in the plugin.

Sessions

Setting Type Default Effect
Default Session Provider (defaultProvider) string (host default) Passed to the interactive-session factory as defaultProvider. Blank → host picks.
Default Session Model (defaultModelId) string (host default) Passed to the factory as defaultModelId. Blank → host picks.
Enabled Stages (enabledStages) string[] full registry Only these stage IDs may be launched; the orchestrator rejects others.

Sync

Setting Type Default Effect
Reconcile on Board Changes (reconcileOnHooks) boolean true When on, the reconcile sweep auto-fires after task move/complete hooks. When off, the hook still enqueues so an on-demand sweep converges later.
Reconcile Cadence (minutes) (reconcileIntervalMinutes) number 15 Cadence hint for an on-demand refresh surface. Not a continuous poll loop.

Getters live in src/settings.ts (getDefaultProvider, getDefaultModelId, getEnabledStages, getReconcileOnHooks, getReconcileIntervalMinutes), each returning its default when the setting is absent.