Flow Metrics & Analytics
LumenFlow tracks flow metrics to help teams identify bottlenecks and improve delivery performance.
Overview
Section titled “Overview”The Software Delivery Pack owns these APIs. Import them from
@hellmai/lumenflow-packs-software-delivery/metrics or its supported dora, flow,
and telemetry subpaths. The former @hellmai/lumenflow-metrics package is a
deprecated compatibility shell and contains no independent implementation.
- DORA Metrics — The dora.dev 2025 canonical 5-metric model
- Flow Analysis — Bottleneck detection and critical path calculation
- Telemetry — Event emission for local NDJSON logs and cloud sync
DORA Metrics (2025 canonical 5-metric model)
Section titled “DORA Metrics (2025 canonical 5-metric model)”LumenFlow tracks the five metrics defined by dora.dev and refreshed by the CDF Oct 2025 announcement. Aggregation follows DORA canonical guidance: mean for deployment frequency, median for lead time and FDRT.
| Metric | Group | Formula | Unit | Aggregation | Target |
|---|---|---|---|---|---|
| Deployment Frequency | Throughput | commits_in_window / days_in_window * 7 | /week | mean | Daily → weekly |
| Lead Time for Changes | Throughput | WU cycle time = completed_at − claimed_at | hours | median | < 24h |
| Failed Deployment Recovery (FDRT) | Throughput | median(time between paired EMERGENCY commits) | hours | median | < 1h |
| Change Failure Rate (CFR) | Instability | failures / total_deployments * 100 | % | ratio | < 15% |
| Deployment Rework Rate | Instability | (revert + hotfix commits) / total_deployments * 100 | % | ratio | < 5% |
Viewing DORA Metrics
Section titled “Viewing DORA Metrics”Example output:
Cloud Sync
Section titled “Cloud Sync”When a workspace has a control_plane endpoint configured, DORA records are shipped to
POST <endpoint>/api/v1/telemetry in batches of up to 1000 records. A typical
metrics:snapshot run emits 5 records and fits in a single batch.
NDJSON staging
Section titled “NDJSON staging”Records are first appended to .lumenflow/telemetry/dora.ndjson, then the cloud sync worker
reads from the persisted cursor offset and posts batched payloads. This gives offline resilience:
retries resume from the last acknowledged offset.
Tag taxonomy
Section titled “Tag taxonomy”Every record carries a tags bag the control plane can slice dashboards by. Values are
primitive (string | number | boolean); missing values are omitted rather than emitted
as empty strings.
| Tag | Source | Example |
|---|---|---|
source_type | Hard-coded | "dora" |
calculated_by | Hard-coded | "metrics:snapshot" |
tier | Per-metric classification | "elite" |
repo | git config --get remote.origin.url → parsed owner/repo | "hellmai/lumenflow" |
branch | git rev-parse --abbrev-ref HEAD | "lane/framework-metrics/wu-2635" |
commit_sha | git rev-parse HEAD | "deadbeef…" |
service | workspace.yaml → service (or software_delivery.service) | "control-plane" |
environment | workspace.yaml → environment, fallback LUMENFLOW_ENV | "prod" |
snapshot_window | --days flag | "7d", "30d" |
pipeline | CI_PIPELINE_NAME, fallback GITHUB_WORKFLOW | "main-ci" |
deploy_target | DEPLOY_TARGET | "prod-eu" |
workflow_run_id | GITHUB_RUN_ID, fallback CI_PIPELINE_ID | "987654" |
Lead time and FDRT records additionally carry aggregation: "median", mean_hours, and
p90_hours so trend dashboards can plot all three aggregations without re-running the CLI.
CFR records carry failures + total_deployments; Deployment Rework Rate carries
rework_commits + total_deployments.
Enabling cloud sync
Section titled “Enabling cloud sync”See Workspace spec for the full control_plane schema.
Full-stream telemetry routing (INIT-078)
Section titled “Full-stream telemetry routing (INIT-078)”A connected control plane no longer sees only the original four NDJSON sources
(gates, flow, dora, costs). INIT-078 routes every audited local
stream to an existing transport — the telemetry registry, the kernel-event
envelope, or a memory/signal port — or records why it is excluded. The routing
contract is ratified once, vendor-neutrally, in ADR-023 in the internal
architecture decision record set. No new packages and no new sync endpoints are
introduced.
Six new telemetry source ids join the registry, bringing it to ten append-only NDJSON sources synced with offset tracking:
| Source id | Local file | Default payload mode |
|---|---|---|
llm-classification | .lumenflow/telemetry/llm-classification.ndjson | full (PII-free at emit) |
lane-signals | .lumenflow/telemetry/lane-signals.ndjson | full |
tools | .lumenflow/telemetry/tools.ndjson | metadata-only (hashes + sizes) |
methodology | .lumenflow/telemetry/methodology.ndjson | full |
prompt-lint | .lumenflow/telemetry/prompt-lint.ndjson | full |
incidents | .lumenflow/incidents/*.ndjson (directory) | metadata-only (stack → hash) |
The sidecar tick and session-end flush keep these sources current for a connected session, not just at gate-run time.
Per-stream payload modes
Section titled “Per-stream payload modes”Each cloud-synced stream is configured under
software_delivery.telemetry.cloud_sync.streams.<source-id> with enabled and
an optional payload_mode:
payload_mode has two values:
metadata-only(default for the free-text-bearing streams —tools,memory,incidents): free-text fields are replaced by a content hash plus a byte size (e.g.arg_sha256+arg_bytes,stack_sha256+stack_bytes). Structured low-risk fields (tool name, exit code, duration, lane, WU id, memory type, tags) pass through unredacted.full: the complete payload is sent. Streams that are already free of user content (llm-classification,lane-signals,methodology,prompt-lint) default tofull.
Redaction is applied in mappers, not emitters: the local file always keeps
the complete record for local debugging (cost:summary, flow:report, forensic
inspection). Only the wire projection is redacted, and flipping a stream to
full changes only the mapper output, never the on-disk file.
Governance evidence as kernel events
Section titled “Governance evidence as kernel events”Force-bypass and skip-gates audit logs are now cloud-visible as schema-validated
kernel events on POST /api/v1/events, forming a governance event domain:
| Kernel event | Source log |
|---|---|
governance:force_bypass_recorded | force-bypasses.log |
governance:gates_skipped | skip-gates-audit.log |
Both carry full governance evidence (the audit trail is the point). A
wu:done --skip-telemetry-check bypass is itself audited and surfaces as a
governance:force_bypass_recorded event, so bypasses cannot hide.
Completed-WU change impact
Section titled “Completed-WU change impact”wu:done attaches a versioned delivery_change_manifest.v1 object to the
existing task_completed kernel event. This gives connected control planes a
source-independent description of what the completed WU changed:
- base and head commit identities;
- declared
code_pathsand paths changed outside that declaration; - added, modified, deleted, and renamed files, including both rename paths;
- source, test, documentation, configuration, environment-template, dependency, migration, infrastructure, and generated-file classifications;
- safely detected environment-variable names and configuration keys; and
- full summary counts plus explicit completeness and truncation metadata.
The manifest is deterministic and bounded to 200 entries per detail array.
Summary counts always describe the full resolved change set. When details are
bounded, completeness.complete is false and truncated_fields identifies
which arrays are partial.
The payload never includes file contents, patches, environment-variable values, secrets, credentials, or token-shaped values. Existing completion events without the optional manifest remain valid.
Connected services should store the nested object by task_id and use it to
project role-appropriate /work views. This lets managers and third-party
systems inspect migration, dependency, test, configuration, and scope impact
without repository access or independent Git analysis.
Mission Control WU telemetry
Section titled “Mission Control WU telemetry”Connected workspaces add an optional wu_telemetry.v1 snapshot to their
existing WU lifecycle events. wu:create and wu:claim send provisional
snapshots. After gates pass, wu:prep sends a checkpoint with a provisional
change manifest. wu:done sends the final snapshot and authoritative final
manifest on task_completed. Consumers that only understand the legacy event
shape can ignore the additive fields.
The rich snapshot gives an authorised Mission Control everything needed to
render active and completed work without checking out the repository: stable
WU/workspace/project identity, lifecycle and ownership, lane/priority/type,
declared scope, dependencies and acceptance, checkpoint/gate/evidence
summaries, plus deterministic repository navigation. When a canonical browser
URL is available, navigation.wu_web_url links directly to the governed WU
spec in the repository; source ref, exact commit, and known PR/CI/evidence links
are included separately.
The contract is strict, bounded, and content-free. Raw YAML, notes, completion prose, environment values, patches, source contents, local/worktree paths, token-shaped values, credential assignments, and clone or credential-bearing URLs are not valid telemetry. Cloud still applies workspace/role authorization, and opening a private repository link still requires the reader’s own Git-provider access.
Memory and signal routing
Section titled “Memory and signal routing”Shared memory (.lumenflow/memory/memory.jsonl) and A2A signals
(.lumenflow/memory/signals.jsonl) route through dedicated memory/signal sync
ports rather than the telemetry registry. Memory rows default to
metadata-only (body → hash + size); signals send their structured keys
verbatim and apply the redaction policy only to free-text bodies.
Sync-health enforcement
Section titled “Sync-health enforcement”Sync health is loud and, when configured, enforceable at wu:done via
software_delivery.telemetry.cloud_sync.enforcement:
| Level | wu:done behaviour |
|---|---|
off | No gate. Health still surfaces in the doctors. |
warn | Default. Prints per-stream pending counts + remediation; never blocks. |
block | Blocks wu:done only on a deterministic failure class (HTTP 4xx auth/config rejection, missing token). |
Transient failures (timeouts, 5xx, DNS, connection resets) never block, even
under block — they degrade to a warning so a flaky network cannot brick
wu:done. Bypass a single run with the audited
pnpm wu:done --id WU-XXX --skip-telemetry-check --reason "...".
Sync health is also a first-class line in both doctors:
Each reports per-source last_success_at / last_error, so a silent skip is no
longer the only observable state.
Flow Analysis
Section titled “Flow Analysis”Identifying Bottlenecks
Section titled “Identifying Bottlenecks”This analyzes your WU flow to identify:
- Lane Congestion — Lanes exceeding WIP limits
- Blocked WUs — Work units waiting on dependencies
- Stale WUs — WUs in progress for too long
- Critical Path — WUs blocking the most downstream work
Metrics Snapshots
Section titled “Metrics Snapshots”Capture point-in-time metrics for dashboards or CI:
Dispatchability and Throughput
Section titled “Dispatchability and Throughput”metrics:snapshot --type all and --type flow add a dispatchability and
throughput section derived from the gate_attempt ledger and the work-unit
lifecycle records. No external script produces these numbers: quoting one means
quoting a command anyone can re-run. --type lanes and --type dora are
unchanged and carry N/A for this section, so the delivery-flow auto-emit
path pays none of its cost.
| Field | Meaning |
|---|---|
dispatchable_ready | Ready work units that are dependency-clear, free of code-path overlap with any active claim, and inside lane WIP |
gate_run_cost_per_day | Gate-attempt duration p50 and p90 grouped by UTC day, over the --days window |
first_attempt_landing | Share of landed work units whose gate ledger records no failed attempt |
ownerless_in_progress | Active claims that are not bound to a recoverable owner (see below) |
unread_ack_required_signals | Ack-required coordination signals with no acked/rejected receipt |
What ownerless_in_progress measures — and what it does not
Section titled “What ownerless_in_progress measures — and what it does not”An in_progress record counts as bound only when all of these hold:
| Checked | How |
|---|---|
It names an owner (assigned_to) | Field present and non-empty |
It carries the canonical branch (claimed_branch) | Field present and non-empty |
Its declared worktree_path exists | Filesystem check, resolved against the main checkout |
| A session record exists for it | <main>/.lumenflow/sessions/WU-N.json exists |
The worktree check applies only to the claimed modes that own one. A
branch-pr claim (the cloud shape) and a branch-only claim have no worktree
by design — wu:claim writes worktree_path only when it created one — so
they are asked for a branch and a session record and nothing more. A record
that declares no claimed_mode predates the field and is treated as worktree
backed.
Anything else is unbound, and the missing piece is named in the gaps
histogram beside the count (missing_owner, missing_branch,
missing_worktree, missing_session; one record can show several). A bare
total would hide which remedy applies — a claim with a vanished worktree needs
wu:recover, one with no branch binding needs a different fix.
Session records persist after a work unit lands, so missing_session only
discriminates claims made before session records were minted on the claim path
(WU-3954). On a board of current claims it is a real signal; on an old one it
is expected, and the histogram is what makes the difference visible.
Process liveness is not measured. Nothing durable on the claim path records
a process id, so this metric never asserts that an owning process is alive or
dead. The payload states this explicitly as "liveness_probed": false, and the
human table prints process liveness not measured, so the count cannot be read
as a liveness claim it did not earn.
Field presence alone is not sufficient: a record keeps its assigned_to and
worktree_path strings long after the worktree and the session are gone, which
is how a “zero ownerless claims” reading coexisted with a board full of dead
claims before WU-3973.
Both surfaces are emitted from the same captured object: the stable JSON
document (--json, and the file written to
.lumenflow/snapshots/metrics-latest.json) and the human table printed by
default.
Every classifier reports considered, classified and excluded with a
histogram of exclusion reasons:
A metric that reported only its own numerator could hide an under-matching filter behind a confident, small, wrong answer. The counts make the size of the population, and everything dropped from it, part of the result.
Throughput as a release precondition
Section titled “Throughput as a release precondition”These numbers are also a gate, not only a report. pnpm release runs a
throughput ratchet before it creates a tag: it reads a recorded baseline, takes a
fresh measurement from this same governed command, and refuses the release when a
metric has moved in its bad direction beyond the configured tolerance.
Record or re-record the baseline — never hand-write it:
That reads this command through the same projection the release comparison uses, over the window the existing baseline declares, and refuses to write a document the comparison could not read back.
| Baseline metric | Direction | Derived from |
|---|---|---|
gate_run_p50_ms | lower is better | median per-day p50 in gate_run_cost_per_day |
gate_run_p90_ms | lower is better | median per-day p90 in gate_run_cost_per_day |
warm_path_prep_ms | lower is better | elapsed time of a warm, evidence-reusing prep |
first_attempt_landing_share_percent | higher is better | first_attempt_landing.share_percent |
Because gate_run_cost_per_day is reported per calendar day and the raw
per-attempt durations are not recoverable from the stable output shape, the
baseline reduces the window to the median of the per-day values, taken
independently per percentile.
That statistic is calibrated against this project’s ledger rather than preferred: with a per-day spread of 3.6x (p50) and 2.7x (p90), recording the worst day and comparing at 20% blocked 7 of 10 regression-free 7-day windows, while the per-day median at the shipped 25% tolerance blocks 1 of 10.
The baseline’s window_days decides the candidate’s window: percentiles taken
over different windows are not comparable, so a candidate measured over a
different one fails closed rather than being compared anyway.
A metric recorded as null is a declared absence for that workspace. An
absence declared on both sides passes and is reported; a metric the baseline
declares present that the candidate cannot supply is a failure, as are a
missing, unparseable or window-less baseline, one carrying a metric that is
neither a number nor null, one declaring every metric absent, a metrics command
that will not run, and a non-finite value. An unmeasured release is never a pass.
The complementary per-run control is the opt-in prep latency budget, which fails
an individual wu:prep that overruns the budget for its weight class once a
workspace enables it — see the
Gates reference.
Withheld Ready Work
Section titled “Withheld Ready Work”code_paths overlap blocks a claim that collides with work already in
progress. The opposite direction is just as expensive and was previously
silent: an accepted claim withholds every ready work unit that overlaps it,
including higher-priority ones.
wu:claim prints this report before any mutation, and
orchestrate:init-status prints it for every active claim in an initiative:
The report is advisory: it never blocks a claim and never fails a status view. A board it cannot read degrades to a printed note naming the cause, because a broken advisory must not become a broken claim path.
Overlap is computed over declared code_paths only. Both sides of the
intersection are expanded against the repository’s tracked files, so the answer
is a set of real files rather than a string comparison of patterns — but a file
a work unit touches without declaring it in code_paths is invisible here, in
the same way it is invisible to the overlap check that refuses a claim. The
report therefore under-reports exactly as far as code_paths under-declares.
Declarations are expanded with the same expression semantics as the blocking
claim overlap check: a directory entry (with or without a trailing slash)
covers everything beneath it, ./x and x are the same declaration, and
platform backslash separators are normalised before matching. This is not a
detail — a directory declaration compared as a raw string against enumerated
file paths matches nothing, which would let a claim holding a whole directory
report that it withholds no work at all.
Only alternatives wu:claim would accept are offered. A ready work unit
whose dependency chain is unresolved, or whose lane has no WIP headroom, is
excluded from the report (dependency_blocked, lane_wip_exhausted in the
counts) rather than named as “claim this first” — the dispatchable-share metric
and this report evaluate one shared predicate, so they cannot disagree about
what stops a claim.
The report prints its own cost as a trailing line
(Shadow report cost: <n>ms over <n> WU records). It runs last in the claim
preflight, after every non-mutating refusal and before any claim state is
written, so a refused claim does not pay for it.
Efficiency Ratio (ADR-032 §4)
Section titled “Efficiency Ratio (ADR-032 §4)”ARC-AGI-3’s RHAE metric scores completion time-per-level action efficiency
against a first-time human baseline. LumenFlow holds the same two halves per
WU: the terminal outcome, and the sizing_estimate declared before work
began. For each done WU with gate_attempt ledger evidence, flow:report,
wu:status, and metrics:snapshot compute:
gate_attemptsis self-baselined from the WU’s own ledger — the ideal is exactly one attempt per distinct gate the WU ran.commitsandtokensare baselined from the wu-sizing-guide tier ceiling matching the WU’s declaredsizing_estimate.strategy, or from the lane+type rolling median of other done WUs when no tiered estimate was declared (baseline_source: inferred).- A dimension with no actual value (for example no
costs.ndjsonrows for that WU) is omitted, never imputed.
A WU with no gate_attempt ledger evidence shows no efficiency line at all —
this is advisory, fleet-level signal, not a gate: it is not wired into
eval:run or cert:verify. See
Trajectory Supervision for the
classifier and redirect surface that produces the underlying ledger.
Telemetry Events
Section titled “Telemetry Events”LumenFlow emits structured NDJSON telemetry under .lumenflow/telemetry/:
| File | Purpose |
|---|---|
.lumenflow/telemetry/gates.ndjson | Gate execution events (duration, pass/fail, WU, lane) |
.lumenflow/flow.log | WU lifecycle events (wu:claim, wu:prep, wu:done) |
.lumenflow/telemetry/dora.ndjson | DORA metric records with canonical tag bag |
.lumenflow/telemetry/costs.ndjson | LLM cost events (model, tokens, USD) |
.lumenflow/telemetry/brief-metrics.ndjson | wu:brief prompt token reports by WU, client, section |
.lumenflow/telemetry/llm-classification.ndjson | LLM classification lifecycle events |
.lumenflow/telemetry/lane-signals.ndjson | Lane signal events |
.lumenflow/telemetry/tools.ndjson | Tool invocation events (metadata-only by default) |
.lumenflow/telemetry/methodology.ndjson | Methodology metrics |
.lumenflow/telemetry/prompt-lint.ndjson | Prompt-lint metrics |
.lumenflow/incidents/*.ndjson | Incident records (metadata-only by default) |
See Full-stream telemetry routing above for which of these stream to the control plane and at what payload mode.
pnpm wu:brief --id WU-XXX --client <client> --report-tokens prints the same
total and section-level token accounting it appends to
brief-metrics.ndjson. The file uses schema-versioned control-plane event
records and stays local by default; hosted, self-hosted, and third-party
consumers are downstream of the same neutral event shape.
CLI Reference
Section titled “CLI Reference”| Command | Description |
|---|---|
pnpm metrics:snapshot | Capture 5-metric DORA snapshot plus dispatchability and throughput |
pnpm orchestrate:init-status | Initiative progress, including the ready work its claims withhold |
pnpm flow:report | Generate DORA + gate + WU flow report |
pnpm flow:bottlenecks | Identify workflow bottlenecks and critical path |
pnpm cost:summary --brief | Summarize local wu:brief token metrics by WU, client, and date |
Best Practices
Section titled “Best Practices”-
Review metrics weekly
Schedule a weekly review of flow metrics to identify trends before they become problems.
-
Set WIP limits appropriately
If a lane is consistently at 100%+ capacity, consider splitting the lane, adding capacity, or reducing WU scope.
-
Address blockers quickly
Blocked WUs create cascading delays. Prioritize unblocking over new work.
-
Track trends, not absolutes
DORA research emphasises continuous improvement over hitting specific numbers. Watch the slope, not the intercept.
Next Steps
Section titled “Next Steps”- DORA Metrics (concept) — formal definitions, formulas, rationale
- Initiatives — multi-phase project coordination
- Team Workflow — team practices and conventions
- Workspace spec —
control_planeconfiguration