Skip to content

Changelog

All notable changes to Busbar are documented here. The format is based on Keep a Changelog and the project follows Semantic Versioning.

August 20, 2026

An emergency log-spam hotfix. 1.5.4 is otherwise fine, but if you run Busbar with a durable audit store (store: sqlite/postgres) it floods the log with WARN lines — up to ~10 per second — for a condition that is expected and harmless. There is no config change and no behaviour change; only the log severity of two durable-audit write-through cases changes.

  • The durable-audit write-through no longer spams the log. The periodic write-behind flusher re-offers the audit tail to the durable store every tick (~10/s), and two of its skip paths logged a WARN on every one of those ticks:
    • A recovered durable floor sitting ABOVE a freshly-numbered RAM-ring seq — the normal state right after a restart against a durable store — made the write-through skip that already-durable seq and WARN each time. This is expected and benign, so it is now logged at DEBUG.
    • A permanent hash-chain gap (a seq pruned from the RAM ring before it could be persisted, which can never be backfilled in-process) is a genuine data-loss signal, but it was re-WARNed on every flush forever. It now WARNs exactly ONCE for a given gap seq (with the seq), and logs at DEBUG on every subsequent flush of the same hole.

August 14, 2026

A hotfix. 1.5.3’s Docker image does not start, so if you run Busbar in a container, upgrade. There is no config change and no behaviour change beyond the two fixes below.

  • The Docker image starts again. docker run getbusbar/busbar:1.5.3 exited 1 before binding a port, with “the overlay backend ‘/etc/busbar/busbar-overlay.json’ is not writable”. The image runs as an unprivileged user on a read-only /etc/busbar, and the documented quickstart mounts your config.yaml read-only on top of that, so the config overlay 1.5.3 introduced had nowhere to write — and 1.5.3 treated that as a reason to refuse to start. It no longer does. Busbar boots, serves traffic, warns clearly at startup that it has no durable config overlay, and refuses admin-API config changes outright rather than applying them in memory and losing them on restart. If you want those changes to persist, point config.overlay.file at a writable volume; if you never wanted them, config.locked: true says so explicitly and silences the warning. A config that explicitly sets config.overlay: false while remaining mutable still refuses to start, because that one is a contradiction you can only reach by writing it down. ([#50])

  • First-party plugins verify on ARM Linux. busbar-aarch64-unknown-linux-gnu shipped without the embedded Busbar release public key in 1.5.1, 1.5.2 and 1.5.3, so every correctly signed first-party plugin was refused on ARM Linux — and only there — as unsigned. That target was the one platform built by cross-compiling inside a container the key never reached; it now builds on a native ARM runner. The release build additionally refuses to compile at all without a well-formed key, and asserts the key is present in the finished artifact before it is uploaded, so this cannot recur silently on any platform. ([#52])

  • The release pipeline boots the container image and checks /healthz before any tag, latest included, is allowed to point at it. Nothing had ever started the image before publishing it.
  • The post-release verifier runs all of its checks and reports every failure, instead of stopping at the first. A check that could not reach getbusbar.com from CI was aborting the run before the check that boots the published image, which is why the defect above stayed invisible for six days.

August 8, 2026

This release reshapes the config file, so give yourself a few minutes for the upgrade. busbar --migrate-config <config.yaml> does most of it for you and tells you what it changed. Busbar will not start on the old spellings, which is deliberate: a config that quietly means something different is worse than one that stops and says so.

Every breaking change below is a config change. If you would rather see the finished shape than a list of edits, config at a glance is one annotated file with every section on a single page, and the 1.5 migration guide walks the path from 1.4.

  • busbar --validate now resolves env: and file: secret references, and exits 1 when one of them cannot be resolved. It previously checked only that the reference was well formed and exited 0, so a config naming an unset variable reported “ok: config valid”. A CI job that runs --validate without production secrets in its environment will now go red: give that job the environment variables and files your config names, or point it at a config whose references resolve there. Boot is unchanged, an unresolvable reference still logs a warning and Busbar still serves. See the operations guide.
  • The Redis-protocol store plugin is now Valkey. Change store.module: redis to valkey and install the busbar-store-valkey artifact. Your connection URL does not change. Re-pin any plugin version pin under the new name, and delete the old plugin file from your plugin directory.
  • Hooks are defined once by name and attached by name. Inline hook definitions and global_hooks: no longer load: define each hook under the top-level hooks: block and list its name under pools.hooks: (every pool) or under one pool. Stage names are now request, candidate, routing and response; the old route, attempt and completion fail at startup. A pool may not be named hooks. A hand-written hook with no stage list now fires at all four stages rather than once per request; set phase: [request] for the old behaviour. See the hooks guide.
  • Identity providers are defined once by name and referenced by name. Define each under the top-level identity-providers: block; auth.chain: and auth.admin_auth: are lists of those names. The auth.methods: block is gone and its contents belong on the provider, auth.role_bindings: is keyed by provider name, and an unstated admin trust ceiling is now the most restrictive one. A ceiling can only be raised in the config file, never through the admin API.
  • Export sinks are named, and observability: is gone. Write export: as <your-name>: {module, settings} rather than keyed by type, which lets you run two sinks of one kind, for example one request log to your own store and one to a SIEM. generic-webhook is now part of request-log-webhook. Move observability.otlp_url to an export sink using the otlp module. See the observability guide.
  • Response headers are off by default. Everything Busbar used to add to a response, timing and route headers included, must be enabled under advanced.response_headers. observability.emit_server_timing no longer exists. Enable what your dashboards and clients read.
  • admin_insecure is now admin_require_mtls, with the meaning reversed and safe by default. A network-exposed admin listener with no client CA still refuses to start; the waiver is now admin_require_mtls: false.
  • Upstream credentials are configured per pool. auth.upstream_credentials moves to pools.upstream_credentials, and any pool can override it.
  • Identity providers and export sinks can be managed through the admin API, as hooks already were. See the admin API reference.
  • Config changes made through the admin API now survive a restart out of the box. Set config.locked: true to make the file the only way to change configuration.
  • Plugins can serve their own HTTP endpoints.
  • A plugin’s own log lines now reach your log sink. Store, auth, hook and secret plugins previously had their logging discarded or written straight to stderr, so lines like a failed token-signature check or an ambiguous directory match never appeared. They now arrive through Busbar’s logging with their level and structured fields intact, named by plugin, and are filtered by RUST_LOG like everything else. Existing signed plugin artifacts keep loading unchanged.
  • A guide to pointing Busbar at a local inference server (Ollama, LM Studio, llama.cpp, vLLM): what to put in your own providers.yaml, how local members mix with hosted ones in a pool, and what changes when Busbar runs in Docker. See the providers guide.
  • Operational settings that were environment variables are now config keys: BUSBAR_PROVIDERS, BUSBAR_CONFIG_OVERLAY, BUSBAR_WORKER_THREADS, BUSBAR_UPSTREAM_HTTP1_ONLY and BUSBAR_UPSTREAM_H2_PRIOR_KNOWLEDGE. Each still works for one more release, and the config key wins if you set both. BUSBAR_CONFIG is unchanged.
  • The persist field on admin config calls is ignored: durability is now a property of the deployment.
  • The admin hooks API calls the field module rather than plugin, matching the config file. plugin is still accepted.
  • Every durable store now answers the same way for the same request, where the answer used to depend on which store you had deployed. Deleting a key that never existed is an error rather than a silent success, deleting one already deleted stays a success, revoking an already-revoked credential is a success while revoking an unknown one is an error, and an audit write that lands on an occupied position is a success only if the record is identical and an error if it differs. Tooling that read a lenient backend’s silent success as confirmation should be checked.
  • Admin reads returned the raw values of a module’s settings, including credentials such as a client secret or a store password. They now return only the setting names.
  • Deleting or rotating a key could return an error while the key went on working, and flushing the authentication cache could return success without revoking anything.
  • An admin deletion of a user’s self-serve key survived only until that user’s next login, which silently recreated the deleted credential and put every token minted before the deletion back into service. The deletion now stands.
  • Rotating a user’s self-serve key and then changing their group’s pools left the user holding two valid keys at once, each metering and enforcing budget separately, so spend was counted against two buckets and neither reflected the real total.
  • A hook that could not reach its own dependency had no way to say so, so it read as “no opinion” and a gate configured with on_error: reject admitted the request instead of refusing it. A hook can now report the failure and on_error applies. See the hooks guide.
  • Busbar sent an empty client_secret when exchanging a code for a public identity-provider client. An identity provider is entitled to read an empty secret as a wrong one and answer invalid_client, so browser login against a public client could fail outright. The parameter is now omitted when there is no secret; a confidential client is unaffected.
  • The SSRF guard on an OTLP export sink checked only the literal text of the collector endpoint, so https://169.254.169.254/v1/traces was blocked while a hostname resolving to that same cloud metadata address was allowed through. Span data carries key ids, pool names and governance decisions, so the endpoint is now resolved and every resulting address is checked. A collector whose DNS is briefly unavailable is not treated as a rejection. See the observability guide.
  • Budget accounting could allow spend it should have blocked: an exhausted lifetime budget on a group with an email-shaped name reset to zero on restart, deleting one principal could reclaim another’s budget, and deleted groups left budget entries behind that no admin call could see.
  • Admin config writes could report success without taking effect. An unknown field was accepted then dropped at reload, config.locked was not enforced on two endpoints, and a write with nowhere to persist returned success.
  • An identity provider’s max_admin_scope was ignored, leaving it read-only even when you granted more.
  • busbar --migrate-config could change or drop what you wrote: a hook attached with a single value rather than a list migrated to a pool with no hooks and still passed --validate, so a compliance gate could vanish silently; an unrecognized budget period became a lifetime cap; a yearly budget carried onto a monthly window unrescaled, a twelve-fold increase; and a provider used on both planes could lose one plane’s settings.
  • Busbar could refuse to start in a writable directory when the config file was named with no directory path.
  • The request-log file export could grow without bound if the destination stalled, and every webhook export shared one queue limit, so a slow sink could consume capacity you had capped elsewhere.
  • A hook whose settings referenced a secret reported a settings mismatch on every check, forever.
  • advanced.worker_threads: 0 was silently ignored instead of reported.

August 2, 2026

  • auth.chain: [keys] with no way to mint an admin token now refuses to start. It previously booted as a silent open relay admitting anonymous requests. Give auth.admin_auth an admin-tokens entry with a token:, or an admin module granting mint or full, or set an explicit admin_auth: [] for development. See the 1.5 migration guide.
  • Setting an admin token no longer forces data-plane requests to carry a virtual key, so chain: [] plus an admin token is now an open relay with a protected admin API.

August 2, 2026

  • Busbar no longer generates a signing key at boot. If auth.chain names the built-in keys verifier, auth.signing_key is required and startup fails without it. Generate one with busbar --generate-signing-key and point auth.signing_key at a file or environment variable. It is fleet-shared, so generate once and distribute to every node; rotating it revokes every outstanding key. 1.5.0 wrote this file itself beside your config, which boot-looped on a read-only mount.
  • /stats and /metrics report why a lane cannot take a request (at capacity, breaker open, dead, budget exhausted), when it might recover, and how many requests are parked. See the observability guide.
  • on_exhausted: { queue: { max_ms } } holds a request for a bounded wait when every pool member is at capacity, then dispatches it or falls through to reject.
  • A pool whose members are all at max_concurrent now spills or sheds per on_exhausted instead of queueing to the failover deadline, so a burst against a small pool with a cloud overflow pool spills immediately rather than serializing.
  • busbar_lane_at_capacity is replaced by busbar_lane_available. Update dashboards that use it.
  • on_exhausted: least_bad returned a 503 when the best member was at capacity even though a sibling had a free slot.
  • Retry-After on an exhaustion 503 always said one second under saturation, rather than the real cooldown.
  • limits.max_inbound_concurrent queued excess requests behind the cap instead of shedding them, so clients got no backpressure.

August 1, 2026

The config, identity and cost release. The config file changed shape and every 1.4.x virtual key stops working, so plan the migration and the key rotation together. The data-plane HTTP surface is unaffected: an application posting to /v1/chat/completions gets a byte-identical response after the upgrade.

  • The config file changed shape and a 1.x config refuses to start. Run busbar --migrate-config <old.yaml> > config.yaml, review every WARNING and TODO it prints, then run busbar --validate. Read every allowed_pools: [] carefully: its meaning flipped from all pools to no pools.
  • Every 1.4.x virtual key stops working and must be re-minted through POST /api/v1/admin/keys, with the new tokens rolled out to callers. Keys are now signed tokens that expire (90 days by default) and can be revoked fleet-wide, where a 1.x key was a bearer secret that never expired.
  • A durable store is dropped and recreated on first open. Usage history resets with it.
  • Limits moved off keys and onto groups. rpm_limit, tpm_limit, max_budget_cents and budget_period are gone from minting, from PATCH /keys/{id} and from key metadata; a key resolves to a group and the group carries the limits. The per-key busbar_key_budget_remaining_cents gauge is gone with them, so use the bucket gauges.
  • The governance: block is gone. store, rate_card, per_request_fee, groups and advanced are top-level, and the admin token is a secret reference on the admin-tokens module. governance.enabled and governance.budget_on_store_error no longer exist. Handled by --migrate-config.
  • Static token auth is gone. The tokens module and auth.client_tokens are removed; data-plane auth is the built-in keys verifier or an identity provider.
  • The top-level hooks: registry is gone, with the hook global: and default: flags. A hook instance is referenced inline in a pool’s hooks: list or in global_hooks:. (Reversed in 1.5.3, which restores a named hooks: definition map.)
  • cost_per_mtok on pool members and governance.price_per_1k_tokens_cents are gone. rate_card: is the only cost source; --migrate-config synthesizes entries and flags them for review.
  • Config aliases are gone, one canonical name each. window_s becomes window_secs, n becomes consecutive_n, deadline_secs becomes timeout_secs, cap becomes max_hops, otlp_endpoint becomes otlp_url, a member’s target becomes model, api_key_env becomes api_key: { env: ... }, and auth.mode becomes auth.chain plus auth.upstream_credentials.
  • groups: is the one place limits live: a named tree where requests, tokens, budget and concurrency all use one shape. Admission checks every group up the chain and a rejection names the bucket that blocked it. A user is just a leaf group under their team. See the configuration guide.
  • A limit can carry pool: <name>, so a team’s spend splits across model tiers and exhausting the frontier budget stops only frontier traffic.
  • A pool-scoped budget can declare on_exhaust: downgrade with downgrade_to: <pool>, so running out routes to a cheaper pool instead of refusing the request.
  • Groups are editable live over the admin API with no restart, past accrual survives the edit, and per-group usage is readable at GET /api/v1/admin/groups/{name}/usage.
  • POST /api/v1/admin/keys can auto-provision a personal group under a parent, and the new mint admin scope lets a portal issue keys without full admin rights. limits.max_keys_per_principal caps how many keys one principal may hold.
  • rate_card: is the only source of cost, priced per model and tier. Omit it and everything prices at zero; include it and it must be complete, with a missing model failing startup with a paste-ready stub.
  • Every secret in the config is a reference: { env: VAR }, { file: /path }, or a secret plugin for a vault or cloud secret manager.
  • Durable stores are plugins. SQLite, Postgres and Valkey ship as signed tarballs you install and name in store.module; the compiled-in memory store is still the zero-setup default. See the plugins guide.
  • Store, secret, identity and hook plugins share one signed artifact format and trust model. Unsigned, tampered or unknown-publisher plugins are skipped and never loaded; trust.allow_unsigned and trust.allow_third_party are opt-ins that default to off, and plugins.min_versions sets anti-downgrade floors.
  • Identity providers are plugins: name one in auth.chain and it loads at boot, and one that cannot load is a hard startup failure rather than a silently open front door. The bundled oidc module is the first.
  • Hooks are signed plugins loaded in process. Two ship with this release: busbar-headroom-hook compresses prompts before dispatch, and busbar-webrequest-hook forwards to an HTTPS sidecar you run yourself. The socket and webhook transports remain as built-in hook modules.
  • Plugins can be listed, installed, removed, hot-reloaded and rolled back over the admin API with the same trust checks boot applies. Changing the store module still needs a restart.
  • GET/PUT /api/v1/admin/config/settings covers every config section, and POST /api/v1/admin/restart applies the settings that need a restart (listeners, TLS, store backend) without shell access.
  • Admin config changes persist to a Busbar-owned overlay file and your config.yaml is never written. DELETE /api/v1/admin/overlay/{section} reverts one section back to the file.
  • busbar --validate covers the whole new surface with paste-ready fixes, and busbar --list-plugins prints the plugin inventory without loading plugin code.
  • Spend, budget-remaining and token metrics are labelled by group and window, and key labels set at mint time echo onto per-key series, so a dashboard can sum by team.
  • The SemVer contract is now stated explicitly: the frozen surface is the data-plane HTTP surface and the wire protocols. config.yaml is an operator artifact outside that freeze and may change between releases, always with a migration path and a loud failure on an outdated config. The admin API carries its own version.
  • Spend is derived, not stored. The store keeps a token ledger and money is computed at read time from the current rate card, so correcting a rate is a config edit and a reload with no re-billing.
  • PATCH /keys/{id} takes enabled and group only; the 1.4.x cap fields are rejected.
  • A hook granted prompt: ro or prompt: rw now also sees reasoning and thinking text, which it could not see before even though that text reached the provider in full. Nothing to configure, but review any path where your hook forwards or logs that projection. Opaque redacted reasoning is still never plaintext.
  • An exhausted budget could be spent again: a request straddling a window boundary could rewind a live budget cell and zero its totals, a store error while loading budgets at boot started with empty counters, and a large enough ledger overflowed the derived total to a negative number that read as free.
  • A caller could escape the requests limit by hammering failing requests, because the refund on a non-2xx outcome also refunded the admission slot.
  • An identity provider could hand a caller a principal id shaped like a real key or group and take over that budget bucket.
  • A typo in a security-relevant config key was silently ignored, so client_c: for client_ca: disabled mTLS without complaint. Unknown fields now fail startup.
  • Concurrent budget flushes could double-count spend against a shared store, the Valkey store wrote duplicate audit entries, and store errors could include the connection password.
  • An environment variable interpolated into the config could splice extra structure into it, for example widening an allowlist.

July 20, 2026

  • The repository moved to github.com/GetBusbar/busbar; older links redirect. Verify release artifacts with --repo GetBusbar/busbar. The Docker Hub image is unchanged.
  • Every tagged release attaches the admin API’s OpenAPI document, so you can generate a client or diff the API surface without running the gateway.

July 19, 2026

  • Google Vertex AI, Azure OpenAI and Oracle OCI Generative AI, all as configuration rather than code. See the providers guide.
  • Two new ways to authenticate outward to a provider: auth: jwt-bearer (a signed assertion, which a Google service-account JSON satisfies directly) and auth: oauth-client-credentials. Both refresh in the background before expiry.
  • path_base, token_url and scope provider fields, which is what lets the above reach non-standard provider URLs without new code.
  • The default worker-thread count is one per available core rather than a cap of four, so throughput scales with the machine. It reads the node’s core count and cannot see a CPU bandwidth quota, so on a Kubernetes pod with a CPU limit it oversubscribes: pin BUSBAR_WORKER_THREADS to your CPU limit there, or to 1 or 2 in a footprint-sensitive sidecar.
  • Memory now falls back toward idle after a burst instead of staying at the peak: a soak that plateaus around 1.2 GB drops to roughly 250 MB within 30 seconds of the load stopping. Windows builds keep the system allocator and do not get this.
  • A cross-protocol stream whose backend reports usage in a trailing chunk now folds it into the terminal frame, so a non-OpenAI client receives real token counts instead of zeros. A Gemini JSON-array client on such a stream now receives one extra trailing element carrying that usage.
  • Budgets shared across several nodes no longer clobber each other: the usage flush writes the delta since the last flush rather than an absolute value, so nodes sharing one store sum to the true total.
  • The Valkey store’s key deletion and credential writes are now atomic, so a partial failure cannot orphan an upstream credential behind a deleted key. It also gains reconnect and rediss:// TLS.
  • The token endpoint an OAuth provider posts its client secret to was checked less strictly than the provider base URL, so a typo could send the secret to a cloud metadata address. Both self-minting clients now also refuse redirects and carry timeouts.
  • Health probes were not re-spawned on config reload, so reloaded lanes went unprobed and each reload leaked probe tasks.
  • A scope: configured on a jwt-bearer provider was ignored, and a mid-stream transport error billed the tokens accumulated before the cut.
  • A Cohere backend’s pre-tool-call reasoning was dropped on any hop to another protocol, a raw-string tool argument was JSON-encoded twice by two writers, and an aborted Gemini JSON-array stream emitted two trailing error elements.
  • busbar --validate reported false errors on a config templating its URLs from environment variables, and missed a model whose context_max conflicted across pools, so a clean validate could still fail at startup.
  • A config still carrying the removed auth.mode: key now fails with a hint naming its replacement.

July 16, 2026

  • busbar --validate checks a config file without booting or binding a socket, the nginx -t workflow, and runs in CI without the runtime environment present.
  • BUSBAR_WORKER_THREADS caps the worker pool, which lowers memory on many-core hosts.
  • A slow fire-and-forget hook could grow in-flight work without bound; those spawns are now capped and over-cap notifications dropped and counted.
  • An unreadable config overlay file was overwritten rather than refused, which could silently discard persisted admin state.
  • A queued request rewrite that could not be re-applied on failover forwarded the original un-rewritten body; the request is now rejected.
  • The outbound guard now also blocks the Azure and Oracle Cloud metadata addresses, and the host Busbar signs for can no longer differ from the host it dials.

July 14, 2026

Maintenance release: CI fixes and dependency bumps only, no change in behaviour.

July 14, 2026

Maintenance release: no change in behaviour.

July 13, 2026

The API release: anything you could previously do only by editing YAML and restarting, you can now do over an authenticated, audited API. Hooks and policies are configured differently, so existing configs need a one-time update: see the 1.2.x to 1.3 migration guide. An old-form key reports a startup error naming exactly what to write instead.

  • The management API moved under /api/v1/admin/. The key endpoints at /admin/keys* are now /api/v1/admin/keys*; scripts calling the old paths need a one-line URL update.
  • A network-exposed admin listener refuses to start without client-certificate mTLS. Set admin_tls.client_ca_file, keep admin on loopback, or waive it with admin_insecure: true if a mesh terminates mTLS for you.
  • The inline policy: block and transport-named route: values. A pool’s route: now takes a hook name or a built-in policy name (weighted, cheapest, fastest, least_busy, usage). Each removed key reports a startup error with its exact replacement.
  • The embedded Rhai script routing policy (route: script), deprecated in 1.2.1, is gone. A compiled hook over a socket or an HTTP webhook does the same job with real process isolation.
  • The admin API is a full config plane: read the running config, apply a validated change atomically, roll back to a previous version, register hooks, adjust pools, budgets and rate limits. Drive Busbar from Terraform, Ansible or CI with no SSH and no restarts. See the admin API reference.
  • The admin API is on its own listener, always, with its own TLS and optional client-certificate mTLS, so the control plane binds and is firewalled independently of public traffic. It defaults to loopback.
  • Admin credentials are scoped (read-only, hooks-register, full) rather than one shared token, every mutation is audited against whoever made it, and the admin auth chain is live-mutable with a guard refusing a change that would lock the caller out.
  • Gates, taps and the restrict verb. A gate can reject a request or restrict which pool members may serve it, which is how data residency or a BAA-only lane is expressed without teaching the router about compliance. A tap observes and can never delay or fail a request. A request’s hooks fire at once, so added latency is the slowest hook, not the sum. See the hooks guide.
  • The rewrite verb: a trusted gate can replace the request body before dispatch, for context compression or redaction, across every protocol at once. A malformed or slow rewrite proceeds with the original body, so a broken compressor cannot corrupt a request.
  • Hooks are defined once under hooks: and referenced by name in a pool’s hooks: list or in global_hooks:. One list carries both the ranking strategy and any gates.
  • Hook settings can be pushed to a running hook over the admin API, committing only when the hook acknowledges, and a restarted hook gets its current settings before any traffic. Its observed settings and metrics are readable at GET /api/v1/admin/hooks/{name}/status.
  • Per-model and per-key metering at GET /api/v1/admin/usage, reporting the raw token split in daily buckets with spend derived at read time, so a consumer with negotiated pricing can reconstruct cost.
  • Authentication is a chain of modules, each identifying the caller, rejecting, or passing to the next. Token auth is the first module and is removable. group_map: maps identity-provider groups to admin scope and data-plane access in one place, with per-module caps bounding what any module may assert.
  • API-applied changes persist to a Busbar-owned overlay file; your hand-written config.yaml is never touched, so “who set this” is always answerable.
  • POST /api/v1/admin/config/reload applies your config files atomically. Lane health is carried across by model identity rather than list position, so reordering or adding a model never resets what Busbar has learned, and that state now survives a restart. --safe-mode boots from your base config alone when an API-applied change is the problem.

July 11, 2026

  • A routing hook can run as a compiled binary on a local Unix socket rather than over HTTP, deciding in roughly 8 microseconds. Same wire contract as the webhook. You run the hook process; Busbar connects lazily and reconnects across restarts, and requests keep flowing on the pool’s fallback if you kill it.
  • A hook can reject a request outright, and the caller gets an error in its own dialect. With the prompt payload below, this is the content-screening primitive: a hook that sees content can stop a request before it leaves your network.
  • Two per-pool opt-ins extend the hook payload, both off by default: policy.send_prompt adds the prompt content so a trusted hook can screen for PII, and policy.send_user adds caller identity so a hook can route by who is asking. The caller’s own credential is never in the payload. Each candidate also carries the tags you declared on it.
  • The default hook deadline is 1 ms, down from 150, because a co-located socket hook decides in about 8 microseconds. Raise policy.timeout_ms if your hook does I/O; on timeout the decision falls back per on_error and the request proceeds either way.
  • route: script (the embedded Rhai interpreter) works behind a build flag but warns at startup. Migrate to a compiled socket hook or an HTTP webhook. Removed in 1.3.0.

July 10, 2026

Busbar now carries more than chat. Embeddings, moderations, image generation, audio and rerank all translate across protocols the way chat already did, so a client in one dialect can reach a backend in another and get its answer back in its own dialect, errors included. Chat itself is byte-for-byte unchanged.

  • Embeddings, routable to OpenAI, Amazon Bedrock, Cohere or Google Gemini from any dialect, with vectors, usage accounting and errors surviving the hop.
  • Moderations, image generation (OpenAI, Gemini, Bedrock), audio transcription and speech (OpenAI, Gemini), and rerank (Cohere and Bedrock), all cross-protocol. A backend that lacks an operation answers with a clean 404 in the caller’s dialect rather than a crash or malformed body.
  • attempt_timeout_ms catches a provider that fails by hanging, where the connection opens and headers never arrive, silently eating the whole failover budget on one member. Set it on a model and override per pool member. It covers connect and headers only, so it never cuts off a stream that has started.
  • Per-token log probabilities cross the OpenAI and Gemini seam both ways, buffered and streaming.
  • The reasoning and thinking budget translates between the protocols that model it, gated by an operator flag: set reasoning: true on a model to declare the backend accepts thinking parameters. Without it the ask is dropped with a warning, so a non-reasoning model can never fail because of translation.
  • An end-user identifier and the parallel-tool-calls switch now translate between OpenAI and Anthropic.
  • Busbar is licensed under the Apache License 2.0 from this release onward: permissive, commercial-friendly, with an explicit patent grant.
  • Error envelopes come back in the caller’s own dialect, and usage accounting survives a cross-protocol round trip on every operation, not just chat.
  • A Gemini backend’s streamed reasoning was concatenated into the visible reply for every client in another dialect.

July 9, 2026

  • GET /v1/models and GET /v1beta/models list every routable name in the caller’s dialect. This is the first call SDKs and self-hosted UIs make to build a model picker, and it previously returned 404. A key restricted by allowed_pools sees only what it may reach.
  • /metrics was empty until the first request arrived, so a freshly booted gateway exposed nothing to Prometheus, and direct model lanes with no pool were missing their health gauge.
  • /stats output and lane ordering are now stable across restarts, so scrapes and dashboards are reproducible.

June 30, 2026

  • upstream_model separates a model’s config key from the id sent on the wire, which lets the same model sit behind two providers in one failover pool, for example Claude via both Anthropic and Bedrock. Contributed by @lguzzon.

June 30, 2026

A hardened maintenance release, functionally identical to 1.0.0.

  • Releases ship a CycloneDX SBOM and a build-provenance attestation, so an artifact can be verified with gh attestation verify <file> --repo GetBusbar/busbar. Dependencies are checked against the RustSec advisory database on every change and weekly.

June 21, 2026

First stable release. The HTTP API, configuration schema and wire-protocol contracts are stable under Semantic Versioning from here: no breaking change without a major version bump.

  • Migration from rc.7: governance.rate_sweep_interval must now be at least 1; rc.7 silently disabled the sweep on 0. No other change for a default deployment.
  • Structured output, stop reasons, image sources and redacted reasoning survive a cross-protocol hop intact rather than passing through as opaque blobs.
  • Two Bedrock request shapes returned a 400 on a valid request, Anthropic cache markers were dropped on thinking and image blocks, and a streaming refusal could lose content.
  • Billing corrections: sub-cent attribution, cancelled mid-stream requests, and no token billing for a stream aborted during translation.
  • A client could hold a connection open indefinitely by trickling request headers on either listener.