Skip to content

feat(web): per-backend spend on the fleet rail - #334

Open
saucam wants to merge 3 commits into
mainfrom
feat/p5-4-fleet-economics
Open

feat(web): per-backend spend on the fleet rail#334
saucam wants to merge 3 commits into
mainfrom
feat/p5-4-fleet-economics

Conversation

@saucam

@saucam saucam commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator

First buildable slice of P5.4 (§9, §7). The rail's cost figure now opens into a per-backend split — which backend is most of the bill, how many of its sessions are working, what it has spent.

§7 calls this a metaharness differentiator rather than a nicety, and the reason is structural: no single-vendor tool needs a per-backend breakdown, because it only ever has one backend.

Three decisions worth reviewing

The daemon's agg.totalCostUsd stays the headline number, not a sum of these rows. It counts tasks that may have aged off the capped client board, so a locally derived total would drift low exactly on the long-lived fleets where spend matters. This table answers a different question — how the spend splits — scoped to the sessions the board actually describes.

A session with no providerId is reported as unknown, never folded into the default backend. Attributing spend to a provider that may not have incurred it is worse than admitting the gap, in a view whose entire purpose is comparing backends against each other.

Share is zero when nothing has been spent — never an even split. Dividing by a zero total to show four backends at 25% would invent a fact from an absence.

Two smaller ones: sessions with no usage still count while contributing no spend (ten idle sessions read as ten sessions at $0, not an empty fleet), and active counts only genuinely in-flight turns — a session at waiting_approval is stopped and belongs in the attention queue, not the concurrency number.

Collapsed by default: it answers a question you ask occasionally; the lanes answer the one you ask constantly.

P5.4 triage — why this piece first

I checked the wire before picking. Of §12's five P5.4 items:

Buildable today (pure client):

  • per-backend cost — this PR; providerId + SessionUsage are both on SessionInfo
  • tree/map lens — lineage is derivable from role plus the task→session join
  • event timelineFleetEventWire carries type + createdAt

Blocked on daemon/protocol work:

  • routing rationale + override ("↳ Opus · high-stakes edit") — no wire field carries why a backend was chosen
  • cross-vendor review edges — the "reviewer gates worker" relationship isn't modelled anywhere
  • rate-limit gauges (§9's reroute trigger) — no wire data at all
  • ⚠️ full dispatch-timeline scrubberFleetTaskWire has only createdAt, no updatedAt or transition history, so state transitions can't be reconstructed client-side. An event timeline is buildable; the scrubber §4 describes is not.

Happy to take the tree/map lens next, or to spec the protocol additions the blocked three would need.

Verification: 9 economics tests, 252 web tests total, typecheck / lint / build clean.

🤖 Generated with Claude Code

saucam and others added 2 commits September 12, 2026 21:27
First buildable slice of P5.4 (docs/conductor-frontends-design.md §9,
§7). The rail's cost figure now opens into a per-backend split — which
backend is most of the bill, how many of its sessions are working, and
what it has spent.

§7 calls this a metaharness differentiator rather than a nicety, and the
reason is structural: no single-vendor tool needs a per-backend breakdown
because it only ever has one backend. §9 asks for exactly this zoom above
a single session.

The rollup is pure (`lib/fleet-economics.ts`), so the part worth testing
needs no reactive root.

Three decisions worth naming:

**The daemon's `agg.totalCostUsd` stays the headline number** rather than
a sum of these rows. It counts tasks that may have aged off the capped
client board, so a locally derived total would drift low exactly on the
long-lived fleets where spend matters. This table answers a different
question — how the spend SPLITS — and is scoped to the sessions the board
actually describes.

**A session with no `providerId` is reported as `unknown`**, never folded
into the default backend. Attributing spend to a provider that may not
have incurred it is worse than admitting the gap, in a view whose entire
purpose is comparing backends against each other.

**Share is zero when nothing has been spent, never an even split.**
Dividing by a zero total to show four backends at 25% would invent a fact
from an absence.

Sessions with no usage still COUNT while contributing no spend, so a
fleet of ten idle sessions reads as ten sessions at $0 rather than as an
empty fleet. `active` counts only genuinely in-flight turns — a session
at `waiting_approval` is stopped, and belongs in the attention queue, not
the concurrency number.

Collapsed by default: it answers a question you ask occasionally, and the
lanes answer the one you ask constantly.

9 economics tests; 252 web tests; typecheck, lint and build clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@highflame-oracle highflame-oracle Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔮 Oracle Review

🎯 Start Here

web/src/components/FleetRail.tsx (~20 min) — Security changes in FleetRail.tsx


📋 PR Summary

What this PR does: feat(web): per-backend spend on the fleet rail


🔍 Code Review

Review completed.

Review Stats: suggestion:2


Generated by Oracle - Highflame's AI Code Reviewer

Comment thread web/src/components/FleetRail.tsx
Comment thread web/src/lib/fleet-economics.ts
Review follow-up on #334 (Oracle suggestion 2).

`"unknown"` is a sentinel that reaches the UI, and it was a bare literal
in two places — the rollup that produces it and the test that asserts on
it. Exported rather than file-local so a consumer that wants to style or
filter the bucket compares against the constant instead of re-typing the
string; the test now imports it, which is what makes this more than
ceremonial.

266 web tests; typecheck, lint and build clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@highflame-oracle highflame-oracle Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔮 Oracle Review

🎯 Start Here

web/src/lib/fleet-economics.ts (~21 min) — Security changes in fleet-economics.ts


📋 PR Summary

What this PR does: feat(web): per-backend spend on the fleet rail


🔍 Code Review

Review completed.

Review Stats: suggestion:1


Generated by Oracle - Highflame's AI Code Reviewer

row.costUsd += u.totalCostUsd;
}
byId.set(id, row);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion

Consider using Intl.NumberFormat for share percentages

While the current implementation returns a 0-1 ratio which is correct for the data layer, consider adding a helper for UI formatting that uses Intl.NumberFormat with percentage style. This would provide consistent localization (e.g., 25% vs 25,00%) across the application.

Suggested fix:

Suggested change
}
export function formatShare(share: number): string {
return new Intl.NumberFormat(undefined, {
style: 'percent',
minimumFractionDigits: 1,
maximumFractionDigits: 1,
}).format(share);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants