Skip to content

Releases

Atrium 0.103.4 — A long conversation of heavy turns opens again

Choose a tag to compare

@github-actions github-actions released this 03 Nov 18:20

Patch release (hotfix). The Convex functions and the frontend change; the bridge and the
schema do not, and nothing needs migrating. Deploy the Convex functions, then the frontend.
Either half works with the other half of 0.103.3 during the rollout.

  • A long conversation no longer fails to open. The conversation view loads the most
    recent 200 messages with everything they carry (tool results, rows, files). On a long
    conversation of heavy turns this added up past the backend's per-request read limit
    (16 MiB): the request failed, the page showed its error screen, and retrying failed the
    same way. Nothing was lost — only the read failed. The view now stops reading
    before that limit: it shows the most recent messages that fit, complete, and leaves the
    older ones out, exactly as it already did past 200 messages. Your messages and their
    content stay stored; only how many of them the page loads at once changes.
  • A single turn too heavy to load on its own still opens. When the most recent message
    alone exceeds the budget, it is shown with its text but without its attachments, tool
    cards and rows, rather than failing the page.
  • The export says how many messages it holds. The "export limited to the most recent
    messages" note now gives the number the export contains. For a conversation past 200
    messages it reads exactly as before; for a shorter window cut by the read limit it no
    longer claims 200.
  • Branching and the diagnostics hold the same bound. Branching such a conversation
    into a new chat copies the most recent history that fits (reported as truncated, as a
    branch past 200 messages already was) instead of failing; the branch point is always
    copied. The chat-state and diagnose_chat diagnostics read the same bounded window
    and report windowTruncated (and partsOmitted on a turn shown without its parts).

Rollback: redeploy the 0.103.3 Convex functions and frontend. No data changes in
either direction; a heavy conversation fails to open again after a rollback.

Atrium 0.103.3 — Prepare a bridge credential before retiring the one in use

Choose a tag to compare

@github-actions github-actions released this 03 Nov 01:36

Patch release. The Convex backend gains an opt-in provisioning API; existing
provisioning clients and bridge credentials keep working. Deploy the updated
Convex schema and functions before an installer uses staged rotation. The release
does not enable automatic rotation or deploy an installer by itself.

  • Replace a bridge credential without revoking the running bridge's key first.
    Provisioning an existing instance with stageBridgeSecret: true returns a new
    secret once and a rotation handle, while the old credential remains valid. The
    candidate cannot authenticate yet. After saving it in durable encrypted storage,
    the installer calls seal to activate it alongside the predecessor, deploys it,
    and independently verifies the bridge is working. Only then does retire remove
    the exact old credential. Seal and retirement can be replayed safely with the
    same instance, handle and authenticated service identity. See
    staged replacement.
  • Interrupted provisioning cannot silently replace a working credential. A
    normal provision replay rechecks for an existing credential inside the write
    transaction rather than overwriting a key installed concurrently. Expected
    staged-rotation conflicts return stable HTTP 409 errors across Convex calls;
    unexpected failures return a generic error rather than internal details.
  • Recovery stays explicit. Do not retry an uncertain staging request: its
    plaintext cannot be returned again. Pending rotations do not expire
    automatically. Administrator replacement or revocation invalidates the pending
    handle; the existing rotateBridgeSecret recovery still revokes the old key
    immediately and can disconnect the bridge. The staged protocol preserves
    credential continuity, not zero downtime for arbitrary gateway changes. The
    installer remains responsible for encrypted storage, deployment and verification
    before retirement.
  • Vulnerabilities are reported without hiding broken scans. CI retains npm
    and container vulnerability reports and severity summaries for 90 days. Findings,
    including high and critical ones, warn without blocking CI; scanner failures,
    malformed reports, functional failures and container-policy violations still
    block it. A green build does not mean vulnerability-free or risk-accepted.

Atrium 0.103.2 — OpenClaw 2026.9.36 supported, and images rebuilt on fixed Node.js and go-jose

Choose a tag to compare

@github-actions github-actions released this 03 Nov 00:32

Patch release. OpenClaw 2026.9.36, the latest maintenance release of the extended-stable line,
is supported, validated on the live bench alongside Hermes 0.21.7. The bridge and the frontend
images change; Convex stays on 0.103.1 and nothing needs migrating. Deploy the new bridge and
frontend images, in either order.

OpenClaw 2026.9.36 is supported. It continues the 2026.9.33 line with fixes only (2026.9.34
and 2026.9.35 included). Its protocol additions are ones Atrium does not use — a new Control UI
capability, and a new label for where a timeout struck (tool_execution) — and its event
catalogues are identical to 2026.9.33's, so Atrium reads it exactly as it reads 2026.9.33: same
features, same behaviour. An instance on 2026.9.36 no longer shows the "newer than validated"
notice; 2026.9.34 and 2026.9.35, never benched on their own, now sit inside the supported range
too. The OpenClaw 2026.10 releases are newer and not yet validated, so they keep the notice.

One behaviour is new in 2026.9.36 and shown differently by Atrium: a run stopped because a tool
took too long now ends as interrupted, and the OpenClaw Control UI tells you to resume it and
check the tool's effect before repeating it. Atrium does not show that advice: it shows the end of
the run as the gateway's live events report it. The live bench does not exercise this case.

Before upgrading a gateway: Node.js 24.22.1 or newer. From 2026.9.34, OpenClaw refuses to
start on an older Node.js (openclaw requires Node >=24.22.1 <25, or >=26.12.1). The official
image already ships 24.22.1. A custom gateway image that installs its own Node.js must be
rebuilt with 24.22.1 or newer, or the gateway will not start after the upgrade. On the bench
the upgrade from 2026.9.33 needed no migration of the stored state: the gateway started on it
as is. Going back to 2026.9.33 afterwards is a downgrade OpenClaw refuses by default once
2026.9.36 has written its configuration — keep a copy of the gateway's state before upgrading.

Container images rebuilt on fixed Node.js and go-jose. The image supply-chain gate started
rejecting both images on 2026-10-30 after new advisories were published — our code did not change.
The bridge image ran Node.js 22.23.2, affected by CVE-2026-111497 (critical, actively exploited)
and two high-severity issues; it now runs Node.js 22.23.4. The frontend image's Caddy server
carried go-jose 4.1.4 (GHSA-mgcc-cww5-fp2q, high); it is now built with go-jose 4.1.6. The
frontend's build stage also moves to Node.js 24.22.2 (Node is not part of the published frontend
image). Pull the new bridge and frontend images; nothing to migrate.

Rollback. Redeploy the 0.103.1 bridge and frontend images (the frontend image then carries
the go-jose advisory again). A 0.103.1 bridge keeps working with a 2026.9.36 gateway: it reads it
with the 2026.9.33 profile and shows the "newer than validated" notice.

Atrium 0.103.1 — Switching conversation mid-answer, and dictating without stopping

Choose a tag to compare

@github-actions github-actions released this 31 Oct 13:50

Patch release. Only the frontend changes: Convex and the bridge stay on 0.103.0, nothing to
migrate. Deploy the frontend.

Switching conversation while an answer is coming no longer replaces the page with an error.
Clicking another conversation in the sidebar right after sending a message could show Une
erreur est survenue — Réessayer
instead of the conversation clicked; Réessayer or a reload
brought it back. The thread view is shared by every conversation, and for an instant a bubble
of the conversation being left could be drawn against the new one's (shorter) list; the error
that read raised took the whole page down. Each bubble now absorbs that instant on its own: it
draws nothing for a moment and the conversation clicked appears. Any other error is reported
as before, and a bubble that keeps failing still reaches the error page rather than vanishing.
This was not new in 0.103.0 — 0.102.1 behaves the same way.

Dictation keeps listening until you stop it. The browser ends a recognition session on its
own — after a silence, at its own session limit, on a network hiccup. The microphone button then
stayed lit while nothing was heard any more, and dictating meant turning it off and on again.
Atrium now opens a new session at once and carries on: what you dictate keeps landing in the
message, a phrase the browser had not finished is kept, and nothing is written twice. A pause to
think does not end it: only about a minute and a half of complete silence does. The button goes
off — with a short message — only when dictation really cannot go on (microphone refused or
unavailable, a browser engine that keeps stopping without hearing anything, or one that will not
start again), so one click always starts it again. A recognition still producing words is never
cut short. Stopping it lets the browser settle the last phrase — as long as it keeps making
progress, up to 5 seconds of silence and 20 seconds in all — and keeps the phrase shown if it
never does; turning the
microphone back on at once keeps that phrase too, exactly once — as does bringing a detached
dictation back into the composer (Récupérer ici) or releasing it while it still listens, and
detaching a dictation keeps the phrase it was recognising. The same applies
to the detached composer.

Send while dictating. The send button stays available during a dictation as soon as anything
is shown — even a phrase still being recognised, including just after you stopped the microphone
while the browser settles it (the phrase stays shown until then). Sending (button or Enter) sends exactly what the
composer shows at that moment, phrase in progress included, stops the dictation, and clears the
composer as any send does; nothing heard afterwards can slip into the next message or another
conversation. The microphone stays off afterwards. The detached composer's Envoyer works the
same way instead of asking to stop the microphone first; if that send is refused (conversation
unavailable, queue full), the detached draft keeps every word that was shown, the phrase in
progress included. Words still being recognised from one conversation, or from a dictation you
already sent or stopped, never land in another conversation or in the next message.

Rolling back is redeploying the 0.103.0 frontend.

Atrium 0.103.0 — See the model's reasoning, apart from the agent's notes and the answer

Choose a tag to compare

@github-actions github-actions released this 30 Oct 15:25

Minor release. Convex, the frontend and the bridge change. Deploy Convex first, then the
frontend, then the bridge
. The schema gains optional fields (a conversation's
showReasoning, an instance's and an agent's showReasoningDefault, a reasoning part's
nature, a row's reasoning signature and work-note fact) and one table,
transcriptRowReasoning, that keeps a row's reasoning apart from its text.

Show reasoning. A new setting, Afficher le raisonnement, in a conversation's advanced
settings: when it is on, Atrium shows the reasoning the model provides, in a Réflexion
block of the bubble — muted, set off by a rule, folded as soon as the answer begins, and
open again on a click. An administrator sets its default once per instance, and only the
agents that differ need an exception (Settings ▸ Platform ▸ Chat defaults ▸ Raisonnement: a switch for
the instance, then the list of exceptions, each shown or hidden; a new one is picked from a
searchable list, so an instance with a hundred agents stays readable). A conversation can
override both, and its setting says whether the value it inherits is the agent's or the
instance's. A changed default holds at once for what every conversation that inherits it
shows, and from its next turn for the session. It is an Atrium setting: the gateway's
configuration is not written. For an OpenClaw conversation the bridge
sets the session's reasoningLevel from it before each turn, the Control UI's own condition
for showing reasoning; once a session is known not to show reasoning, a conversation that
hides it costs no extra call (a session not yet described gets one reasoningLevel: off
patch on its first turn, since nothing proves its state before). For Hermes nothing is written to the gateway: its
only reasoning setter also rewrites the instance's global display configuration, so Atrium
keeps or drops what the gateway already sends (message.complete.reasoning).

Three natures, told apart. The model's reasoning (Réflexion), the agent's work notes
between its actions, and the answer each have their own look. OpenClaw's work notes now show
while the run goes on, among the tool cards, as soon as the gateway completes them (they used
to appear only once the run had ended, as part of the answer); once settled, the rows that
hold them are shown as notes. Hermes' interim messages are work notes, now placed where the
agent wrote them.

What is shown, and what is kept. OpenClaw shows a reasoning once its transcript row
arrives, above that row — between two tool cards when the model reasoned between them — as
the Control UI does; live reasoning comes in a later release. A model whose reasoning is
encrypted (most turns of gpt-6.1-sol) leaves an empty block, and Atrium shows nothing for it.
The reasoning is stored only while the conversation shows it, and never enters the answer's
text: no export, fork, feedback report, metadata API, search, notification or spoken reply
carries it. A row's reasoning is read when its block is opened, one at a time, never with the
conversation: a conversation full of long reasonings opens like any other, and a corrected
reasoning is the one shown. Hermes' reasoning is kept up to 16 KiB per message; a longer one
says it was cut. A reasoning the gateway cut to 8 000 characters is read again in full, like
a cut answer; when it cannot be, its block says it is cut. A work note alone in the part of a
run before a steered message stays shown with tool work hidden, and a cut note says so. A provider block that only looks like text (reasoning, redacted_thinking)
can no longer leak into an answer.

Rolling back is redeploying the bridge, then the frontend of 0.102.1, and keeping
Convex on 0.103.0. Its schema only adds optional fields and its functions only optional
arguments, so it accepts every call a 0.102.1 bridge or frontend makes, and a 0.102.1 bridge
ignores the reasoningLevel it is sent. Do not redeploy Convex 0.102.1 once 0.103.0 has
run: its schema rejects the documents that carry the new fields (any reasoning setting
written, any reasoning kept), so that deploy fails, and this release ships no tool to remove
them first.

Atrium 0.102.1 — An OpenClaw run ends when the gateway says so, never on a clock

Choose a tag to compare

@github-actions github-actions released this 26 Oct 18:34

Patch release. Convex, the frontend and the bridge change. Deploy Convex first, then the
frontend, then the bridge
. The schema gains one optional field (messages.projectedTurn).

A long silence no longer turns a running answer into an error. Production, 2026-10-25:
one model call streamed a tool call for seventeen minutes, and the arguments of a tool call
in progress produce no event a client sees. Every read of the transcript said the run was
active, yet the stuck-stream watchdog, which closes a bubble nothing has written to for
12 minutes, marked it "orphaned" — and when the gateway retried the call and the run
answered two minutes later, the answer landed under the error card. The OpenClaw Control
UI has no such clock: a run ends on the gateway's terminal, or on a read of the gateway's
state that finds it over (chat-history-stream.ts:187-215), and it re-reads that state on
every reconnection. Atrium now does the same for every OpenClaw conversation: the watchdog
no longer closes a projected bubble, whatever its silence. The bridge that runs the turn
marks the bubble as an OpenClaw one when it opens it, so this holds from a new
conversation's first answer, even before any read of its transcript succeeded.

What the watchdog covered is now asked of the gateway, on every cycle. Each time an
OpenClaw instance answers Atrium's periodic agent discovery, Atrium looks at every OpenClaw
answer still running on it and asks the gateway about each one nobody is already following:
still running, it streams on; over, it is settled from the conversation's transcript. The
same question is asked at once when the bridge restarts or a conversation's socket closes,
to save a cycle — but the cycle is what guarantees it, so an early, lost or refused
question costs a cycle, never an answer. The gateway's answer is applied: still running, the bubble streams on;
no longer running, the transcript is read and settles it from the run's own rows, and only
what that read leaves open is closed. "No longer running" needs a proof — an idle session,
or the gateway's exact list of active runs without this one; an active session whose list
the gateway withholds (it does while a run's identity is incomplete) proves nothing. No
answer is never taken for "over", and an answer is closed only once its end was actually
read from the transcript and stored: while the instance cannot be reached, or its
transcript cannot be read, the bubble stays running, the conversation shows the instance
unavailable, and the next cycle asks again. In a conversation that switches agents between
turns, an earlier agent's answer still running is read on its own, without disturbing the
current one. The question goes to the instance and agent the turn was sent to, under the
answer's own session; when Atrium cannot tell where to ask, the answer stays running —
being unable to ask is never taken for an end. The read that settles it starts where
Atrium's reads of that session stopped; when the gateway cannot answer from there, it
takes the newest page, as the Control UI does. The run's final answer is in that page, and
it now shows in the bubble even when rows in between were never read (before, such a
bubble kept only what had streamed live). During the deployment, a bridge not yet updated
answers without saying why: that answer closes nothing either — the bubble is settled once
its bridge is updated.
When the gateway cannot yet say, the bridge keeps listening to that conversation, so the
run's end settles the bubble when it comes. The deliberate reconcile of the diagnostic API (reconcile_chat) asks the
same question for an OpenClaw bubble instead of closing it.

Stop is always offered while a turn runs, including when the instance is unavailable —
it settles the bubble in Convex whatever the bridge answers. It used to disappear with the
greyed-out send button.

A bubble already marked "orphaned" — or "connection lost" by a restart sweep — is
corrected by the end of its own run.
The run's
terminal, or the settle of its transcript rows, now replaces the watchdog's error with the
answer (status, text, error cleared). Only the same run: another run's terminal leaves it as
it is, and a user's Stop or a gateway error — which are terminals — still win. A bubble
closed before this release whose run has already ended is not revisited by itself; an
operator can ask for it once with the internal mutation
stuckStreams:reprojectOrphanedBubble ({ "messageId": "…" }), which re-projects the run
from its stored rows and does nothing unless the bubble is such an orphan and the run is
known over.

Hermes is unchanged. A Hermes conversation is not projected, and the watchdog remains its
safety net, as before.

Dependencies. zod 4.6.6 (high-severity advisory GHSA-mrhx-c7xc-m3w5).

Rolling back is redeploying 0.102.0 (bridge, then frontend, then Convex). Nothing is
migrated.

Atrium 0.102.0 — OpenClaw conversations always follow the session transcript

Choose a tag to compare

@github-actions github-actions released this 22 Oct 22:22

Minor release. Convex, the frontend and the bridge change. Deploy Convex first, then the
frontend, then the bridge
. The schema does not change: the fields only the old path wrote
stay readable, and are removed in a later release.

The transcript projection is no longer a setting for OpenClaw. Since 0.101.1 an OpenClaw
instance has drawn its conversations from the gateway's session transcript (on), as the
OpenClaw Control UI does, and off and shadow were kept as a way back. That way back is
gone: every OpenClaw instance runs the projection, and a stored off or shadow is ignored
(read as on). The admin API still accepts the value, without effect; a save from the
Bridge or Injections tab drops it. Hermes is unchanged: a Hermes instance was never
projected, and its bubbles are still written from the live stream.

The old live-stream path is removed from the bridge. It had not run on a production
OpenClaw instance since 0.101.1. What goes: the timers that closed a turn on an empty or
truncated final, on a "sent" acknowledgement or on a deferred finishing; the rule that
read an acknowledgement's wording; the transcript polling (sessions.get) after a socket
drop or a long silence; the hold of a send while a delivery run was live; files taken from
the live stream or from a sub-agent's frames. A turn ends on the gateway's terminal or on
the transcript's own fact, and a file reaches a bubble only through the transcript row that
carries it.

What an operator may notice — little, on an instance that was already on. Two
corners used the old path even then, and no longer do: a session the bridge had just
opened (after a restart, before its first send), and a gateway whose handshake did not
report a version. In both, a connection lost in the middle of a turn no longer ends the
turn as "connection lost" after polling the transcript: the bubble stays as the run left it
and the next read of the transcript (the reconnect, or the stuck-stream net's resume)
settles it, as it already did for every other projected turn. A delivery run (a sub-agent's
announce, a requester-settle, a background task) always opens its own bubble, grouped with
its delegation chain, even in a conversation Convex cannot attribute to an instance;
bubbles merged by earlier releases are still shown as they are, and a gateway replay of
such a merge is still recognised.

Supported gateways are unchanged. The projection needs nothing beyond OpenClaw 2026.8.2,
the supported minimum: the two newer parameters it uses are already sent only to gateways
that accept them (chat.history maxBytes from 2026.9.2, chat.abort
discardPendingInput from 2026.9.7).

Dependencies. The lockfiles take the security fixes published since 0.101.4: DOMPurify
(critical, pulled in through the Markdown renderer streamdown, now 2.7.1, which no longer
bundles Mermaid — Atrium never enabled Mermaid diagrams) and the MCP server's
@modelcontextprotocol/sdk (1.32.1).

Rolling back is redeploying 0.101.4 (bridge, then frontend, then Convex). Nothing is
migrated: a stored off or shadow applies again under 0.101.4.

Atrium 0.101.4 — A sub-agent shows the status the gateway gives it

Choose a tag to compare

@github-actions github-actions released this 21 Oct 01:08

Patch release. Convex, the frontend and the bridge change. Deploy Convex first, then the
frontend, then the bridge
. The schema only gains optional fields.

A sub-agent that timed out no longer reads "done". A child cut off by its time limit, or
that failed, was killed, or was interrupted, showed a check mark: Atrium derived its status
from the child's last frame, and a run cut off ends with an ordinary final. The OpenClaw
Control UI derives nothing. It reads the child's session row from the gateway and shows the
row's status (session-status.ts:17-37). Atrium now does the same, in every projection mode:
the bridge reads the row (sessions.describe) when the gateway announces a change to that
child, when its event connection (re)subscribes, and when a child run ends; a new conversation
socket lists the parent's children first (sessions.list filtered by spawnedBy, as the
Control UI does, page after page, with another pass when rows move between pages) and
applies every row it returns, however old; each child it follows is then read once more, so a
child the gateway corrected while the bridge was disconnected shows its real end. A child whose
session the gateway deleted (cleanup: "delete") no longer stays "running": when the row is
gone — the gateway says so when the row is read, including for the children a complete child
list no longer has — the card takes the end the child's own frames reported (a later one still
replaces a stand-in), and a held send is released.
Convex records the row as read and orders two reads by the gateway's own sampling clock
(snapshotAt): an older read never replaces a newer one. Once a row has been read, frames no
longer change the status; the child's answer, files and tools still come from its frames.

The card says what the Control UI says. Timed out ("délai dépassé"), failed, killed
("arrêt forcé"), interrupted, queued, idle — with the Control UI's tones: a timeout or a kill
reads as a failure, an interruption as stopped. A child cut off by the limit its spawn declared
still names that limit ("dépassé son délai de 20 s"). After "Interagir" on a finished child,
the card follows that turn, as the Control UI's row does.

A sub-agent the reader stopped reads "stopped". It no longer shows a check mark when the
gateway records the stopped run as finished, and a row read while the stop is still travelling
does not bring the "working" state back. The protection holds for the stopped run only: after
"Interagir", the new run shows as running, then with its own end; the stopped run's report
stays out. Each Stop protects the run it stops, however many Stop / "Interagir" cycles came
before.

Atrium 0.101.3 — Long answers are shown in full

Choose a tag to compare

@github-actions github-actions released this 19 Oct 23:22

Patch release. Convex, the frontend and the bridge change. Deploy Convex first, then the
frontend, then the bridge
. The schema only gains optional fields.

A long answer is no longer cut at 8,000 characters. In projection on, a reply is shown
from the conversation's transcript, and the gateway serves each text of that transcript cut at
8,000 characters, ending with "...(truncated)..." (production feedback, 2026-10-19). The gateway
marks such a message as cut, and the OpenClaw Control UI then asks for the whole message. Atrium
now does the same: the bridge asks the gateway for the full message once, and the reply shows
its whole text. A message longer than Atrium's own limit (768 KiB per message) stays cut.

A cut that remains is said plainly. If the full message cannot be retrieved, the answer
shows the part that arrived, followed by a note saying the message was cut by the gateway. The
"...(truncated)..." marker, which read like part of the answer, is no longer shown.

Answers already cut are repaired. The first time the bridge serves a conversation after it
starts, it reads the conversation's latest messages once more and replaces any answer stored cut
with its full text. This covers the most recent messages of each conversation, not its whole
history. An answer stored whole is never replaced by a cut version of the same message, even
when the bridge cannot reach the gateway; and the bridge asks for each message at most once
while it runs.

Outside projection on (off, shadow), nothing changes.

Atrium 0.101.2 — A message the gateway did not acknowledge is sent again, once

Choose a tag to compare

@github-actions github-actions released this 19 Oct 19:10

Patch release. Convex, the frontend and the bridge change. Deploy Convex first, then the
frontend, then the bridge
. The schema does not change.

A send that timed out is no longer simply lost. When the gateway did not acknowledge a
message within 20 seconds, Atrium showed "Délai dépassé" and the reader had to send it again,
in an emergency too (production feedback, 2026-10-19). Atrium now asks the gateway whether it
received the message, with the same question the OpenClaw Control UI asks (its input
receipts). If it did, nothing is sent again and the reply arrives as usual. If it did not,
Atrium sends it once more, with the same identity and the same attachments; the gateway
answers a repeated identity with the run it already holds, so a message never runs twice. If
the gateway cannot answer, or the second send times out too, the failure stays on screen and
the reader can send it again. This resend is a deliberate departure from the Control UI,
which never resends on its own: it parks an uncertain send and waits for the reader to retry
it (chat-outbox-drain.ts:190-198). It is listed with Atrium's other deliberate departures in
docs/UPSTREAM_INTERPRETATION.md §7.

The activity summary counts what the Control UI counts. Routine work — sessions_yield, a
process poll, a native wait, the plan tools — is no longer counted ("1 command · 1 other
operation" for a hand-off, not "2 other operations"). A command that exits with a nonzero code
is counted as failed. An exec interrupted after its code started other calls stays in the
count instead of disappearing once the reply settles. Each rule is the Control UI's own
(projectAgentActivityItem).

A rebuilt tool card shows a failure the Control UI shows. A card rebuilt from the
transcript read only one spelling of the error flag. It now decides as OpenClaw does: the error
flag first, then the result's structured details (isToolResultError: a failure status such as
forbidden, denied or unavailable, ok: false, a timeout, an error field), and the output
text ("Tool not found", or a JSON error or status: "error") only for a result that has no
details. A refused sessions_send now shows as failed, and a command that exits 0 while
printing an error-shaped JSON stays a success. A failure the live stream reported is no longer
turned into a success when the transcript row arrives without one.

The waiting line goes when you write again, while the sub-agent runs too. 0.101.1 said
the line disappears as soon as you write again; while the sub-agent was still running, it came
back. It now stays gone. The line going out at the end of the agent's run, even when Atrium
cannot reread the conversation, holds for a run that has a reply bubble; a resume the agent
keeps silent goes out with the gateway's next report that nothing runs.

Rejected transcript writes say why. A rejected transcript write was traced only as "the
handler threw". The trace now carries the class of the error — a write conflict, a platform
limit, a refused value, or another error — and a hashed reference to the conversation, never
its id or any content.

Fewer device-token requests. 0.101.1 stopped asking Convex again for a device token it had
confirmed, but only within one connection: every short connection of the bridge (discovery, the
transcript reader, the session events) started over, and Convex was still asked every two
minutes per instance. The confirmation is now shared by every connection of the bridge to the
same instance: once confirmed, a token is not asked about again until something changes.