Skip to content

VAF v0.1.0a20

Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 05 Aug 19:20
· 92 commits to main since this release

Added

  • The terminal now shows how far a sub-agent has got. The task line above the
    prompt showed only that something was running and for how long. It now also shows
    the count, like 2/5, for the two sub-agents that plan their work upfront: the
    coder and the document agent. It is a count and never a percentage, because a
    coding run legitimately finishes below its total when a task fails, and a bar that
    has to reach 100% would have to lie about that. The three sub-agents that do not
    plan upfront show nothing rather than a made-up number.
  • A session now opens by telling you where you are. Instead of a bare "new
    session" line, vaf run greets you with the Veyllo mark and the facts beside it,
    centred on screen: version, your agent's name, the session name and id, the
    active model, the local date and time - and the one line that was missing
    entirely, how to get back into an earlier conversation.
  • Most settings are editable inside the terminal app now. Rows that used to say
    "use vaf settings" work where they can: the AI provider and its model take effect
    on the running agent without a restart, and the speech engine, input language,
    sub-agent provider, sub-agent timeout, auto-open tab limit and local-server
    auto-start are simply set. The three that genuinely need a fresh start - the local
    model, the context limit and downloading a model - still point at vaf settings,
    and now say why rather than just deferring.
  • Sessions you never wrote in do not pile up any more. Opening vaf run and
    closing it again left an empty session behind every time, and the list filled up
    with rows that held nothing. An untouched session is now dropped when you leave
    it, whether you quit or load a different one; anything you actually wrote in is
    kept as before.
  • You can switch sessions from inside the terminal app. The sessions panel was
    a list you could look at but not use; the only way back into an earlier
    conversation was quitting and passing its id on the command line. Ctrl+S now
    opens it, the arrow keys walk it and enter loads the one you picked. And when
    you leave, VAF prints the session id and both commands that bring you back,
    instead of letting it disappear.
  • The prompt remembers, suggests and completes again. The full-screen vaf run
    had a plain text box: no history on the arrow keys, no inline suggestion, no
    completion for commands or file paths. All three are back, and they share the
    older interface's history file, so your past messages are the same list whichever
    way you start VAF. Typing / or @ opens a menu you can walk with the arrow
    keys; while it is open Enter picks an entry, and the next Enter sends.
  • The terminal commands are back, and all of them work in both interfaces. Typing
    clear, tools, undo, restore, context, halt or restart in the new
    full-screen vaf run used to send the word to the model as a chat message. They
    are commands again, alongside the ones that already worked, and a mistyped
    /command now says so (with the closest match) instead of costing a turn.
    Arguments work too: theme dark picks that theme instead of cycling, and
    session <id> loads that session. The ones that cannot be undone - clearing the
    conversation, rolling back files, restarting - ask first. Behind this, the command
    list lived in six places that had already drifted apart: the older interface
    offered completions for words it could not run, and ran a word it did not offer.
    There is one list now, and both interfaces read it.
  • Answers in the terminal are formatted again. The full-screen vaf run showed
    the model's markdown verbatim: literal asterisks around bold text, raw list
    markers, and code fences as three backticks and unhighlighted text. Headings,
    lists and emphasis now render, and code blocks are syntax-highlighted and follow
    the active theme. Long answers stream just as smoothly as before, because the
    text is redrawn on a fixed rhythm instead of on every word.
  • Tool cards in the terminal now show what the tool actually returned. The
    observation events an application can subscribe to reported that a tool finished,
    how long it took and whether it failed, but never what came back - so anyone
    building on VAF had to correlate results out of debug log files, and three places
    inside VAF carried their own private copy of the result for the same reason, each
    cut to a different length. The tool_end event now carries a result field
    (capped at 800 characters, always a string, safe to serialize). In vaf run the
    tool card is no longer an empty fold: it opens onto the result.
  • vaf run opens a full-screen terminal app. The terminal chat is no longer a
    scrolling prompt: it is a full-screen app with a live transcript (strictly
    chronological, streamed answers with the model's reasoning as a separate muted
    think block), tool cards, event narration, the sub-agent status line, the
    context-usage bar, the agent's animated avatar beside the newest reply, and
    keyboard-complete overlays for settings, model, history, sessions and help. The
    classic run-loop letters (s, c, t, h, l, ?) still work typed into the
    prompt, as do slash commands and @file attachments. Tool confirmations finally
    work in the terminal: previously they silently waited on the web dashboard and
    timed out after five minutes if no browser was open; the app shows the question
    and answers it in place. The previous interfaces stay available - vaf run --classic for the plain prompt, and the new tui_mode config key to pick the
    default lane (vaf run --web keeps the previous lane, which owns the web-server
    startup). Voice capture and provider switching inside the app land next.
  • You can now ask for one completion without a conversation. Building something on
    VAF that just needs one answer - a classification, a summary, a commit message -
    meant either running a full chat turn (history, tools, memory, routers) or
    hand-rolling the backend call, and about twenty places inside VAF had done exactly
    that, each slightly differently wrong. agent.complete(prompt) is one call with the
    agent's configured backend: it never enters the conversation, runs no tools, writes
    no memory, strips model reasoning from the result, and returns text or None - never
    an exception and never an error message dressed as an answer. The same primitive now
    powers the tools' own utility completions and the CLI features below.
  • An application built on VAF now decides which tools each account may use - with one
    registered resolver instead of VAF's own user database.
    The per-account tool allowlist
    used to be wired straight to the product's auth DB inside the dispatch pipeline, so an
    application embedding VAF got a check it could neither feed nor replace.
    set_account_allowlist_resolver is now part of the public interface: register one
    function that answers "which tools for this account" from your own storage, and it is
    enforced everywhere the pipeline runs - before the per-call authorizer, so an allow()
    cannot lift an account-level ban, and inside the coding agent, where the answer crosses
    into the child process as data. VAF's own product registers its resolver through the
    same primitive, and the pipeline no longer imports the product's auth layer at all (a
    test now keeps it that way). A registered resolver that crashes refuses rather than
    quietly enforcing nothing; registering nothing means unrestricted, as before.
  • A tool can now say that it touches files, and the per-user boundary is installed for
    it.
    Building something on VAF that reads or writes a user's files meant declaring who is
    calling and then writing the confinement yourself, inside every tool, exactly right, every
    time. Eleven built-in tools did precisely that, across five files, and five of the
    twenty-two that needed it had simply forgotten. A tool now declares file_access = "read"
    or "write" next to its identity, and the boundary is applied around it on every path -
    including when something calls the tool directly, with no dispatcher involved. Declaring a
    mode without the matching identity is refused when the class is defined, rather than
    quietly doing nothing later: a tool that receives no identity would otherwise run
    completely unconfined while looking confined. A boundary inside another can only narrow
    what it inherited, never widen it.
  • A new example runs without needing a model at all.
    examples/07_tool_caller_and_authorizer.py shows the two pieces below in one runnable
    script: running a tool with VAF's rules but no conversation, and deciding about each call.
    It needs no API key, no provider and no network, which makes it the quickest way to check
    a fresh install actually works.
  • An application built on VAF can now decide about each tool call itself. Until now the
    only way to keep a tool away from someone was to leave it out entirely - a choice made once
    at startup, for everybody. set_tool_authorizer is asked before every call and can refuse
    it, insist on a confirmation question, or let one through without the question. It sees who
    is calling and what the call would do, so decisions like "this customer's plan has no shell
    access" or "not that file, it belongs to someone else" are finally expressible. Refusing is
    the safe direction throughout: an authorizer that answers nothing changes nothing, and one
    that crashes refuses rather than waving the call through.
  • You can now run a single tool without starting a conversation. Building something on
    VAF that just needs a tool done - a scheduled job, a queue worker, your own agent loop -
    meant either wrapping a whole chat agent around it or rebuilding the safety checks by
    hand, and rebuilt checks drift apart from the real ones. ToolCaller is now part of the
    public interface, and it is the exact same one the agent itself uses: the same permission
    rules, the same confirmation question, the same identity handling, the same time limits.
    Documented in the embedding guide.

Changed

  • An embedded agent created with the machine owner's account key now reads the
    owner's profile
    instead of an empty per-key workspace. Keys belonging to anyone
    else are unaffected and still never resolve to the owner.
  • The repository root is quieter. The contributor, conduct and security documents
    moved into .github/, where GitHub still finds them, and the commercial terms and
    third-party inventory moved to docs/legal/. The separate ruff.toml is gone; its one
    setting lives in pyproject.toml. Nothing a user runs changed: the installers, the
    start scripts and the licence files stayed exactly where they were.
  • The default look is monochrome. The terminal theme is a black-to-white ramp
    now, so nothing in the frame competes with what the agent is actually saying, and
    the agent's white dot is the brightest thing on screen. Success, warning and error
    keep a colour, muted enough to belong but distinct enough to still be read at a
    glance. Both terminal interfaces share the theme, so both change together, and
    whichever one you pick is the one you get next time. The light theme is gone:
    it sat at the end of the cycle, so pressing the theme key often enough landed
    on it and stuck, and on a white background the agent's white mark and every
    white accent simply disappeared. Anyone who had it selected comes up on the
    default instead.
  • The terminal app's bottom row no longer cuts itself in half. The key hints and
    the context usage share one line and do not fit together below roughly 120
    columns, and the overflow used to be resolved by clipping - mid-label, so
    /exit appeared without its Quit and on a busy context bar even Help was
    gone. Now the context bar gives up its token counts first and hint pairs drop
    whole, so whatever is on the line is readable.
  • The terminal transcript follows a streaming answer again. It scrolled when a
    message was mounted but not while one grew, so a long reply ran on below the
    fold while the view sat on its first lines. It now follows the text - and
    stops following the moment you scroll up to read something, instead of
    yanking you back down.
  • Long command descriptions in the terminal help wrap under themselves instead
    of continuing beneath the key column, where they read like another command.
    The session list, the start block and the mark itself also survive a narrow
    window now rather than losing their last characters.
  • The mark in the terminal's start block is now the Veyllo logo converted to
    terminal art rather than drawn in solid blocks.
  • The README opens on the mark rather than on an ASCII wordmark, and shows the
    terminal and the web UI as one image above the installation steps.
    The desktop and terminal modes each show what they look like.
  • Workflow steps now pass the same security checks as chat. A step used to run its
    tool directly, skipping every rule a chat turn answers: admin-only tools, channel
    policy, the per-user tool selection, and the veto an application embedding VAF can
    attach. Steps now run through the shared dispatch pipeline, so all of those hold
    inside workflows too. Three deliberate differences remain: the confirmation question
    stays off for workflows (they run unattended - taking that away is its own decision),
    the heavy sub-agent steps of a temporary workflow still run as child processes outside
    the step pipeline (their internal tools remain constrained separately), and a step
    whose tool crashes now fails that step and lets the workflow branch, instead of
    aborting the whole run. Malformed step arguments are now refused cleanly before the
    tool runs. The existing rollback switch restores the entire previous behaviour if
    needed.
  • A workflow now runs as the person who started it, by default. Until this release a
    saved workflow always acted as the machine owner, whoever ran it: its files went to the
    owner's folders, its GitHub calls used the owner's account, and anything it created was
    filed under the owner. That was switchable before and is now the default. What changes in
    practice is that 47 further tools finally learn who is running the workflow - files,
    GitHub, automations, skills and reading messages among them - and none of them lose an
    identity they already had. If a workflow of yours relied on reaching the owner's files by
    an absolute path or a folder name like Desktop/..., that step will now be refused for
    anyone who is not an administrator; the setting workflow_identity_injection set to
    legacy restores the previous behaviour.
  • Workflows carrying an identity now pass it to far more tools. The setting introduced
    in 0.1.0a19 only reached a fixed list of tools; set to declared it now asks each tool
    what it needs, the same way a normal chat message already did. In practice that means
    files, GitHub, the browser, skills and automations finally know who is running the
    workflow, and mail learns the person's role rather than only their name. Nothing loses
    access - every tool that had an identity before keeps exactly the one it had. Still off
    by default (legacy).

Removed

  • Three launcher scripts that nothing called. stop_vaf.sh, launch_vaf.scpt and
    Start VAF.command were not referenced anywhere: not by the installers, not by the
    Linux desktop entry or the systemd service (which stops VAF by signalling the process),
    not by any documentation. On macOS the installers already build a proper VAF.app that
    launches from wherever the project actually lives, while launch_vaf.scpt still assumed
    it sat in ~/VAF. Stopping a persisted server is documented separately and unchanged.

Fixed

  • vaf run --web now says that it changes the interface. The flag has always
    routed to the previous prompt interface, because that is where the web server and
    its watchers are wired, but it did so without a word: you typed a flag you had used
    for months and got a different-looking VAF with no reason given. It now says so at
    startup, and only when the interface actually changes.
  • The terminal app asks for an API key instead of storing a provider that cannot
    work.
    Switching to a provider you had no key for wrote it to the config anyway
    and told you to restart, which was the one thing that could not help: the next
    start came up on a provider with no way to reach it. Now a provider with no key
    opens a masked key field, k on a provider row opens it for a key you want to
    replace, and the key is checked with one real request before anything moves. A
    key that does not verify is kept but the provider stays where it was, because
    that request can fail on the network as easily as on the key. The key itself is
    never shown, never written into the conversation, and the check runs in the
    background so the app keeps responding while the provider answers.
  • Choosing a provider for sub-agents in the terminal app now actually moves them.
    The row wrote the provider's name but not the switch that turns the choice on, so
    it reported success, moved its marker onto your choice, and every sub-agent went
    on using the main agent's provider. Six places worked out that pair by hand and
    now ask one function, so the two halves cannot drift apart again. Two more things
    come with it: the older vaf settings menu announced the chosen provider in its
    panel even when the switch was off, and it does not any more; and picking a
    provider you have no API key for is now refused in the terminal app too, instead
    of being stored and failing inside every sub-agent afterwards.
  • VAF no longer names one theme while showing another. If your saved theme was
    not the default, the terminal app's Theme list put its marker on the default row,
    and the vaf settings menu labelled the default too, while the screen was painted
    in the theme you had actually chosen. The name came from a value that only two of
    the four terminal lanes ever loaded from your config; every surface now reads the
    saved choice, and a --theme flag reaches them all. Worth knowing, because it made
    the above look like the theme was not applied at all: t writes your choice
    immediately, so cycling through the themes to look at them leaves you on the last
    one you stopped at, and that is what the next start uses.
  • A timer set in the terminal app now actually arrives. Asking for a reminder
    in vaf run set the timer, said so, and then nothing happened when it elapsed -
    the terminal app never looked at the queue the timer fires into, so it expired
    in silence. It now shows an amber card with what woke the agent, followed by the
    agent's answer. A timer belonging to a different conversation is dropped with a
    note saying so, rather than swapping the conversation you are reading.
  • A note the agent saves for itself now belongs to the person it was talking to.
    On an installation with more than one account, a note written while a background
    pass happened to be running was filed under that pass's account instead - and notes
    are read back into that account's next background pass as instructions to follow.
  • Background thinking no longer changes what a waiting person's turn is allowed to
    do.
    While it ran, anyone chatting at the same time silently got a fraction of the
    tool budget, a cap after three lookups, no retry when the model returned nothing -
    which is what leaves the web page stuck on a loading block - and a refusal from
    their own "update what I'm working on" action. Each of those limits is meant for
    the background pass alone.
  • The program keeps asking you for confirmation after an automation has run.
    The first automation switched the whole program into a mode that never prompts and
    never switched it back, so from then on confirmations a person should have seen were
    answered without them.
  • A scheduled automation can no longer put its output in someone else's place.
    On an installation with more than one account, a background task had no session
    of its own and quietly adopted whichever conversation was last active in the
    program. Its document was then created inside that person's workspace folder,
    announced in that person's browser, and their conversation was permanently
    re-pointed at the folder. In the same run their Stop button would cancel the
    background task, while the task's real owner could not stop it at all. A run now
    states which conversation it belongs to - a scheduled task says "none" - and
    nothing falls back to a program-wide value. Single-user installations were never
    exposed to the cross-account part of this.
  • An update can no longer make sub-agent results stop arriving. If a newer VAF
    wrote one entry into the shared sub-agent file and an older one read it, the older
    one gave up on the entire file rather than on that one entry - so every finished
    sub-agent result was silently dropped, with nothing in any log. This only needed
    one mixed-version moment to trigger and did not repair itself.
  • A coding run without an open Web UI no longer does the work of drawing one.
    Every loop iteration and every written file gathered a full project snapshot -
    several git calls and a walk of the project tree - before checking whether
    anyone was watching. On the terminal, where nobody is, that work was thrown away.
    The librarian did the same with a filesystem scan that also wrote a cache file.
  • A skill you shared with one person is visible to that person again. On the
    lanes that stored the account key as a parsed object rather than as text, the
    visibility check compared two different kinds of value and never matched, so
    use_skill and read_skill answered "not available to you" to the very person
    the skill had been shared with.
  • The machine owner is no longer locked out of their own files. If the owner
    key was ever stored in a different spelling than the one VAF generates, the
    terminal app rewrote it on startup and then compared the rewritten value against
    the stored one. It never matched, so the owner was treated as a guest: jailed out
    of their own project folder and refused on their own files.
  • A timer or automation can no longer leave a conversation without an owner.
    Stamping the owner onto a fresh conversation could fail silently on the
    background lanes, and a conversation with no owner loses the channel it came
    from, so a later sub-agent result had nowhere to go back to.
  • A background "thinking" run no longer borrows the machine owner's profile.
    After loading a person's chat history, the run took its identity from that
    conversation; if the conversation had no name stored, three fallbacks quietly
    substituted the owner's, and the owner's personal profile went into a different
    person's prompt and into the message sent to them.
  • An elevated permission no longer survives from one queued message into the
    next.
    The role was only ever cleared on two of the paths that start a turn.
  • A damaged owner key in a restored or hand-edited automation file no longer
    resolves to the machine owner.
    It gets its own isolated bucket instead.
  • The classic CLI no longer loses its prompt for good after a second message
    from the web UI.
    Once two messages had been queued for the same conversation,
    the terminal dropped to a plain Message: line with no completion, no history
    and no voice, and stayed there until you restarted it. Behind it, a finished
    task was never marked finished, so the conversation looked permanently busy and
    every later message for it was held back with nothing to release it. Both the
    hold and the crash it caused are gone, and any lane that consumes queued work -
    including ones written later - is covered by the same fix rather than having to
    remember the release itself.
  • A long answer no longer streams past the bottom of the terminal app. The
    transcript stopped following after the first few lines and the reply ran on
    below the fold, so you had to scroll down yourself to see the agent finish.
    Whether it happened at all depended on your terminal size, which is why it
    looked intermittent. Scrolling up still leaves you where you are reading, and
    coming back down picks the answer up again.
  • On Windows, attaching a file with @ works. Typing @C:\Users\me\notes.txt
    attached nothing and left the path sitting in your message: the reference stopped at
    the drive letter's colon, so VAF only ever saw @C. Relative paths and every other
    platform were unaffected, which is why it went unnoticed.
  • The licence note now points somewhere you can actually go. vaf info and the
    licence panel in settings told you to read LICENSE, LICENSING.md and
    COMMERCIAL.md, which is fine in a checkout and useless after pip install vaf:
    the licence texts live inside the package metadata there, and the commercial terms
    were never part of the distribution at all. Both now show a link instead.
  • VAF understands German written without umlauts. Plenty of people type "taeglich"
    or "ueberweisung" because their keyboard has no umlaut keys, and language models have
    lately taken to writing German that way too. Everything VAF matches on - the words that
    start a workflow, the risk check on an outgoing mail, the phishing score, the guard that
    catches the assistant claiming success it never earned - looked only for the spelling
    with umlauts and quietly did nothing for the other one. Both spellings now reach the
    same place. The guard against unearned success claims is the one that mattered most: it
    had been blind to exactly the models it exists to catch.
  • Sending a message no longer stalls while VAF saves what it learned. Every
    submitted line rewrote the entire learned-phrases file before anything else could
    happen - on a well-used install that is a 2.5 MB write and about a seventh of a
    second, paid in the terminal and in the web app alike. The learning is kept in
    memory immediately and written out a few seconds later, in the background, and
    the file is replaced atomically so an interrupted save cannot corrupt it.
  • A sub-agent no longer draws over the terminal app. When the coding agent, the
    librarian or the research agent ran without their own window, each painted a live
    progress panel straight onto the terminal - which shredded the display of the new
    full-screen vaf run. All three now ask one shared question first, and while an
    application owns the screen they report their progress into the transcript instead
    of over it.
  • The terminal app no longer loses its model, its settings or its crash reports.
    Five things went wrong quietly in the new full-screen vaf run: the desktop tray
    could unload the local model in the middle of a session, because the app never
    told it that a session was alive; a failing turn left no traceback anywhere; a
    theme picked in Settings only changed half the colors; the model name in the top
    bar always read "local"; the speech-input row could show "off" while the
    microphone was actually live; and switching server persistence did not reach the
    running agent. vaf run now also checks for Git before it starts, instead of
    letting tools fail deep inside an answer, and falls back to the previous
    interface if the full-screen one cannot start at all.
  • Files sent through the messengers are confined to your own data on shared
    machines.
    Attaching a file to an outgoing Telegram, WhatsApp or Discord message
    (or to the main-messenger delivery) accepted any path the general safety checks
    allow - including another user's project tree. Only email attachments were
    confined. All senders now enter the same per-user boundary as the file tools, so
    a non-admin can only send files from their own tree.
  • A symbolic link can no longer smuggle a protected file into a file operation or
    an outgoing attachment.
    The shared path rule checked the link's own location and
    the file was then opened at the link's target - so a link inside an allowed folder
    could reach VAF's credential store or system files. The rule now resolves links
    and re-checks the real target, for every caller.
  • Generated text no longer carries stream debris or hidden reasoning. Several
    features collected the model's streamed answer with their own hand-written loops,
    and the loops disagreed: AI git commit messages could end in a raw
    {"finish_reason": "stop"} control frame, tool utility completions could return a
    provider ERROR message as if it were the answer, and on local reasoning models the
    coder's template detection and other short calls received empty text because the
    whole token budget went into hidden reasoning. All of these now share one collector
    that filters control frames, treats backend errors as "no answer", strips reasoning
    blocks, and disables reasoning for local utility calls.
  • Streamed memory answers work again. Asking the memory panel a question with
    streaming on crashed on the very first token and returned an error string instead of
    the answer - every time, on every backend. The stream now delivers the text.
  • Attachment summaries respect their time budget. The per-section summarizer
    declared a timeout that was never wired up, so a slow backend could stall document
    ingestion far beyond the budget the caller computed. The timeout now binds.
  • Settings no longer claims "API key missing" for a key that is safely stored. Since
    keys moved into the encrypted store, the browser's copy of the config deliberately
    carries no key values - but six places in Settings still judged "is a key configured"
    from that copy: the warning banner in the provider section, the model-refresh buttons
    next to the provider, vision and voice-agent model pickers, and the three voice hints.
    A stored key therefore showed as missing, and refresh buttons stayed disabled although
    the server could resolve the key fine. All of them now ask the same stored-state the
    key fields already use, count a freshly typed key as present, and stay silent while the
    state is still loading or unreadable - "nothing configured" and "cannot tell" are
    different answers and no longer render as the same warning.
  • Editing an offline user no longer deactivates their account. The user table showed
    "Active/Inactive" for presence - is the user connected right now - but the edit dialog
    read that same field as the account state and wrote it back on save. Editing anyone who
    happened to be offline and clicking Save silently blocked their sign-in, and a
    reactivation appeared not to stick because the list refresh dropped the mapped fields.
    Presence and account state are now two separate things everywhere, and the list refresh
    is one shared loader instead of four drifting copies. The user list also says
    "Online/Offline" for presence now, so it no longer borrows the words the account
    setting uses, and a deactivated account is marked as such right in the list.
  • An admin can no longer lock themselves - or everyone - out. You cannot deactivate,
    demote or delete your own account (another admin can still do all three to you), and
    nobody can deactivate, demote or delete the last remaining admin who is able to sign
    in. Deleting was the gap: it only checked that some other admin account existed, even
    a deactivated one that could never log in to repair anything. The server refuses with a
    clear message, and the dialog does not offer the self-lockout controls in the first
    place.
  • A generated password is shown once instead of being thrown away. Creating a user
    with the password field left empty is the advertised way to let VAF generate one - and
    the generated password was then discarded without ever being displayed, so the new
    account could not be used until an admin ran a separate password reset. It now appears
    in a confirmation dialog with a copy button, once, because it exists nowhere else.
  • Opening the user editor from the detail view no longer carries the previous user's
    state.
    The access preset picked for the last user stayed selected and immediately
    rewrote the newly opened user's tool selection, and a temporary password from the
    previous user could appear in the new user's dialog.
  • The account status toggle says what it does. It flips whether the user may sign
    in, but showed only a nameless switch. It now shows the state in words - Active or
    Deactivated - plus what each means, including the honest limit: deactivating blocks
    sign-in and token renewal immediately, while a session that is already signed in ends
    when its token expires.
  • The per-user access picker is searchable. With 117 tools in the grid, finding the
    one to grant or revoke meant scrolling; both the tool and the workflow list now have a
    search field. Filtering is visual only - presets and "Select All" keep operating on
    the full list, so a filter can never silently shrink what they apply to.
  • The per-user tool permission is now actually enforced. Since user management
    existed, an admin could choose which tools each user may use - and the choice was
    saved, displayed, and checked by nothing (the interface said so honestly). It is now
    enforced on every tool call: a user can only run the tools selected for their account,
    in chat and inside the coding agent, and a revocation takes effect within seconds, not
    at the next login. The coding agent's internal tools - the shell above all - now appear
    in the admin's picker as well, so "this user gets the coder but not the shell" is a
    choice the admin can express. Admins themselves are never restricted and an empty
    selection means unrestricted.
  • The per-user workflow permission is enforced as well. The other half of the same
    admin choice - which saved workflows a user may run - was also stored and checked by
    nothing. A saved workflow now passes that list when it starts, wherever it is started
    from: chat, the workflow tool, an automation, the command line, or the resume of a
    paused run - so revoking a workflow while it is paused means it does not come back.
    The same rules apply as for tools: admins are never restricted, an empty selection
    means unrestricted. One-off workflows the agent designs on the fly have no saved
    identity to check; what governs them is the tool permission of the feature that
    builds them.
  • The coding agent now acts as the person who asked. It runs as a separate process,
    and no identity crossed that boundary - so its file tools ran with the machine owner's
    rights for every caller. The caller's identity now travels with the task, and the
    coder's file tools are confined to that person's own workspace. The owner's own runs are
    unchanged, and the shell deliberately keeps full strength - controlling who may use the
    coder at all remains the admin's lever.
  • Each user's cloud accounts are their own. Connected cloud storage - Google Drive,
    OneDrive, Dropbox, Nextcloud, iCloud - was addressed by user NAME, and any part of VAF
    that had no name to give fell back to the machine owner's. On a shared installation that
    meant another person's request could list, read and download from the owner's cloud
    accounts using the owner's own login. Credentials are now addressed by the account they
    belong to, end to end, including when a login token is refreshed. Existing connections
    keep working and move across by themselves the first time they are used; the owner's own
    accounts are unchanged. Cloud downloads follow the same rule: they used to land in the
    owner's Downloads folder no matter who asked, and now arrive in the requester's own
    space.
  • The OAuth client secrets get the same treatment as the API keys - and stop travelling
    to the browser too.
    The Google, Microsoft, Dropbox and GitHub client secrets in
    Settings showed their stored value to any admin session and echoed it back on every
    save - the same mechanism that corrupted a stored API key. They are now write-only like
    the keys: the field shows a partial glimpse and locks until you choose to change it,
    deleting is its own confirmed action, and what never leaves the server cannot be echoed
    back into it. One consequence to know: clearing such a field and saving no longer
    removes the secret - the delete action does, deliberately.
  • A stored key field shows you which key it holds. "Key stored" answers whether, not
    which - so each field with a stored key now shows a recognisable glimpse in grey: the
    first characters, a run of dots, the tail (like vaf_live_q0``...``Ab4d). It is a display
    built on the server and deliberately partial; the key itself still never travels to the
    browser, which is what keeps the save round-trip incapable of corrupting it.
  • Your API keys no longer travel to the browser at all - and a Settings save can no
    longer corrupt a stored key.
    The Settings page used to receive the key values still
    sitting in the configuration file and showed them as dots; saving any setting then sent
    them back, and VAF stored that echo as if it were the key. For most keys the echo
    happened to be identical and nothing broke; for one whose on-disk form was encoded, the
    stored key was silently replaced by its encoded shell and every request with it would
    have been refused - found because one key field looked different from the others. Key
    fields now always arrive empty (the stored value is confirmed, never displayed), the
    save response no longer carries secrets to anyone, and the round trip is pinned by a
    test that replays it against the real save route.
  • "Fetch models" works again for a stored key. It used to read the key from the form
    field, which is empty by design now; the server resolves the stored key itself, and the
    form only needs a value while you are typing a new one.
  • A new or changed API key is checked against the provider when you save it. Until now
    a wrong key looked exactly like a right one until the next message failed, and the answer
    arrived as a chat error with nothing connecting it to the screen that caused it. Saving a
    key you entered or changed now asks the provider whether it works, and reports back next to
    that key. A refused key says so with the provider's own error number and is left in place
    for you to correct; VAF does not delete it, because a check is not a revocation. A provider
    that cannot be reached is reported as exactly that and never as a bad key - an outage or a
    rate limit says nothing about what you typed, and treating it as a verdict would send you
    chasing a key that was never the problem.
  • Settings no longer closes when you save. Saving applies your changes; closing the
    window is your decision, with Escape or a click outside. They used to be the same action,
    which also meant a key check had nowhere to report to.
  • You can now actually delete an API key, and Settings shows you which keys are stored.
    Clearing the field and saving did nothing: a blank value has always meant "the form did not
    re-send this", which is what stops a half-filled page from wiping a key, so there was no way
    at all to remove one. That matters more than it sounds, because the usual reason to delete a
    key is that it leaked - and the interface reported success while the key stayed live. Each
    key now has its own delete action, and it removes the key from both places it can be stored,
    in the order that matters: get it wrong and the next use quietly restores it from the older
    location. If any part of the removal fails, you are told the key is still live and should be
    rotated at the provider instead of being told it worked.
  • Settings tells you again which API keys are stored. When keys moved out of the plain
    configuration file into the encrypted store, the key fields in Settings went blank and
    stayed blank - a configured, working key looked exactly like no key at all, on the one page
    where you go to check. Each field now says whether a key is stored, without ever showing the
    key itself, and says so honestly when the stored keys cannot be read rather than reporting
    "none".
  • Changing your API key now takes effect without restarting VAF. Saving a new key
    appeared to work while the old one kept answering, and only a full restart applied the
    change - which meant a key you had just deleted or replaced could still be in use, with
    nothing on screen saying so. VAF runs several chat workers side by side, and the change
    was being applied to one of them; the rest carried on with the key they started with. It
    now reaches every one of them. Agents belonging to an application that embeds VAF are
    deliberately left alone, so a key passed in by that application is never replaced by the
    one on the machine.
  • The protection shield no longer reports "no anomalies" on a day something was
    blocked.
    The shield summarised the state of each protection module but ignored what had
    actually happened that day, so a stopped high-risk skill installation and an admin
    overriding a security refusal appeared only as two small numbers in the Skills panel,
    next to a large green "no anomalies". Today's blocked attempts now appear at the shield
    itself as a clickable badge that opens the full list, and an overridden refusal or a
    worsened re-scan raises the shield to amber. A block on its own stays green with a count:
    a blocked attempt is the protection working, not a problem.
  • The security event list now names every kind of event. Half of what VAF records -
    every skill event and both mail events - had no label anywhere in the interface and would
    have been shown as a raw internal identifier instead of a description.
  • A broken stored API key now says so instead of looking unconfigured. Until now any
    problem reading a key produced an empty value, and an empty value means "not set up" to
    everything that asks - so a damaged key silently dropped you to the local model, or to no
    provider at all, with nothing to indicate why. It now reports the problem for the provider
    you are actually using, at the moment it is used: a damaged entry for a provider you never
    touch cannot take the installation down. If you see it, re-enter that key in Settings.
  • Your API keys move out of the plain configuration file. They were stored in
    config.json, scrambled but not encrypted - readable to anyone who opened the file, and
    carried along in any backup or screenshot of it. They now live in the same encrypted store
    as your mail, GitHub and cloud logins. Existing keys keep working and move across by
    themselves the first time they are used; nothing is deleted, so going back to an older
    version still finds them. Worth being precise about what this buys: unless you set a master
    passphrase, the protection is still only your file permissions - the win is that the secret
    is no longer sitting in the file everything else reads.
  • Cloud storage no longer treats everyone as the machine owner. The assistant's cloud
    tool worked out who was calling from a setting that is never actually set, so it always
    concluded it was the owner - whoever was really asking. On a shared installation that
    meant anyone could list, read, download and upload through the owner's connected Google
    Drive, OneDrive, Dropbox, Nextcloud and iCloud accounts, using the owner's stored logins.
    It now uses the identity of the actual caller. In a normal chat that means you see your
    own connected accounts and nobody else's. When the assistant reaches the tool through its
    file assistant, your name is not passed along yet, so you see nothing rather than someone
    else's accounts - safe, but not yet complete; the remaining half is in progress.
    Single-user installations are unaffected.
    In the same place, two file paths are no longer taken at face value: asking the tool to
    upload a local file, or to fetch one back out of the sync folder, is refused when the path
    points outside your own area instead of copying the file somewhere the assistant can serve
    it back.
  • A tool run without a username no longer guesses whose data it is. Some runs carry no
    username - after switching into a chat that has none stored, from the command line, from a
    workflow started without one - and the assistant used to fill in the literal name "admin".
    On any installation where you registered under a different name, that named nobody: things
    stored under a name (your cloud accounts and their sync folder, your GitHub account,
    credentials in the lanes that have no scope) are filed under the name you registered with,
    so the same person could be recognised in the web interface and be a stranger with an empty
    account list one lane over. The missing name is now resolved from the user the run belongs
    to: your own runs get your account, and a run belonging to somebody else gets their own
    separate space instead of yours. Installations with a single user called "admin" behave
    exactly as before. Some traces of the old behaviour remain visible in an old credential key
    shape the store keeps reading, and in stored chat history filed under the literal name.
  • The macOS launcher works on any machine. launch_vaf.scpt opened a hardcoded home
    directory belonging to one account, so it failed for everybody else. It now uses $HOME.
  • Examples in the assistant's own instructions no longer name one particular person. The
    shipped prompt text used a real first name in its examples ("... said", "Tell ... to call me
    back"), which read oddly for anybody else and put a private name in a public repository.
    Examples, default display names and documentation now use neutral placeholders. A guard
    checks committed content for real home directories by shape, so the next one is caught
    before it is published rather than after.
  • Code search and the linter stayed inside your own files. Both took a path and worked on
    it without asking whether it was yours. Code search looked contained - it clamps a path back
    into the project it was given - but that only applies to the copy the coding assistant uses;
    the one in normal chat is created without a project, so the clamp did nothing and an absolute
    path was searched and its matching lines returned. Both now confine to the calling user's
    own files, on every path they can be called from.
  • On a shared machine, one person could reach into another's chat session. Three of the
    commands the browser sends act on a session named in the message itself, and they never
    checked that the session belonged to whoever sent it. The consequences differed: the
    attachment panel of another user's chat could be emptied or filled with someone else's
    documents, which they would then find there and could unknowingly teach to VAF; a voice call
    could place a task into another person's chat queue; and a Stop press could interrupt
    another person's running answer. All three now refuse to act on a session that is not
    yours, the same way the other commands already did.
  • Asking VAF to write a document can no longer put it outside your documents folder.
    The tool takes a file NAME, but never checked that it got one: a name that was actually a
    path replaced the target folder entirely, so an absolute one wrote wherever it pointed. The
    file-type restriction did not help - it limits which formats may be written, not where. A
    name containing a path is now refused with a pointer to the tool that does take one, and
    the document lands in the calling user's own folder on a shared machine rather than
    wherever the name led.
  • Asking VAF to learn a document can no longer pull in files it protects. This tool
    carried its own idea of which files were readable, and it disagreed with the one every
    other file tool uses in both directions: it allowed anything in your home folder, including
    SSH keys, .env files and VAF's own settings and credential store, while refusing ordinary
    files kept outside it. Learning is also the worst place for that to go
    wrong, because it keeps what it reads: the contents are summarised into long-term memory
    and stay searchable long after the conversation ends. The tool's private rule is gone and
    the shared one decides, which on a multi-user machine also applies the per-user boundary it
    previously skipped. Together with the document panel fix below, a protected file is now
    refused whether you ask to view it or to learn it.
  • Opening a protected file in the document panel no longer showed its contents. The
    viewer read every file twice: once through the checked reader, and once raw to send the
    original bytes to the browser. Only the first read was checked, and its refusal was not
    acted on, so a file VAF protects for everyone - SSH keys, .env files, VAF's own settings
    and credential store - was refused in the text pane while the panel received the real file,
    and the tool reported it as opened. It is now one decision, taken before anything is read,
    and it covers both the protected locations and each user's own boundary.
    Its sibling, the document editor, now also reports honestly: it says it asked the
    interface to open a file, because whether the file is actually served is decided
    elsewhere and can still be refused. Claiming success for something that cannot
    happen is the same problem one level up.
  • The file assistant's own tools now follow the same rules as everything else - this time
    on both of its paths.
    When you ask VAF to work with your files, it hands the job to an
    assistant that runs its own small agent, and that agent called its thirteen tools directly,
    bypassing the checks every other tool call goes through. An earlier entry said this was
    fixed; it was fixed for one of the assistant's two paths. The other one answers simple
    requests straight from the wording, without ever starting the inner agent - "how big is this
    folder", "rename this to that", "show me the structure" - and it kept calling five tools
    directly, including the one that moves files.
    Both paths go through the shared checks now, which also means that for the first time these
    actions appear in the activity log: previously the assistant could rename or write a file
    and leave no record of it anywhere. Your files were never less protected on that path - the
    per-user boundary applied throughout - but nothing recorded what happened.
    One thing reads differently as a result: when one of those actions fails, the message is now
    the same short one used everywhere else instead of a hand-written sentence, so the assistant
    says what it is about to do beforehand ("Renaming X to Y...") to keep the context.
  • A failed web search could answer with someone else's memories. When every web provider
    is unavailable, VAF falls back to your own long-term memory - but on a server running
    several conversations at once, it picked whose memory to read from a process-wide value
    that any of them could have overwritten a moment earlier. The answer could therefore come
    from another person's memories. The tool is now told who is asking, the same way every
    other per-user tool already was. Single-user installations were never affected, and a
    request with no user attached was already refused rather than answered broadly.