Skip to content

feat: Markdown progress rendering for human-readable dashboards #386

@jafreck

Description

@jafreck

Summary

Add a MarkdownProgressRenderer that produces human-readable Markdown progress reports from the framework's progress data model, providing a dashboard-like view without requiring a web UI.

Motivation

The framework has IssueProgressWriter and FleetProgressWriter that produce JSON progress files. These are great for machine consumption but aren't human-readable. When operators are monitoring a long-running agent pipeline (especially via SSH, CI logs, or file watchers), a well-formatted Markdown file is immediately useful:

  • Can be viewed in any terminal with cat or less
  • Renders natively in GitHub, VS Code, and most editors
  • Can be committed to a repository for persistent status tracking
  • Works as a lightweight alternative to building a monitoring web UI

AAMF implements a ProgressWriter that generates a rich progress.md with phase status tables, task completion progress bars, event timelines, retry history, wave lifecycle tracking, and terminal exhaustion summaries — all atomically updated after each state change.

Proposed API

MarkdownProgressRenderer

interface MarkdownProgressConfig {
  /** Project/run name displayed in the header. */
  projectName: string;
  /** Path to write the progress.md file. */
  outputPath: string;
  /** Whether to include the event timeline section (default: true). */
  showEvents?: boolean;
  /** Maximum number of events to show (default: 50, most recent). */
  maxEvents?: number;
  /** Whether to include token usage breakdown (default: true). */
  showTokenUsage?: boolean;
  /** Custom sections appended after the standard sections. */
  customSections?: Array<{ title: string; render: () => string }>;
}

class MarkdownProgressRenderer {
  constructor(config: MarkdownProgressConfig, logger: LoggerLike);

  /** Set the phase definitions for rendering the phase table. */
  setPhases(phases: PhaseDefinition[]): void;

  /** Update a phase's status. */
  updatePhase(phaseId: number, status: 'pending' | 'running' | 'completed' | 'failed' | 'skipped', notes?: string): void;

  /** Set the total task count (for progress percentage). */
  setTaskCount(total: number): void;

  /** Update a task's status. */
  updateTask(taskId: string, status: 'pending' | 'running' | 'completed' | 'failed' | 'blocked', details?: Record<string, unknown>): void;

  /** Append a timestamped event to the timeline. */
  appendEvent(message: string): void;

  /** Update token usage stats. */
  updateTokenUsage(usage: { total: number; byPhase?: Record<number, number>; byAgent?: Record<string, number> }): void;

  /** Update cumulative wall-clock duration. */
  updateDuration(durationMs: number): void;

  /** Write the progress.md file atomically. */
  flush(): Promise<void>;
}

Generated Markdown Structure

# Migration Progress — {projectName}

**Started:** 2026-03-09T14:30:00Z | **Duration:** 2h 34m | **Tokens:** 1,234,567

## Phases

| # | Phase | Status | Notes |
|---|-------|--------|-------|
| 0 | KB Indexing | :white_check_mark: Completed | 6,336 symbols indexed |
| 1 | Task Graph | :white_check_mark: Completed | 142 tasks, 8 SCCs |
| 2 | Impact Assessment | :white_check_mark: Completed | |
| 3 | Migration Strategy | :arrow_forward: Running | |
| 4 | Iterative Migration | :hourglass: Pending | |

## Tasks — 47/142 completed (33%)

`████████████░░░░░░░░░░░░░░░░░░░░` 33%

| Status | Count |
|--------|-------|
| Completed | 47 |
| Running | 3 |
| Failed | 1 |
| Blocked | 2 |
| Pending | 89 |

## Token Usage

| Agent | Tokens | Est. Cost |
|-------|--------|-----------|
| code-migrator | 890,000 | $2.67 |
| parity-verifier | 234,000 | $0.70 |
| test-writer | 110,567 | $0.33 |

## Recent Events

- `14:32:05` Phase 0 completed — KB indexed 6,336 symbols
- `14:33:12` Phase 1 completed — 142 tasks, 12 waves
- `14:45:00` Phase 2 completed — impact-assessor succeeded
- `15:01:33` Wave 1 started — 8 tasks
- `15:12:44` Wave 1 barrier entered — build passed, 2 test failures
- `15:14:02` Wave 1 convergence iteration 1 — 1 failure remaining

FleetEventBus Integration

The renderer can subscribe to framework events for automatic updates:

/** Create middleware that auto-updates the renderer from framework events. */
function createProgressMiddleware(renderer: MarkdownProgressRenderer): EventBusMiddleware;

Events mapped:

  • phase-startedupdatePhase(id, 'running')
  • phase-completedupdatePhase(id, 'completed')
  • issue-started / work-step-startedupdateTask(id, 'running')
  • work-step-completedupdateTask(id, 'completed')
  • All events → appendEvent(formatted message) + flush()

Implementation Notes

  • flush() uses atomic write (write to temp file + rename) so readers never see partial content
  • The renderer debounces flush() calls — if multiple updates arrive within 100ms, only one write occurs
  • Progress bar is built from Unicode block characters for terminal compatibility
  • Emoji status markers (:white_check_mark:, :arrow_forward:) render in GitHub/VS Code; plain text fallbacks for terminals
  • customSections allows consumers to append domain-specific sections (e.g., AAMF adds wave lifecycle and retry target tables)
  • The renderer is independent of IssueProgressWriter — they can run in parallel (JSON for machines, Markdown for humans)

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions