Skip to content

Releases: nanoinfraorg/nanoinfra

v2.2.7 — the Calls table filters by source and actor

Choose a tag to compare

@bet0x bet0x released this 06 Sep 07:58

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

Choose a tag to compare

@bet0x bet0x released this 06 Sep 07:15

Changed

  • Abilities in 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. Infrastructure already 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

Choose a tag to compare

@bet0x bet0x released this 06 Sep 06:30

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

Choose a tag to compare

@bet0x bet0x released this 06 Sep 06:17

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 way rate() 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/counters serves 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

Choose a tag to compare

@bet0x bet0x released this 06 Sep 05:45

Added

  • An Approvals tab 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)
  • /metrics exports 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, and nanoinfra_llm_duration_ms over nine buckets. Accumulated in
    memory for the life of the process, because a count over a table with a purge is not monotonic
    and a rate() over a falling counter is nonsense.
    (#274)
  • Two process-health gauges, nanoinfra_rss_bytes and nanoinfra_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 deny as 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 as denied so 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

Choose a tag to compare

@bet0x bet0x released this 06 Sep 03:41

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 the chown in prepare, 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 its chmod 2710 dropped the bit again and returned success. All five
    directories were still at 710 on 2.2.1. One set_socket_dir_mode helper 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 the 0700 one the Python side creates.

v2.2.1 — the socket directories are actually setgid

Choose a tag to compare

@bet0x bet0x released this 06 Sep 03:26

Fixed

  • Every helper socket directory is actually setgid now, so a socket keeps its shared group when
    the helper rebinds it. chmod 2710 ran after chown, and without CAP_FSETID — which the
    published compose file does not grant — that silently drops the setgid bit and returns success,
    so all five directories sat at 710 while the code's own comments described 2710. The
    operator socket is the one that paid: the executor deliberately does not join nanoinfra-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 permitted on every boot.
  • apply_socket_group sets the mode even when it cannot set the group. The two were in one try,
    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

Choose a tag to compare

@bet0x bet0x released this 06 Sep 03:03

Added

  • A Metrics destination in the rail, with three tabs. Usage shows 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. Live shows seven point-in-time gauges, starting with how many
    suspended actions are waiting for a person. Calls reads the tool_calls table, which had a
    writer, a pruner and a purge log and no reader at all.
    (#235,
    #232)
  • pricing in 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.metricsEnabled serves the same gauges in Prometheus text format at /metrics on the
    gateway's own port. Off by default. gateway.metricsToken sets 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. source was 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_tokens is 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.
    OpenAICompatProvider serves every OpenAI-compatible API, so Moonshot, DeepSeek, Groq,
    OpenRouter and forty others were all recorded as openaicompat — which left "which provider is
    expensive" unanswerable and meant a rate set for moonshot/kimi-k3 could never match its own
    rows. Single-provider backends were wrong too: openai_codex recorded openaicodex.
    (#235)
  • Both spellings of the pricing rate keys are accepted (inputPerMtok and inputPerMTok), 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 pricing sets 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 of llm_calls that a settings page left open used to run.
    (#235)

v2.1.0

Choose a tag to compare

@bet0x bet0x released this 04 Sep 21:55

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 new tool_search tool, with one shared
    pointer in place of a per-item advertised line. mention stays 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

Choose a tag to compare

@bet0x bet0x released this 04 Sep 09:54

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)