Skip to content

Biorouter v1.90.4

Choose a tag to compare

@Broccolito Broccolito released this 14 Sep 20:51

Biorouter v1.90.4

A patch release, and a large one: 112 merged pull requests, thirteen of which landed together
through one integration pull request. Most of it falls into two groups. The first closes places
where a caller holding only the daemon's secret, which a chat's own shell can recover, could read,
list or delete a private chat, and places where a model could raise a chat's privacy tier or act
without the approval a person would expect. The second fixes work the app lost or misreported: a
typed message destroyed by a failed chat start, a partial reply gone after Stop, a model chip naming
a model the chat was not on.

The daemon's secret alone no longer reads, lists or deletes a private chat

Several daemon routes treated any caller holding the daemon's secret key as the user, and a public
chat's shell can recover that secret. With the secret alone, a caller could read a private knowledge
base, see private chats in session lists, list a private chat's tools, and rename or delete a
private chat. Those routes now answer such a caller as they would a public model: private chats are
left out of lists, and private chats and knowledge bases are refused. The desktop app proves each
user action to its daemon, so it works as before.

The same rule now covers the routes around them. The list of running work, which covers background
jobs, commands, subagents and scheduled runs, shows only work from chats the caller could open, and
its cancel stops only that work. GET /sessions/running no longer names a running private chat;
biorouter session list and session watch still show which chats are running. The secret alone
can no longer read a private chat's private-extension resources. /config/read no longer returns up
to the first eight characters of a stored secret.

In biorouter serve, what a browser can reach follows the model the operator configured. On a host
configured with a private model, the browser lists and opens private chats. On a host configured
with a public model, History lists public chats only, and may read "No chats yet" while private
chats exist.

Twenty-one requests without a secret from one address, such as from a stale serve tab, used to
lock out every request from that address for 60 seconds, the user's included. A request with the
correct secret is no longer locked out by someone else's failed attempts.

Deleting a chat now also removes its recall-search text, its checkpoints, its cross-affiliation
approvals and its per-turn usage records, and its id is no longer handed to a new chat. Totals in the
Usage panel and biorouter usage do not drop: a deleted chat's spend is kept as an anonymous total by
day, model and provider. Before, deleting the day's newest chat or resetting History freed its id,
and the next new chat could inherit the deleted chat's state, including a standing privacy approval
the user never saw.

Tier raises, approvals and credentials

A chat or knowledge base made private does not go back to public on its own, and two tools let a
model cause that raise. workspace_set_tools could move a conversation onto a provider of a higher
tier, including from a Code Execution script, where no approval card was shown; through that tool, a
public conversation can now be switched only to a public provider, and a private one only to a
private provider. Switching models yourself works as before. platform__ingest_source could make a
public chat's knowledge base private by naming a private model, even when curation failed; the
alternate model must now match the chat's tier.

A tool call made from inside a Code Execution script is now judged by the permission system under
that tool's own name. always_allow for execute_code now means only "don't ask before running a
script", so a single script can ask more than once; in Autonomous mode, ordinary script calls still
run without a card. When a subagent's script needs a person's approval, the card now appears in the
parent chat you are watching. The approval card for a sensitive file operation now accounts for every
operation the call performs, such as every directory a script deletes, not just the first.

When an agent creates, runs now, pauses, unpauses, deletes or kills a schedule, an approval card now
appears in every permission mode. biorouter schedule add, remove and run-now now ask a person
at the terminal to confirm when they write the schedule file directly. Without a terminal these
commands refuse, and there is deliberately no --yes; sending the change through a running daemon
with BIOROUTER_SERVER__SECRET_KEY still works.

The secret guard now reads a command's paths the way the shell will before deciding, in every chat
and tier, and refuses a credential file whether or not it exists. That covers spellings such as
cat ~/.aws/credentials, $HOME/..., ~/.aws/cred* and cd ~/.aws && head credentials, as well
as SSH private keys and secrets.yaml; before, only an absolute path was refused. Private keys and
AWS keys are now also removed from tool output.

The Amazon Bedrock and SageMaker providers copied stored AWS settings, including
AWS_SECRET_ACCESS_KEY, into the process environment, where every process started afterwards could
read them, including the agent's shell in a public-model chat. Stored settings now go to the AWS SDK
directly. Secret fields in the built-in provider setup forms are now hidden as you type; keys there
used to show in plain text. Tool results relayed through the Claude Code and Codex tool bridge are
now marked untrusted and scanned for injection and PII/PHI, as every other provider's results
already were.

The browser interface's file routes now refuse any path through a broken or looping symlink.
biorouter web no longer serves a list of every chat or any chat's full transcript, its socket now
always requires the token (an empty --auth-token "" is refused), and a cross-site scripting hole in
it, which could have become command execution as the operator, is closed. In the desktop app, the
Content-Security-Policy header and both permission handlers now apply to the app's windows, where
before only the policy inside index.html did. frame-src in the app and the serve page is
narrowed to 'self'; in testing, an iframe to a remote https address is now blocked. The live-update
WebSockets now accept only the daemon's own origin or the renderer the app's launcher declared. If
you run serve behind a TLS proxy, the proxy must forward Host and set X-Forwarded-Proto, or the
interface's live views never connect. Stopping biorouter serve with kill, a SIGINT sent to it, or
on Unix even SIGKILL, now also stops the daemon it started. Before, only a Ctrl-C typed in its
terminal reached the daemon; otherwise the daemon kept holding the port, the browser token and the
secret.

biorouter serve controls work, or say they cannot

A serve daemon has no way to confirm that a person, rather than a program, is acting, so some
actions can never be allowed from a browser. Before this release, some of those looked available
and then failed, and some actions that should work there did not.

These now work in a browser: new chats on a host configured with a private model such as Versa
Azure, which serve used to refuse, now start on that configured model; Stop and Stop & send, which
answered 403 on every serve daemon; installing from Browse skills; and a fixed browser token set
through BIOROUTER_BROWSER_TOKEN.

These can never work in a browser, and now say so before they are clicked: adding a message to a
running turn, where the message waits in the queue and is not lost; making a chat public, which the
refusal says is available on the machine running the daemon; and sending to, steering or stopping a
subagent, whose tab now opens read-only and updates live instead of showing "Could not load this
chat".

Work the app lost or misreported

When a chat fails to start, every place a chat can be started now shows a "Failed to start chat"
toast, in the desktop app and in a browser. Before, the Home composer cleared as if the message had
been sent. The toast now says "Your message was kept", and from Home, from a new tab's composer, when
Send fails again, and in a split layout, the message stays where you typed it, with its pasted images
and attached files. A reload does not restore a kept message.

A chat whose working folder was the home folder could not get a turn to the model at all, because
the scan of the working folder before each turn got stuck there. The home folder, filesystem roots,
and OS and cloud-sync folders are now skipped, and any other scan has a 1500 ms limit. The
trade-off: a new file at the top of the working folder can take up to 30 seconds to appear in the
agent's view of the workspace.

After you press Stop, the text of the partial reply and a "Stopped." marker are now saved and
survive a reload. A Stop the daemon could not confirm used to leave the chat stuck; if the turn has
in fact ended, the composer now works again. workspace_set_tools wiped the saved extension list of
a conversation that was not open and still answered "Applied"; the list is now kept. The default
provider and its model are now saved to config.yaml in one write.

When a turn makes a chat private, the composer now shows it while the turn is still running, and the
chat's tab dot, which used to stay public, turns private when the turn ends. An open chat now picks
up model and privacy changes made elsewhere, such as in another window or another process, without
a new turn or a reload, and when another process changes a chat's model, the chat's next turn now
really runs on the new model.

Switching models from inside a chat now changes only that chat, unless you tick the new "Also use
for new chats" box. The "Change model" dialog opened in a chat now starts on that chat's own model;
before, pressing "Select model" without changing anything could move the chat to a different model,
and possibly a different privacy tier. When privacy tiers are turned off, a note that cannot be
dismissed now sits above the Home composer and every chat's composer.

Providers

On UCSF Versa Azure, choosing any model other than gpt-5.5-2026-04-24 changed only the label, and
gpt-5.5 still answered. Each of the nine models offered now reaches its own deployment. Versa Azure
and Versa Bedrock now read only their own settings, so the settings of the public Azure OpenAI and
Amazon Bedrock cards can no longer redirect a Versa chat. The public Bedrock provider no longer
sends requests to UCSF's gateway. For existing installs:

  • A Versa Azure deployment you typed over the one the form filled in is no longer used; set
    VERSA_AZURE_DEPLOYMENT_NAME to use it again.
  • Installs set up through onboarding between 2026-05-30 and 2026-07-02, and not reconnected since
    2026-09-03, move from API version 2024-10-21 to 2025-01-01-preview. This was not tested against
    the gateway.
  • A Versa Bedrock endpoint or region you typed over the default is no longer used; set it again
    under the VERSA_BEDROCK_* keys.
  • On an install where an earlier Versa setup wrote AWS_REGION, the public Amazon Bedrock card
    keeps showing as configured until that key is removed.
  • Public Amazon Bedrock users who routed requests through AWS_ENDPOINT_URL_BEDROCK must now use
    AWS_ENDPOINT_URL_BEDROCK_RUNTIME.

Codex can now use Biorouter's tools again; Codex CLI 0.148.0 and later had refused every one of
those calls. On Claude Code and Codex, a tool result no longer reaches the model twice. A Claude Code
or Codex provider whose CLI cannot be found is no longer marked "Configured".

Knowledge bases, workflows, search and skills

The desktop app could not load a private chat's knowledge-base selection. The knowledge-base chip,
the / palette and "Create workflow from this chat" now load it; before, the chip could show a
hidden base as switched on, and one click could silently make that base visible to the chat again.

Workflows made with "Create workflow from this chat" or "Make workflow" can now be saved; before, a
parameter the prompt never used made saving impossible. A workflow created from a chat with no
primary base now saves no default knowledge base. Jobs added with biorouter schedule add now
appear in the running app within 60 seconds.

Skill and extension searches in the model's marketplace search tool, Browse skills, Browse
extensions, the website's marketplace pages, Settings → Skills and the composer's skill picker now
match a query word by word and rank the results; before, a search with several words, such as
R scripting ggplot visualization, found no skills. Short words and license names no longer match
most of the catalog. A skill bundle installed from Browse skills now installs under its real name,
and reinstalling replaces it, where before it got a random prefix and could be installed again and
again.

Command line and other changes

On Windows, a CLI installed from the app can now find its daemon and web interface, which fixes the
Windows gap listed in v1.90.3's notes. biorouter session send --no-wait now returns as soon as
the turn is accepted. session cancel, attach and send no longer ask for the user-action key
before sending anything; against a daemon that has a key, the terminal asks for it once, and only
when the daemon refuses.

When you ask for something in several steps, the agent is now held to a To Do checklist. This
applies outside Chat mode when the Todo capability is on, and not to subagents or to turns on the
Claude Code and Codex providers; the check for several steps reads English only. When an agent
sends a prompt into another chat and waits for the reply, it now gets that chat's whole final
message and a status. Tab titles now follow the automatic renames a chat gets after its first few
turns, and opening a chat now loads one small summary instead of about ten full transcripts.

Known gaps

  • The Windows CLI fix has been tested only in CI, not on a real Windows machine.
  • Deleting a chat does not yet remove the files that store its knowledge-base selection, because they
    live outside the chat store.
  • A tab's privacy dot changes when the turn ends, not during it. The composer shows the change while
    the turn runs.
  • In a browser on serve, a message cannot join a running turn, and sending to, steering or stopping
    a subagent needs the desktop app.
  • The daemon still stops a turn after five minutes if nothing is reading its reply. That includes
    turns started with session send --no-wait.
  • A caller holding only the daemon's secret can still create, run, pause, resume or delete a
    schedule. A schedule runs a workflow file in a new chat rather than an existing one, so this opens
    no private chat, but the run it starts can use a private model. GET /sessions/changes still
    reports the provider, model and privacy tier of the chats it is asked about.
  • When a subagent's script asks for approval, the card appears in the parent chat but still not in
    the subagent's own tab.

For contributors

CI now runs all but two of the tests/*.rs integration test programs on Ubuntu with no network
access. The Playwright end-to-end suite passes again when run locally (CI does not run it), and every
test launch now runs in a sandbox. cargo test -p biorouter-cli no longer writes to your real
~/.config/biorouter, npm run test:run exits 0 again, and a number of flaky tests were fixed
without changing the shipped app.