Skip to content

Structured per-step run reporting as an emitted, consumable schema

State: open/active · Genre: analysis

Use case: structured-run-reporting RFC mapping: new (elevates RFC cross-cutting ‘structured per-step reporting’) Source: cross-repo survey of sksizer/* (2026-07-05), companion to the workflow-constellation RFC (PR #682).

The demand for per-step status/cost reporting is product-scale, not console-scale: an entire observability product was built to reverse-engineer run/step/status records that no orchestrator emits, a dashboard ships a bespoke run-record document schema with a crash-unsafe two-phase write protocol, and engines copy-paste step-journaling boilerplate per step type. Flowline should treat the run/step report as a first-class serializable schema — stable step ids, parent links, status lifecycle, timings, cost — that UIs and stores consume directly.

A whole product (hook → server → SQLite → WebSocket → dashboard) built solely to reconstruct sessions(runs)/agents(steps)/events(journal) with derived status enums; because the telemetry carries no step/parent ids, Pre/Post correlation is a fuzzy timestamp join and the agent tree shipped flat.

Where: docs/vision.md · src-server/src/models/agents.rs · src-server/src/models/events.rs

Pain: The core feature degraded to forensic reconstruction because hierarchy was never a field; the vision doc is a literal spec of what a workflow layer should emit.

collection_sync: a hand-designed run-record schema (status running/completed/failed, trigger, per-step stats, error) with a two-phase POST-running/PUT-terminal protocol rendered as a Sync History UI — a crash between phases leaves a stuck ‘running’ record forever.

Where: server/schemas/documents/collection_sync.json · server/api/collection-sync.post.ts · pages/collections/[id].vue

Pain: Every field invented by hand; no lease/heartbeat/timeout means no crash-safe terminal state.

StepResult journaling (Running→Completed/Failed with timestamps/output/error) copy-pasted six times across step handlers — roughly half of the 1000-line session.rs is this boilerplate.

Where: src-tauri/src/engine/session.rs · src-tauri/src/schema/step_result.rs

Pain: ~400 lines of repeated create/update bracketing a library report would replace.

InstanceState enum variants carry workflow progress data (current_step/total_steps/step_label, ProvisionFailed context), serialized to SQLite and manually mirrored into a frontend TypeScript union; two planning phases spent retrofitting it. The deprecated visualizer prototype in the same repo is second demand-evidence.

Where: prototypes/zoo/code2/src-tauri/src/vm/types.rs · prototypes/zoo/code2/src-nuxt/app/utils/types.ts

Pain: A serializable run-state schema would have eliminated the bespoke enum, DB serialization, and Rust↔TS mirror maintenance.

The scan UI hand-rolls a four-stage progress machine with pause/cancel buttons that mutate an integer, mocked success/error counts, and a fake activity log — because no backend workflow emits real step events to subscribe to.

Where: src-nuxt/app/stores/scan_state.ts · src-nuxt/app/pages/scan.vue

Pain: The entire per-step status/cancellation surface exists as UI simulation.

The RFC lists per-step reporting as cross-cutting; the evidence argues it should be a named deliverable: a versioned, serializable run/step report schema (step id, parent id, status, start/stop, cost, error) emitted by every module, so dashboards, visualizers, and DB rows become projections instead of reimplementations. Current design gap: no stated wire/storage schema or stable step-identity contract.

Builds on: any runner (03, 09) Introduces: extension: StepEvent, onEvent

A versioned per-step event schema every runner emits — the thing two dashboards were built to reconstruct after the fact:

interface StepEvent {
v: 1;
runId: string;
stepId: string; // stable across retries of the same step
parentId?: string; // links nested subloops / fan-out branches
status: "pending" | "running" | "ok" | "failed" | "skipped";
startedAt?: string;
endedAt?: string;
usage?: { inputTokens: number; outputTokens: number; costUsd?: number };
}
const result = await run(wf, { onEvent: (e) => bus.publish(e) });

Entirely an extension — the RFC lists reporting as cross-cutting but ships no schema. agent-dashboard becomes a subscriber; ontological’s six copy-pasted StepResult journals become one emitter.


← Back to Notes