VAF v0.1.0a24
Pre-releaseFixed
-
The searching magnifier is back, at full size. The scene where the agent
hunts with a magnifier and the search dust flares up had been switched off in
the actions timeline because it was assumed there was no room for it. There
usually is: the timeline now measures the space beside the chat column and
plays the whole scene whenever it fits, which on a normal window is always.
Only a genuinely cramped column, or a phone, falls back - and that fallback is
no longer a figure merely glancing about, but a compact magnifier sweeping
inside the agent's own square, on the same clock and in the same phase as the
glance. -
A chat no longer dies with a provider error when the agent corrects
itself. When the agent caught itself claiming a tool result it had not
received, it re-asked with a correction, and on Veyllo that second request
came back as an error twice in a row, leaving the raw error text where the
answer belonged. Measured against the gateway: it refuses a request in which
the agent speaks again after a tool result without the conversation being
handed back to the person. The correction now travels as such a turn, which
is what the same code already does for local models, and the identical
request that failed comes back answered. -
A reopened chat shows its own pictures again. A picture sent with a
message was stored and was still on the server, but reopening the chat left
the message without it: the browser rebuilt each message field by field when
it loaded a conversation, and the attachments were not among the fields it
carried over. They are now, so a chat looks the same after a reload as it did
when it was written. -
Copying now works where it quietly did nothing. Six places offered a copy
button that could fail without saying so: the verification codes in the
Telegram and Discord setup, the device code in the GitHub setup, the network
address in Settings, a newly created user's password, and the two-factor key
during login. Reached over the local network rather than on the machine
itself, the browser does not offer the clipboard at all there, so the press
either did nothing or broke the dialog it was in. All of them now fall back to
a way of copying that works anywhere. -
A link inside a chat's workspace can no longer lead out of it. Every
workspace lane (browsing, upload, delete and the new folder action) decided
"is this still inside the chat's folder" by comparing text. A symbolic link
that lives inside the workspace and points elsewhere passes that test, so the
read or write landed at the link's target instead. Containment is now decided
on resolved paths, for all four lanes at once, and a folder or file name that
carries a separator or an invisible character is refused as bad input instead
of failing as an internal error. -
Idle windows stop pretending to work. A specialist window opened by hand
used to claim activity that did not exist: the Coder said "Planning…", every
window's banner said "Starting - waiting for the agent…", the Librarian said
"Scanning", consoles said "Waiting for output…" - all with no run behind
them. Every one of these now tells the truth while idle, and the spinners
only spin when something actually runs. -
You are no longer offered tools your account cannot use. The per-user tool
permission was enforced only when a tool was actually called, so a tool an
administrator had disabled still appeared in the/suggestions - and the
refusal arrived after you picked it. Every tool list the app sends now carries
only what that account may run, and it errs on the side of hiding an entry if
the permission lookup fails. -
Escape closes the workspace window, and closes exactly one thing. The
chat's workspace explorer could only be left through the X in its header:
Escape did nothing there, so a delete confirmation, which covers that X, left
no way out but answering it. Escape now steps back one level, innermost
first: the delete confirmation, then the right-click menu, then a half-typed
folder name, then the search box, then the window. It is also no longer a
press that dismisses two things at once. The app answers Escape in one place
now and hands it to the topmost thing on screen only, so a panel open beside
the chat no longer closes underneath the dialog you meant to leave. -
Reopening the workspace window lands where you expect. Closing it kept
the folder you had drilled into, the workspace you had opened from the list
of all workspaces, and the search you had typed, so opening it again from a
chat could show another chat's files, or that list instead of the chat's own
folder, with a Back button that looked live and did nothing. Closing it now
clears all of that.
Added
- A specialist's window shows the specialist. The generic glyph in the top
left of every sub-agent window is now the agent itself, wearing its trade's
colour, so a window is recognisable as that specialist's workplace at a
glance. All eight window headers draw the same seat, and the kinds and their
colours are declared once instead of in three places. - Each specialist wears its own colour in the picker. In the sub-agent
window the researcher, the document writer and the librarian now carry their
trade's colour in the agent's body while the eye stays white, so you can tell
them apart before reading the name. The coder keeps the plain body: its
workplace is a code editor, black and white like the tools it imitates. - Every chat has a header, and its name is where you rename it. The chat's
name now stands at the top of the conversation, and clicking it renames the
chat right there, through the same lane the sidebar has always used. The
header has no edge of its own: it is the same colour as the page behind it,
and the text scrolling up dissolves into it instead of sliding under a line.
On a phone the app's own top bar carries the name instead, so there is still
only one bar. - The specialists sit in that header now, left to right. The globe, the
specialists you picked and the plus that adds one used to run down the right
edge of the conversation; they now run along the header, in the same order,
and the plus stays in one place instead of sliding down as specialists
arrive. Nothing about picking or opening them changed. - An agent reply can be read aloud, saved, copied and asked again. Next to
the timestamp under every reply sit four small actions. Read aloud moved here
from beside the bubble, where it sat alone and read as a different kind of
control than the rest; it now also reaches replies from a turn that used
tools, which never had it. Save writes that reply
to a Markdown file through your own system's Save dialog. Copy puts the same
Markdown on the clipboard, and now works over the local network too, where
the browser's own clipboard is simply not available and copying used to do
nothing at all. Ask again is offered on the newest reply only, and only while
the chat is idle: it asks once before it fires, then discards that exchange
and puts the same question again. It is refused while an answer is still
being written and while a specialist of that chat is still working, so
nothing that is still being produced can be thrown away. - The workspace window gets a right-click menu. Right-clicking the file
area of a chat's workspace offers "New folder" (a draft tile in the grid:
type the name, Enter creates it) and "Upload files"; right-clicking a
folder adds "Open folder" and "Delete folder". Until now a folder could
only be created here from the Coder window's idle Explorer. vaf.contained_path,vaf.safe_entry_name,vaf.PathEscape: keeping a
path that came from outside inside the directory it may touch, now part of the
public framework surface. Containment is decided on resolved paths, so a
symlink cannot carry a caller out of the root, and a path that does not exist
yet still gets an answer, which is what a tool needs before it creates
anything. See docs/EMBEDDING.md.- Specialist windows are useful while they rest, and windows you opened
stay yours. Idle now moves INTO each window's own face instead of
replacing it. The Librarian shows this chat's workspace inside its own
folder view - the same explorer it uses when working, only now the rows
are yours to click: folders drill in, files open straight into the right
viewer (code, document, image, web page), until the Librarian itself
starts working. The Coder keeps its editor and, like any editor before a
project is opened, says so - while its Explorer offers the workspace, and
a clicked file opens read-only in its own tabs. Whatever folder you are
looking at travels with your next message, so "sort this" means the
folder on your screen. In the Coder you can also mark a folder as the
project - the next run then continues in exactly that folder instead of
creating a fresh one; the welcome screen offers the folders around you as
one-click picks, and a right-click in the Explorer opens a small context
menu that can pick a project, open a folder, or create a new folder right
there in the workspace. And every hand-opened specialist window follows
the browser's rule: it never closes itself after a run, successful or
not - only your own close or a chat switch ends it. A window that already
shows a finished run keeps showing it - that result is the useful view
after work. - Your specialists now have a window you can just open. Clicking a
sub-agent in the rail opens its window whether or not it is running, the way
the globe opens the browser, and clicking it again slides it away. While a
window is open the message box shows which specialist you are in - and the
agent is told the same thing, so "fix the failing test" typed with the Coder
open is understood as being about that work, and the agent knows it can hand
it straight to the Coder instead of answering into the void. Each specialist
wears its own colour there - the one its window already uses, so the
librarian is orange, the document writer teal, the researcher violet and the
browser stays blue; the coder keeps the black and white of a code editor. - A hotbar for the sub-agents. A plus below the globe opens a panel with the
specialists the agent can hand work to - each with its own trade symbol - and
the ones you pick sit in the rail from then on, one click from any chat. The
choice belongs to your account, not to the browser, so a second person signing
in on the same machine gets their own rail; and a specialist an administrator
has not granted you never appears in the panel at all. - The coder now proves its work before claiming it, and you can watch the
guardrails fire. Three deterministic gates joined the coder's loop:
finishing a task is blocked while changes have not been verified (a green
run_tests, or a cleanrender_checkfor web pages - only where such a
lane exists, and degrading to an explicit "untested" note instead of
looping when the environment cannot verify); editing a file the run has
never read is refused ("read it first" - editing from memory is where
doom loops start); and creating a file whose name matches an existing one
points out the sibling instead of letting a duplicate be born. Every gate
block, stuck-detection firing and context reset now also reports into a
new Guards tab in the coder window's bottom panel, so the run's
self-corrections are visible instead of terminal-only. Lint feedback is
immediate on BOTH write lanes now - an edited file used to stay unlinted
until the task ended - and a fixed file stops blocking completion the
moment its latest lint passes. A lifecycle stepper above the task list
shows where the run is (plan, build, document, commit) - the
documentation pass and the final commit used to happen invisibly - and
test runs and render checks now announce themselves in the live action
line instead of looking like a frozen editor. - The coder can now look at the page it just built. A new
render_check
tool opens an HTML file from the project (or a URL) in the sandbox browser
and reports what a developer checks first: page errors, console output,
failed requests, the rendered text, and a screenshot. The coder gets it as
an inner tool next torun_tests- write the page, render it, read what
actually happened, fix, render again - and the chat agent gets it as
render_check(the screenshot lands in the chat workspace for
analyze_image). Dev servers on the host are reachable via rewritten
localhostURLs when they listen on0.0.0.0. While you or an agent run
are using the browser, the probe answers busy instead of taking over your
tab. - Uploading in the browser starts from your own files. The sandbox
browser's file picker used to open into an empty container home - your
files simply did not exist in there. Your file area is now mirrored into
the browser (kept fresh within seconds, size-capped), the picker opens in
it with a Workspace bookmark in the sidebar, and agent runs can attach
exactly those files through their upload action - nothing else. A change
of user wipes the mirror unread;VAF_BROWSER_WORKSPACE_SYNC=offturns
the mirroring off entirely. - Browser downloads land in your files, not in the sandbox. A file
downloaded in the sandbox browser - by you or by the agent - used to exist
only inside the browser container, where nobody could reach it. Finished
downloads now appear in your own file area (Downloadsfolder) within
seconds, after passing the same threat scan every other arriving file gets;
blocked files are refused and show up in the security dashboard. Downloads
belong to whoever holds the browser, a change of user purges leftovers
unread, andVAF_BROWSER_DOWNLOADS=offdisables downloading entirely,
enforced in the browser itself. - The browser agent can see now - and it browses smarter. With a
vision-capable model, browser runs take screenshots on demand and stop
scrolling blind: scroll and layout decisions finally have eyes. Without
one, nothing breaks - a configured vision model describes screenshots on
request, and with no vision at all the run simply continues on the page
text as before. A new "Browser agent model" setting (Settings, AI & Model)
gives browser runs a dedicated strong vision model without changing your
chat model. Runs are also coached to navigate efficiently (jump to matching
text instead of scrolling one screen per reasoning step), and a new
one-step action reads a whole page at once, lazy-loaded content included.
The underlying browser-use engine moved from 0.13.1 to 0.13.8. - The chat centres its text for as long as there is room, and only shifts
left when the column gets tight. The conversation used to centre twice
over - the container as a block and every message row again inside it - so
in a narrow column (a browser window or viewer docked on the right) a wide
empty margin sat on the left while the text starved. The rows now follow
the column's actual width: plenty of room means centred exactly as always,
even with a panel open on a wide monitor; a genuinely tight column moves
the text to the left edge and gives it the space - live, including while
the panel is being resized. The sidebar's hover expansion also floats over
the conversation now instead of pushing it sideways. - Every user gets a browser of their own, in parallel. Two people at a
time now each get their own browser container with its own persistent
profile and its own container network: history, saved passwords and
downloads are genuinely per-user, no user's browser can reach another's,
"busy" between users disappears, and two people (or their agents) browse at
the same time. Settings > Advanced > Browser pool sets how many browsers may
run at once (admin only, two by default, zero switches the feature off);
budget about 2 GB of RAM per user you raise it by. The pool refuses new
browsers when memory runs low, parks idle ones after a while (keeping their
data), and falls back to the shared browser with its handover scrub whenever
it cannot serve. - The shared browser forgets the previous user when it changes hands. On
any change of user - an interactive session, an agent run, or an unknown
state after a server restart - the sandbox browser now scrubs cookies and
every site's stored data (localStorage, IndexedDB and friends) before the
next user touches it; previously only cookies were cleared, and the agent
lane inherited whatever the last user left behind. A non-persistent
browser_agentrun now truly starts clean, as its description always
promised.VAF_BROWSER_SCRUB=fulldeepens the handover to a whole-profile
wipe (history, browser-saved passwords, autofill, downloads) with a short
browser relaunch. - The sandbox browser blocks ads and malware domains. uBlock Origin Lite
(the official release build, version-pinned and checksum-verified at image
build) now rides in the browser container and filters ads, trackers and
malicious ad payloads for hand-driven and agent-driven browsing alike. DNS
resolution goes through Cloudflare's security resolver (1.1.1.2, malware and
phishing blocking - deliberately not the family variant, which censors
content): encrypted via DNS-over-HTTPS where reachable, with the container's
plain DNS pointed at the same resolver as the fallback. Takes effect after a
browser image rebuild. - Watching the agent browse now shows the real browser. During a
browser_agentrun started from a chat, the browser window streams the
sandbox Chromium itself - real tab strip, real omnibox, live - instead of a
rebuilt address bar over 1.5-second screenshots. The stream is watch-only
(the run must not be typed into) and is offered only to the chat that owns
the run; the task, action plan, history and activity panels stay below the
viewport. Where no live stream exists (workflow tile, spawned child runs)
the screenshot view remains. - An agent run only borrows your browser. If a
browser_agentrun takes
over while you are driving the sandbox browser, the window no longer closes
on you when the run ends: the server remembers whose interactive session was
evicted and hands the browser straight back, and the window returns to the
interactive mode by itself. A short handover veil plays in both directions -
you to agent, agent back to you - so the change of hands reads as one motion.
Runs that took a free browser close the window afterwards, as before. - The browser window closes with an animation. Closing used to snap the
window away while the panel beside it collapsed smoothly; the slide-out now
plays on close too, and the interactive stream is stopped only after it has
played. - The agent knows it can take the browser over from you. While you are
driving the sandbox browser, your messages already carried the page, your
selection and a screenshot along - but nothing said what the agent could do
about it, so asking it to open a page and wait got you an answer instead of
an action. That turn now also says thatbrowser_agentdrives the very same
browser: it takes over for the length of the run, hands control back when it
finishes, and sees whatever you are logged into. - The sandbox browser is now yours to drive. Opening the browser window
(the globe in the chat's top-right corner) with no agent run underway shows
the sandbox Chromium fullscreen and USABLE: click, type, scroll, use
Chromium's own tabs and omnibox, streamed at up to 60 fps over a KasmVNC
lane instead of the agent view's 1.5-second screenshots. Logins persist the
way they do in any browser - the browser itself asks whether to remember
them, no extra switch in our chrome - and they land in your personal browser
store, the same one the agent's persistent sessions read, so a login you
performed by hand is a login the agent has on its next run. One person
drives at a time;
another user asking sees "busy" without learning who. While you drive, your
chat knows it: a small "Browser" chip appears beside the workspace chip, and
every message you send carries the page you are on, any selected text, and a
screenshot of your current view - as a normal image attachment the agent can
see. The agent always wins:
when abrowser_agentrun starts it takes the browser back and the window
returns to the familiar task/actions/history view, and when the run ends the
interactive browser is one click away again. The stream reaches the page
only through the VAF server with a per-session ticket; the container port
stays loopback-only. - The chat and the side panel share the space you give them. The border
between the chat and the right panel carries a drag handle: pull it to
resize both, with sensible minimum widths on each side, double-click to go
back to the automatic layout. The chosen width survives reloads. - A browser button in the chat, so the browser window is no longer only
reachable while an agent happens to be using it. A globe in the chat's
top-right corner, on the same line as the sidebar logo, opens and closes the
Browser Agent window; while the window is open the button stays marked. It
opens a window rather than starting anything:browser_agentis a tool the
agent calls, so what the button shows is what actually exists - the last
browser run's screenshot, visited URLs and action plan after a run, and an
empty browser saying "No browser session yet" when there was none. That
empty state replaces a "Starting Browser Agent" banner which, in a window
opened by hand, announced an agent nobody had called. While another
sub-agent is actually running, the button stands down and says so, so a
click can never hide live work behind a browser view. - The agent can now update a memory instead of saving it twice. A new
memory_updatetool rewrites an existing long-term memory in place - the
same operation the Memory page has always offered, now available to the
agent itself. Andmemory_savechecks first: when a nearly identical memory
already exists, it does not write a twin but answers with the existing
memory and its id, and the agent decides - update it, or insist on a
separate save. Memory search results now name each memory's id so the agent
can refer to one. The duplicate check is deliberately careful in the other
direction too: if it cannot run (memory database down), saving simply
proceeds, so a check never stands between "remember this" and the save.
Learned documents are protected: a section of a learned PDF is a record of
what that source says, somemory_updaterefuses it and points to the
honest lanes instead - learn the newer document version, or save a
correcting note that is retrieved alongside the section. Nothing valuable
can be silently overwritten that way. - Tools are grouped into bundles instead of one long wall of cards. Settings
-> Available Tools now shows one card per integration or area - GitHub,
WhatsApp, Telegram, Email, Calendar and so on - with the number of tools in
it and how many of them VAF has learned. Clicking a bundle opens a shelf
across the full width, directly under the card you clicked, holding that
bundle's tools; a notch on the card points at the shelf it opened. The rest of
the grid keeps its place, so opening a bundle no longer rearranges everything
else on screen. Searching still searches tools: type anything and the grid
switches to the matching tools, ungrouped. The same grouping now applies in
the terminal - thevafsettings tool table and the terminal app's tool
overlay print one section per bundle instead of 120 alphabetical rows, and the
agent's own tool list is grouped too. Which bundle a tool belongs to is
declared on the tool itself, so a tool you write - or a connected MCP server -
can name a bundle of its own. Tools you upload yourself stay recognisable:
they get their own bundles ("Custom GitHub" next to "GitHub", never inside
it), and the custom-tool editor now has a bundle picker instead of expecting
you to know the attribute by heart. - The tools window now says how much of your toolbox VAF has learned. Settings
-> Available Tools counts the installed tools along the top: how many there
are in total, how many VAF has trained itself on, and how many it has not.
The numbers cover every installed tool, not only the ones matching the search
box, so they agree with the "modules installed" line beside them. A tool whose
training was invalidated because the tool itself changed counts as not learned
until it is trained again, while its own card keeps saying "Stale". The header
also names the tool that is being trained right now, whichever part of VAF
started that run, and clicking the name opens the run's training window. A run
started by avafcommand in its own terminal belongs to that process and is
not shown here. - Around sixty mail providers now come pre-configured. Adding an IMAP
account no longer needs server settings typed by hand for GMX, WEB.DE,
mail.com, T-Online, IONOS, 1&1, freenet, Posteo, mailbox.org, Zoho, Fastmail,
AOL, Yandex, Mail.ru, Vodafone, Bluewin, A1, Orange, La Poste, Libero and
Seznam, alongside the Gmail, Outlook, Yahoo and iCloud entries that were
already there. Every host was checked against the live server before it was
added. - A dangerous file is now recognised everywhere, not just once. When an
administrator deletes a quarantined skill, VAF keeps the verdict: it records
the fingerprint of the bundle and of the files that earned the block in a
machine-wide list. From then on, every place a file can arrive checks against
that list before doing anything with it - chat attachments and images, the
chat workspace upload, files put into a shared agent room, Telegram, Discord
and WhatsApp media, mail attachments, cloud-sync downloads, and skill
installs. A match is refused outright and appears on the security dashboard.
Renaming the file or repacking it into a different bundle does not help: the
content itself is what is recognised, using two independent secure hashes.
Administrators can see and manage the list under Logs -> Overview, or from a
terminal withvaf security threats list | check | add | remove. Removing an
entry re-opens every lane at once, so it asks for the admin's 2FA code. - Files that merely look suspicious are pointed out, never blocked.
Alongside the check above, uploaded text is scanned for patterns that are
often unsafe - commands piped into a shell, embedded credentials, hidden
characters used to smuggle instructions. Anything found is noted next to the
file and on the dashboard, and the file is delivered as normal. Legitimate
scripts do these things too, so this is information, not a refusal.
Changed
- The Telegram and WhatsApp dashboards show the conversation inline.
Selecting a chat now shows the actual conversation (user and bot bubbles,
oldest at the top, auto-scrolled to the newest message) directly in the
dashboard instead of a short "Incoming/Outgoing" list that needed a click to
open a popup; the separate history popup is gone. Internal<think>blocks
are stripped from this view since they are never sent to the channel. A
search field on the conversation header works like Ctrl+F over the chat:
matches are highlighted, Enter / Shift+Enter (or the arrow buttons) jump
between them, Escape clears the search. The Memory Learning progress line
now sits in the conversation header next to the title. The panel grows to fill the window height,
so the Full access / Relay contacts panels sit at the bottom instead of
floating above empty space. In the Telegram dashboard the "Open in Telegram"
link moved into the dialog header next to the title, freeing the former
"Chat with bot" block's space for the chart and the conversation. - The soul's Continuity section now names the agent's real memory lane.
The default soul text (the onboarding wizard's suggestions and the fallback
soul) told the agent to "read your memory files" - but the agent's long-term
memory is not a file, it is its tools. The text now says exactly that:
recall withmemory_searchbefore asking or guessing, persist what matters
withmemory_save, because a fact that was never saved is gone next
session. Existing souls are not rewritten; the wording changes for new
setups and for anyone re-running the Soul Wizard. The lane also no longer
depends on the soul's text at all: the system prompt appends a short,
fixed continuity note to the personality section that cannot be edited or
removed with the soul, so an agent whose soul never mentions memory still
knows its memory tools. - "Train tool now" asks before it starts, and says what the run costs. The
button used to begin training the moment it was clicked, with nothing said
about what that means. It now asks first, and the question states what was
measured: training runs the tool for real dozens of times and makes dozens of
model calls to learn from the results, and those calls count towards usage
like any other; a run cannot be stopped once it has started and has no time
limit; and if it is interrupted, the tool falls back to "Learning" until it is
trained again. The safe answer carries the emphasis, because it is the one
that costs nothing. What training does not do is hold anything else up: the
agent, automations and other runs carry on beside it, and the only thing
refused while a run is in flight is a second run of the same tool. Tools whose
effect cannot be taken back - anything that sends a message - are never run
during training at all.
Fixed
-
"Take over" now means take over, not start over. Asking the agent to
take over while you are browsing hands it your actual session: the run is
told which page you were on and continues there - your tab stays open, your
logins stay live - instead of opening the site again in a tab of its own.
A takeover run also no longer wipes the session it was handed, even when it
would normally start with a clean browser. The way back mirrors it: whether
the run finishes, fails or is stopped, you get the browser back exactly as
the agent left it - tabs included - instead of a blanked window, with the
same handover animation in both directions. -
Closing the side panel finally plays its animation. If the panel's
width had ever been adjusted by dragging its edge (the choice is
remembered), every close snapped shut instantly instead of sliding: the
dragged state carried an unbounded CSS max-width, andnonecannot animate
to the closed state's zero, so the browser clamped the width to nothing on
the spot (measured: closed at 60ms of a 300ms transition). The cap is now a
finite value that never binds, and the window keeps its content painted
while the panel closes over it - the close is the open, played backwards. -
A browser window you opened yourself never closes itself again. Opening
the browser by hand now marks the window as yours until you close it: an
agent run may still borrow the browser, but afterwards - whether the run
succeeded, failed with a browser error, or was stopped - the window returns
to your interactive session instead of auto-closing. Previously the mark was
shared with every worker view and got reset the moment agent data streamed,
so a failed takeover could close the browser over the error it caused. -
The agent's browser view is the same window you drive. One browser, two
modes: in agent mode the window keeps the exact frame of the interactive
browser (header, viewport, status bar) and adds the Task, Activity and
History panels beneath the viewport. The rebuilt tab strip and address bar
are gone - the live stream shows Chromium's real UI, and where only
screenshots exist (workflow runs) they appear on the same surface. -
An automation's saved file now follows its message to the messenger. A
prompt-based automation that sends its summary itself in-run (say, a morning
weather text via Telegram) produces its output file only after the run, so
that message could never carry the file - and the post-run push, the only
lane that attaches it, was skipped entirely by the double-delivery guard.
The guard now suppresses only the duplicate text: the produced file is still
handed over as a follow-up document with a filename caption, on Telegram,
WhatsApp and Discord alike. Workflow runs whose send step already attached
the document stay at one copy - the second send is recognized and skipped. -
The interactive browser looks and behaves like a browser now. The window
is a real browser, with its own tab strip, address bar, bookmarks and
downloads, themed dark to match the app - so everything a browser can do is
there rather than a rebuilt fraction of it. The page fills the window instead
of sitting in black bars, and the sub-window with Task, Actions, History and
Activity stays away while you are driving, returning when the agent takes
over. Opening it shows VAF's own quiet loading state until the picture is
really there, rather than the stream viewer's foreign splash. Several smaller
repairs went with it: streams no longer die after exactly 40 seconds of
reading, scrolling no longer smears the page, and closing and re-opening the
browser no longer leaves a second browser UI inside the window. -
The interactive browser window now actually shows the browser. It fetched
its viewer correctly and then stayed blank, because the server sends
X-Frame-Options: DENYon every response and the window is a frame - so the
browser downloaded the page and refused to paint it. That header is now
SAMEORIGINfor the stream path only, which is the one page meant to be
framed, and only by VAF's own UI. Two more defects on the same lane went with
it: the viewer's address is no longer built with a guessedhttp://, which
was wrong whenever LAN hosting with TLS is on (the backend port speaks HTTPS
then and answered nothing), and the stream reaches LAN users at all now - the
HTTPS proxy had no websocket route for it and its relay could only carry text
frames, while a browser stream is binary from the first byte. -
Quickly restarting VAF no longer leaves the Docker services dead. Quitting
VAF stops the Docker stack in a background thread with up to 25 seconds of
headroom; stopping and restarting VAF inside that window raced the old
instance's stop against the new instance's startup, and the stop won - the
new VAF ran with every container down (memory search empty, browser gone)
until someone noticed. The stop now checks the containers' own start time
first: restarted after the shutdown began means a new instance owns the
stack, and the old stop stands down. -
The tool self-learning loop no longer goes quiet for the tools you use
most. Three repairs in the Whare Wananga lane. A tool whose definition
changed (marked stale) was excluded from the proactive pitfall injection AND
refused new lessons from live errors, so the agent repeated mistakes the
system had already seen - stale records now keep learning from runtime
surprises while their retraining is still owed. The known-pitfall matcher
saw a real failure as novel although the record's first pitfall described
exactly that trap, because most of the error's words were VAF's own error
wrapping - the matcher now strips that wrapping and recognizes
argument-contract errors by the argument they name. And the learning events
are finally visible: a re-fed know-how, a newly learned pitfall, and
know-how going stale (with how to retrain) now appear in the log timeline
instead of only in a debug file nobody reads. -
Nothing a tool learned from real use is silently forgotten anymore. Two
quiet loss lanes: when a tool's pitfall list was full, the newest lesson -
the one just learned from a live failure - was the entry that got dropped;
and a retraining run started all knowledge baskets empty, wiping the
lessons learned from real incidents along with the retrainable ones. Now
the cap evicts by replaceability (entries a training run can re-derive go
first, live-incident lessons only as a last resort, the newest lesson is
always kept), and a retraining run carries the live-incident lessons over
and re-attaches them after every distillation pass. Every eviction and
every carry-over is logged. -
A thinking turn with a text-recovered tool call no longer dies with a
Veyllo 400. Some models occasionally write a tool call as text instead of
a structured call; VAF recovers those and, for Veyllo, replays them as plain
context because the gateway refuses tool-call ids it did not issue itself.
That replay rebuilt the assistant message from its text alone and dropped
the model's reasoning - and Veyllo's thinking mode, which knows the turn had
reasoning, rejected the whole request ("The reasoning_content in the
thinking mode must be passed back"). The rebuilt message now carries the
reasoning when there is any and survives even when thinking was all the
model said; a turn that never reasoned sends no such field, and none is
demanded. -
The agent's avatar stays in one piece while it works in a chat's action
timeline. During a tool call the timeline's walking dot used to switch to
the wide tool scene built for the free-standing loading bubble - a magnifier
and particles more than a hundred pixels wide, in a gutter that has the tool
cards directly to its right. The scene was clipped under the cards and its
layout lean pushed the dot far off its rail, so the reader saw scattered
fragments (a stray dot here, a lens handle there) instead of an agent. The
timeline now keeps the plain living dot on the rail, the way it was designed;
a running web search shows as the figure's own looking-around motion and
other tools as its working pulse. The same fallback also animates the mobile
avatar during tool calls, which used to freeze to a still dot there. -
Each chat now keeps its own working state; switching no longer bleeds one
chat into another. With the agent busy in one chat, opening another showed
that chat's stop button too, and coming back to the first could leave its
loader, stop button and tool animation running forever with nothing left to
clear them. Three causes, all fixed: the sub-agent window, the workflow panel
and the stop-press feedback lived in state shared by every chat and now
belong to the chat they came from (the sub-agent view is swapped per chat
like the messages, the workflow panel records which chat its run belongs to
and only appears there); the events that end a turn used to be thrown away
when they raced a chat switch, so a chat could never learn its turn had
finished - they are kept for bookkeeping now, and a chat that finishes in
the background shows an unread mark instead of a sound; and the server's own
"is this chat busy" answer was read from one process-wide status field that
parallel workers overwrite, so it now comes from the task queue, which knows
it per chat. A worker display whose updates stop arriving settles itself
after 30 seconds instead of claiming a running task forever, and a failed
turn now tells the browser it is over instead of leaving the stop button
armed. -
Reading a large file no longer cuts it off blind. Every tool result in
the chat used to be capped at 2,000 characters, and a file read hit that wall
with nothing to go on: no length, no line count, no way to ask for the rest.
A YouTube summary of 2,781 characters arrived cut, and reading a long report
meant guessing ever-smaller page ranges. File reads are now exempt from that
generic cap and budget themselves instead: a large text file returns its
first section together with the facts (how many lines and characters it has,
which lines are shown, how to continue withstart_line/end_line) and a
structure index, a list of the file's headings with their line numbers, so
the agent can jump straight to the part it needs, the same way PDFs already
read by page range. Word, Excel and PowerPoint reads share one honest
ceiling that names what was left out instead of a bare "(truncated)". -
A background bookkeeping task could kill the tool spinner, silently. Every
30 seconds a debug profiler counted all objects in memory to watch for leaks.
That census briefly touches objects other threads are still in the middle of
building, which is a way to make Python abort whichever thread is building
them (CPython issuebpo-15108). In practice the victim was the terminal
spinner shown during a tool call: it froze mid-run with no error anywhere but
the terminal's own error stream. The census is gone; the profiler still logs
memory usage and its growth warnings, which is what the log line is for. And
because that crash left no trace in any log file, uncaught errors from
background threads are now written tocrash_<date>.logon every lane the
app starts, always, regardless of the debug-logs switch. Embedders get the
same viavaf.install_thread_excepthook(). -
Deleting a chat cannot skip its confirmation any more. The trash icon in
the sidebar deletes a chat that is genuinely empty without asking, which is
right: there is nothing to decide about a chat nobody used. Whether it was
empty, though, was judged in the browser from a number that arrives with the
session list and is refreshed by nothing a conversation does, so a chat that
filled up while the list stood still still counted as empty and was deleted on
one click, with no dialog and no copy kept. The browser no longer decides
this. It asks, and VAF answers from the chat itself: a chat holding messages,
an attached document or a file in its folder always brings up the dialog
first. The same question is now asked everywhere a chat can be removed, so a
cleanup started from the terminal cannot quietly delete what the app just
refused to delete, and a chat that only holds an automation result or a
proactive question the user has not answered yet counts as worth keeping.
Keeping a copy in the archive is offered afresh every time the dialog opens. -
A rejected mail login now says what to do about it. Connecting a GMX
account with two-factor authentication switched on failed with nothing but
"authentication failed", which named no action: IMAP has no step where a
six-digit code can be entered, so the mailbox password can never work once
2FA is on, and GMX additionally ships POP3/IMAP access switched off. VAF now
answers a refused login with what that provider actually needs - an
app-specific password, a separate mail-program password, a sign-in instead of
a password, or a local bridge - whether IMAP has to be switched on first, and
a link to the provider's own page for it, in the language of the interface.
An unknown provider gets the general advice rather than nothing. Guidance
appears only when the server refused the login, so a name-resolution failure
is no longer answered with password advice. An address with no known server
and no host typed in is now refused with that instruction instead of being
quietly tried against Gmail's servers. -
Sending a room message no longer shows it twice. The pending copy was
matched against the delivered message by exact text, but the server trims
what it stores - one trailing space (a phone's autocomplete) and the copy
stood under its own delivered message for 30 seconds. The comparison is
trimmed on both sides now. The pending copy also looks like the message it
is about to become - your initials, name and text, dimmed, a light band
sweeping while it sends, a small "sending" note - and on delivery the
message blends up in place instead of drifting in as if it were new. -
The room no longer draws a "VAF is typing" row. Whether the agent has
seen a message is what the read receipts under it already say; the extra
presence row said the same thing a second time, sitting exactly where the
answer was about to land. A human member typing keeps the small dots bubble. -
A room's shared folder lists the same paths on every OS. On a Windows
host, the seat-authenticated file listing (and the push answer) rendered
relative paths with backslashes, so a file pushed assub/a.bincame back
assub\a.bin- a name the fetch endpoint on another machine would then
miss. Paths on the wire are POSIX now, whatever the host runs on. -
The update dialog now knows how the update ended. Updating from
Settings -> Update and Repair used to show a spinner for as long as ten
minutes with no verdict: the dialog watched the version only through the web
interface, which is down for its own rebuild for minutes after the backend
is already back, and a failed update that rolled itself back looked exactly
like nothing ever happened. Now everyvaf updaterun records how it ended
(~/.vaf/update_result.json), the dialog reads that verdict and shows a real
failure screen (rolled back, recovery needed, or aborted before anything
changed) with the error and the log path - and while the interface is still
rebuilding it asks the backend directly, so a successful update says "server
updated, interface rebuilding" instead of spinning blind.
Added
- A file in the room can be named, not just described. A message may now
carryfiles- the names of files in the room's shared folder it is about -
so a receiving agent sees machine-readably what was left for it instead of
having to find the filename inside a sentence. It works the same way
everywhere, which is the point: the agent'sroom_sendtakesfiles, the
CLI'ssay,answerandreporttake--file, the guest client takes
--file(and its MCP tools afilesargument), the browser draws a chip
under the message, and the agent's own room turn names the file in its
prompt. References are read defensively in one place: an absolute path or a
traversal is dropped rather than rendered. - A guest in MCP mode holds its room open. An MCP server is one
long-lived process, so the guest client now keeps each joined room's
connection open instead of dialling per tool call: the writer lease is
renewed from there, what the room says is mirrored as it arrives, reads
answer from that mirror without a connection at all, and sends ride the same
line - the collision a per-call send and a held wait used to produce cannot
happen any more. A wait is instant when something is already there and safe
to leave running. Shell verbs are unchanged: one process per command has
nothing to hold a line with. Nothing here is a push, and the protocol
document says so: no harness wakes an idle model, so an agent still has to
ask - asking is just cheap now. - An agent in MCP mode is told when a room is waiting. Every tool answer
now carries a line naming rooms with unread messages, even for tools that
have nothing to do with them. No harness wakes an idle model, so the moment
it is already reading an answer is the only moment it can be told - and this
costs nothing, since the held line has the messages anyway and counting them
does not consume them. - The guest client can refetch itself, verified.
updatepulls the host's
current client over the authority the guest already pinned, so nobody has to
hand-type acurl -kagain: full certificate verification, no checksum to
copy, and the download is compiled before it replaces anything, because a
truncated file would break the one command that could fetch a new one. - A room invitation now fits an MCP host. The downloadable guest client
grew anmcpsubcommand:python3 a2a_client.py mcpis a stdio MCP server,
so Claude Desktop, Claude Code or Cursor get the room verbs asa2a_tools
from the same single file - standard library only, same checksum lane, same
seats. The invitation's guest section carries the ready-to-paste host
config,roomsandhowtowork from the shell too, and the join keeps the
room's welcome sohowtocan reprint what the room said about itself. - A room's shared folder is reachable from another machine. A remote seat
holder could talk about files but never exchange them - the workspace is a
folder on the host. The guest client gainsfiles,fetchandpush(and
the matchinga2a_files/a2a_fetch/a2a_pushMCP tools), speaking to
three seat-authenticated endpoints on the host. Uploads are capped, paths
are contained to the workspace (traversal and symlink escapes refused), and
deleting over the wire deliberately does not exist - destruction stays with
the members on the machine that owns the folder. - Loading shows the shape of what is coming. While a chat's history or a
clicked group chat's transcript loads, the message area shows skeleton
bubbles under a thin progress bar that races to two thirds and then creeps -
both vanish the moment real messages arrive, instead of a spinner over a
blank area. A message sent into a group chat appears immediately as a
visibly pending bubble ("wird gesendet…") and is reconciled against the
room's store, which alone decides the order of a room with many writers.
Fixed
- Tool results that ARE the deliverable are no longer cut mid-artifact. The
dispatch funnel caps every tool result at 2000 characters to protect the
model's context - a good default with one measured failure mode: a loaded
skill body was cut mid-instruction, and a room invitation briefing was cut
inside the very block the result orders the agent to pass on "unchanged and
complete". The agent then correctly refused to hand over the torn half and
spent a whole turn hunting the rest in encrypted stores and capped logs,
until the model collapsed into raw markup. Tools can now declare
result_is_deliverableand reach the model whole;use_skill,read_skill
androom_invitedo, each keeping its own output bounded in exchange. The
declaration is honored along the whole path, not only at the funnel: the
in-history compression stage no longer prunes such results (it had become the
new cut once the funnel stepped aside), and the error classifier no longer
paints a briefing as failed for containing the words "failed" and "tool" in
its own vocabulary - which had marked perfectly successful skill loads and
invitations as errors on the step chips. - A half-opened room connection can no longer mute the whole room. When a
remote client vanished between taking the writer lease and receiving the
welcome (a timed-out dialer hanging up), the server never released the
lease - so the client's own reconnects were refused for the full 90 second
lease lifetime, and every half-successful retry armed another dead lease.
The room read as permanently dead while the server printed a traceback per
attempt. The lease is now released on every exit path, and the handshake's
store work runs off the shared event loop, so a remote connect storm no
longer stalls the WebUI socket beside it. - Leaving a group chat no longer bounces back into it. The room view's
3-second refresh could have one answer still in flight when the person
switched to a normal chat; that late answer re-opened the room seconds
later, over and over on a slow server. A transcript now only opens the view
when it answers the person's own click, or refreshes the room already on
screen. A room message typed while the connection is down also stays in the
input box now instead of being silently dropped with a cleared box. - A held room session keeps its write right. The server renewed a remote
connection's writer lease only after a successful send, so a session that
read and thought for longer than the 90 second lease lost the right to speak
while staying connected and receiving - and a conversation is exactly
read-think-answer. Found by the first foreign agent to hold a session (a
Claude agent on another machine driving the VAF CLI). The wire gains a
renewtransport verb, the session daemon sends it every 30 seconds, and a
host too old to know the verb is asked exactly once. Protocol contract C9
("leases are renewed while attached") is now true. - The session outbox no longer counts refused messages as sent. An answer
ofnot_writerdeleted the payload and countedsent: 1- a rejected
message that read as delivered. The room's answer now decides the file's
fate: committed sends leave, an unauthorized send stays for the next round,
and a judged refusal moves aside with the room's answer beside it, counted
asrejected. - The downloadable guest client holds a line without losing it. The
single-file client a host serves (/api/a2a/client.py) now keeps its writer
lease alive during a longwaitwith therenewtransport verb (asking a
host that predates the verb exactly once), exposesRoomConnection.renew()
for guests holding a line of their own, and no longer drops frames that
arrive while it awaits an ack for its own send - a message somebody sent in
that window was silently never seen. A guest gets all of this by simply
re-downloading the client from the host. vaf a2a missionandvaf a2a introducestop denying remote rooms.
Both answered "there is no room on this machine" for a room the caller holds
a seat in. Mission now reads from the join handshake (labeled as of joining)
and refuses a remote write with the way that works; introduce names the
path that works today (say it in the room) instead of denying the room.- An agent enters a room under its own name. When the model passed no
display name, room_open and room_join seated the agent as "VAF" - the product,
not the persona its user had named - while every other surface (greeting,
system prompt, TUI title) already used the persona name. Both tools now
resolve the agent's own name through one shared resolver, the way an agent
card is meant to present the agent's identity; an explicit display still wins,
and "VAF" remains only the last resort when no persona is resolvable. - Veyllo no longer rejects the empty-response retry. Veyllo speaks
DeepSeek's thinking dialect and demands that a replayed assistant message
carry its reasoning as a separate field; VAF restored that field for DeepSeek
only, so the first lane that rebuilds and resends history - the
empty-response retry - died with a 400 instead of recovering the turn. The
restore now covers the family. - A group chat no longer shows phantom notifications. The sidebar's unread
badge counted the room's own check-in pings, which the transcript view
deliberately never shows - so the dot lit up, the room had nothing new, and
the dot came back with the next check-in. The badge now counts exactly what
the view would show. The check-in interval itself is also derived from the
room's log now instead of process memory, so restarting the app no longer
re-asks every idle member within seconds (on a day of live restarts, a
quarter of a busy room's frames had become check-ins). - A reply that is only an unclosed thinking block counts as empty again.
When a model opens a<think>block and never returns from it, the block's
prose used to pass the empty-response check as if it were the answer - so the
retry that replaces a dead generation never fired and the user saw leaked
markup where a reply should be. Thinking now counts as thinking whether the
model closed the block or not, in both empty checks (which share one probe
now instead of two hand copies). - Memory search stopped throwing away most of its own candidates. Two
retrieval defects capped answer quality regardless of the embedding model.
The vector lane handed the rank fusion only its top 5 candidates while the
keyword lane handed 20, so a correct memory ranked sixth by the vector side
never even reached the fusion; both lanes now feed the same depth. And the
keyword lane scanned only the first 400 stored chunks in no particular
order, silently ignoring the rest of a larger store; the cap is now 2000,
and existing installations that carry the old value in their config are
lifted by a config migration (a deliberately customized value is kept). On
the golden-question set this took first-hit accuracy from 12/26 to 18/26. - Memory search knows which model wrote each vector. Every stored memory
and chunk now records the embedding model that produced its vector. Two
models can emit same-sized vectors that are mutually meaningless, and until
now nothing could tell such a mixed store from a healthy one - search would
just quietly get worse. Existing rows are stamped on the next start. The
embedding caches include the model in their identity for the same reason,
editing a memory no longer writes an unencrypted content preview back into
its metadata (re-introducing a leak that was already cleaned up), and an
edited memory's summary vector is computed from the content again instead
of from its deliberately content-free title. - A model that asks for four files now gets four, not one. When a model
writes its tool calls as text instead of using the structured field - which
DeepSeek does intermittently, emitting several calls inside one wrapper - VAF
recovered only the first and then removed the rest from the visible text while
cleaning up. The result was a reply that read normally, one file actually
read, three quietly skipped, and nothing anywhere saying so. All calls in such
a batch are recovered now. A batch entry naming a tool that does not exist is
skipped on its own rather than cancelling the real calls beside it.
Changed
- Memory understands more languages. The default embedding model for
long-term memory is nowintfloat/multilingual-e5-small(100+ languages)
instead of the English-centric MiniLM: a question asked in German now finds
facts that were stored in English, and the other way round. Existing
installations are migrated automatically in the background on the next
start, with a progress banner in the app and a status line in the terminal;
until the migration finishes, search keeps working on the previous model.
Added
- Archived chats look like an archive. They are shown as boxes in a grid
instead of list rows; hovering lifts a box and opens its lid. Opening one now
has a readable way back - the old link was grey on near-black - and a Delete
from archive action next to it. Deleting there is final and the dialog says
what that means: it is the last copy, and the agent can no longer recall the
conversation afterwards. - The memory store can move to a new embedding model without losing anyone's
data.vaf memory reembedre-embeds every stored memory and chunk whose
vector was written by another model - resumable, idempotent, nothing is
deleted, and rows the encryption key cannot open are set aside instead of
blocking the run. The app start does this automatically when the configured
model and the stored vectors diverge, in a background worker process; until
the store is fully migrated, search keeps using the model the vectors were
written with, so results never come from a half-converted store. - Deleting a chat asks first. The trash icon opened no dialog at all: one
mis-click removed a conversation and its attachments for good. It now opens
the same kind of confirmation the group chats already had, naming the
documents that go with it, and the confirm button is disarmed for three
seconds - a closed padlock and a count, opening to an unlocked one and the
wording when it is safe to press. A destructive button under the cursor is
pressed before the sentence above it is read. That delay now guards the
group-chat dialog too. The same dialog offers to keep a copy in your archive
instead of losing the conversation, ticked by default - the usual regret is a
chat deleted for tidiness that the agent later needed. Archived chats stay
readable by everything that reads a chat, so the agent's memory can use them,
and they are private to your account. An empty chat - no messages, no
attachments - is still deleted with one click: there is nothing to lose,
so there is nothing to confirm. Archived chats have their own window:
Settings -> Persona & Memory -> Archive, with the search and its hits
on the left and the chats on the right; opening a hit jumps to the message
it was found in, with every match highlighted. The search runs on the
server across all your archived chats - it no longer needs you to open the
right one first - and it finds things the same way the agent does, so
"Reisekosten" finds "Reisekostenabrechnung" and "Pruefung" finds
"Prüfung". What matched is highlighted in the result list and in the chat
itself, so a hit shows you the words it found rather than only the chat -
and it outlines the whole passage the agent would receive for that hit, so
you can see exactly what the model gets. Archived chats also stay available to the agent's memory:
Cross Chat Hints read them like any other chat of yours, which is what the
"keep a copy" option promises. - Settings -> Usage: what was actually consumed, and by whom. A new tab
showing total tokens, request count, an estimated cost, and a table of
accounts with the heaviest first. The token numbers do not depend on anyone
agreeing about tokenizers: each one is what the provider itself reported for
a call it billed, so providers that count differently still add up to the
invoice. The cost next to them is the single estimate on the page - it comes
from a price list that ages, and a model missing from that list is priced at
the expensive end and marked as an upper bound. Everyone can open the tab,
but only an admin sees costs and other accounts. Everyone else sees their
own tokens and requests - no money, no percentage of the total, nothing
about anyone else - and that is stripped from the response itself rather
than hidden in the page. The chat's Context Window header now has a Usage
button that opens this tab directly. Clicking a bar in the 7-day chart
opens that day's breakdown: which lane spent the tokens - chat, thinking,
automation, a sub-agent, the browser - and how much each one used.
Token counts were also running low against the providers' own dashboards:
a call whose token counts happened to match the previous one exactly was
treated as "nothing new" and dropped, and the utility lanes send nearly
identical prompts back to back. Calls are now measured as the growth of
the running total, so identical repeats count. A call the provider never
reports usage for - an aborted or failed stream - is no longer silently
missing either: it is counted, sized by a rough word count, and marked as
an estimate, so the part of the total that was estimated can be seen and
subtracted.
Coverage is complete now: individual tools bill under their own name, the
coder's own connection is counted, and local model calls are counted too -
they cost nothing, but leaving them out meant the page could not answer
what the machine actually did. Amounts also show the right currency: Veyllo
bills in euros and the other providers in dollars, so the figures were
euros wearing a dollar sign. Each call now records the unit it was priced
in, and a period spanning two providers shows both amounts instead of
adding them together. Spending recorded before that change is still shown,
marked as being of unknown currency rather than dropped or assumed. A
EUR/USD toggle converts the figures at the European Central Bank's daily
reference rate, showing the rate, its date and its source beside them; it
changes the view only, never what was recorded, and it stays hidden when
no rate is available. The choice is remembered, and it applies to the price
comparison as well, so the whole tab can be read in one currency. Spending recorded before currencies were stored can
be attributed once, by stating which currency it was - the software will
not guess, and it backs the ledger up before touching it. Both live in a
newvaf usagecommand as well (show,set-currency), so an install
without a browser can read the same figures and run the same action. And a new breakdown shows which provider and model
did the work - chat, vision, sub-agents, the tool model and the thinker can
each run somewhere else, or locally, and their prices differ by an order of
magnitude, so the total alone said very little. The XML export carries all
of it - period, per day, per lane, per provider and model, each amount in
its own currency - and opens with a plainly written note saying the money
is an estimate and how it was arrived at.
Local models contribute tokens and no cost.
The tab also carries a 7-day bar chart with the busiest day marked, a share
bar per account (percentage, requests, tokens), a panel pricing the same
tokens against every provider's public list price - tap one to see which
model and rates the figure used, or define your own price in the last row -
and an XML export of the last 30 days that states in prose how each number
was measured. The comparison quotes each provider at its cheapest model for
the usage in question, opens a dialog with that provider's full model list
and per-model figures when tapped, and reports currency rather than
converting it (Veyllo publishes EUR, the others USD). Both dates are stated
rather than left to be guessed: which period the compared tokens come from,
and when the price list was last checked against the providers' own pages.
Requests recorded before token counting existed are now labelled as such
instead of showing a bare zero. - The Settings window is slightly larger, so the new tab is not cramped.
Security
- Dependency updates (Dependabot, both npm trees): Next.js 16.2.11 to 16.3.0,
whose vendored lodash closes CVE-2025-13465, with eslint-config-next in sync;
monaco-editor 0.56.0, next-intl 4.13.6, mammoth 1.12.1, zustand 5.0.15 and
autoprefixer 10.5.4 ride in the same group. The WhatsApp bridge moves to
Baileys 6.7.24, which mainly refreshes the pinned WhatsApp Web version. The
dompurify override stays required: monaco-editor 0.56.0 still pins an old
copy. Next.js now requires sharp 0.35 itself, so the sharp override has
become redundant and can go with the next dependency pass.
Changed
- Every model call is counted now, not just the chat. Usage was recorded at
the end of a chat turn, so the coder, sub-agents, vision, voice, memory
compaction, the mail composer and the browser agent all reached a model by
other routes and spent invisibly - the Usage tab and the daily spend cap were
both reading a fraction of the real total. Recording moved into the one
method every lane passes through, so completeness no longer depends on nine
places remembering to ask. Each call is also written to a new
usage_YYYY-MM-DD.logwith its lane, model, tokens and cost, which is the
fastest way to see what a single sub-agent or coder run consumed. That log is
a copy for reading, never the source: the per-user ledger stays the record,
so deleting logs cannot lose history. It is the one log that ignores the
debug-logging switch, because a spend record that can be switched off is not
a record. Each lane names itself in that log -memory,vision,voice,librarian,mail,browser,thinking,main- so it answers which part
of the product spent the tokens - including the lanes that run while nobody
is watching:automation,thinking,subagent,roomandbackground
are named rather than billed as if a person had typed them. The Usage tab
also gained a Refresh button,
and the price comparison now shows the arithmetic behind its figures -
sent plus received equals the instance total - so the number it prices
cannot be mistaken for one account's. - The API price table is current again (checked 2026-08-17 against the
providers' own pricing pages). It had been carrying a previous model
generation, which made every cost estimate in the product wrong in both
directions. Model names from the older generation keep their prices, so
existing ledgers are not silently repriced at the unknown-model rate. - "Context effort": choose what a reply is allowed to cost. Settings -> AI
& Model now carries a stepped slider from 8,000 tokens up to whatever the
configured model's real context window is - seven positions on a 128k model,
four on a 32k local one, always ending at the model's true maximum. It sets
the budget the compression lane triggers on, and the number it shows is the
honest one: an API is sent the entire conversation again on every reply and
bills every token, so the setting is the price of one reply rather than a
capacity. The default moved from 30,000 to 45,000 tokens. Moving it down
deletes nothing - older turns are summarized, and the full history stays
recoverable with/restore. The same ladder is in the terminal under
Settings -> Context -> Context effort, and embedders get it from
resolve_context_effort(). For a local model the slider is disabled with a
note, because local tokens are free and the budget is ignored there.
Fixed
- API costs no longer grow without bound in long chats. On a pay-per-token
provider every reply resends the whole conversation, and compression only
fired at 85% of the 128k model window - a three-week chat sat at ~65k tokens
forever, so even a one-line question paid ~65k tokens again, in every single
round-trip. Compression now triggers at a cost budget (context_compress_tokens,
default 30,000 tokens;0restores the old window-based behavior), posts a
visible system message with the before/after counts, and local models are
unaffected. Two bugs in the same lane went with it: the agent kept two
separate context managers, so acheckpoint_contextsummary was stored on an
object the session never persisted; and loading a session (or restarting the
app) replayed the full transcript back into the context, silently undoing
every checkpoint. One manager remains, its state persists, and a loaded
session is compressed again on arrival, reusing the saved summary without an
extra LLM call.
Changed
- Reading a group chat no longer shows as typing; it shows as a read receipt.
The three bouncing dots used to appear for any member that had merely read the
newest message, for up to two minutes - an agent that only monitors its room
looked permanently busy. The dots now mean composing and nothing else: the
agent is really writing an answer, or a person is pressing keys in the input
box. Reading shows as small stacked profile circles under the last message
each member has read, moving down as they read on, capped at twenty faces
with the remainder as a number.