# Workflow monitor pane (Option D)

A dedicated **conductor-side pane** that renders the *full* Pi task-panel tree —
every active workflow's phases → agents (with `done`/`working`/`error`/`skipped`
markers + token counts) — in one place, so a conductor can watch every
`WorkflowManager` run at a glance without opening N separate Pi sessions.

```
Workflows running (2):
  ◆ auth_audit  1/4 agents · Scan · 3.9K tok · $0.02
  ▶ Scan  1/3 agents · 3.9K tok
    [1] ✓ discover_routes 2.1K tok · claude-haiku-4-5
    [2] ● audit_auth 1.8K tok
    [3] ○ scan_middleware
    Review  0/1 agents
    [4] ○ cross_check
  ◆ research_workflow  1/2 agents · Synthesize · 1.7K tok
  ▶ Synthesize  1/2 agents · 1.7K tok
    [1] ✓ research_topic — capital is paris 1.2K tok
    [2] ● draft_report 500 tok
```

## Why a dedicated pane?

`herdr` cells are **flat** — one status line per pane. The full per-run
phase/agent tree (phases, per-agent rows, markers, token subtotals, result
previews, error reasons) does not fit in a cell. `src/herdr-reporter.ts` already
mirrors a *one-line* aggregate into the host cell (`◆ research_topic Synthesize
12/40 · 3.2K tok`); the monitor pane layers the *full tree* into a separate
dedicated pane so a conductor gets the watchable, scrollable view across all
concurrent workflows.

## Enabling it

The monitor is **opt-in / off-by-default** — no behavior change unless you
point it at a herdr pane. Set the `WORKFLOW_MONITOR_PANE` environment variable to
the herdr pane id that should display the tree:

```sh
export WORKFLOW_MONITOR_PANE="wH:monitor"
```

When unset (the default), `installWorkflowMonitorPane` is a **no-op** — it
registers no listeners and pushes nothing, so existing sessions and any running
issue panes are completely unaffected.

When set, the monitor writes the rendered tree into that pane's
`--display-agent` metadata field via the `herdr` CLI (fire-and-forget spawn,
never throws), mirroring the `herdr-reporter.ts` CLI boundary:

```
herdr pane report-metadata <pane> --source pi-workflow-monitor \
  --display-agent "<full tree>" --ttl-ms 5000 --seq <n>
```

When no active runs remain, the monitor clears the pane:

```
herdr pane report-metadata <pane> --source pi-workflow-monitor \
  --clear-display-agent --seq <n>
```

## How it works

- **Reuses the task-panel render functions.** The monitor does not duplicate the
  renderer. `src/task-panel.ts` exports a reusable
  `renderWorkflowTree(runs, { theme, maxAgents, now })` that renders the same
  per-run header + `renderRunBody` phase/agent tree that `renderPanelDetailed`
  uses for the live in-Pi panel. `renderPanel`/`renderPanelDetailed` signatures
  and byte-identical output are unchanged — `renderWorkflowTree` is a *new*
  export that mirrors their per-run rendering from plain run-view data.
- **Refresh model.** The monitor subscribes to the same `WorkflowManager`
  events as `herdr-reporter.ts` (`agentStart`/`agentEnd`/`phase`/`tokenUsage`/
  `resumed`/`semanticStatus`) plus the run-end events (`complete`/`error`/
  `stopped`/`paused`). Pushes are **throttled** to at most one per
  `throttleMs` (default ~1s) and **deduped** — identical frames are not re-pushed,
  so a quiet run does not spam the pane. Run-end transitions flush immediately so
  a finished run clears (or re-renders the remaining active runs) without
  waiting for the next tick.
- **Self-healing.** Every push carries `--ttl-ms` (default 5000), so a crashed
  or killed workflow's tree expires from the pane instead of lingering.
- **Best-effort / never throws.** Every `herdr` call is fire-and-forget with
  `stdio: "ignore"` and `unref()`; failures (missing `herdr` binary, dead socket)
  are silently swallowed and never escape into the workflow runtime. All timers
  are `unref`'d so the monitor costs nothing when idle.
- **Idempotent.** Guards on `manager.__workflowMonitorInstalled` so a
  `session_start` re-fire (e.g. after `/reload`) does not double-register
  listeners. (The guard is *not* set when no target is configured, so a later
  install with a target still activates.)

## Wiring

`installWorkflowMonitorPane` is wired into the end of
`installHerdrReporter` (`src/herdr-reporter.ts`), so it activates alongside the
existing cell-status push whenever `WORKFLOW_MONITOR_PANE` is set. The
herdr-reporter's existing cell-status behavior is **unchanged** — the monitor is
a strictly additive sidecar. `src/index.ts` is not touched.

## Programmatic / test use

`src/workflow-monitor-pane.ts` also exports the pure, manager-decoupled pieces
so they are testable without a real pane:

- `renderWorkflowMonitorTree(manager, { theme?, maxAgents?, now? }) → string[]`
  — builds run views from `manager.listRuns()` (active = `running` | `paused`)
  - `manager.getRun(runId).snapshot` and returns `renderWorkflowTree` output.
- `buildMonitorRunViews(manager) → RenderWorkflowRunView[]` — the plain
  run-view data (prefers the live in-memory snapshot over the persisted
  summary, mirroring `renderPanelDetailed`).
- `workflowMonitorPaneTarget(env?) → string | null` — reads
  `WORKFLOW_MONITOR_PANE`.
- `installWorkflowMonitorPane(manager, opts)` — accepts a `push` callback (for
  tests / custom delivery), a `run` override (herdr invoker), `paneId`, `env`,
  `throttleMs`, `ttlMs`, `theme`, `maxAgents`, and a `now` clock for deterministic
  token/s in tests.

The default theme is a plain no-ANSI theme (`PLAIN_MONITOR_THEME`:
`fg: (_c, t) => t, bold: (t) => t`) so the pane shows readable plain text; a
real themed Pi host can pass its own `Theme` for color.

## Sample render

With two concurrent runs (`auth_audit` scanning/reviewing + `research_workflow`
synthesizing), the monitor pane shows (see the top of this doc). Each run gets
its `◆`/`⏸` icon + bold name + dim meta (`done/total agents · current phase ·
total tokens · cost · tok/s`), followed by per-phase headers (`▶`/`✓`/` `
markers + `done/total · running · errors · phase tokens`) and per-agent rows
(`[id] <icon> <label> [— error] [tokens] [· model] [— preview]`), capped at
`maxAgents` per phase with an `… N earlier agents` overflow line.
