Releases: nanoinfraorg/nanoinfra
Releases · nanoinfraorg/nanoinfra
Release list
v2.2.7 — the Calls table filters by source and actor
Added
- Metrics → Calls filters by source and actor. Both were already on every row and in the
payload with no control to filter on them, so finding what one approver authorised, or what one
channel ran, meant reading the table by eye.
(#274)
v2.2.6 — Abilities starts closed
Changed
Abilitiesin the sidebar starts closed, and opens only when you click it. It opened by
default, and it also reopened itself whenever the active page was one of its two members — so a
visit to Skills undid the collapse. Both are gone: absent means closed, and the heading is the
only thing that opens it.Infrastructurealready started closed and keeps its own reopen
behaviour, which was not part of the ask.
(#253)- Both rail groups remember whether you closed them. They held that in local state, so the choice
lasted until the next reload — and the comment on that state called collapsing "the operator's
choice, not the default", which a choice that does not survive a reload is not. They now use
collapsed_groups, the map the sidebar already round-trips for chat project groups, under
nav:-prefixed keys so they cannot collide with a project of the same name.
(#253)
v2.2.5 — a call's detail opens under its row
Fixed
- A row's detail in Metrics → Calls opens under that row instead of after the whole table.
Opening row 1 of 100 put its fields below row 100, so reading a call meant scrolling the page
away from the row that was clicked. The chevron on the left already promised an inline
disclosure, and a disclosure that opens a hundred rows away is a broken affordance rather than a
layout preference. Thirteen fields also lay out in three columns on a wide screen now, so the
rows below do not travel far.
(#274)
v2.2.4 — Live draws its numbers
Added
- The Live tab draws its numbers as well as printing them. Each gauge grows a sparkline of the
last three minutes, kept in the tab — nothing stores gauge history, because the gauges are
sampled when read, and the panel says so rather than implying it holds yesterday. A gauge that
could not be read gets its dash and no plot: an empty plot area reads as flat at zero.
(#274) - Context used and its limit are one meter instead of two tiles. They were never two facts,
and reading 428K against 1,048,576 is arithmetic the panel should do. The fill carries severity
past 75% and 90%, with the percentage always spelled out — a status colour never carries meaning
alone. An absent or zero limit reads as unknown rather than as 0%.
(#274) - Two charts under the gauges: calls per minute, derived from the difference between
successive reads of the cumulative counters the wayrate()does, and a latency histogram
over the nine buckets. An empty latency bucket keeps its row, because a band with no calls is
information.GET /api/webui/metrics/countersserves both.
(#274)
Fixed
- The counters charts cannot take the Live tab down. They render inside it, so trusting the
route's shape meant an older gateway — or any unexpected answer — unmounted every gauge above
them. The same failure the scale row had one release earlier.
(#274)
v2.2.3 — the Approvals tab, the scale row, and a scrape with rates
Added
- An
Approvalstab in Metrics: how many actions the gate held for a person, how many they
answered, how many they refused, how many expired, and the median time to answer — the
number that says whether the gate is working rather than merely running. A person's refusal is
counted apart from a policy refusal nobody was asked about, because merging them overstated the
approver's denials sixteen-fold on the deployment this was measured against. An ask that was
neither answered nor expired is named on its own: nothing ran and nothing said why.
(#274) - A scale row at the top of Metrics → Usage: servers, skills, agents, MCP servers and connectors,
in one request. Every one of these existed and was scattered across five settings pages. A
count that cannot be read shows—and names itself rather than reading as zero.
(#274) /metricsexports counters and a latency histogram, so a Prometheus install can ask for a rate
and a quantile rather than only a level:nanoinfra_llm_calls_total,
nanoinfra_llm_tokens_total(by input, output, cache read and cache write),
nanoinfra_tool_calls_total, andnanoinfra_llm_duration_msover nine buckets. Accumulated in
memory for the life of the process, because a count over a table with a purge is not monotonic
and arate()over a falling counter is nonsense.
(#274)- Two process-health gauges,
nanoinfra_rss_bytesandnanoinfra_event_loop_lag_ms. A rising lag
is a blocked loop, and neither number has an event to be driven by, so both are sampled at read.
(#274)
Fixed
- The gate audit viewer no longer offers
denyas a decision filter. Nothing writes it: the name
lives on as the outcome enum and as the operator socket's wire verb, and the log records that
outcome asdeniedso it speaks the operator's vocabulary. The filter could only ever match
zero records. (#274)
v2.2.2 — the socket directories are setgid for real
Fixed
- The socket directories are setgid for real this time, verified in the built image rather than
reasoned about. v2.2.1 set the mode before thechowninprepare, which was right and not
enough: the post-bind block re-applies the mode, and by then the directory already carries the
helper's group, so itschmod 2710dropped the bit again and returned success. All five
directories were still at710on 2.2.1. Oneset_socket_dir_modehelper now owns that mode
and takes the directory back to root before setting it, which is the only order that works on a
fresh directory, on a re-apply, and on the0700one the Python side creates.
v2.2.1 — the socket directories are actually setgid
Fixed
- Every helper socket directory is actually setgid now, so a socket keeps its shared group when
the helper rebinds it.chmod 2710ran afterchown, and withoutCAP_FSETID— which the
published compose file does not grant — that silently drops the setgid bit and returns success,
so all five directories sat at710while the code's own comments described2710. The
operator socket is the one that paid: the executor deliberately does not joinnanoinfra-op,
so the inherited group is the only mechanism it has, and its absence left the racy root chown
as the only thing setting it — plus a[Errno 1] Operation not permittedon every boot. apply_socket_groupsets the mode even when it cannot set the group. The two were in onetry,
so a refused chown skipped the chmod — and the mode is the half that grants a peer its write
bit.
v2.2.0 — Metrics, and rates where the model is added
Added
- A
Metricsdestination in the rail, with three tabs.Usageshows spend per model over a
chosen window of 7, 30, 90 or 365 days, with cache writes, truncated answers, time to first
token, wall clock and a per-model cost — five of which the store has recorded since 2.0.0 and
none of which reached a pixel.Liveshows seven point-in-time gauges, starting with how many
suspended actions are waiting for a person.Callsreads thetool_callstable, which had a
writer, a pruner and a purge log and no reader at all.
(#235,
#232) pricingin config gives a model four rates in USD per million tokens — input, output, cache
read and cache write — keyed"<provider>/<model>". Until one is set the Usage tab says no
prices are configured rather than showing a spend of$0.00.
(#235)gateway.metricsEnabledserves the same gauges in Prometheus text format at/metricson the
gateway's own port. Off by default.gateway.metricsTokensets the bearer token a scrape must
present; with no token only a loopback bind is served, so enabling metrics on a port a reverse
proxy fronts does not publish them.
(#235)- The Usage tab lists why calls failed, per error kind, status code and provider. The failure
count was already shown; the reason behind it was recorded and never read.
(#235) - The Usage tab breaks the window down by what started the turns — chat, API, automations, memory,
system.sourcewas aggregated per day and readable only inside one heatmap cell's tooltip, so
"what does automation cost me this month" meant opening thirty tooltips.
(#235) - A model row in the Usage tab expands to the four measurements the columns are checked against:
how many calls the provider reported against how many were tokenized locally, output tokens per
second of generation, measured against reported output, and the streamed split that time to
first token is averaged over. (#235)
Fixed
- A cached token is no longer billed twice.
prompt_tokensis the logical input and includes
the cached halves, so charging it at the input rate and the cached count at the cache rate
over-stated a warm cache badly — 5.7× on published Kimi K3 rates for a 90%-cached prompt. The
three input buckets are now disjoint.
(#235) - A usage row names the provider that was configured rather than the class that made the call.
OpenAICompatProviderserves every OpenAI-compatible API, so Moonshot, DeepSeek, Groq,
OpenRouter and forty others were all recorded asopenaicompat— which left "which provider is
expensive" unanswerable and meant a rate set formoonshot/kimi-k3could never match its own
rows. Single-provider backends were wrong too:openai_codexrecordedopenaicodex.
(#235) - Both spellings of the pricing rate keys are accepted (
inputPerMtokandinputPerMTok), and an
entry that states no rate nanoinfra recognises reads as unpriced rather than as$0.00. A
mistyped key used to produce a confident zero over a month of real spend.
(#235)
Changed
- Model rates are set where the model is:
Settings → Models → Pricing, with a live figure
showing what the recorded window would have cost at the rates typed, a warning when the model
reads cached tokens and the cache rate is still zero, and a note when two configurations name
the same model and therefore share one bill.Settings → Providers → Default pricingsets rates
for a whole provider, including a free checkbox — the one-edit answer for a local fleet.
(#235) - The Settings overview no longer opens with the token summary and the heatmap. They are the
Usage tab now, and the overview keeps one row that leads there — which also ends the
five-second re-aggregation ofllm_callsthat a settings page left open used to run.
(#235)
v2.1.0
Added
- A tool group, MCP server or connector can be set to
attach: "search": its schemas leave every
prompt and the model loads them itself by calling the newtool_searchtool, with one shared
pointer in place of a per-item advertised line.mentionstays the user-driven deferral; both
respect the acting agent's ceiling, which neither can widen past.
Fixed
- A tag no longer publishes to PyPI or GHCR before the Test Suite has finished on that commit.
v2.0.0 shipped a wheel that could not be imported on the minimum supported Python because the
publish jobs are quicker than the tests and nothing connected them.
(#270)
Changed
- CI builds the image with Buildx and the Actions cache, the way the publish workflow already
did. (#271)
v2.0.4 — @agent:<name> answers as that agent
Fixed
@agent:<name>in a message answers as that agent. The composer offered the token, completed
it, and then ignored it: the turn ran as the deployment default, so naming an agent looked like
it did nothing. (#269)