Skip to content

M6-06 Statistics views

No Date Ruling Reason
D-M6-06-1 2026-10-24 Data per section of A7: getRunStats gives the run tile (the total of runs; a second call for the previous range of the same length gives the comparison), runs by result per bucket (completed, failed, cancelled of every agent together), the run duration per agent and the failure reasons (failed_reason_<code> summed); getCostStats is called twice, group_by=agent (tiles and costs per agent) and group_by=space; getToolCallStats, getErrorStats and getUsageStats give their sections, the usage always in weeks because active_people exists only per week or month (D-M6-05-3). Only counters and the amounts of one billing mode are summed over buckets; rates, percentiles and counts of people are shown per bucket as delivered. The duration chart shows p50 and p95 of the latest bucket per agent, its table every bucket, because a percentile of the range cannot be built from bucket percentiles. Bucket sizes follow the range: hours up to statsHourlyUpTo (two days), days up to statsDailyUpTo (92 days), weeks above. The server widens every range to whole buckets (hours in UTC; days and weeks from local midnight of stats.time_zone, 23 or 25 hours around a clock change), and the client cannot reproduce that without the zone, so getInstance reports it as the new member stats_time_zone (spec first) and every range is built on those buckets: the 24-hour preset on whole UTC hours, the day presets and custom ranges as calendar days of that zone, and a custom range of up to two days gets hour buckets only when both its ends are whole UTC hours (otherwise days, for zones with a half or quarter hour offset) (StatsDays, over L10nFormat.dayStart). "Last 7 days" are today and the six days before it, the previous range the seven days before those, also across midnight and a change of daylight saving time; nothing is widened, and a bucket present in both answers (possible only with week buckets of a long custom range) counts for the current range only. The tile "waiting for people" is left out: listActiveRuns has no state filter, and the brief forbids deriving it from a statistic. Brief Design note 1; the cut-offs are presentation (bars stay readable), the limits stay the server's (stats.max_range, api.stats_max_buckets).
D-M6-06-2 2026-10-24 Presentation: tiles are MetricTileMark, every chart is the BarMark of bothy_viz on a shared value scale (each value a span from a fixed origin, parts stacked), run results in the run state roles with their icons, other series in color.viz.series.* in the fixed order; each chart row carries its values as text and each section has "As table" with the same values. personal and organization are separate tiles and separate rows, never added; organization reads "Estimate" while any summed entry is one, personal always "information only"; a cost query refused with 403 makes the cost tile "not shown". A value named in below_min_group reads "fewer than the minimum group" without a number, because InstanceInfo.limits has no privacy.stats_min_group_size. With currency_required the screen loads the first named currency and offers the others. Brief Design note 2; admin-statistics.md 2 and 3; 25 4 rule 5 and 7.4.
D-M6-06-3 2026-10-24 The scope selector offers "All spaces" and the spaces of listSpaces; the first query (getRunStats) of the chosen entry decides: a 403 or 404 removes the entry with a note naming the server's reason and loads the next entry ("All spaces" first, then the spaces in the server's order); with no entry left the screen shows the server's reason. The client derives no permission. The desktop lists "Statistics" for everyone, like the other lists the server filters. Brief Design note 3; 15 1 principle 1.
D-M6-06-4 2026-10-24 getInstance reports stats.time_zone to every caller as stats_time_zone, because the zone is no secret and the clients need it to ask for whole statistics buckets (D-M6-06-1). telemetry.dashboard_url is a string key with an empty default (the schema allows an empty default only for string kinds) and the start check telemetry_dashboard_url (http or https, a host, no user information). getInstance sends it as telemetry_dashboard_url only when authorize allows stats.read on the instance in filter mode (owners), so no error and no deny audit reach anyone else. The desktop opens it with url_launcher in external mode through the app's BrowserLauncher, the console with window.open(url, '_blank', 'noopener,noreferrer') through the new port ExternalLinks; never a WebView or iframe. Brief Design note 4; the URL names internal infrastructure; 25 9.1.
D-M6-06-5 2026-10-24 Differences to the brief (D-W-100, the merged code wins): the hosts already share ApiAdminRepository over their own client (DPoP on the desktop, the cookie client in the console), so AdminStatsSource is implemented once in admin_ui (ApiAdminStatsSource) and AdminRepository includes it, instead of one implementation per host; the desktop place is /admin/statistics (AdminSection.statistics), the console page is /stats; the tool section shows calls and failed calls per tool, without the median duration, which cannot be combined across buckets. The same client per host already carries DPoP or the cookie and CSRF header; one source keeps the two hosts equal.