Skip to content

feat(harness): team scoped usage and spend chart - #145

Merged
youngzheng-oss merged 9 commits into
mainfrom
young/harness-usage-by-team
Oct 14, 2026
Merged

youngzheng-oss merged 9 commits into
mainfrom
young/harness-usage-by-team

Conversation

@youngzheng-oss

@youngzheng-oss youngzheng-oss commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

baseten harness usage shows what you spent on routes this month in each of your teams, against your limit there, with a chart of spend per day per model and each model's spend and share. --team narrows it to one team; --start/--end/--since pick another window, like route usage.

Every team, no --team:

October 2026 · resets Nov 1
Infra                                $56 of $100

  $6 ┤            ╭──╮
  $5 ┤            │  ╰──╮
  $4 ┤      ╭─────╯     │        ╭──╮
  $3 ┤   ╭──╯     ╭──╮  ╰──╮  ╭──╯  ╰──╮
  $2 ┤───╯     ╭──╯  ╰─────╰──╯        │
  $1 ┤───╭─────╯───────────╰───────────╰──╮
  $0 ┼───╯                    ╰───────────╰─────
        Oct 2    Oct 5    Oct 8    Oct 11   Oct 14

  ● zai-org/GLM-5.3                  $41  (73%)
  ● claude-opus-5-5                  $15  (26%)
  ● deepseek-ai/DeepSeek-V4.1-Flash  <$1   (1%)

FDE                                  $13 of $10 (team default $100) · limit reached

  $3 ┤            ╭─────╮
  $2 ┤      ╭─────╯     ╰──╮
  $1 ┤      │              ╰──╮
  $0 ┼──────╯                 ╰─────────────────
        Oct 2    Oct 5    Oct 8    Oct 11   Oct 14

  ● claude-opus-5-5                  $13 (100%)

Research                              $0 of $50

One team, --team FDE:

October 2026 · resets Nov 1
FDE                  $13 of $10 (team default $100) · limit reached

  $3 ┤            ╭─────╮
  $2 ┤      ╭─────╯     ╰──╮
  $1 ┤      │              ╰──╮
  $0 ┼──────╯                 ╰─────────────────
        Oct 2    Oct 5    Oct 8    Oct 11   Oct 14

  ● claude-opus-5-5  $13 (100%)

Another window, --since 7d (no limit, since limits are monthly):

Oct 7 to Oct 14, 2026
Infra                                $23

  $4 ┤                  ╭─────╮
  $3 ┤──────╮     ╭─────╯     │
  $2 ┤──────╰─────╯           ╰─────╮
  $1 ┤──────╰───────────────────────╰─────╮
  $0 ┼            ╰───────────────────────╰───────────
            Oct 8       Oct 10      Oct 12      Oct 14

  ● zai-org/GLM-5.3                  $17  (73%)
  ● claude-opus-5-5                   $6  (27%)
  ● deepseek-ai/DeepSeek-V4.1-Flash  <$1   (0%)

FDE                                   $3

  $2 ┤──────╮
  $1 ┤      ╰─────╮
  $0 ┼            ╰───────────────────────────────────
            Oct 8       Oct 10      Oct 12      Oct 14

  ● claude-opus-5-5                   $3 (100%)

Research                              $0

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@youngzheng-oss
youngzheng-oss force-pushed the young/harness-usage-by-team branch from 31476df to fcf86b4 Compare October 12, 2026 20:30
@youngzheng-oss youngzheng-oss changed the title feat(harness): Show usage this month per team, with a chart of spend per day feat(harness): team scoped harness usage and usage chart Oct 12, 2026
@youngzheng-oss youngzheng-oss changed the title feat(harness): team scoped harness usage and usage chart feat(harness): team scoped usage and spend chart Oct 12, 2026
@youngzheng-oss
youngzheng-oss force-pushed the young/harness-usage-by-team branch from 4a99542 to ef77d99 Compare October 12, 2026 22:06
@youngzheng-oss
youngzheng-oss changed the base branch from main to young/harness-setup-team October 12, 2026 22:06
@youngzheng-oss
youngzheng-oss added this pull request to stack #149 October 12, 2026 22:06
@youngzheng-oss
youngzheng-oss marked this pull request as ready for review October 12, 2026 22:52
Copilot AI balanced review requested due to automatic review settings October 12, 2026 22:52
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-12T22:56:19.812878Z ef77d99 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ef77d99743

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/cmd/command.harness_usage.go Outdated
Comment on lines +59 to +61
case len(teams) == 0:
ctx.LogLine("No usage this month.")
return nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Emit an empty JSON array when the user has no teams

When listing.Teams is empty and the command is invoked with --output json, this returns before NewJSONArrayWriter is created, so stdout contains no bytes rather than the valid [] produced by other streamed-array commands. Any automation parsing the documented JSON output receives an unexpected EOF; initialize/close the array writer before this branch or explicitly emit an empty array in JSON mode.

Useful? React with 👍 / 👎.

var team managementapi.RouteTeamUserSettings
switch {
case f.Team != "":
i := slices.IndexFunc(teams, func(t managementapi.RouteTeamUserSettings) bool { return t.TeamId == f.Team || t.TeamName == f.Team })

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject ambiguous team names before selecting a team

When a user belongs to multiple teams with the same name, this predicate silently selects the first one and reports that team's spend even though the supplied name is ambiguous. It can also let an earlier name match override a later exact ID match. Team resolution elsewhere in the CLI explicitly gives IDs precedence and rejects duplicate-name matches, so this lookup should scan for an exact ID first and otherwise require exactly one matching name.

Useful? React with 👍 / 👎.

Copilot AI 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.

🟡 Changes recommended

Chart width overflow and ambiguous series identifiers can make the new visualization incorrect or unreadable.

2 open findings
What changed in this PR

Adds team-scoped monthly Harness usage reporting with spend limits, per-model totals, and a terminal chart.

Changes:

  • Selects a team and fetches month-to-date usage.
  • Renders spend limits, model shares, and daily charts.
  • Updates usage tests and the management API dependency.
File Description
internal/​cmd/​usage_chart.go Implements the terminal chart.
internal/​cmd/​export_test.go Exposes chart rendering to tests.
internal/​cmd/​command.route_usage_test.go Moves route usage flag coverage.
internal/​cmd/​command.harness_usage.go Implements team-scoped usage reporting.
internal/​cmd/​command.harness_usage_test.go Tests teams, output, and charts.
cmd/​command.harness.go Updates flags and help text.
go.mod Updates the management API client.
go.sum Updates dependency checksums.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +169 to +170
paint := func(s string, i int) string {
return ansi.Style{}.ForegroundColor(usageChartColors[i%len(usageChartColors)]).Styled(s)
Comment thread internal/cmd/usage_chart.go Outdated
Comment on lines +93 to +97
axis := []byte(strings.Repeat(" ", labelWidth+2+cols+6))
every := (8 + step - 1) / step
for d := (days - 1) % every; d < days; d += every {
label := first.AddDate(0, 0, d).Format("Jan 2")
copy(axis[labelWidth+2+max(0, d*step+(step-len(label))/2):], label)
@youngzheng-oss
youngzheng-oss force-pushed the young/harness-usage-by-team branch from ef77d99 to 1597722 Compare October 13, 2026 01:16
Comment thread cmd/command.harness.go Outdated
{
Name: "usage",
Summary: "Show your routes spend and tokens, month to date by default (PRE-RELEASE)",
Summary: "Show your routes spend this month in one of your teams (PRE-RELEASE)",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

in one of your teams

Can you confirm the cross-team-viewing limitation will exists in API and UI too?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Everything in the UI is scoped down to the team you're drilled down into (except of course admins can look at cross team)

Image

For the API, we did want to have the ability to return data for all teams. Do you feel like we should have parity between the three? I could scope down the API to also force passing in --team in a followup, but it felt right to make team_id optional in the API, open to having my mind changed

@cretz cretz Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

except of course admins can look at cross team

If admins can do this in UI, how do they do it in API/CLI/SDKs/MCP/etc?

@youngzheng-oss youngzheng-oss Oct 13, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

sorry, i shoul've clarified: admins can do this in the API /SDK as well.

We're delaying shipping admin controls (e.g. setting spend limits) in the CLI for now, but parity will come.

MCP not supported at all for now

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We're delaying shipping admin controls (e.g. setting spend limits) in the CLI for now, but parity will come.

For this one, I was more curious about viewing usage than setting spend limits. Don't have to add now of course, I am just wondering what the command would look like. If a common API call is allowed to view single or multi-team usage, will that be the case for CLI? Specifically, what is the default when you don't provide a team in API/SDK vs CLI and why do they have to be different?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I couldn't come up w a strong argument for why API and CLI should behave differently, so now behavior is, when --team is not passed in, it shows usages across all teams!

Comment thread internal/cmd/usage_chart.go Outdated

@cretz cretz Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Not sure this deserves its own file IMO, can just put in one of the two files it is used and reference from the other IMO (there is plenty of precedent on this)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ah okay! sounds good lemme move this

Comment thread internal/cmd/export_test.go Outdated

// RenderUsageChart exposes the chart renderer to tests, painting series i's
// characters as <i>…</i>.
func RenderUsageChart(first time.Time, series [][]float64, width int) string {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Sign of a code smell IMO, where is this used?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ended up changing to call renderUsageChart from inside the package so no need for this shim anymore. thanks for catching

Comment thread cmd/command.harness.go
Comment on lines -206 to -208
Start time.Time `flag:"start" desc:"Start of the range, inclusive, snapped down to its UTC day. ISO 8601, local when no timezone is given. Defaults to the start of the current UTC month."`
End time.Time `flag:"end" desc:"End of the range, exclusive, rounded up to the end of its UTC day. ISO 8601, local when no timezone is given. Defaults to now."`
Since time.Duration `flag:"since" desc:"Window from a relative time ago until now (e.g. '7d'). Mutually exclusive with --start and --end."`

@cretz cretz Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I can no longer give a range? Is it supported in API? UI?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

UI only allows picking by billing month. Let me actually add in the ability to do that in CLI now since it's cheap

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

added baseten harness usage --month 2026-09

@cretz cretz Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

But unlike UI, API and CLI are quite rigid, does API/SDK accept full date? Why not CLI? And reusing the start/end or since has consistency across CLI.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

that is very fair. supports full date just like API now baseten harness usage --start 2026-06-01T00:00:00Z --end 2026-06-15T00:00:00Z

Base automatically changed from young/harness-setup-team to main October 13, 2026 18:09
@youngzheng-oss
youngzheng-oss force-pushed the young/harness-usage-by-team branch from 1cc40ba to 40fffaa Compare October 13, 2026 18:09
…per day

`baseten harness usage` now shows, for each team you're in, what you
spent there this month of your limit there (with the team's default when
yours differs, and "limit reached" or "team limit reached" when your
requests are rejected), a chart of spend per day per model when stdout is
a terminal, and each model's spend and share. It covers this month only,
the period spend limits apply to: the window, filter, grouping and
--user-id flags are gone, leaving --team and --output; `baseten route
usage` keeps all of them. JSON output is the API's daily buckets as
before, now with team_id.

`baseten route usage` gains --group-by team, showing usage metered before
it was attributed to a team as "(no team)".

Requires GET /v1/routes/settings/users/me/teams from
basetenlabs/baseten#32884 and the regenerated
client.

Claude-Session: https://claude.ai/code/session_01NWBCrJFG8msRVeZnny39uT
The JSON writer opened after the no-teams return, so --output json printed
nothing. The last date label can stick out two columns past the plot, which
the width check didn't count, so a chart at the threshold width wrapped.

Claude-Session: https://claude.ai/code/session_01NWBCrJFG8msRVeZnny39uT
The renderer had one caller, and the export shim existed only because the
tests are in an external package. A white-box test file needs neither.

Claude-Session: https://claude.ai/code/session_01NWBCrJFG8msRVeZnny39uT
Mirrors the UI's month picker rather than a free range: limits are per
month, so the window is a month. A past month shows what the usage adds up
to with no limit, reset date, or blocked state, since the settings endpoint
only knows this month's.

Claude-Session: https://claude.ai/code/session_01NWBCrJFG8msRVeZnny39uT
Same flags and shared window parser as `baseten route usage`, so the CLI is
consistent and any window the API takes works here. The limit, reset date,
and blocked state are only shown for this month so far, since the settings
endpoint only knows this month's; other windows show the usage summed and
the window's dates. Daily spend is indexed from the window start, not the
day of the month.

Claude-Session: https://claude.ai/code/session_01NWBCrJFG8msRVeZnny39uT
@youngzheng-oss
youngzheng-oss force-pushed the young/harness-usage-by-team branch from 313338c to 5e7435a Compare October 14, 2026 13:48

@cretz cretz left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good, but wanting to really resolve #145 (comment). I think understanding the intentional inconsistency with API/SDK and at least envisioning what a cross-team form of this command looks like is important for this one.

Chad asked why harness usage needed a team when the API shows everything
you're allowed to see without one. Now --team only narrows: without it,
one usage call grouped by team splits your spend into a block per team
you're in, each with its line, chart, and models, sharing one pair of
columns. Usage in a team you've left, or from before team attribution,
has no settings to show it against and is left out of the text view.

Claude-Session: https://claude.ai/code/session_01NWBCrJFG8msRVeZnny39uT
@youngzheng-oss
youngzheng-oss merged commit d569272 into main Oct 14, 2026
3 of 4 checks passed
@youngzheng-oss
youngzheng-oss deleted the young/harness-usage-by-team branch October 14, 2026 21:47
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.

4 participants