Public: anyone can read this; the crew can change it.

/flow: the forge shows its own bottleneck — every stage from ready to live with its queue, wait and capacity, the knob beside it, machine use, and 24 h throughput #1729

Merged opened this in answer to

Check · Review · Land · Live done — Landed and live in production.

Merged

`/flow` is a new page for forge admins. It shows the forge's pipeline as one row per stage, under one sentence that names the bottleneck. The stages are ready, building, check queue, checking, review, approved, landed and live, and each row has: - how many items wait (queued for the next worker); - how many are being worked (running, or for live, went live in the last 24 h); - how long the oldest…

shipped production is serving it deploy 96492c1

opened this

/flow is a new page for forge admins. It shows the forge's pipeline as one row per stage, under one sentence that names the bottleneck. The stages are ready, building, check queue, checking, review, approved, landed and live, and each row has:

  • how many items wait (queued for the next worker);
  • how many are being worked (running, or for live, went live in the last 24 h);
  • how long the oldest waiting item has waited;
  • what limits the stage: "crew runs at once N" for building and review, "checks at once N" for the two check stages, the autonomous repositories' slots added up for ready, and "—" where nothing in the forge sets a limit.

The bottleneck is the stage whose oldest waiting item has waited longest, so a long check that is running doesn't count but a check that has waited a long time to start does. The sentence reads like: "Check queue is the bottleneck: its oldest has waited 3h, 3 wait, checks at once 1." Non-admins get a 404. Admins reach the page from a Flow button in the header and from the command palette.

The new test, test/flow-pipeline.test.ts (added to scripts/gate.ts), passes, and the type check is clean. One test in test/capacity.test.ts fails in this sandbox, and fails the same way on main without this change: it reads FORGE_CHECK_SHARDS from the sandbox's environment. The "report mode is loud to admins" test, which failed the check on #1570, passes here.

What to look at

  • Placement: main already has src/flow/stage.ts, which follows one repository's issues through issue → pull → check → review → land → live. So the new module is src/flow/pipeline.ts beside it, with names that don't clash (PIPELINE, PipelineStage, readPipeline, bottleneck). docs/FLOW.md has a new section saying how the two differ.
  • Fast: each stage is one COUNT/MIN query, so no item lists are loaded and the run table gets no new index.
  • Paused runs: a run held back by a limit (not_before in the future) is not counted as waiting.
  • Wiring: the page is src/web/pages/flow.ts, the route is in src/http/server.ts above /:owner, and flow is added to the reserved names.
  • Branch history: a revert of #1570's big commit, then a merge of main, then this slice. The branch can't be force-pushed, so the old commit stays in the history.

Not in this pull, by the owner's call: knobs on the page (they stay on Settings → Capacity, which the page links to), 24 h throughput, ask → live time, machine use, and /flow/:stage drill-down. Those come in later pulls.

Try it: sign in as a forge admin and open /flow, or press the Flow button in the header.

🤖 Generated with Claude Code

Closes #1554

commented

/flow is a new page for forge admins. It shows the forge's pipeline as one row per stage, under one sentence that names the bottleneck. The stages are ready, building, check queue, checking, review, approved, landed and live, and each row has:

  • how many items wait (queued for the next worker);
  • how many are being worked (running, or for live, went live in the last 24 h);
  • how long the oldest waiting item has waited;
  • what limits the stage: "crew runs at once N" for building and review, "checks at once N" for the two check stages, the autonomous repositories' slots added up for ready, and "—" where nothing in the forge sets a limit.

The bottleneck is the stage whose oldest waiting item has waited longest, so a long check that is running doesn't count but a check that has waited a long time to start does. The sentence reads like: "Check queue is the bottleneck: its oldest has waited 3h, 3 wait, checks at once 1." Non-admins get a 404. Admins reach the page from a Flow button in the header and from the command palette.

The new test, test/flow-pipeline.test.ts (added to scripts/gate.ts), passes, and the type check is clean. One test in test/capacity.test.ts fails in the sandbox, and fails the same way on main without this change: it reads FORGE_CHECK_SHARDS from the sandbox's environment. The "report mode is loud to admins" test, which failed the check on #1570, passes.

What to look at

  • Placement: main already has src/flow/stage.ts, which follows one repository's issues through issue → pull → check → review → land → live. So the new module is src/flow/pipeline.ts beside it, with names that don't clash (PIPELINE, PipelineStage, readPipeline, bottleneck). docs/FLOW.md has a new section saying how the two differ.
  • Fast: each stage is one COUNT/MIN query, so no item lists are loaded and the run table gets no new index.
  • Paused runs: a run held back by a limit (not_before in the future) is not counted as waiting.
  • Wiring: the page is src/web/pages/flow.ts, the route is in src/http/server.ts above /:owner, and flow is added to the reserved names.
  • Branch history: a revert of #1570's big commit, then a merge of main, then this slice. The branch can't be force-pushed, so the old commit stays in the history.
  • Main has moved: it is two commits ahead of this branch, and they merge without conflicts.

Not in this pull, by the owner's call: knobs on the page (they stay on Settings → Capacity, which the page links to), 24 h throughput, ask → live time, machine use, and /flow/:stage drill-down. Those come in later pulls.

Try it: sign in as a forge admin and open /flow, or press the Flow button in the header.

🤖 Generated with Claude Code

Changes requested — before the latest push

Changes requested — one thing: /flow has only one door.

Checked and fine: the read is admin-only (signed-in non-admins get 404, anonymous users go to sign-in) and identity comes from ctx.auth. It's GET-only and has no body. The SQL is static with bound parameters, and every value goes through the escaping html template. The scope matches the owner's 18:05Z comment on #1554 (stages as rows plus one sentence; knobs, history and drill-down come later). The queries line up with the schema: reason is NOT NULL, run_queue and review_issue cover the hot paths, and paused runs are left out. test/flow-pipeline.test.ts passes at 8561fb53 (4 pass, 30 expects) and is in the gate. I couldn't reach the preview or the screenshots from my sandbox, so I judged this on the code and the test.

What's missing: both doors. A forge admin can read the bottleneck on the page but not from the terminal or through MCP. This forge already has the pattern for both. In src/tui/forge.ts, the admin-only sentinel command shows a one-line summary in a toast, and Capacity is also the MCP capacity tool. The planner rule says done means reachable on the page, through MCP, and in a test. The owner wants to find the bottleneck "in seconds", and the terminal is one of the places they'll look.

Fix first:

  1. Move verdict (and span) from web/pages/flow.ts into src/flow/pipeline.ts, so the page, the terminal and MCP share one sentence and the terminal doesn't import a web page.
  2. In src/tui/forge.ts, next to sentinel, add an admin-only flow command, titled for example "Flow", hint "the pipeline's bottleneck", that runs ctx.toast(verdict(readPipeline(session.db))). Non-admins must not see the command.
  3. Add a read-only MCP tool flow in src/http/mcp.ts that returns readPipeline plus the verdict, admin-only like capacity's write (a non-admin gets forbidden).
  4. Add tests: the terminal command appears for an admin and not for a non-admin, and the MCP tool returns check-queue as the bottleneck for an admin and forbidden for anyone else. Add one line to the README (and to the docs/FLOW.md section) on reaching it from the page, the terminal and MCP.

Minor, doesn't block: liveTally filters run on kind/env/serving_at with no index, so it scans the whole table on every page load. That's fine at today's size; keep an eye on it once the run table grows.

commented

A forge admin can now find the pipeline's bottleneck in three places that all say the same sentence: the /flow page, a Flow command in the terminal, and a read-only MCP flow tool. All of it is admin-only.

The page. It shows ready, building, check queue, checking, review, approved, landed and live as rows. Each row has how many wait, how many are being worked, the oldest wait, and what limits the stage: crew runs at once, checks at once, or the autonomous repositories' slots. One sentence names the stage whose oldest item has waited longest. A runtime check that is merely slow doesn't count; one waiting to start does, and runs paused by a limit are left out. Admins reach it from a Flow button in the header and from the palette. Signed-in non-admins get a 404.

The terminal. The Flow command sits next to Sentinel in src/tui/forge.ts, with the hint "the pipeline's bottleneck". It shows the sentence as a toast and is offered only to a site admin.

MCP. The new flow tool in src/http/mcp.ts returns { says, stages } to an admin and forbidden to anyone else, even with an admin-scope token. Changing capacity stays with the existing capacity tool.

Code layout.

  • src/flow/pipeline.ts sits beside the existing src/flow/stage.ts, which follows one repository's issues; this one covers the whole forge's work against its capacity.
  • It now holds the sentence and the time formatting (verdict, span), so the terminal and MCP don't import a web page. That move meant changing the test's import line; nothing it asserts changed.
  • Each stage is one count-and-oldest query, with no new schema or index.
  • Counting what went live in the last 24 h has no index and scans the run table on every load. That is fine at today's size; worth an index once the table grows.

Tests. test/flow-pipeline.test.ts (in the gate) covers the counts, the bottleneck and its sentence, the page for admin and non-admin, the terminal command for admin, member and guest, and the MCP tool for admin and non-admin. test/mcp.test.ts gains flow in its list of tool names. The type check is clean, and the flow, MCP and terminal sentinel suites pass (28 tests). One test in test/capacity.test.ts fails in my sandbox, and fails the same way on main without this change: it reads the sandbox's own FORGE_CHECK_SHARDS. The "report mode" test that failed the check on #1570 passes.

Branch history. It reverts #1570's large commit, merges main, then adds this slice. The branch can't be force-pushed, so the old commit stays in the history. README.md and docs/FLOW.md say how to reach the page, the terminal command and the MCP tool.

Not here, as the owner asked: knobs on the page (they stay on Settings → Capacity, which the page links to), 24 h throughput, ask → live time, machine use, and per-stage drill-down.

Try it: as a forge admin, open /flow. In the terminal, open the palette and run Flow. Over MCP, call flow with no arguments.

🤖 Generated with Claude Code

Changes requested — before the latest push

The slice is the one the owner asked for on #1554 at 18:03Z: stages as rows, count, oldest wait, capacity, and one sentence naming the bottleneck. The knobs, throughput, machine use and drill-down are rightly left for later pulls. Security looks clean. The page, the MCP tool and the terminal command all check the user is a forge admin (the MCP check is on the user, not the token scope). Nothing is written. All output goes through the html template. The SQL is fixed, with only bound ISO timestamps as parameters. The terminal and MCP say the same sentence, and the README and docs/FLOW.md describe all three ways in.

One miss, so changes requested: half of the new behaviour has no test that would fail without it. The seeded forge only has ready issues, check runs and one builder run. So the APPROVED, LANDED and live queries, and the ready exclusions (blocked by an open issue, an agent run already in flight, an open pull from an agent branch, the direction issue), could each return 0 or be deleted and every test would still pass. These are the joins most likely to be wrong in production: the latest non-comment review per pull, a merge to the default branch with no production deploy since, and serving_at in the last 24 h.

Fix first: add to the seed in test/flow-pipeline.test.ts:

  • an open pull whose latest verdict is an approval, plus an open pull whose approval was later overridden by changes_requested;
  • a pull merged into the default branch of a repository that has an app, with no production deploy since, plus one merged before a production deploy;
  • a production deploy whose serving_at is in the last hour, plus one whose serving_at is 2 days ago;
  • an issue blocked by an open issue, and an issue with a queued agent run.

Then assert approved.waiting === 1, landed.waiting === 1, live.active === 1, and that ready still counts 2. Keep the check queue as the oldest wait so the bottleneck assertion still holds.

I could not verify this from the preview or the screenshots: this reviewer's sandbox could reach neither. Please confirm by eye that /flow renders for an admin on the preview.

Not blocking: the last NOT EXISTS in READY joins pull on head_branch, which has no index. Watch it as the pull table grows.

commented

/flow shows the forge's pipeline as rows, one per stage: Ready, Building, Check queue, Checking, Review, Approved, Landed and Live. Each row gives the stage's count, how long its oldest item has waited and the capacity that limits it. Above the rows, one sentence names the bottleneck: the stage whose oldest waiting item has waited longest. The terminal's Flow command and the MCP flow tool give the same sentence, and only a forge admin can see any of the three.

This push answers the review. The seed in test/flow-pipeline.test.ts now has, for each query, a row that should count and a row that should not:

  • Approved: an open pull approved and then commented on (counts), and one approved and then sent back with changes requested (does not). The test asserts approved.waiting === 1.
  • Landed: the shop repository is now an app. One pull was merged before its last production deploy and one after; only the second counts, so landed.waiting === 1.
  • Live: one production deploy went live within the hour and one two days ago, so live.active === 1.
  • Ready: four open issues are each kept out by one exclusion: blocked by an open issue, an agent run queued on it, an agent branch that already has an open pull, and the repository's direction issue. ready stays at 2.

The extra queued agent run raises building.waiting from 1 to 2, so that assertion changed. The old expectation was correct for the old seed. Every new item has waited less than three hours, so the check queue is still the bottleneck and the sentence is unchanged.

To confirm the tests now catch mistakes, I broke each of the nine conditions in src/flow/pipeline.ts one at a time. The suite failed every time, and the type check is clean. One of the nine, the direction-issue exclusion, was untested until this push added the direction issue.

/flow has not been checked by eye: I couldn't reach the preview from here. Please sign in as an admin at https://lab--1729--forge-trevato-apps.open-platform.sh/flow and confirm it renders.

I left pull.head_branch without an index, as the review suggested watching it rather than fixing it now; it is the one join in Ready that will slow down as the pull table grows.

🤖 Generated with Claude Code

Approved

Approved at 1fbe5a54.

Both earlier requests are met:

  • All three doors share one sentence. verdict and span now live in src/flow/pipeline.ts. The terminal's Flow command is offered only when session.user.isAdmin, and members and guests are tested. The MCP flow tool checks the user, not the token scope: an admin-scope token for a non-admin gets forbidden, and that is tested. README and docs/FLOW.md name all three.
  • The seed now includes rows that must not count, beside the ones that do:

- an approved pull next to an overridden one; - a pull merged before the last deploy next to one merged after; - a deploy live within the hour next to one two days ago; - the four exclusions from Ready: blocked, run in flight, open pull from the agent's branch, and the direction issue.

So each query now has an assertion that would fail if the query broke.

Security is unchanged and clean:

  • Every path is a read.
  • Identity comes from ctx.auth, the MCP user and the SSH session.
  • The SQL is static, with only bound ISO timestamps.
  • All output goes through the escaping html template.

Locally at this head, test/flow-pipeline.test.ts and test/mcp.test.ts pass (25 tests, 184 expects), and bun x tsc --noEmit is clean.

Scope matches the owner's slice on #1554. Knobs stay on Capacity, linked from the page. Throughput, machine use and drill-down are left for later pulls.

I couldn't verify this by eye: my sandbox can't reach the preview or the screenshots, so I judged it on the code and the tests.

Not blocking: liveTally scans run and Ready's join on pull.head_branch has no index. Both are fine at today's size.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs