CHANGELOG.md
Rama main · réplica de lectura del repositorio original.
# Changelog
All notable changes to Aivyx are recorded here. This project adheres to
[Semantic Versioning](https://semver.org). Dates are ISO-8601.
## [Unreleased]
### Added
- **`[agent] max_reply_tokens`**: the most tokens one reply may use.
Default 4096 (it was a fixed 1024). The model server's own limit still
applies. The larger value also raises the context size routing asks a
model for and the budget gate's worst-case cost estimate, so in routed
mode a model with a small context is passed over sooner.
- **`[agent] role`**: the role to start in when neither `--role` nor
`AIVYX_PA_ROLE` picks one. `aivyx-pa init` writes it for templates that
have no `default` role (coder, researcher, personal, kitchen), so a
config made from them starts without `--role`. A schedule, webhook or
file watch with no `role` now runs as this role. For a config made
earlier, `aivyx-pa doctor` names the line to add.
- **Notify webhooks carry a version.** The JSON a `kind = "webhook"`
notify target receives has `"version": 1`. Receivers that ignore
fields they don't know need no change.
- **Versions on what pa stores, before 1.0.** Each instance gets a
`layout-version` file in its data directory, the encrypted store
records each table's record version, the audit chain gets a header
with its format version (beside the chain, so existing chains verify
as before), and each `aivyx-pa audit export` line carries
`"version": 1`. Data from earlier releases is version 1 and is
recorded as such the first time this release opens it; nothing to do.
A newer release's data is refused with a message instead of misread.
### Changed
- **`aivyx-pa audit export --event-type` takes snake_case**
(`tool_call`, `scope_denied`, ...), like every other CLI value. The
CamelCase names (`ToolCall`) still work.
- **A reply the model server cut off says so.** It ends with "⚠ The reply
was cut off by the model server's output limit…" instead of passing for
a finished reply.
- **A trigger with no `role` follows `--role`.** With `--role X` or
`AIVYX_PA_ROLE=X`, a trigger with no `role` is now checked (notify
scopes) and listed as role X instead of `default`, so an old config
whose role X lacks a trigger's notify scope now fails to load, with a
message.
- **`--headless` prints a turn's notes on stdout**, after the reply (the
cut-off note, for example), and prints a reply that wasn't streamed.
- **Image generation reports the backend's notes.** `vision.generate_image`
output gains `warnings`, for example when mold adjusted the requested
size.
### Security
- **Local traffic no longer goes through a proxy.** With `HTTP_PROXY`,
`HTTPS_PROXY` or `ALL_PROXY` set, requests to local services (Ollama,
llama.cpp, Lemonade, the broker, mold, a self-hosted n8n or MCP server,
a local webhook) went through the proxy, prompts and keys included.
Requests to a local address now always go direct. Cloud providers and
internet services still use the configured proxy.
- **Generated images are owner-only.** A new `vision` output directory is
created `0700` and each image `0600`.
### Fixed
- **Studio text is readable in both themes.** Links and brass text in the
light theme were 3.09:1 and are now 4.98:1. Error, success, info and
warning text and their chips, notices and the "forget skill" button now pass
WCAG AA (4.5:1). Rust-coloured text uses a lighter shade in the dark
theme.
- **Image generation works with mold 0.37.** mold now requires the model,
size and step count on every request, so every `vision.generate_image`
call failed. They're now filled from the model's own defaults, a size
off the model's grid is rounded (with a note), and the vision tool's
`[mold]` section takes an optional `model` (unset: the image model
downloaded on mold). Tested against a real mold 0.37 on an RTX 4090.
A mold in Docker or on another machine must run with
`--allow-direct-api`; the error now says so.
- **Image generation errors say what went wrong.** A busy GPU is reported
only when the broker says so (a proxy's or another service's 503 no
longer reads as "GPU busy"), an unknown mold model and a rejected mold
API key get their own messages, and an unreadable reference image fails
before the GPU is reserved.
## [0.20.1] — 2026-11-08
v0.20.0's binaries were never published: GitHub had retired the macOS 14
build machine its release used. This release ships everything in v0.20.0
(below) plus:
### Security
- **Matrix encryption library updated** (vodozemac 0.11.2,
RUSTSEC-2026-0445 and RUSTSEC-2026-0451: encryption key material was not
always wiped from memory).
### Fixed
- **Release builds** for Apple Silicon and the macOS desktop app use the
macOS 15 build machine.
## [0.20.0] — 2026-11-05
### Changed
- **The Studio sidebar is grouped.** Mission Control sits under Missions,
Wiki and Graph under Memory, and Gallery, Voice, Sessions, MCP and Tools
under a collapsible "More"; Guide and Settings stay pinned at the bottom.
Which groups are open is remembered per browser.
- **Readable Studio text.** Help text, empty screens, routine prompts and the
Audit notes are set in normal sentence case at readable contrast; file and
config names keep their real case. Screens say "Missions" and "Default
team", and the status bar shows the model's short name (full name on hover).
- **Empty screens say how to start.** Memory, Wiki, Graph, Models, Loop,
Gallery and Notifications explain what the feature does and offer a
button or the exact steps with a link to the guide, instead of raw config.
- **Routines wait for the model at start.** A routine due right after the
daemon starts no longer fails while the model is still loading, so a new
install no longer opens on "The routine … failed".
- **Sessions and Tools are plainer.** Sessions shows the channel and client
and drops closed Studio tabs; Tools shows a short description per tool with
the full text under "Details".
- **The terminal chat has `/help`, `/quit` and `/exit`.** An unknown `/word`
gets "unknown command" instead of going to the model. The approval prompt
reads `[y] allow [n] deny (default) [a] show all`, the same words as
aivyx-coder.
- **A timed-out turn says why and what to do:** the limit, that tool results
so far are kept, and the `turn_timeout_secs` setting under `[agent]`. Chat
apps get the same wording.
- **`init` no longer invents an identity from empty answers**, shows the
drafting time and lets Ctrl-C skip the draft, and says what the background
service installs (its name, what it runs, its log and how to remove it)
before asking. The top-level help lists developer commands separately.
### Fixed
- **The Studio link is only printed when the Studio is running, at its real
port.** If its port is taken (for example by another pa instance), the
REPL, `aivyx-pa studio` and `doctor` say so and name the setting to change,
instead of printing a link to the other instance.
- **A daemon that fails to start shows the reason** from its log in the
message, and names the log once. The Studio's bind error says "port".
- The daemon names `aivyx-pa studio` for the sign-in link.
### Security
- **Spreadsheet reading:** calamine 0.38 (RUSTSEC-2026-0436), so a malformed
`.xls` file can no longer crash or exhaust memory while being read.
- Dependency updates: async-executor 1.14.1 (1.14.0 was yanked); the zip
extract advisory RUSTSEC-2026-0429 is accepted with its reason (the
affected function is never called).
## [0.19.4] — 2026-11-01
### Changed
- **A `./aivyx-pa.toml` in the current directory is no longer read.** The
config comes from `AIVYX_PA_CONFIG_PATH` or the instance's own file
(`~/.config/aivyx-pa/aivyx-pa.toml`). To keep using a config elsewhere, set
`AIVYX_PA_CONFIG_PATH=/path/to/aivyx-pa.toml`. `aivyx-pa daemon install`
run from a folder that holds a config now writes that path into the unit;
`aivyx-pa doctor` (and a one-line notice on start) reports a config left in
the current directory or the service's working directory, and says how to
point at it. Re-run `aivyx-pa daemon install` for a service that relied on
its working directory.
- **Mission-gate `/approve` and `/reject` on Telegram, Discord and Slack now
need your id in `team_command_allowed_senders`**, the list `/team` commands
already use (empty means nobody). Add your id if you resolve gates from
chat. Matrix, Signal and Proton admit only you already.
- **A bare `daemon run --web-ui` now serves the Studio on the instance's
`[daemon] web_ui_port`** (it used the default port and ignored it, so a
named instance failed to bind). `init` for a named instance writes its own
port into the config and names it in "Next steps".
- **A config pack can no longer ask for shell, delete, send, post or write
scopes** in its `[[role]]` entries or team members; `pack check`, `pack
inspect` and `pack install` list every scope a pack asks for.
### Security
- **Approval prompts show everything they cover.** Recipients (`to`, `cc`,
`bcc`, arrays too) are named in the summary, every argument is listed with
short ones first, and any cut says how many characters are hidden: type `a`
at the terminal prompt to see it all; the Studio card has a "Show
everything" section. Control, bidirectional and zero-width characters are
shown as escapes. (audit P4)
- **Chat sessions that run in-process (the fallback when the daemon is down)
now honour `manual` autonomy areas** like the daemon does. (audit P7)
- **A parked-step preview and the Documents browser refuse a named pipe** in
the agent's folder instead of hanging a worker. (audit P5)
- **Telegram photo-download errors no longer log the bot token; recalled
memory topics cannot forge a prompt section; a tool process's stderr is
drained; the federation signature names its recipient; secret-holding
structs print `<redacted>` under `{:?}`; CI tokens are read-only; a failed
tool call shows its reason; an empty reply after tool calls says what ran
rather than "try again".** (audit P8-P14, P19, P20, P22)
- **The daemon no longer loads a config from its working directory.** The
installed service runs in `$HOME`, where the agent's shell could create an
`aivyx-pa.toml` that the next start would obey (tool processes, MCP
servers, access level). Configs are now loaded only from the explicit path
or the instance's standard path.
- **Login and session environment files are protected from the file tools**
(`~/.config/environment.d`, `.pam_environment`, `.xsessionrc`, `.xinitrc`,
`plasma-workspace/env`, fish `conf.d`, macOS `environment.plist` and
`LaunchAgents`), so the agent cannot set `AIVYX_PA_CONFIG_PATH` or
`XDG_CONFIG_HOME` for the next login. `shell.exec` also asks for awk/sed
programs that run commands or write files, git aliases that run a shell, and
the destructive archiver forms.
- **At access `home`/`full` the file tools refuse every dot entry of `$HOME`.**
Login and session files cannot be listed by name (`uwsm/env`,
`hypr/hyprland.conf`, `niri`, `sway`, fish `functions`, `.kshrc`,
`~/.local/share/applications/*.desktop`, ...), and each sets
`AIVYX_PA_CONFIG_PATH` or runs code at the next login. `fs.write`,
`fs.delete` and the data writers now apply the same dot-entry rule
`shell.exec`'s confiner enforces: nothing under `~/.*` or `~/bin` is
writable, except the personal workspace, the generated-image folder and
`[access] allow_sensitive_paths` (those exceptions are the file tools' own;
the confiner has its own).
**Behaviour change:** the agent can no longer edit dotfiles under `$HOME`
unless you allow the path. The named list still applies to roots that do not
cover `$HOME`. `shell.exec`'s confirmation now skips options per wrapper
(`time -p`, `sudo -E`, `timeout -s KILL 5`) and asks for more wrappers,
interpreters, git settings that store a command, `git apply`, `find -fprint`
and similar.
- **An approval applies to exactly one call.** The "operator replied" ledger
was keyed by tool name, so one refusal or one declined prompt let a later
call to the same tool run unasked. It is now keyed by the call itself
(tool, arguments, resolved target), counts only in the operator's next
turn, expires after ten minutes, is used once, and honours only a short
yes (`yes`, `y`, `ok`, `approve`, `go ahead`, ...): a "no" or any other
message drops the pending approval.
- **The agent can no longer write pa's own config, packs or tool-process
files.** The file tools' guard denies the instance config, data,
`~/.aivyx-pa` and runtime folders, any file named `aivyx-pa.toml` or
`team.toml`, and the resolved config paths, at every access level, for new
files as well as overwrites. A refused `fs.write` leaves no directories
behind.
- **The loop's gate command runs confined.** It executes what the loop agent
wrote, so it now runs under the `shell.exec` confinement (only the
working folder is writable), with a cleared environment, in its own process
group. **Behaviour change:** `[loop] working_dir` is required when
`gate_command` is set (reported when the config loads), and the gate
inherits only `PATH`, `HOME`, `LANG`, `TERM` plus the new `[loop] gate_env`
list (secret-looking names are refused). Captured command output is capped
at its last 1 MiB.
- **`shell.exec` asks before a destructive command** when
`confirm_destructive` is on, as the docs and the init wizard promise:
removals, overwrites, forced or history-rewriting git, `dd`, `mkfs`,
`chmod -R`, and what the check cannot read (scripts, interpreters,
`sh -c "$VAR"`). `git` commands follow the git area's posture, the rest the
file area's. The question is an escalation, so it also applies in loop,
trigger and headless runs: there it is refused or parked like any other
confirm-first call instead of running.
## [0.19.3] — 2026-10-29
### Security
- **A repository's own git settings can no longer steer the checkpoint
git.** The checkpoint git runs outside the sandbox on a repository that
the sandboxed agent can write. Settings planted in that repository could
make it run a program, snapshot files from outside the working folder, or
pick a planted checkpoint when restoring. The checkpoint git now works only
on its own folder with fixed settings, accepts a repository's config only
from an allow-list, refuses symlinks in its own git folders, and restores
only checkpoints it took itself (aivyx-checkpoint `a41da40`).
- **The prompt-injection check now ignores invisible Unicode tag characters
and variation selectors** that could hide a known injection phrase from it
(aivyx-injection-guard `a506c9c`).
- Dependency updates for RustSec advisories: rustls 0.23.46, rustls-webpki
0.103.16, the root-certificate bundles, quinn-proto 0.11.19, tokio 1.54,
ctutils; zip 9 for the spreadsheet readers and the Kokoro voice loader.
### Changed
- **When a repository's checkpoints are refused, pa says so.** A git
repository whose settings fall outside the allow-list has no checkpoints,
so changes there can't be undone or rolled back. pa now prints one warning
per folder naming the setting, and `aivyx-pa doctor` lists such folders
under "Checkpoints".
- CI checks dependency advisories with cargo-deny and runs weekly.
## [0.19.2] — 2026-10-25
### Fixed
- **A checkpoint is no longer skipped after a failed one.** When git
couldn't save a checkpoint, the next checkpoint of the same files was
taken for a duplicate of it and reported success without saving
anything, so there was nothing to go back to. Now a checkpoint only
counts as a duplicate of one that was actually saved (aivyx-checkpoint
`b8072e7`).
### Security
- `slack-morphism` bumped to 2.28.0 (Dependabot alert, GHSA-26w4-8w32-4x3v:
when verifying a Slack request failed, the error written to the logs
could include a valid request signature).
## [0.19.1] — 2026-10-21
### Fixed
- **A killed aivyx-pa no longer leaves its commands running.** A
`shell.exec` command runs in a process group of its own, which never
sees the terminal's signals and was only killed when the tool call
finished. So when aivyx-pa ended on `SIGTERM`, on `SIGHUP` (its terminal
closed) or on a second Ctrl-C, a running command was left behind and ran
on indefinitely; `daemon run` did the same on `SIGHUP`. Running commands
are now killed first in every case, and the daemon kills them before its
graceful shutdown instead of waiting for them (up to a command's
ten-minute timeout). `SIGKILL` still leaves them running; a systemd
service is covered either way.
- **Ctrl-C stops a running command.** Cancelling a turn used to wait for a
running `shell.exec` command to finish or time out (30 seconds by
default). It is now killed at once, and the tool call returns what it had
printed, marked `"cancelled": true`. The same applies when a turn hits
its deadline.
## [0.19.0] — 2026-10-18
### Added
Three private chat channels, each a **preview**: tested end to end against
local stand-ins, not yet with real accounts. See
[Chat apps and accounts](docs/guide/13-chat-apps-and-accounts.md).
- **Matrix:** talk to your assistant in an end-to-end-encrypted room on a
homeserver you choose. It answers only people you list whose device
you've verified (`aivyx-pa connect matrix`, `aivyx-pa matrix verify`).
- **Proton Mail:** email your assistant through Proton Mail Bridge and get
the answer in the same thread, and let it search, read, draft and send
in your mailbox (`proton.*` tools; sending asks you every time). It
answers only mail from your own Proton addresses, or from outside senders
you list whose mail passed DMARC.
- **Signal:** message your assistant on its own Signal number through a
signal-cli you run, and let it send a Signal message to a contact you
list (`signal.send`, asking you every time). It answers only contacts
whose safety number you've verified (`aivyx-pa signal verify`).
### Security
- **`shell.exec` at access `home` or `full` can't plant code or read your
secrets in your home folder** (aivyx-confine `6e46575`, audit SH1). A
confined command could read `~/.ssh` keys, GitHub tokens and keyrings,
and add code that runs unconfined at your next login (`~/.bashrc`,
`~/.config/autostart`, a missing `~/.bash_profile`). Now nothing can be
created, deleted or renamed directly in `~`; dot folders and files,
`~/bin` and linked dotfiles are read-only; secret stores and Aivyx PA's
own state (every instance's) are unreadable. Other folders work as
before. The assistant's `~/.aivyx-pa/workspace` can't be used from
`shell.exec` at these levels.
## [0.18.2] — 2026-10-11
### Security
- **Tool packs can't write outside the packs folder.** A pack's version
became part of its install path unchecked, so a signed pack could unpack
its files anywhere in your home — for example a program onto your PATH.
A pack's version must now be `x.y.z`.
- **Config packs can't change what's yours to decide.** A pack's starter
config may only set who the assistant is and what it does (its model,
profile, persona, roles, autonomy within `supervised`, routines, team,
memory and skills). A pack that sets how far it reaches, whether its
Studio is open to the network, which programs it runs, where its requests
go, whom it trusts, or the prompt-injection check now fails `pack check`,
`pack inspect` and `pack install`.
- **`web.fetch` blocks more private addresses:** Tailscale and other
carrier-grade NAT addresses (`100.64.0.0/10`), `0.0.0.0/8`, broadcast,
multicast, and IPv6 forms that wrap a private IPv4 address.
- **`pack inspect` shows a pack's text escaped**, so a pack can't hide part
of what you read before installing it.
- Dependency updates clear every `cargo audit` vulnerability, including
the XML parser behind the xlsx reader (a crafted spreadsheet could stall
it).
### Fixed
- **The terminal says why a turn stopped.** It showed only
`[turn escalated]` or `[turn failed]`; it now gives the reason or the
error. With the model server down, a turn showed `[turn completed]` and
nothing else — it now fails and says the server couldn't be reached
(`--headless` exits 1). Notes the assistant adds after its reply are
shown too.
- Reading a named pipe with `fs.read` or the data readers is refused
instead of hanging the turn.
- An MCP server can't make the daemon hold a single message over 64 MiB.
- `team run` and `team roster` use the installed team (a pack's, or
`team.toml` beside your config) without `--config`, like the daemon.
- `doctor` and the end of `init` check llama.cpp and Jan as local servers
instead of asking for an API key.
- `connect toolkit` (and notion, obsidian, n8n, vision) explains that the
integration runs as a tool process instead of saying "unknown service".
- `pack inspect` shows a pack's aivyx-pa checks under its aivyx-pa part.
## [0.18.1] — 2026-10-10
### Security
- `rustls` bumped to 0.23.45 (Dependabot alert: TLS 1.3 handshake messages
were accepted across encryption-level boundaries), with the newer
`aws-lc-rs` it needs.
## [0.18.0] — 2026-10-10
### Added
- **Config packs.** A pack can now be configuration only — a starter
config, a team, skills and routines over the tools Aivyx PA already has.
`aivyx-pa pack install` makes one a **new assistant** (a named instance,
named after the pack or `--instance <name>`): it runs the usual setup
pre-filled from the pack, then says which accounts to connect. A pack
can't set autonomy above `supervised`, and an existing instance is never
changed. `aivyx-pa pack inspect` shows a config pack's routines, autonomy
and whether it passes its checks; `aivyx-pa pack check <dir>` runs those
checks for pack authors; `aivyx-pa instances list` shows which pack each
assistant came from.
### Changed
- **The pack format moved to its own shared crate,
[`aivyx-pack`](https://github.com/Aivyx-Agent/aivyx-pack)**, so aivyx-pa
and aivyx-coder verify packs with the same code. Packs install exactly as
before; a newer config-only pack (format 2) is refused with a clear
message until this release learns to install it.
### Fixed
- **`aivyx-pa pack inspect` works before any assistant is set up.** On a
fresh machine it refused to read a pack at all; it now checks the
signature against the built-in publisher keys, as `pack install` does.
- **`aivyx-pa pack check` checks routine schedules.** A routine whose
schedule isn't a valid 6- or 7-field pattern (seconds first) is reported
by the check instead of failing later in the scheduler.
- **A pack's assistant gets only the pack's routines.** Installing a config
pack no longer also adds the five starter routines or asks about them;
on a cloud model, setup says the pack's routines are on and each run
costs tokens.
## [0.17.0] — 2026-10-07
### Added
- **Supervised batching.** At the `supervised` autonomy level — globally or
for one area with `[[autonomy.override]]` — an unattended run (a routine,
webhook, file watch, the autonomous loop or a team mission) parks a step
that needs your approval instead of refusing it, and carries on without
it. Review parked steps under *Needs you* in the Command Center, which
shows what each one touches as it is now, or with `aivyx-pa review`; an
approved step runs once, exactly as parked. Your default notification
target hears when something parks, and steps left unreviewed lapse after
`[autonomy] review_expiry_days` (default 7); at most 100 wait at once. With no area at
`supervised`, nothing changes.
## [0.16.0] — 2026-10-06
### Added
- **Different autonomy levels for different areas.** `[[autonomy.override]]`
now does what it says: `domain = "email"`, `level = "manual"` makes every
email change ask first, and `domain = "fs"`, `level = "unleashed"` stops
file deletes and overwrites asking, while the rest follow the global
level. An area is the first word of a capability (`fs`, `shell`, `git`,
`email`…); an unknown or repeated area is a config error, an override
looser than the global level is called out at start-up, and
`aivyx-pa autonomy show` and the Studio list them.
### Fixed
- **Switching to the sandbox stopped deletes and overwrites asking.**
`aivyx-pa access set` (and the Studio's *Apply access level*) wrote
`confirm_destructive` into `[access]` — `false` for `sandbox` — and an
explicit setting beats the autonomy level, so after moving to the sandbox
nothing asked, at any autonomy level. Changing the access level now
removes the key instead, so the autonomy level decides again; run
`aivyx-pa access set <your level>` once to clear an old one.
- **`unleashed` didn't switch confirmation off above the sandbox.** The
access level's own default was combined with the autonomy level, so at
`workspace`, `home` or `full` deletes still asked. Now, unless you set
`confirm_destructive` yourself, the autonomy level alone decides: on at
every level except `unleashed`. `aivyx-pa init` writes the key only when
you decline confirmation, and `access show` and the start-up summary show
the setting that's actually in effect.
- **Emails and other integration writes keep asking at `unleashed`.**
Sending mail and writing to Drive, Calendar, Contacts, Notion, Obsidian
or n8n asks first at every autonomy level; only an explicit
`[access] confirm_destructive = false` turns that off. The Studio's
Settings now also shows the confirmation setting actually in effect.
## [0.15.0] — 2026-10-06
### Added
- **Named instances** — run several fully separate agents as one OS user,
each with its own roles, Profile, Persona, memory, audit log, passphrase,
background service and Studio (amendment A17):
- `--instance <name>` on every command, or `AIVYX_PA_INSTANCE`.
- `aivyx-pa instances list | create <name> | remove <name>`; `create`
picks a free Studio port from 7844 and runs setup for the new instance;
`remove` refuses the default instance, a running one, or one with an
installed service, and asks for the name to be typed back.
- Each Studio shows its instance name in the tab title; `daemon status`
prints it.
- The default instance keeps every existing path, so current installs
are unchanged. The desktop app and chat channels still use the default
instance.
## [0.14.8] — 2026-10-05
### Security
- `xxhash-rust` bumped to 0.8.19 (Dependabot alert: its safe xxh3
custom-secret API accepted a too-short secret in release builds).
## [0.14.7] — 2026-10-05
### Changed
- Model routing decides local or cloud by address, not just by kind
(`aivyx-route` bumped to `d387a52`):
- A `[routing.endpoints.*]` entry of a non-cloud kind (`openai_compat`,
`ollama`, `llama_router`, `lemonade`) at a hosted address — e.g.
`https://api.groq.com/openai/v1` — or an `openai_compat` one with no
`base_url` now counts
as cloud: it is rejected under `[routing.escalation] mode = "never"`
(the error says why), turns escalation on otherwise, and is reachable
only through the escalation gate, never as an ordinary candidate.
Previously it counted as local and bypassed `mode = "never"` and the
sensitivity taint. Mark a box on your own network with a
public-looking name `locality = "local"`.
- The `[agent]` backend is judged by its real address (the configured
`base_url`, `[broker] base_url` for the broker, else the provider's
default): an Ollama/llama.cpp/Jan/Lemonade/broker backend on a public
host counts as cloud, so cloud-tagged roster entries on it may be
chosen, and `routing` warns at startup. `provider = "openai"` with
any local `base_url` (not just loopback) counts as a local
OpenAI-compatible server; mistral.rs is always local.
- `aivyx-pa doctor`, `aivyx-pa init` and residency polling still read
your own `[agent]` backend whatever its address. Discovery now runs
concurrently under a 15 s deadline, and never probes or polls a cloud
endpoint.
- `aivyx-pa federation yubikey-init` (behind the optional `yubikey`
feature) is safer against hardware mistakes (`aivyx-yubi` bumped to
`fd93451`):
- Lists every attached YubiKey (serial + Signature-slot occupancy)
before touching any of them; with more than one attached, a new
`--card <serial>` option is now required to pick which one.
- Refuses to overwrite a Signature slot that already holds a key
(e.g. a real GPG signing key) unless a new
`--overwrite-existing-key` flag is passed, and even then requires
typing the card's serial back at an interactive prompt to confirm
— previously this command silently destroyed an existing key on
every run.
- Surfaces a reduced PIN retry counter (found before this command
touched the card at all) with guidance to check `gpg --card-status`
first, instead of the (wrong, in that case) factory-default-PIN
instructions.
- The closing post-provisioning verification pass now prompts for the
real User PIN (hidden) instead of constructing a throwaway signer
with an empty one, which the bumped `aivyx-yubi` now rejects
outright at construction.
- `aivyx-vision` bumped to `531aa7d`: generated SVG loses all CSS and
every reference that doesn't point inside the document, input over
256 KiB or nested deeper than 64 elements is rejected, and the output
is no longer indented.
### Security
- `shell.exec` and `git.*` run under a stricter sandbox (`aivyx-confine`
bumped to `671e0bd`, which fixes the 2026-10-04 subsystem-audit
findings). What a confined command notices:
- **No local daemons.** Unix sockets can't be opened and the session
IPC variables are removed, so ssh-agent (`git push` over ssh),
`git commit -S`, `docker`, `psql`/`mysql` over their default
sockets, git credential-cache/libsecret helpers and `systemctl
--user` fail. Previously the D-Bus session bus (`systemd-run
--user`) let a confined command run code outside the sandbox.
- **Private `TMPDIR`.** Each tool gets its own temp directory,
exported as `TMPDIR`; the shared `/tmp` is no longer writable, and
tools that hard-code `/tmp` fail.
- **No leaving the process group.** `setsid`/`setpgid` fail, which
breaks the `setsid` tool, interactive job control, Python
`start_new_session=True` and test runners that use per-test process
groups. Git's background auto-maintenance prints `fatal: setsid
failed` and skips that run; the git command itself still succeeds.
- **Carved directories.** Under the `home`/`full` access levels, when
`~/.cargo/credentials(.toml)`, `~/.config/git/credentials` or
`~/.git-credentials` (git's default credential store) exists
(now always unreadable), `rm`/`mv` of entries directly in `$HOME`
(and the other directories on the path to that file) fail, and a
file created there can't be written until the next call. Deeper
subdirectories are unaffected. `aivyx-pa doctor` and `aivyx-pa
access show` say when this applies.
- Namespaces (`unshare`, `bwrap`, rootless containers) and signals to
processes outside the call are blocked.
- Everything a `shell.exec` or `git.*` call starts is killed when the
command exits, errors, times out or the turn is cancelled. A
background job (`cmd &`, a git hook's detached child) no longer
outlives the call, and one still holding the output pipe no longer
stalls the call until its timeout. On Linux nothing can leave the
group; on other platforms (no sandbox backend) a process that calls
`setsid` itself escapes the kill.
- `git.*` commands (and so git hooks) no longer inherit the daemon's
environment: like `shell.exec`, they get only `PATH`, `HOME`, `USER`,
`LANG` and `TERM`. A hook planted in a configured repo could read
`AIVYX_PA_PASSPHRASE` and provider API keys. The operator's
`GIT_DIR`/`GIT_CONFIG_*` variables no longer apply to `git.*` either.
- New `[confine]` opt-outs, all default `false`: `allow_unix_sockets`,
`allow_leaving_process_group` and `share_system_tmp` (see
`examples/aivyx-pa.toml`). With `allow_unix_sockets` on, the sandbox
no longer contains code execution.
- `git.status`, `git.diff` and `git.commit` refuse a repo whose `.git` is
a file (a linked worktree or submodule) instead of running git
unconfined for it. A confined `shell.exec` in the repo could write such
a file pointing at a gitdir it planted, so the next `git.*` call ran
that gitdir's hooks outside the sandbox. The daemon warns about such
entries at startup.
## [0.14.6] — 2026-10-05
### Fixed
- The prompt-injection check (`aivyx-injection-guard` `a0b0e7a`) now
catches phrases broken up by line breaks, extra spaces, JSON escapes or
invisible characters, and scans the whole of a fetched page or file
instead of only its first 64 KB.
- Ordinary text such as "you are now ready" or licence wording no longer
trips it.
## [0.14.5] — 2026-10-04
### Fixed
- Checkpointing no longer switches itself off for good after a cancelled
or timed-out snapshot: the stale lock git leaves on the checkpointer's
private index is cleared on the next run (`aivyx-checkpoint` `0711690`).
- Files matched by a bare deny pattern (`.env`, `.env.*`, `*.pem`, …) are
now excluded from checkpoint snapshots and left alone by a restore.
- A git configuration check that fails for a real reason now stops the git
command instead of letting it run without its usual protections.
- Skill files (`SKILL.md`) are read only when they are regular files, up to
a size cap, so an unusual file can no longer stall or exhaust memory
(`aivyx-skills` `4ee706e`).
## [0.14.4] — 2026-10-03
### Security
- Checkpoint snapshots and `/undo`-style restores no longer run any program
a project's own git configuration names. The vendored `aivyx-checkpoint`
dependency (bumped to `b3aef65`) now runs its git commands with
repository-configured `clean`/`smudge`/`process` filters switched off,
hooks disabled (including `reference-transaction`, which `update-ref`
fires), fsmonitor off and commit signing off. Filters configured in your
own global git config (such as git-lfs) still apply.
## [0.14.3] — 2026-10-02
### Security
- No tool can write (or delete) inside a `.git` directory any more —
`fs.write`, `fs.delete`, `workspace.write`, `workspace.delete`,
`workspace.note`, `data.xlsx.write`, and `data.pdf.write` all refuse a
`.git`-component path unconditionally, before any confirm-first logic.
This closes a path to redefining what `git` itself runs on a later
command (`core.fsmonitor`, a `filter.*.clean`/`.smudge` entry, a planted
hook) via the checkpointer's own unconfined `git add -A`. The `git.*`
tools additionally run every git invocation with `-c
core.fsmonitor=false` (plus `--no-ext-diff --no-textconv` on
`git.diff`), and the vendored `aivyx-checkpoint` dependency is bumped to
pick up the same hardening in its own `git add`.
## [0.14.2] — 2026-10-02
### Fixed
- Builds cleanly with Rust 1.99 (`async-trait` 0.1.92). The v0.14.1 tag's
release build failed on Rust 1.99's new `double_must_use` lint, so no
v0.14.1 CLI release was published; v0.14.2 carries all of v0.14.1's changes.
## [0.14.1] — 2026-10-02
### Changed
- Command Center briefing polls are cheaper: the daemon caches the audit
walk across repeated `GetBriefing` polls, and the operator-activity clock
persists to disk only when it meaningfully changes, not on every action.
### Fixed
- A digest routine that fell back to a plain mission (no digest builder
configured) no longer counts twice in the Command Center log.
- The 24 h spend instrument now says when it's a lower bound: if any model
used has no known price, Studio shows `$0.42+ · 24 h` instead of a total
that looks exact but isn't.
## [0.14.0] — 2026-10-01
### Changed
- **Studio Command Center is now a logbook.** One readable column instead of stat cards: a ruled instrument strip (daemon, model, 24 h spend against your day budget, memory, audit chain), what needs you (mission and team approvals, pending proposals, failed routines and notifications, reminders coming up in the next 2 hours — with Approve/Deny/Done/Snooze right there), a first-person log of what your assistant did since you were last here, and what's coming up. Written from the record with fixed wording — no model involved.
### Added
- **The `manual` autonomy level does what it says.** Every tool call that can
change something — a note, a file, a command, a message — asks you first
(Studio card, terminal prompt; reply "yes" in chat apps); looking things up
doesn't. Scheduled routines aren't affected. It used to behave like
`assisted`.
- **IPC:** `GetBriefing`, `CompleteReminder`, `SnoozeReminder` queries (see `docs/DAEMON_IPC.md`); the daemon remembers your last activity across restarts.
## [0.13.0] — 2026-10-01
### Added
- **Approve or deny, and the reply carries on.** When the assistant wants to do
something that can't be undone — delete or overwrite a file, commit, send an
email, place an order — the terminal chat asks `Approve? [y/N]` and the
Studio shows an *Approval needed* card. Approve and it does exactly what it
showed you, then continues; deny (or no answer in 10 minutes) and it tells
you it didn't. Chat apps keep "reply yes to approve". The assistant can never
approve for you: the confirmation flag is set by Aivyx PA, not the model.
### Fixed
- **Ctrl-C didn't cancel a reply through the daemon.** The daemon didn't read
the connection while a turn ran, and the terminal's channel ignored the
cancel anyway; both fixed, so Ctrl-C stops a reply at once.
## [0.12.2] — 2026-10-01
### Fixed
- **Integrations couldn't act from chat once confirm-first was on** (the
default since 0.12.1): every email send, calendar or Drive write escalated,
and plain chat has no approve-and-continue step. Your reply is now the
approval — the first call asks, and in your next message that tool may run
once. Unattended runs never get it.
- **The in-process chat ignored `[budget]` and `[rate_limit]` caps**, as did the
in-process Telegram, Discord and Slack fallbacks; they apply everywhere now.
- **The spreadsheet and PDF writers replaced files unasked** on the model's own
`overwrite: true`; with confirm-first on, replacing asks you first.
- **The schedule tool told the model crons run in UTC** — they run in your
local time — and gave no weekday guidance (numbered days count from
Sunday = 1). It says both now.
- **Hugging Face models pulled through Ollama** (`hf.co/Qwen/…`) lost their
prompt strategy; family detection reads them now.
- **A failed macOS service install left the passphrase plist behind**; it's
removed, like the Linux path.
- **The autonomy options overstated what they do.** The Studio's labels and a
new *Autonomy* section in the guide describe what each level changes today.
### Changed
- **`git.commit` and out-of-process confirm-first tools (like
`kitchen.order.send`) follow the same rule as file deletes:** the model's
`confirmed: true` only counts in a turn after the tool refused that same
commit or call, so the operator's reply is what approves it. For a
bridged tool the check happens on the daemon side, which knows the real
conversation.
## [0.12.1] — 2026-09-30
### Changed
- **Deleting or overwriting a file always asks you first — and it has to be
you.** Confirm-first (`[access] confirm_destructive`) was off at the
default `sandbox` level, where the files you ask for now live, and even
where it was on the model could pass `confirmed: true` on its first try.
It now defaults on at every access level (the autonomy level decides;
only `unleashed` turns it off, and an explicit setting still wins), and
for `fs.delete` and overwrites `confirmed: true` only counts in a turn
after the tool refused that same file — so the assistant has to ask and
wait for your reply.
## [0.12.0] — 2026-09-30
### Changed
- **`aivyx-pa` finds its config from any directory.** `init` writes
`~/.config/aivyx-pa/aivyx-pa.toml`, and every command looks there. A
`./aivyx-pa.toml` in the current directory still takes precedence, so an
existing setup keeps working unchanged; `AIVYX_PA_CONFIG_PATH` points
anywhere else. Before, a config written in one folder was "not set up"
from every other, including for the desktop app.
- **`init` sets up the passphrase.** It asks for the passphrase that
encrypts your store (twice) and keeps it in the OS keyring, so the daemon
and the Studio start on their own from the first launch. Without a
keyring it offers (default yes) to save it in
`~/.config/aivyx-pa/daemon.env`, readable only by you — the file the
background service already used — and `aivyx-pa`, the daemon it starts
and `daemon install` all read it after the keyring. A file other users
can read is refused. Decline, and aivyx-pa asks each time it starts.
- **`init` offers the background service only where it can work** (a
systemd user session, or macOS), and ends by checking that your model
answers — after the last question, not before the passphrase.
- **Every model is told where files live.** The note on the `fs.*` root
(your files) versus the assistant's own notebook (`workspace.*`) only
reached Ollama models; Lemonade, llama.cpp, Jan, broker and cloud models
never got it, so a file you asked for could land in the notebook where
`fs.*` couldn't find it, and the assistant couldn't say where it was.
- **The Studio welcomes a newcomer.** The Command Center shows a start-here
card until anything has run; an empty Chat greets you by the assistant's
name with example first messages; routines read "every day at 07:00"
instead of cron; the audit feed says "Model usage", not "LlmCost".
- **`aivyx-pa connect` names the Google steps that gate sign-in** (the
consent screen, adding yourself as a test user) and no longer asks for a
redirect URI a Desktop-app client doesn't have. A blank Client ID
cancels.
- **The installer ends with the next step** (`aivyx-pa init`), and the
README's quick start is installer → `init` → chat. A new guide page,
*Chat apps & accounts*, covers Telegram, Discord, Slack and Google.
- **`init` asks before turning on background routines**, listing them in
plain words (default yes). Declined routines are still written, disabled.
- **Quieter first launch.** An interactive chat no longer prints the
config-provenance block (the daemon's log and `aivyx-pa doctor` still
have it), and the keyring and persona-seed notices read plainly.
### Fixed
- **The desktop app couldn't start on its own.** Its `.deb` (and `.app`)
contained only the window, not the `aivyx-pa` daemon it runs, and when the
daemon failed the window showed a bare connection error. The packages now
include the daemon (the app prefers the one beside it, so the versions
match), and a failed start shows a page with what the daemon said, how to
fix it (usually `aivyx-pa init`), and a **Try again** button. The daemon's
output goes to `~/.local/state/aivyx-pa/desktop-daemon.log`.
- **Before any setup, `aivyx-pa` said `anthropic_api_key` was missing**, as
if Anthropic were required. It now says aivyx-pa isn't set up yet and
where it looked (`aivyx-pa: not set up yet — there's no config at …`).
- **`init`'s service install defaulted to yes, then failed** on an empty
passphrase in the middle of the "Next steps" list. It reuses the
passphrase you just chose, and the next steps come last, in one list.
- **A reply ran into the next prompt** in daemon-mode chat (`84.> `).
- **`init` wrote the config readable by others for a moment** before
tightening it to `0600`; it's created `0600` from the start.
- **Chat without the daemon forgot the conversation.** In-process chat (a
first run without a keyring, or `--no-daemon`) started every message
from scratch; it now replays recent messages like the daemon does
(`[agent] conversation_history_turns`).
- **A failed service install left files behind** — a unit file and a copy
of the passphrase. `daemon install` now checks for a systemd user
session before writing anything, and removes what it wrote if a later
step fails.
- **The weekly digest ran on Sundays.** `init` promised Mondays but wrote
cron day `1`, which the scheduler counts as Sunday; it's `Mon` now.
Existing configs keep `1`, and the Studio labels it honestly.
- **Before `init`, `doctor` reported a made-up Anthropic setup** and
`studio` said to start the daemon; both now say aivyx-pa isn't set up.
- **Smaller first-run rough edges:** the identity preview read "Never
never …"; drafted names could be built from tasks ("EmailEva"); a
launch line cited `[persona_seed]` when you'd declined seeding;
diagnostic lines (skill injection, tool counts) printed into the chat.
## [0.11.0] — 2026-09-30
### Changed
- **Cloud-consent requests read as a request, not an error.** Instead of
"LLM error: model routing: this needs a cloud model: `m@e` (tier)…",
the assistant says which model, why, and roughly how many tokens would
be sent, worded for where you are: send `/allow-cloud` in the terminal,
press **Allow cloud for this conversation** on the Studio's new consent
card, or — in Telegram, Discord and Slack, which can't grant it — allow
it from the terminal or the Studio. Those chat apps no longer show
"(no reply)" in front of it.
- **The Studio is on by default, behind a sign-in token.** The daemon now
serves the Studio at `http://127.0.0.1:7843` without any `[daemon]`
setting. Loopback is reachable by every account on the machine, so
unless you set `web_ui_auth_token` (or `web_ui_insecure_no_auth`), the
daemon creates a random token in `studio-token` next to the store
(0600, reused; delete it to rotate), and the agent can't read it (Ward).
Sign in once with the link from `aivyx-pa studio`; `aivyx-pa daemon run`
shows it too when started in a terminal (never in the journal or
`docker logs`), and so does `aivyx-pa doctor`. **To opt out:
`[daemon] web_ui = false`**, which now also wins over an explicit
`web_ui_port`. Side effects of the default: the in-memory "studio"
notify target now exists on every install that has no default target,
and a config that already set `web_ui_host` beyond loopback with
`web_ui_insecure_no_auth = true` but never enabled the Studio now
serves it (both keys were signed; check that's intended).
- **`aivyx-pa` starts the daemon for you.** An interactive `aivyx-pa`
with no daemon running now starts one in the background (log next to
the socket) and connects, when the passphrase is available without a
prompt (the OS keyring, `AIVYX_PA_PASSPHRASE`, or the TOML). Otherwise
it chats in-process and says why — and what's unavailable. Piped input
and `--no-daemon` behave as before; `--provider`, `--mcp-server` and
`--mcp-sse` keep a session in-process, since they apply to it alone.
Daemons started this way (and by `aivyx-pa tui`) now run in their own
process group, so ctrl-C or closing the terminal no longer kills them.
- **`aivyx-pa init` detects every local runtime and never defaults to a
cloud provider.** It probes Ollama, Lemonade, llama.cpp, Jan and the
broker, lists the ones running first, and offers their models; with
nothing found it asks you to choose (no pre-selected answer, unless
`--template` names a local provider). Every
provider is listed, and the separate "Also serve the Studio web UI?"
question is gone.
### Added
- **Model routing is visible everywhere, not just in the CLI.**
- The Studio's status bar shows the model that answered the last
message (hover for why), and a new **Models** screen lists the
candidates, what's loaded, VRAM, and this conversation's model with a
pin control.
- `/models`, `/models why`, `/models refresh`, `/model <id>` and
`/model auto` work in chat — the REPL, terminal UI, Studio and chat
apps — with the same wording as aivyx-coder. (Chat without the daemon
isn't routed, so there they say so.)
- The terminal UI shows the routed model in its status bar; the REPL
prints `routing → <model> (<why>)` when it changes.
- New IPC: `ModelRouted` and `CloudConsentRequested` stream events,
`GetRoutingStatus` and `SetRoutingPin` queries (see
`docs/DAEMON_IPC.md`). Adapters that skip unknown event kinds are
unaffected.
- A new guide page, "Models & routing".
- **Real help.** `aivyx-pa --help` (or `-h`, `help`) lists every command;
`aivyx-pa <command> --help` shows that command's usage; and an
unrecognized argument points at the right help instead of a stale list.
- **`aivyx-pa studio [--token]`** prints the Studio sign-in link (or just
the token). The desktop app uses it to sign in by itself.
- **`aivyx-pa doctor` checks local broker and mistral.rs setups.** With
`provider = "broker"` it checks that `aivyx-broker` answers and that the
llama-server behind it is up (a running broker in front of a stopped
llama-server is reported as such). With `provider = "mistralrs"` it
checks that the binary was built with mistral.rs support and that the
configured GGUF exists, without loading it. Both previously fell into
the cloud check and printed "an API key is configured".
### Fixed
- **`aivyx-pa --headless` treated a cloud-consent stop as success (exit
0)** — nothing had run, but a cron or batch caller saw a completed
turn. It now exits 3, the "refused" code, and a one-shot run explains
how to give consent (in chat or the Studio, or by piping `/allow-cloud`
and the task into one `--headless` conversation).
- **The Studio's access-level menu always said the sandbox is
`~/aivyx-pa-sandbox`**, even when `[fs] root` or `AIVYX_PA_FS_ROOT`
moved it. It now shows the real folder while sandbox is the current
level, and plain words otherwise.
- **An interactive `aivyx-pa` could never use a running daemon** — it
opened the store first and hit the daemon's lock. The daemon decision
now comes before the store is opened.
- **`/allow-cloud` in a session without the daemon was sent to the
model** as an ordinary message. It now gets a local reply explaining
that cloud escalation needs the daemon, and such sessions warn at
startup when routing has a cloud endpoint.
- **`provider = "jan"` sent every request to `/v1/v1/chat/completions`**:
its default address (and the documented override) ended in `/v1`, which
the OpenAI provider adds itself. The default is now
`http://localhost:1337`, and a trailing `/v1` is dropped.
- **The desktop app's approval watcher printed long runs of spaces** in
its two "401" messages.
- **The release pipeline can no longer publish a release without its CLI
binaries.** cargo-dist's generated `host` job treated skipped build
jobs as fine and didn't wait for the quality gate, so a dropped plan
output (what hit 0.10.0) or a failing quality gate still published a
green, binary-less release. `host` now depends on the quality gate and
fails, before uploading anything, unless the gate and every build
succeeded. `release.yml` is hand-edited for this, so
`dist-workspace.toml` sets `allow-dirty = ["ci"]` (regenerate and
re-apply the guard when upgrading cargo-dist).
## [0.10.1] — 2026-09-28
Republishes 0.10.0 with its CLI binaries and shell installer; no code
changes. The 0.10.0 release published only the desktop, WSL and Docker
builds: a literal bearer-token header example in its release notes
looked like a credential to GitHub Actions, which silently dropped the
release plan, so the CLI build jobs were skipped. See 0.10.0 below for
everything in this release.
## [0.10.0] — 2026-09-28
**The product is now Aivyx PA** — a breaking rename of the binary, config
file and data paths (see Changed for the manual migration). Also in this
release: opt-in per-call **model routing** over the shared `aivyx-route`
crate (discovery, residency, consent-gated cloud escalation, a tier
classifier), new local providers (**Lemonade Server**, **aivyx-broker**),
a **2026-09-16 security audit**'s fixes (three rated HIGH — upgrade
promptly), a shared default skill library, local generation tools
(`vision.*`), and YubiKey-backed federation identity.
### Changed
- **The product is now Aivyx PA. BREAKING: binary, config file, and data
paths all renamed, with no automatic migration.** The CLI binary is
`aivyx-pa` (previously `aivyx`); the config file the CLI looks for by
default is `aivyx-pa.toml` (previously `aivyx.toml`), and its
passphrase section is now `[aivyx_pa]` (previously `[aivyx]`); the
config/data directories moved from `~/.config/aivyx/` and
`~/.local/share/aivyx/` to `~/.config/aivyx-pa/` and
`~/.local/share/aivyx-pa/`; the `AIVYX_*` environment-variable prefix
is now `AIVYX_PA_*`; and the OS-keyring service name used to store the
passphrase moved from `aivyx` to `aivyx-pa`. Nothing above is copied,
renamed, or migrated automatically — an existing install that wants to
keep its data has to move it by hand before running the new binary:
```sh
mv ~/.config/aivyx ~/.config/aivyx-pa
mv ~/.local/share/aivyx ~/.local/share/aivyx-pa
mv aivyx.toml aivyx-pa.toml # if using a CWD-relative config
```
then edit the moved config's `[aivyx]` section header to `[aivyx_pa]`,
rename any `AIVYX_*` environment variables it relies on to
`AIVYX_PA_*`, re-enter the passphrase once if it was stored in the OS
keyring (a fresh `aivyx-pa` lookup under the new service name won't
find an entry saved under the old one), and update the binary name in
any systemd/launchd unit, shell alias, or script that invokes it. The
passphrase itself and the encrypted store's contents are unaffected —
this is a path/name rename only, not a re-encryption.
- **The Studio web UI moves to the Wick & Compass identity** — new
palette, Fraunces + IBM Plex Sans/Mono type, and the finished brand
marks; glass/blur surfaces retired.
- **The shared crates `aivyx-pa` depends on** (`aivyx-confine`,
`aivyx-checkpoint`, `aivyx-kvcache`, `aivyx-recall`,
`aivyx-injection-guard`, …) **are now BUSL-1.1**, matching `aivyx-pa`'s
own license; pins bumped to their relicense commits.
### Added
- **Lemonade Server as a provider and routing endpoint.** `[agent]
provider = "lemonade"` targets a local Lemonade Server at
`http://127.0.0.1:13305/api` by default (override with `[openai]
base_url`, keeping the `/api` suffix), keyless unless `[openai]
api_key` is set, with streamed token usage. `[routing.endpoints.*]
kind = "lemonade"` adds one as a routing endpoint (aivyx-route
af1b748): discovery lists its downloaded models
from `/v1/models`, mapping its labels onto capabilities, and residency
reads `/v1/health` to see which model is loaded. Lemonade holds one LLM
at a time, so the endpoint is never assumed resident, whether it is
the default endpoint or an extra one.
- **Lemonade hardening (aivyx-route 5cdc111).** A model Lemonade labels
`reasoning` is now known to think, so the routing classifier avoids it
without a roster entry (an unlabelled model may still think: declare
`capabilities = ["thinking"]` for those). A Lemonade base ending in
`/v1` is tolerated by discovery and residency. `aivyx-pa doctor` with
`provider = "lemonade"` now checks the server is reachable and the
model is downloaded, and says which model is loaded, instead of the
cloud "an API key is configured" note.
- **The routing classifier avoids thinking models.** A reasoning model
spends the classifier's few tokens thinking and never answers, so it
always fell back to medium. Classification now prefers a model not
known to think (aivyx-route 4ff5b7f), still using one when nothing else
qualifies. Declare `capabilities = ["thinking"]` in `[[routing.models]]`
for a thinking model the backend doesn't report.
- **Local OpenAI-compatible backends now report token usage.** Keyless
providers (`llamacpp`, `jan`, `broker`, and routed OpenAI-compatible or
llama-server endpoints) never asked for streamed usage, so every local
turn was billed as zero tokens: the cost ledger, `[budget]` caps and the
routing classifier's billing all saw nothing. They now request it
(`stream_options.include_usage`), which llama.cpp-family servers answer
in a final chunk. Found in a live run against Lemonade Server.
- **Model routing — the routing classifier is wired in.** New
`[routing.classifier]` (`enabled = false`, `timeout_ms = 2000`). When
on, a chat conversation with no pinned or sticky model yet is
classified at most once per conversation, and only when a local model
can serve it: a side call (`TaskKind::Classify`) asks whether it
needs a small/medium/large model, bounded by `timeout_ms`; the answer
becomes its soft tier, remembered for the conversation's later
unstuck turns, and any failure, timeout, or unparseable answer falls
back to medium. The side call runs on the model `[routing.tasks]
classify` picks among the configured candidates: a small local model
when there is one, but with a cloud `[agent]` provider it can be that
provider's model. No new destination either way.
`aivyx-pa routing status` and the `routing.status` tool now show
whether it's on. Off by default: nothing changes without it. The
classifier's own call is now billed too — its tokens are added to the
turn's cost under its own model, alongside the main call's, instead of
being silently thrown away. See `examples/aivyx-pa.toml`.
- **Model routing (Part 3b) — `on_failure` escalation (Amendment
A16).** New `[routing.escalation] on_failure` (default `false`). When
on, a local turn that got stuck — `Looping`, or both tool-call repair
rounds spent — arms that conversation's **next turn only** for cloud
escalation, under the same gate as every other escalation: never a
replay, never a mid-turn model switch, and a tainted conversation
still never escalates. In `ask` mode the stuck turn ends with "The
local model got stuck; send /allow-cloud and resend to retry on
`<model>`." An ignored offer lapses: a turn that stops for consent
leaves a pending offer that `/allow-cloud` arms for the resend, and
the next turn sent without it runs locally. Only the operator's own
turns arm; trigger, cron, webhook and file-watch fires never do. The
autonomous loop arms only with `mode = "auto"`: a
Verdict FAIL (the completion judge rejecting a left-pending story)
arms the next iteration, and a run the stall breaker would end gets
exactly one armed rescue iteration first (also after a stall on a
Foreman-delegated story: the rescue always runs as a solo turn, since
a delegation can't carry the arm, and a delegation drops any pending
Verdict FAIL arm); a second stall ends it,
with "(after one on_failure rescue)" on the stop reason. New
`EscalationArmed` audit entry per new mark; the escalation itself is
a `CloudEscalation` with trigger `on_failure`. Marks live in memory
only. With `on_failure = false`, nothing changes. See
`examples/aivyx-pa.toml` and `docs/THREAT_MODEL.md` §4.12.
- **Model routing (Part 4) — routing prefers loaded models.** A
background poll, every 5 s, reads which models are already resident:
Ollama's `/api/ps`, llama-server's `/models` (router mode), or
aivyx-broker; the `default` endpoint's own model counts as loaded for
free on a single-model server (llama-server, mistral.rs, or a broker
fronting one). New `[routing] vram_bytes` adds host GPU memory when
nothing else reports it. Residency is a soft signal — worth at most
one tier-step, never a hard filter — so a model that needs a load can
still win on capability/tier alone. `routing.status` and `aivyx-pa
routing status` now show it: total/available VRAM, which endpoints
are fully resident, and each candidate's load note (`loaded` /
`needs_load` / `wont_fit` / unknown). No config required to keep
today's behavior: without a source, residency has no opinion and
ranking is unchanged. See `examples/aivyx-pa.toml`.
- **Model routing (Part 3b) — consent-gated cloud escalation
(Amendment A15).** Off unless a `[routing.endpoints.*]` entry has
`kind = "anthropic"` or `"openai"`. Then a conversation whose call no
local model can serve (`no_local_candidate`), or whose task kind is in
`[routing.escalation] tiers`, may go to that cloud model under
`mode = "ask"` (the turn stops and names the model; send `/allow-cloud`
from a Trusted channel or run `aivyx-pa routing allow-cloud <session>`,
then resend — the grant lasts until the daemon restarts), `"auto"`, or
`"never"`. A conversation that has touched sensitive data — a
`[routing.sensitive] tool_prefixes` tool's output or error (by default
every tool returning the operator's own data: mail, files, repos,
shell, screen, history, finances, health, tasks, memory), memory
recall, a sensitive channel — is tainted permanently (persisted) and
never escalates, in any mode. Calls outside a conversation never escalate. Every decision
is a `CloudEscalation` audit entry carrying a payload hash, never
content; also `CloudConsentGranted` and `ConversationTainted`.
`routing.status` and `aivyx-pa routing status|explain` show it. New
`KeyDomain::RoutingTaint`. See `examples/aivyx-pa.toml` and
`docs/THREAT_MODEL.md` §4.12.
- **Model routing (Part 3a) — an optional `[routing]` section picks a
model per call.** Off by default: without `[routing]` (or with
`enabled = false`) nothing changes. With `enabled = true`, a
`RoutedProvider` (over the shared `aivyx-route` router, the same
`[routing]` shape `aivyx-coder` reads) chooses among the `[agent]`
model (the reserved `default` endpoint), the `[[routing.models]]`
roster, and models discovered on local `[routing.endpoints.*]`
(Ollama, llama-server router mode, OpenAI-compatible). Routed calls:
daemon conversation turns (`chat`, sticky per conversation), the
completion judge and — when `judge_model` is unset — the auto-proposer
judge (`judge`), and team mission planning (`plan`); every other call
uses the `[agent]` model unchanged. No new cloud destinations: cloud
endpoint kinds are rejected at startup, and cloud candidates are
allowed only on a cloud `[agent]` provider's own endpoint. Connection
errors, unknown models and HTTP 404/408/5xx cool a model down for 60 s
and fall back to the next candidate. New read-only, Trusted-tier tools
`routing.status` / `routing.explain` (registered only when routing is
on; in the default role's backcompat floor), new offline `aivyx-pa routing status` and `aivyx-pa routing
explain [--limit N]` commands, a `ModelRouted` audit entry per successfully
routed call, and `LlmCost` recorded per model actually used. A request
carrying a PDF is never routed (it goes to the `[agent]` model), and
models on routed Ollama endpoints are assumed to have the window
aivyx-pa serves them with — `num_ctx = min(trained window, 16384)`; a
roster `context_window` can only lower that. Declare your
`[agent]` model's `capabilities` and `context_window` in
`[[routing.models]]`, or it ranks below discovered tool-capable models
(startup warns). See `examples/aivyx-pa.toml` for the full reference.
- **A shared default skill library (Aivyx-Skills Part 3) — two new
read-only tools plus a `## Default skills` system-prompt section.**
`skill_defaults.list` / `skill_defaults.read` expose the 5 skills
bundled in the new `aivyx-skills` crate (e.g. `systematic-debugging`,
`writing-plans`) — the same small, shared `SKILL.md`-format library
crate adopted by `aivyx-coder`, distinct from the agent's own
persona-chain `LearnedSkill`/`skills.*` system. Both tools are
Trusted-tier and included in the zero-config default role's
capability floor (read-only over a server-side-fixed set, no
model-supplied path). A new, optional `[skill_defaults]` config
section lets an operator layer `project_dir`/`user_dir` overlay
directories on top of the bundled defaults (project overrides user
overrides bundled, per same-named skill) — see `examples/aivyx-pa.toml`
for the required `<dir>/<skill-name>/SKILL.md` directory shape.
- **`aivyx federation yubikey-init` — hardware-backed federation identity
provisioning (Chapter Passport, Task 8).** Discovers a connected
YubiKey's OpenPGP card, refuses to proceed on a still-factory-default
PIN, generates an on-card Ed25519 keypair in the Signature slot, sets
its touch-policy to fixed (physical touch required on every future
signature), and writes the resulting `{instance_id, card_serial,
public_key_base64}` binding record to an operator-chosen path. Off by
default (`cargo build -p aivyx-cli --features yubikey`) — the
underlying `aivyx-yubi`/`aivyx-federation` dependencies transitively
need `libpcsclite`/`pcscd` at build and run time; without the feature
the subcommand still parses but refuses with a clear rebuild message.
See `docs/INSTALL.md`'s "Hardware-backed federation identity (YubiKey)"
section and `docs/FEDERATION.md`.
- **`provider = "broker"` — run through `aivyx-broker`**, a loopback
daemon (default `http://127.0.0.1:8899`, `[broker] base_url`) that
coordinates llama-server KV-cache slots across several local processes
(e.g. `aivyx-pa` and `aivyx-coder`) sharing one server. Also accepted by
`--provider`, and team specialists pass their slot hints through it.
- **Local generation tools (Aivyx-Vision), in the `aivyx-vision` tool
process.** `vision.generate_svg` (LLM-generated, sanitized SVG),
`vision.generate_image` (local image generation via `mold serve`, with
a `[mold]` section in the tool process's own config), and
`vision.generate_3d` (registered but not implemented yet: it always
fails with a clear error). One new scope, `vision.generate`,
SemiTrusted-reachable. See `crates/aivyx-vision/README.md` and
`docs/TOOLS.md`.
- **`[kvcache] store_path`** (env `AIVYX_PA_KVCACHE_STORE_PATH`) makes the
KV-cache store's location configurable, and Ward now denies agent reads
of it (previously unprotected: its file names match none of Ward's
patterns).
- **`[agent] injection_scan_enabled` / `injection_scan_exempt`** tune
Picket's active prompt-injection scan (on by default; exempt named
tools). Bulwark's fencing is never affected by either.
- **CI now fails fast, by name, if a workspace git dependency ever
silently reverts to private.** `scripts/check-git-deps-public.sh`
(Phase 192) is wired into `quality-gate.yml` as an early step —
closes Chapter N (Release & Distribution Integrity). See
`docs/archive/phases/PHASE_197.md`.
### Security
Fixes from a full ecosystem security audit (2026-09-16). **HIGH** first:
- **Rampart (the SSRF guard) had two bypasses.** IPv4-mapped IPv6
addresses (`::ffff:169.254.169.254`, `::ffff:127.0.0.1`, …) were never
unwrapped, and Rampart's hand-rolled URL parser could see a different
host than the one reqwest connects to (a backslash in the authority, a
non-dotted IPv4 literal). Host parsing now uses the same WHATWG parser
reqwest does, and mapped addresses are classified as IPv4.
- **Connected integrations no longer auto-grant their write/send/delete
scopes.** A default `aivyx-pa connect gmail` granted unqualified
`email.send` with no confirmation. The zero-config floor now withholds
`email.write`, `email.send`, `drive.write`, `notion.write`,
`obsidian.write`, `n8n.write`, `contacts.write` and `calendar.write`
unless granted explicitly, and a tool process asking for escalation now
actually escalates (it used to surface as a plain tool failure).
**Grant those scopes in your role if a workflow relies on them.**
- **`[access] confirm_destructive` now takes effect.** The gate existed
but no production agent was built with it; every agent now honors it
(on by default at any access level above `sandbox`), so irreversible
operations ask for confirmation.
- **Webhook-triggered turns now require authentication and run at
`Untrusted`, not `Trusted`.** Part of the 2026-09-16 security-audit
fixes: previously any local process (or any webpage the operator
merely visited, via a CORS "simple request") could fire an agent turn
through the webhook listener with no shared secret, and that turn ran
at the same trust tier as a local CLI session. Every webhook now
carries a `secret` (generated once at creation, shown only then) that
callers must present as a bearer token in the `Authorization` header, and the
triggered turn runs at `Untrusted`. **If an existing webhook's prompt
relies on tools gated above `Untrusted`** (e.g. `fs.write`,
`shell.exec`), those tool calls will now fail their capability check
after upgrading — grant the narrower scope the webhook's task actually
needs via that role's `capability_scopes`, or rework the prompt,
rather than restoring blanket trust. Existing webhook records created
before this change have no `secret` on disk and, by design, an unset
secret never authorizes — re-create any such webhook to get a fresh
secret and keep it callable. The secret is also no longer shown in
LLM-visible tool output.
**MEDIUM:**
- **Telegram, Discord and Slack now authorize each sender.** All three
gave every sender SemiTrusted; an allowlisted sender now gets
SemiTrusted and anyone else Untrusted, as `docs/THREAT_MODEL.md`
specifies (Slack's `team_id` is now actually checked, daemon path
included).
- **`shell.exec` and `git.*` output is now fenced (Bulwark) and scanned
(Picket)** like other untrusted tool output.
- **Tool processes can no longer claim scopes the operator didn't
expect.** A new `[[tool_process]] expected_scopes` map rejects a
registered scope exceeding the configured one and, when non-empty,
refuses tool names it doesn't list. Tool processes also now start
with a scrubbed environment (proxy variables kept).
- **Local secrets and IPC are written safely.** The daemon's Unix socket
is bound at 0600 atomically (its directory at 0700); `config.toml` is
tightened to 0600 on load and written atomically; `daemon.env` is
written atomically at 0600, only after checking the OS keyring; and
the TOML passphrase is no longer copied in plaintext during load.
- **The audit chain now detects tail truncation** through a persisted
chain anchor (last sequence number + MAC); a corrupt anchor fails
closed instead of being ignored.
- **`toolkit.health_check` URLs are checked against Rampart.**
- **Pack bundles are bounded before signature verification:** entries
capped at 512 MiB, decompression at 20× the input, and manifest
`bin`/`team_config` paths validated against traversal.
- **The federation replay guard is hardened:** per-entry expiry instead
of a global flush (up to ~59 s of replayability), a 5 s future-skew
bound, and a 10,000-entry cap.
### Fixed
- **Telegram/Discord/Slack's standalone (`--no-daemon`) session paths now
honor the operator's `[agent]` config.** Previously these three
in-process-fallback paths constructed every agent via
`TurnSafety::default()`, silently ignoring `turn_timeout_secs`,
`cycle_detection`, `injection_scan_enabled`, and `injection_scan_exempt`
— including a configured `injection_scan_enabled = false`, which had no
effect on these paths. They now route through `TurnSafety::interactive(...)`
like every other agent construction site. Daemon-mode dispatch was
never affected (it never constructs agents client-side).
- **First-launch safety: running `aivyx` before `aivyx init` on a genuinely
fresh machine no longer silently creates a permanent, unconfirmed
encrypted store.** Previously a bare `aivyx` invocation with no config
anywhere would create the sandbox/storage directories, prompt once
(unconfirmed) for a brand-new passphrase, and open a real encrypted
store — all before config validation ever ran — so a later, real
`aivyx init` run could write a config pointing at that same orphaned
store under a different passphrase, failing to decrypt it with no
indication why. `run()` now validates first when no store exists yet,
offering to run the setup wizard inline (interactive) or failing
immediately with a clear `aivyx init` pointer (non-interactive) —
zero side effects either way. `aivyx init` also now warns and requires
confirmation before writing a config that points at a storage path
where a store already exists, and the first-time interactive
passphrase prompt for a brand-new store now requires confirm-reentry
(matching `aivyx keyring set`'s existing shape) instead of accepting
an unconfirmed, unrecoverable-if-mistyped entry.
## [0.9.4] — 2026-09-05
**Milestone: the first real, working GitHub Release since `v0.8.3`.**
Ships Chapter N's root-cause and release-cutting work (Phases 192-193,
opened this same day after discovering `v0.9.0` never actually
published — see `docs/archive/phases/PHASE_192.md` and `PHASE_193.md`;
the chapter's closing phase, 197, is the `[Unreleased]` entry above)
and closes Chapter Picket (the prompt-injection tripwire,
`PHASE_194.md`-`PHASE_196.md`) in one release.
### Added
- **Active prompt-injection tripwire (Chapter Picket).**
`aivyx-injection-guard`, a new shared crate extracted from
`aivyx-coder`'s existing production tripwire, scans untrusted tool
output for known injection phrasings and escalates the turn via the
existing `TurnOutcome::Escalated` → `ApprovalGate`/`HeadlessRefusal`
path — a side-channel signal checked after the tool's real outcome
is recorded, so a mutating tool's audit trail always reflects what
actually executed. Complements, doesn't replace, Bulwark's existing
passive `fence_untrusted_output` labeling. See `docs/THREAT_MODEL.md`
and `PHASE_196.md`.
### Fixed
- **`aivyx-confine`'s `require_enforcement` check incorrectly required
`RulesetStatus::FullyEnforced`.** GitHub's own hosted-runner kernel
only ever achieves `PartiallyEnforced` at the crate's hardcoded
Landlock ABI version, which made every release build fail its own
quality gate before reaching the real cross-compile work. Now only
`NotEnforced` fails closed.
- **`aivyx-confine`'s syscall blocklist referenced
`libc::SYS_kexec_file_load`, absent from libc's musl bindings for
aarch64/riscv64,** breaking the `aarch64-unknown-linux-musl` release
target's build.
- **The WSL release workflow's retry budget for pulling the appliance
base image (10 minutes) was too short for real-world timing;**
raised to 30 minutes to match the sibling wait-for-release retry
loop in the same workflow.
- **The three private git dependencies (`aivyx-confine`,
`aivyx-checkpoint`, `aivyx-kvcache`) that silently broke every
`v0.9.0` release workflow — and any outside contributor's
build-from-source path — are now public** (the CI regression guard
that enforces this going forward ships in `[Unreleased]` above,
Phase 197).
## [0.9.0] — 2026-09-03
**Milestone: the Interface Polish phase is complete — v0.9.0 cuts as
the capstone.** `docs/V09_PLAN.md`'s own stated rule ("v0.9.0 cuts as
the capstone when the polish backlog is empty") is now satisfied: all
8 rows of that plan are done. This release folds in the two months of
continuous work since `v0.8.3` that shipped without its own version
cut — **Gatehouse** (Studio remote auth: the refuse-to-bind interlock,
the `web_ui_insecure_no_auth` escape hatch, first-boot token
generation; `docs/GATEHOUSE.md`), **Freight** (signed pack bundles —
format core, the `aivyx pack` CLI, the Kitchen worked example;
`docs/FREIGHT.md`), the full **Vitrine** operator walkthrough across
all 15 Studio screens + the TUI + the desktop shell
(`docs/VITRINE.md`), and the resulting 8-sub-project polish backlog
(`docs/POLISH_WAVES.md`): a small backlog sweep, `/classic` retirement,
Repertoire completions, agent turn-quality fixes, Missions polish, a
UI modernization pass, config-write surface-area completions (memory
profile / embedding / proactive settings, reflection-schedule CRUD),
and tool/server call-stat observability. Also ships as part of this
release: **Chapter Mission Control** (live LEAD/specialist mission
graph, click-to-drill-in, gate approve/reject, abort, pause/resume —
see Added below) and **Phase 185's Terminal TUI foundation**
(`ratatui`/`crossterm` replacing the line-based REPL, plus Missions /
Dashboard / Audit / Tools panels; `docs/ROADMAP.md`'s Chapter I). Full
phase-by-phase detail lives in those docs, not reconstructed here.
### Added
- **KV-cache persistence for `llama-server` users (`provider =
"llama_cpp"`).** A fresh daemon process's first turn on a system
prompt + tool-def combination it has seen before can now skip
re-prefilling that stable prefix, restoring it from disk instead —
requires `llama-server` started with `--slot-save-path` pointed at
`<data_local_dir>/aivyx/kvcache/slots`; see `docs/INSTALL.md`'s
"KV-cache persistence" section. No effect on Ollama or Jan.
- **Pause and resume a running team mission (Chapter Mission Control).**
`aivyx team pause <id>` requests a graceful pause at the mission's next
wave boundary — unlike abort, this is resumable. `aivyx team resume
<id>` continues a paused mission from its preserved checkpoint.
- **A Mission Control screen (`aivyx-web`, Chapter Mission Control).** A
new "Mission Control" nav destination shows one active mission's
LEAD/specialist graph live — click a specialist to drill in (current
step, declared capability scopes, the NT-02 "inert" hint) — plus the
first UI controls for gate approve/reject, abort, and the new
pause/resume.
### Changed
- **`shell.exec` and the `git.rs` tools (`git.status`/`git.diff`/
`git.commit`) now confine every spawned command with Landlock +
seccomp-bpf, on by default.** Previously, at `[access] level =
"sandbox"`, `shell.exec` could read and execute anywhere on disk the
operator's OS permissions allowed, subject only to the string-level
`[access] guard_sensitive_paths` check — a command that got past that
guard had the full authority of the daemon's own OS user. Every
spawned child now additionally runs under kernel-level confinement
(`aivyx-confine`): filesystem writes are scoped to the tool's own
working-directory root (`shell.exec`'s `cwd_root` / the git repo being
operated on) plus a fixed system/toolchain read list, and a
seccomp-bpf denylist blocks a set of syscalls with no legitimate use
in a coding agent's spawned commands (`ptrace`, `mount`, `bpf`,
`perf_event_open`, and others). New `[confine] require_enforcement`
config key (default `true`) controls what happens if Landlock itself
fails to establish a ruleset on a given machine: fail-closed (refuse
to run the command, the default) or fail-open (run unconfined, for
operators on a kernel without Landlock support who still want
`shell.exec` usable). It does not provide a way to turn confinement
off when Landlock is working normally. Linux only — non-Linux builds
are unaffected (no confinement, same as before). See
`docs/THREAT_MODEL.md` §5.6/§6 and `docs/INSTALL.md`'s `[confine]`
section.
## [0.8.3] — 2026-07-08
### Fixed
- **Studio footer showed the wrong version number.** The WASM bundle
released in 0.8.2 was built before that version bump, so
`env!("CARGO_PKG_VERSION")` baked "v0.8.1" into the Studio's status bar
even though the daemon binary itself correctly reported 0.8.2. No
functional change — the bundle is rebuilt from the same 0.8.2 source,
now after the version bump.
## [0.8.2] — 2026-07-07
### Added
- **Studio Gallery — view images your assistant generates.** A new screen
renders images produced through a `comfyui`-named `[[mcp_server]]`
(e.g. the community `comfyui-mcp-server` bridge in front of a local
ComfyUI instance). The daemon reads ComfyUI's own `/history` API
directly and serves bytes to the browser through a new authenticated
`/studio-asset` proxy route — no MCP-call plumbing involved, and the
browser never needs to reach ComfyUI's own (loopback-only) port
directly. Each image shows the prompt that produced it (traced through
the submitted workflow graph, not guessed by node order) and when it
was made, newest first, with a click-to-enlarge view. The screen is a
no-op empty state when no `comfyui` server is configured.
## [0.8.1] — 2026-07-05
**The Vitrine harvest: one day of live operator walkthrough, fifteen-plus
bugs found, nearly all fixed same-day and re-proven on the rig.** The
first five walkthrough sections (Gatehouse baptism, Command Center, Chat,
Missions, Memory, Skills) drove every fix in this release; the standout
theme is honesty under pressure — silent failures now speak, stochastic
judges get deterministic backstops, and skills finally get used.
### Added
- **Conversation-history replay (Chapter Thread).** Interactive-session
turns now replay the session's recent user/assistant messages as real
conversation history, so follow-ups like "did you find the correct
code?" resolve against what was actually said. Default-on
(`[agent] conversation_history_turns = 8`; `0` restores fresh-context
turns exactly); trigger-fired turns are structurally unaffected, and
durable memory remains the persistence layer.
- **Skill trigger injection — skills are finally used.** Each turn, the
best trigger-matching approved skill's procedure is injected into
context alongside memory recall (embedding cosine with live-calibrated
0.50 floor; token-overlap fallback for embedding-free installs). Local
models never took the `skills.list`/`skills.invoke` indirection, so a
fresh agent's skills were dead weight. Verified end-to-end: teach →
use on the next turn with no restart; update → use;
`[skills] trigger_injection = false` opts out.
- **Deterministic identifier backstop in the completion judge.** When a
goal names uppercase identifiers (ICAO codes, tickers) and the
majority appear nowhere in the deliverable or evidence, the verdict
rejects before any LLM opinion — after a live mission's judge
hallucinated a PASS over a brief about entirely different airports.
- **Team specialists can use the operator's MCP servers.** The
researcher/analyst/verifier roles now receive qualified per-server
`mcp.call` grants and see bridged MCP tools; previously the whole team
was structurally MCP-blind and approximated one weather-tool call with
sixteen raw page fetches.
### Fixed
- **Web search reports backend refusals instead of silently returning
nothing.** DuckDuckGo answers bot-flagged traffic with HTTP 202 + a
challenge page; the zero-config backend parsed that to an empty result
set, and the model either went mute or invented answers.
- **Reflection proposals were scope-dead on a clean install** — the
default capability floor granted neither `reflection.propose` nor
`persona.propose`, so every organic proposal died with a denial.
- **Reflection no longer fires on every daemon restart** (the last-fired
anchor was epoch, not boot).
- **Clean systemd stops no longer report "unclean shutdown"** — the
daemon now handles SIGTERM like Ctrl-C.
- **The planner's context budget honors an explicit `[ollama] num_ctx`**
instead of an 8k class default, and **giant tool results are capped**
(~half the context window, with an explicit truncation marker) — one
raw page fetch used to become an un-prunable 30k-token message that
pushed the request past the real window and silently truncated the
system prompt server-side.
- **Pruning can no longer discard the turn's own question.** The task
message is pinned through context pruning; a fat tool turn used to
lose its task and reset to a greeting mid-mission.
- **Mission retries are bounded again.** The Reprise attempt counter is
persisted on the mission record; an approval gate used to reset a
driver-local counter on every "Approve", producing an infinite
reject-retry-gate loop.
- **The trigger-injection embedding cache keys on trigger text**, not
`name@version` — a Tutor `skills update` preserves the version and
would have served the stale trigger embedding forever.
- **`capture-note`'s starter trigger is written in instance nouns**
(dates, expiries, favourites) so real volunteered facts actually match
it; with injection live, a stated fact is now saved to memory and
confirmed in one line.
## [0.8.0] — 2026-07-04
**Milestone: the self-learning arc is real, live-proven, and honest.**
v0.8.0 marks core refinement converged: across the v0.7.38–v0.7.41 run and
this release, the complete self-learning loop was exercised live on real
hardware — skills invoked, effectiveness measured with honest signals,
underperformers refined, new skills authored from the agent's own
consolidated knowledge, every change gated by operator governance on the
signed persona chain, and the restraint properties (no churn, no
re-nagging, no twin proposals) verified under soak. This begins the run-up
to the v0.9 interface-polish phase.
### Fixed
- **Cron schedules now fire on the operator's local wall clock.** The init
template always said "local time," but the engine evaluated cron fields
in UTC — on any non-UTC host every routine fired hours off intent (a
"nightly" reflection at 10 a.m. local). Both schedulers now share one
local-time evaluation. One-time upgrade effect: a schedule whose
local-time tick already passed today fires a single catch-up.
- **Skill authoring no longer proposes twin skills from near-duplicate
topic names** ("triathlon" and "triathlon-basic"): candidacy skips a
topic whose normalized name is contained by — or contains — any covered
skill or prior authoring proposal. Synonym-named twins remain the
operator's call in the proposal inbox, by design.
- **The memory conflict detector no longer flags change-over-time
sequences as contradictions** ("file X present" then "file X removed" is
the world changing, not a conflict) — the recurring false-positive class
produced by daily environment observations.
- **The agent can now see its own routines**: `schedule.list` joins the
default-role capability floor (read-only, like `skills.list`). The
schedule *write* tools deliberately remain outside the floor pending an
autonomy-gating decision.
## [0.7.41] — 2026-07-04
**Chapter Wire** — headless multi-turn sessions. Pipe newline-delimited
turns into `aivyx --headless` and they run as consecutive turns of one
daemon session, making session-scoped behavior (session-partitioned
memory, recall's conversation window, consecutive-turn signals like the
correction detector) reachable from a terminal, a cron line, or a test
harness for the first time.
### Added
- **`printf "first\nsecond\n" | aivyx --headless`** — bare `--headless`
with piped stdin reads each non-empty line as one turn of a single
session. Fail-fast for batch callers: the stream stops at the first
non-completed turn and exits with the existing headless code (`3`
gate-refusal, `1` other failure); all turns completed exits `0`. The
one-shot `aivyx --headless "<task>"` form is unchanged, and bare
`--headless` on an interactive terminal errors with a hint to pipe.
Every turn keeps the unattended posture (gates refuse, never park).
### Notes
- A session carries session-partitioned memory, the conversation-window
relevance feed, and turn adjacency — turns are deliberately
fresh-context, with continuity flowing through memory and recall
rather than transcript replay. `docs/WIRE.md` records the distinction.
## [0.7.40] — 2026-07-04
**Chapter Strop** — skill effectiveness now measures how well a skill
*serves*, not how often it runs, so the self-learning refinement loop fires
organically at its default thresholds. Live-verified on the test rig: one
hollow skill turn (result claimed, tool never called) was enough for the
agent to propose a sharper version of that skill — with no healthy skill
swept up. Refinement remains propose-only and operator-governed.
### Changed
- **A completed turn is no longer an automatic +1 in the per-skill
effectiveness ledger.** The fold now reads Candor's verdict: a turn that
completed but carries an unfulfilled-claim annotation (the skill's result
was claimed while the fulfilling tool was never called — the dominant
local-model failure) folds negative. Failed/looping turns stay negative;
clean completions stay positive. Ledger format and thresholds unchanged.
- **Corrected skill turns count against the skill.** On the reflection
cadence, a completed skill turn the operator immediately reworked (the
correction ledger's definition) retro-folds negative — the
correction cross-reference the original Whetstone design specified. Each
correction folds exactly once across the repeating lookback windows via a
per-schedule watermark keyed on the follow-up turn.
## [0.7.39] — 2026-07-04
The self-learning loops now actually run for a fresh install. A live dogfood
of the skill self-learning arc (Whetstone refinement + Praxis authoring)
found five gaps that together kept it structurally dark; all are fixed and
each was verified live on the test rig — including the first-ever
agent-refined skill and first-ever knowledge-authored skill landing on a
real persona chain under operator governance.
### Fixed
- **The default role can now use its own skills.** `skills.list` and
`skills.invoke` were never granted in the default-role capability floor, so
every fresh install's agent was denied on calling them — no skill invocation
could be audited and the skill-effectiveness ledger could never accumulate.
Both scopes (read-only over the agent's own operator-approved skill set) are
now in the floor; `skills.propose` deliberately stays out.
- **Fresh installs get a real reflection schedule.** `aivyx init` never wrote a
`[[reflection_schedule]]`, so the whole reflection-pass family (persona
proposals, consolidation, skill refinement, skill authoring) never fired —
the starter routine *named* "nightly-reflection" is a plain prompt turn, not
the reflection scheduler. init now plants a daily, skip-when-idle reflection
schedule (enabled on local providers, written-disabled on cloud).
- **Skill authoring can actually find candidates.** Praxis gated candidacy on
the wiki topic name appearing verbatim as a knowledge-graph subject — but
topics are slugs while graph subjects are LLM-extracted phrases, so the
lookup never matched and the pass was silently starved. Candidacy now joins
on normalized word overlap, verified against the exact shapes observed live.
- **The loop's bookkeeping no longer pollutes the knowledge base.** The
`loop:progress` progress log had leaked a knowledge-wiki page;
`is_internal_topic` now covers the loop's reserved prefix and the wiki sweep
purges any already-leaked internal pages.
- **Skill authoring skips the agent's own journal.** The authoring pass could
synthesize a "skill" from a scheduled routine's own memory writes (the agent
talking to itself). Configured routine and reflection schedule names are now
excluded authoring topics.
## [0.7.38] — 2026-07-04
Dogfood-surfaced reliability fixes from the 2026-07-04 single-agent loop run,
each live-verified on the test rig before release.
### Fixed
- **A planner-named unknown specialist no longer kills the story.** When the
LLM planner reaches for a generic role word that isn't in the roster
("Operations", "QA-Engineer"), `SpecialistPool::resolve` now maps the word to
a capability and routes to the best-fit, least-privilege roster member instead
of hard-failing the whole delegation. Truly unmatchable names still error, and
naming the lead is still rejected; Keystone's artifact gate keeps a bad
best-effort attempt honest.
- **`aivyx loop add` no longer trips over the 200-character title cap.** A
second positional argument is now accepted as the story body
(`aivyx loop add <title> [body]`), and an over-long title auto-splits at the
last word boundary that fits — the overflow moves into the body with a printed
note instead of a bare error. The cap error itself now says where the detail
belongs.
### Changed
- **`memory.write` supersedes near-duplicate rewrites.** A refinement of an
already-stored fact (same content with a citation appended, a punctuation or
case tweak) now replaces the older entry instead of piling up beside it as
recall noise. Thresholds are deliberately conservative — distinct facts that
merely share words ("…Tuesday…" vs "…Thursday…") stay side by side — and the
new entry always lands before any older one is removed, so a mid-way failure
leaves noise, never loss. Superseded sequence numbers are reported in the tool
output.
### Security
- Bumped the transitive `cmov` dependency 0.5.3 → 0.5.4 (Dependabot alert:
wrong Cmov/CmovEq results on aarch64 when register high bits are set; sits
under the audit chain's HMAC stack).
## [0.7.37] — 2026-07-04
### Added
- **A WSL distribution now ships with each release (`Aivyx.wsl`).** Windows
users get a one-command-import WSL2 distro with the daemon and all tool
binaries pre-installed — no in-distro install step. It's the cheapest real
"Aivyx on Windows" path: it sidesteps the deferred native Windows port because
the daemon's Unix-domain-socket IPC and `0600` secret-at-rest posture both work
unchanged inside WSL2's real Linux kernel. Built by reusing the Harbor
appliance image (no duplicate compile): `Dockerfile.wsl` layers WSL config
(default user, `/etc/wsl.conf`, `/etc/wsl-distribution.conf`, a first-launch
OOBE script) and `.github/workflows/wsl-release.yml` exports the root
filesystem to the release. Install with `wsl --install --from-file Aivyx.wsl`
(WSL 2.4.4+) or `wsl --import`; see docs/INSTALL.md "Windows".
## [0.7.36] — 2026-07-04
### Added
- **Specialist tool calls are now traced in the mission journal (Chapter
Spyglass).** A Nonagon specialist's sub-turns previously emitted nothing, so
you could not see whether (say) the writer actually called `workspace.write` —
the exact blind spot that made the recent team capability bugs hard to
diagnose. Each specialist channel now logs its tool activity, labelled by
role: `aivyx team: [writer] → workspace.write` / `← workspace.write —
completed`. Pure observability (logging only; no behavioural change).
### Changed
- **The analyst can now fetch data itself.** The Analyst role gained `web.fetch`
(+ the `net.fetch` capability) alongside `web.search`, so it can pull
datasets, APIs, and reference pages directly rather than relying on search
snippets.
- **The `ops` role is now a verify-by-execution `verifier` (Verifier/QA).** The
old Operations role (`shell.exec` + `fs.read`) was a strict capability subset
of the coder. It is recast as a Verifier that proves a deliverable works by
*executing* it — running the tests, reproducing the claim, checking data
against its source — and reports PASS/FAIL with evidence without modifying the
work (same `shell.exec` + `fs.read`, plus `net.fetch` to reproduce against
remote sources). This fills the gap left by the read-only reviewer (which
cannot run anything) while keeping the team a nine-role Nonagon.
## [0.7.35] — 2026-07-04
### Fixed
- **A team mission retries once when it produced nothing (Chapter Reprise).**
A specialist sometimes claims to have written the deliverable but doesn't
(model-ceiling variance — a fresh attempt often succeeds where the first
missed). When the artifact gate (Keystone) finds no deliverable, the mission
now re-drives once from a cleared checkpoint before giving up, converting many
such misses into real completions instead of honest-but-empty rejections.
Bounded (one retry) so a genuinely impossible mission can't loop; only when
verification is on.
- **A reviewer gate no longer kills a whole team mission (Chapter Ombudsman).**
An in-DAG *auto* gate would abort the entire mission on any reviewer `FAIL` —
so a research→review→write plan died at the review step, before the writer
ever produced the deliverable. An auto gate is now **advisory**: its verdict
is recorded and fed to downstream steps as context (a "FAIL: add X" review
makes the next step better), and the mission continues; final quality is
enforced end-to-end by the artifact gate (Keystone), and a **human** gate
still blocks for operator approval. Live-verified: a multi-step mission now
runs its write step instead of aborting at the review.
- **Team specialists can actually use their tools (Chapter Ensemble).** The root
cause of "team missions do nothing": a specialist's caps are `declared ∩ what
the lead grants`, but the coordinator lead held only `[memory, team.delegate]`
and the roster declares *bare* scopes (`fs.write`, `net.fetch`) that can't
match the daemon's *qualified* floor (`fs.write:<root>/**`, `net.fetch:<url>`)
— so every specialist was attenuated to memory-only and couldn't write files,
fetch, or run commands. The mission lead now holds the daemon's real authority
and each specialist inherits the lead's floor scopes for the bases its role
declares. Stays least-privilege (a reviewer remains read-only; a writer gets
no network/shell) and `⊆ daemon floor`. Live-verified: a writer now writes its
file to the workspace and the mission completes `Done`. (Supersedes the
workspace-only Anchorage fix with one general mechanism.)
- **Team missions left `Executing` on a daemon restart no longer become
permanent zombies (Chapter Reckon).** There is no resume machinery for team
missions, so a mission interrupted mid-flight would sit `Executing` forever in
`team list`. On reload, an interrupted `Executing` mission is now reconciled to
`Halted` with a truthful reason ("interrupted by a daemon restart") and
persisted; `AwaitingApproval` (a real operator pause) is left untouched.
- **The planner routes save/create-file steps to a specialist that can actually
write (Chapter Handoff).** The decomposition prompt hid specialist
capabilities, so the coordinator could assign a `save_file` step to a
read-only role (e.g. `Operations`, which has `shell.exec, fs.read` but no
`fs.write`) — a step that then cannot produce the deliverable. The planning
roster now tags each specialist `[writes files]` / `[writes memory]` from its
scopes, with a rule that persist steps must go to a write-capable specialist.
### Changed
- **A team mission is `Done` only if its deliverable exists (Chapter
Keystone).** Live Nonagon dogfooding found the team subsystem's sharpest bug:
a mission ran every step to "done" — including a `create_file` step — and
reported `phase: done`, but the file existed nowhere. Steps completed when the
specialist *sub-turn returned*, with no grounding that the deliverable was
actually produced. Now, when a mission runs to completion, it is graded
against its goal — grounded on the workspace/memory artifacts it produced
(the same `CompletionJudge`) — and a mission that claims done but produced
nothing is flipped to `Rejected` with the verdict on the audit chain. Opt-in
via `TeamRunDeps.verify_missions` (the daemon enables it; default off ⇒
pre-Keystone behavior); best-effort and fails-open (no grounding / LLM error
⇒ keeps `Done`). Covers both direct `team start` and loop/Foreman missions.
## [0.7.34] — 2026-07-03
### Changed
- **Accord detector precision: fewer borderline false positives.** The Soul-
contradiction judge's prompt was teaching itself to over-flag — it used
"warm" vs "be candid" as a *positive* example, so it intermittently flagged
complementary traits on a perfectly coherent Soul. Rewrote the prompt around a
sharp mutual-exclusivity test ("is there NO situation in which both can be
honored at once?") with explicit NOT-a-contradiction cases (complementary
traits, default+exception, broad tone next to a specific rule). Live-verified
on the rig: the real coherent Soul went from intermittent 1–2 false positives
to 8/8 clean, while a genuine contradiction (one-line vs multi-paragraph) is
still caught — recall preserved.
## [0.7.33] — 2026-07-03
### Added
- **Accord skill-layer coherence: contradiction detection across learned
skills.** The contradiction pass now also considers the agent's learned
*skills* (shown to the judge as `skill "X": when <trigger> → <procedure>`), so
two skills that give opposite instructions for the same situation — or a skill
whose procedure contradicts a facet or operator constraint — are flagged by
`aivyx persona conflicts` alongside facet conflicts. Resolution removes the
losing skill BY NAME (reusing the Repertoire operator-forget primitive),
reversible via `aivyx persona revert`; dismiss ("keep both") works too.
Live-verified: seeded two opposite reply-style skills → detected → resolved.
- **Accord prevent-at-write: the Soul can't accrete a contradiction (coherence
gate at approval).** Beyond finding contradictions after the fact, approving a
persona proposal now runs a targeted coherence check first: if the proposed
facet would contradict an existing facet (or an operator Profile constraint),
the approval is refused with the specific conflict named — so the operator
rejects it, resolves the existing facet, or accepts the tension. The override
reuses Accord's dismiss ("keep both"): `aivyx persona dismiss <id>` then
re-approve, so a fuzzy-detector false positive is never an unescapable
lockout. Best-effort (no LLM ⇒ no gate); only gates operator approvals of new
list facets. Reuses the live-verified detector; the dismiss set is the escape
hatch.
- **Accord "keep both": dismiss a Soul contradiction (false positive).** The
contradiction detector is a fuzzy LLM pass, so it can flag a *legitimate*
nuance ("concise by default" + "detailed when asked") — which `persona
conflicts` would otherwise re-surface every run. `aivyx persona dismiss <id>`
now records the pair's stable id (in the existing encrypted dismissals domain,
namespaced `soul:` so it can't collide with a memory dismissal) and future
detection passes suppress it; nothing is removed from the Soul. Mirrors
Chapter Concord's `memory dismiss`. New `DismissSoulConflict` IPC verb.
## [0.7.32] — 2026-07-03
### Added
- **Soul-coherence: contradiction detection over the Persona (Chapter Accord).**
The identity-stack sibling of Chapter Concord (which does this for memory).
The Soul accretes reflection-learned facets; the lifecycle layer merges
near-*duplicates* and decays the *unreinforced*, but nothing flagged two
approved facets that flatly *contradict* — a seeded "communicate concisely"
living next to a later "give thorough, detailed explanations", both injected
every turn — nor a learned facet drifting against an operator Profile
constraint ("be candid, never flatter me" vs a learned "warm and effusive").
New `aivyx persona conflicts` runs an on-demand LLM detection pass (zero
background cost, no config) and `aivyx persona resolve <id> --remove <a|b>`
removes the losing facet via a `RemoveList` persona delta (operator-authored,
reversible via `aivyx persona revert`). Operator Profile constraints are
immutable — flagged, never removed. New wasm-clean `SoulConflict` IPC type +
`GetSoulConflicts` / `ResolveSoulConflict` verbs (Studio can consume later).
## [0.7.31] — 2026-07-02
### Changed
- **The autonomous loop now verifies-and-closes a finished story the agent
forgot to complete (Chapter Capstone).** A recurring dogfood finding: small
local models often do the work but never call `loop.complete` (or report it in
chat), so a genuinely-finished story stays pending until the stall breaker
ends the run. When `[loop] verify_completion` is on, at each iteration's end
the driver judges the still-pending story it handed out against its own
acceptance criteria — grounded on the memory/workspace artifacts the turn
produced — and marks it done iff the judge passes. Genuinely-incomplete work
is left pending (the grounded judge rejects it, e.g. "only 1 of 2 requested
items"). Reuses the existing completion judge; no new config.
## [0.7.30] — 2026-07-02
### Security
- **The web UI can now require an auth token (Chapter Postern).** The Studio's
`/ws` WebSocket is its control plane — agent turns, config writes, and memory
reads all flow over it — and exposing it off-host (`web_ui_host = "0.0.0.0"`,
the Docker appliance) previously left it unauthenticated. Set `[daemon]
web_ui_auth_token = "<opaque>"` to require a shared secret: browsers are
prompted via HTTP Basic (the token is the password) and a cookie is planted
that gates the `/ws` upgrade; non-browser clients send `Authorization:
Bearer`. Default-off (localhost posture byte-identical), with a loud daemon
warning when bound off-host without a token. Not a TLS substitute — terminate
TLS at a proxy for remote access.
## [0.7.29] — 2026-07-02
### Security
- **`shell.exec` now honors the sensitive-path guards (Ward/Portcullis).**
Previously the sensitive-read and persistence-write guards protected only the
`fs.*` tools, so a shell command routed around them — `cat ~/.ssh/id_rsa`,
`echo … >> ~/.bashrc`. `shell.exec` now scans the command text and refuses a
command that references a protected location (secret dirs/files, cloud creds,
or persistence targets like `.bashrc` / `authorized_keys` / `crontab`) before
`sh` runs. Uses the same `[access] allow_sensitive_paths` opt-in. Best-effort
by nature (obfuscated paths can still slip through — the docs continue to
point to OS-level isolation for hard guarantees); disabled-by-default so
behavior is byte-identical until the guard is configured on.
## [0.7.28] — 2026-07-02
### Changed
- **Loop completion verdicts now ground on FILE artifacts, not just memory.**
The acceptance judge that gates `loop.complete` (and delegated team missions)
reads a bounded snapshot of the most recent workspace files alongside recent
memory, and treats both as ground truth. A file-producing task is graded on
the file that actually exists rather than on how tersely the agent phrased its
summary — closing the documented #17b/#17d residual (previously memory-only).
## [0.7.27] — 2026-07-02
### Security
Final hardening pass of the security thread — extending the existing guards to
the last uncovered surfaces:
- **Prompt-injection fencing now covers `fs.read` and every tool-process
integration (Bulwark extension).** A file's contents (attacker-supplied,
downloaded, or in a broad-access location) and the output of Gmail / Calendar
/ Contacts / Drive / Obsidian / the web-search toolkit are fenced as untrusted
data — never instructions. Verified: the agent describes an injection in a
file rather than obeying it.
- **`net.dns` honors the egress policy (closes a DNS-exfil channel).** A DNS
lookup is itself an exfiltration channel (`<secret>.attacker.com` leaks to the
attacker's nameserver); `net.dns` now applies the same
`[access] allow_egress_hosts` allow-list + private-host block as the web
tools, so an allow-listed deployment can't be DNS-tunneled.
## [0.7.26] — 2026-07-02
### Security
- **Master passphrase in the OS keyring (Chapter Keyring).** For interactive /
desktop use, store the master passphrase in the OS credential store (Secret
Service / Keychain / Credential Manager) instead of the `AIVYX_PASSPHRASE`
env var or `[aivyx] passphrase` TOML (both plaintext). `aivyx keyring set` /
`clear` / `status`; the daemon reads it automatically (env/TOML still take
precedence; the keyring is tried before the interactive prompt; an
unavailable/locked keyring falls through). The headless systemd service keeps
using the `0600 daemon.env` file (no session Secret Service there).
- **Fixed a PDF denial-of-service (RUSTSEC-2026-0187).** The `data.pdf` reader
pulled `lopdf 0.34`, where a crafted ~21 KB PDF with deeply nested arrays
triggers unbounded-recursion stack overflow (SIGABRT) — crashing the daemon
on any turn that reads an untrusted PDF. Upgraded `pdf-extract` → 0.12 and
`lopdf` → 0.42 (the patched line); reader behavior unchanged.
## [0.7.25] — 2026-07-02
### Security
Continuing the privacy-first hardening pass — two more layers, both
live-verified on the dogfood rig:
- **Sensitive-path write guard (Portcullis).** The symmetric completion of
Ward: `fs.write` now hard-refuses writes to secret *and* persistence
locations — `~/.ssh/authorized_keys`, shell rc files
(`.bashrc`/`.zshrc`/`.profile`), `~/.config/autostart` & `systemd`, `cron.*`,
git hooks — even inside the sandbox and even at `full`. This closes the
backdoor/persistence vector `confirm_destructive` only *soft*-gated (it's
self-confirmable; this is a hard refusal). Same `[access]
allow_sensitive_paths` opt-in. Reads of these files are still allowed; only
writes are blocked.
- **DNS-rebinding defense (Rampart).** The network egress guard now filters
private/loopback/link-local addresses at DNS-*resolution* time via a custom
resolver, so a public hostname that resolves to `127.0.0.1` /
`169.254.169.254` / an RFC-1918 address is never connected to (TOCTOU-safe).
Failed requests now also surface the full error cause chain, so a blocked
request reports why.
## [0.7.24] — 2026-07-02
### Security
A privacy-first hardening pass — three composing, default-on layers, each
live-verified on the dogfood rig:
- **Sensitive-path read guard (Ward).** The agent can no longer read known
secret locations — `~/.ssh`, `~/.aws`, `~/.gnupg`, cloud/k8s/docker creds,
`.env` files, private keys, **and Aivyx's own encrypted store + passphrase** —
at any access level, on the canonical (symlink-resolved) path, across
`fs.read` and the data readers. Opt specific paths back in with `[access]
allow_sensitive_paths`; disable with `guard_sensitive_paths = false`.
- **Network egress guard (Rampart).** The web tools refuse loopback /
link-local / private / unique-local targets by default — on the initial URL
and every redirect hop — blocking SSRF and cloud-metadata theft
(`169.254.169.254`) and local-service pivots. `[access] allow_private_egress`
re-enables localhost/LAN; `allow_egress_hosts` hard-restricts to named hosts.
- **Prompt-injection resistance (Bulwark).** Content the agent ingests (web
pages, extracted articles, parsed files, third-party MCP outputs) is fenced
as untrusted DATA — never instructions — via a demarcation envelope plus a
standing charter rule. Defense-in-depth, composing with the layers above.
### Fixed
- **Auto-delegated stories are held to the real artifact.** Completion
verification for a loop-delegated team mission is now grounded on the memory
the team actually wrote (matching the solo `loop.complete` path), so a terse
mission result over genuine work is no longer false-rejected.
## [0.7.23] — 2026-07-01
### Fixed
Autonomous-loop hardening, all surfaced by a live dogfood run (gpt-oss:20b):
- **Auto-delegated stories no longer skip on a specialist name/role mismatch.**
The planner refers to a team specialist by its role label ("Operations"), but
resolution matched only the roster id ("ops") → the mission errored `no
specialist "Operations"` → retried → skipped a doable story. Resolution now
matches the name **or** the role, case-insensitively (extends the earlier
case-only fix, which never covered genuine name↔role differences).
- **Completion verification now judges the real artifact, not just the summary.**
With `[loop] verify_completion`, the acceptance judge was rejecting
genuinely-complete research/memory stories because the agent's summary was
terse — even though the note was in memory. The judge is now shown a snapshot
of the recent memory the agent wrote and passes when that evidence satisfies
the criteria (solo `loop.complete` path).
- **A malformed tool call from a local model no longer hard-fails the turn.**
Ollama returns a bare HTTP 500 when a model emits unparseable tool-call JSON;
the provider now retries once (resampling almost always parses), while genuine
500s still surface immediately.
## [0.7.22] — 2026-07-01
### Added
- **`aivyx memory conflicts` / `resolve` / `dismiss` — contradiction detection
for stored memory (Chapter Concord).** The agent stored whatever it was told,
so contradictory facts coexisted silently ("home airport is YPPH/Perth" *and*
"...Sydney/YSSY"). An on-demand LLM pass now flags pairs of entries that assert
incompatible facts about the same subject — within one topic **or across two
topics** — and the operator resolves each by keeping one (`resolve <topic>
--archive <seq>` deletes the other) or dismissing a false positive
(`dismiss <id>`, durably suppressed). No background cost: detection only runs
when asked. New `Memory::delete_entry` substrate primitive + a 26th encrypted
storage domain for dismissals.
- **`aivyx memory wiki` / `graph` — CLI knowledge-base inspection.** The agent's
synthesized wiki pages and typed knowledge graph, previously Studio-only, are
now viewable from the terminal.
### Fixed
- **Internal `context:pruned:*` bookkeeping no longer leaks into the knowledge
base or the agent's context.** The earlier internal-topic filter covered only
`memory list`; the same machine-state archives still surfaced through six more
paths — the wiki + graph sweeps, both graph queries, memory search, the agent's
RAG auto-recall, and its wildcard `memory.read`. All are now filtered from one
shared definition in the memory substrate.
- **The typed knowledge graph no longer fills with conversation-mechanics
noise** ("conversation history → messages", "assistant → tool_call →
web_search"): the extractor drops triples about the session itself and keeps
domain knowledge.
- **The first daemon query after a restart no longer fails** with a spurious
`RecoveryNotice` protocol error (the long-standing "memory list transient
flakiness").
## [0.7.21] — 2026-07-01
### Changed
- **Acceptance verification now covers auto-delegated work too.** When `[loop]
verify_completion` is on, a story the loop auto-delegates to the agent team is
judged against its acceptance criteria — the team mission's result must pass
the same LLM acceptance check a solo `loop.complete` gets — before it's marked
done; otherwise it's retried (then skipped). Previously a delegated mission was
accepted on completion alone, skipping the check solo work received.
## [0.7.20] — 2026-07-01
### Fixed
- **Auto-delegated team missions no longer fail and retry (and can no longer skip
a doable story).** The autonomous loop's `delegate_above` path could hand a
story to the team, have the mission error out, retry, and eventually skip the
story — wasting work. The cause was a case-sensitive specialist lookup: the
planner names a specialist "Researcher" but the roster is "researcher", so the
mission errored with "no specialist". Specialist matching is now
case-insensitive. (Unattended decompositions also no longer emit approval gates
that can't be satisfied without an operator.)
## [0.7.19] — 2026-06-30
### Changed
- **The release pipeline retries transient crates.io network failures.** A
`.cargo/config.toml` with `[net] retry = 10` plus a step-level retry around
`cargo install` in the desktop build stop the recurring "curl failed / HTTP2
framing layer" flakes that needed a manual rerun each release.
- **The loop's completion judge logs its verdict.** When `[loop]
verify_completion` is on, the daemon now logs each ACCEPT/REJECT decision (and
the reason) instead of gating silently — so an operator can see why a story was
held back or accepted.
## [0.7.18] — 2026-06-30
### Added
- **The agent owns up when it claims an action it didn't take (Chapter Candor).**
After a turn, Aivyx compares what its reply claims against the tools it actually
called, and appends an honest note if it said it did something (e.g. "saved to
memory", "scheduled a routine", "sent a notification") without the matching tool
call. Non-blocking — it annotates, never breaks the turn — and upholds the
agent's honesty contract instead of quietly papering over a dropped step.
## [0.7.17] — 2026-06-30
### Fixed
- **`aivyx memory list` (and the Studio Memory browser) no longer shows internal
topics.** The per-session `context:pruned:*` context-pruning archives — machine
bookkeeping, not operator knowledge — are hidden from topic listings (they were
swamping the real topics). They remain reachable by exact `aivyx memory show
<topic>`; only the cluttered enumeration is filtered.
### Changed
- **The autonomous loop skips a story after repeated failed auto-delegations.**
When `[loop] delegate_above` is set, a story whose delegated mission keeps
ending non-Done is marked skipped after a couple of attempts instead of
re-running a full team mission every iteration.
## [0.7.16] — 2026-06-30
### Added
- **Deterministic auto-delegation for the autonomous loop (Chapter Foreman).**
Opt-in `[loop] delegate_above = N`: before each solo turn the loop scores the
next backlog story with a structural complexity heuristic and, if it scores
`>= N`, hands it to the agent team (headless) instead of attempting it solo —
so delegation no longer depends on a small local model choosing to call
`team.run`. Default off.
### Fixed
- **`aivyx team status` shows the real halt reason.** A mission stopped with
`aivyx team abort` (or a budget cap) now reports *why* it halted (e.g.
"aborted by operator") instead of always labelling it "(budget)". The reason
is persisted on the mission record and rendered in the detail view.
## [0.7.15] — 2026-06-29
Three multi-agent / autonomy refinements.
### Added
- **Heterogeneous teams (Chapter Ensemble).** Each `[[team.member]]` may now
declare its own `model` and/or `base_url` (same provider kind). A coordinator
can run on a big model while grunt specialists run on a small fast one
(role-fit + cost), and pointing roles at different endpoints (e.g. a second
Ollama / GPU) gives true parallel execution instead of serializing on one
server. Both optional — omit them and a role uses the team's shared default.
- **Per-story acceptance verification (Chapter Verdict).** Opt-in
`[loop] verify_completion`: when the autonomous loop calls `loop.complete`, an
LLM judge checks the agent's summary against the story's acceptance criteria
and blocks the completion on a FAIL (the story stays pending) instead of
trusting the self-report. Fails open; stack with `gate_command` for
artifact-grounded checks.
- **Abort a running team mission (Chapter Belay).** `aivyx team abort <id>`
stops a running mission — it halts gracefully at its next step boundary
(in-flight work finishes, completed outputs preserved). Completes the operator
control surface alongside budget caps and human-gate approve/reject.
## [0.7.14] — 2026-06-29
### Fixed
- **`aivyx mcp status` no longer falsely reports "no MCP servers".** The status
snapshot is daemon state, but it was being overwritten by transient CLI
invocations (a `--headless` turn, the REPL), which clobbered the running
daemon's snapshot with empty entries — so the command showed no servers while
MCP was actually live. Only the daemon writes the snapshot now. (Backlog #9.)
- **The `trend-scan` routine never broadcasts fabricated findings.** A new
delivery gate (`notify_when = "on_completed_grounded"`, now the default for
`trend-scan`) only pushes a result when the turn actually did work (≥1 tool
call). A trend-scan that produced "findings" with no real web search is
suppressed instead of being sent as if real. (Backlog #6 follow-on.)
## [0.7.13] — 2026-06-29
### Fixed
- **The weekly digest no longer fabricates.** A check-in caught the scheduled
`weekly-digest` inventing profile-themed "accomplishments" that never happened
(a "Flutter revenue dashboard", etc.) — a local model fills a sparse summary
with plausible fiction no matter how the prompt is worded. The digest is now
**assembled deterministically by the daemon** from your actual memory (entries
written since the last digest) plus the live pending-proposal count, with real
dates; if nothing was recorded it says so plainly. There is no LLM in the
content path, so it cannot confabulate. (Backlog #6, Chapter Ledger.)
### Added
- **Chapter Deckhand — opt-in "use your open applications" (experimental).** With
`[applications] enabled = true`, the agent can use the GUI apps open on your own
machine: list windows, focus one, type / press keys / click, and screenshot.
Default **off**; Trusted-tier only; input injection is **confirm-first**; every
action is an audited tool call. Linux X11 / Xwayland in this release (needs
`xdotool` + a screenshot tool); native-Wayland windows are a known limitation.
See `docs/APPLICATIONS.md`.
## [0.7.12] — 2026-06-29
### Added
- **Chapter Helm — autonomous loop runs survive a daemon restart.** A new opt-in
`[loop] resume_on_boot` resumes an interrupted autonomous-loop run when the
daemon restarts — so a "runs for days" agent (e.g. under systemd
`Restart=on-failure`) keeps working through its backlog instead of silently
stopping after a crash. It resumes only when a run was genuinely active when
the daemon stopped (a crash or restart) and stories remain pending; an
explicit `aivyx loop stop` is remembered across the restart and is **not**
resumed. Default off — auto-resuming a code-committing loop is a deliberate
operator choice.
## [0.7.11] — 2026-06-29
### Fixed
- **Release pipeline gate.** A test helper added in 0.7.10 (Chapter Ballast)
triggered a `clone_on_copy` clippy lint, which the cargo-dist release gate
(`clippy --all-targets -D warnings`) treats as an error — so 0.7.10's CLI
installer binaries failed to publish (the desktop app and Docker image, built
by separate workflows, shipped fine). This release republishes the CLI
binaries with the lint fixed. No runtime behavior change from 0.7.10.
## [0.7.10] — 2026-06-29
### Added
- **Chapter Ballast — a per-mission budget for autonomous team missions.** When
the autonomous loop delegates a story to a background team mission, that
mission previously had no aggregate spending limit (only a per-call token
cap). Two new opt-in `[budget]` caps — `per_mission_tokens` and
`per_mission_usd` — bound a single mission's total spend across all its
specialist sub-turns; a tripped cap **halts the mission gracefully** at the
next step boundary (completed work preserved, the reason recorded). Tokens
bound local/free runs where the dollar cap (priced at $0) never trips. Both
default to unbounded, so behavior is unchanged unless you opt in.
### Fixed
- **`aivyx init --template <name>` now matches a plain `aivyx init`.** The
template path silently skipped semantic-memory setup (`[embedding]` +
`[memory] profile`), the onboarding persona seed, and the web-search server —
so a templated agent diverged from a default one. It now applies each of those
(any a template already declares is respected, never duplicated).
## [0.7.9] — 2026-06-28
### Added
- **Chapter Ember — embedding-free "lite" recall.** `[memory] profile = lite`
now delivers real recall with **zero setup** — no embedding model, no vectors,
no paid generation. It fuses BM25 lexical search with a co-occurrence graph
walk (seeded from the lexical hits) over the memory the agent already has.
Previously `lite` produced *no* recall because the whole recall stack was gated
on an embedding provider. `aivyx doctor` reports it as "lite recall active".
Set `profile = smart` with an `[embedding]` section for semantic recall; a
`smart` config with no embeddings stays off (and now warns). (Backlog
opportunity C.)
### Fixed
- **Chapter Etch — the agent now reliably persists explicit "remember this"
requests.** Previously a local model treated "remember X" as conversation and
often never called `memory.write`, so a later turn couldn't recall it (and a
soft charter instruction didn't move it — verified). Now the per-turn memory
hook **deterministically** detects a leading "remember / note / save / don't
forget X" request in the operator's message and persists the fact itself —
independent of the model's tool-calling — embedding it so it's immediately
recallable. Conservative detection: it ignores questions, reminiscing
("remember when…"), reminders ("remember to…"), and first-person mentions.
Live-verified: "Remember my home airport is YSSY" → a fresh-context "is it good
flying at my home airport?" now recalls YSSY and answers, instead of asking
which airport. (Backlog #8.)
- **A `smart` memory profile with no embeddings is no longer silently inert.**
`[memory] profile = smart` arms the semantic recall stack, but with no
`[embedding]` provider the semantic source does nothing — the trap that can
leave "smart" memory dark. The config loader now warns at startup and
`aivyx doctor` flags the mismatch (use `profile = lite` for embedding-free
recall, or add an `[embedding]` section). (Backlog #1.)
- **Clearer error when a non-interactive `aivyx` collides with a running
daemon.** A piped/non-TTY turn (`printf … | aivyx`) opened the store directly
and failed with redb's opaque "Cannot acquire lock" while the daemon held it.
It now explains the situation and points to `aivyx --headless "…"` (which
routes over the daemon) or `aivyx daemon stop`. (Backlog #2.)
- **The daemon no longer logs clean client disconnects as errors.** A finishing
CLI query dropping its socket produced a `connection handler error: Broken
pipe` line on every call, spamming `journalctl` and masking real errors. Clean
hang-ups are now silent; genuine faults still log. (Backlog #4.)
## [0.7.8] — 2026-06-28
### Fixed
- **`aivyx init` no longer shreds list items that contain commas.** The wizard's
comma-separated Profile fields (use cases, behavioral preferences/constraints,
character traits) are now split **paren-aware** — a comma inside `(...)`/`[...]`
no longer splits, so an item like "Research my passions (flying, food, coffee)"
stays a single entry instead of fragmenting.
## [0.7.7] — 2026-06-28
### Fixed
- **Chapter Plumb — default report routines no longer confabulate.** A fresh-agent
check-in caught the `weekly-digest` routine *fabricating* a week of
accomplishments (zero tool calls) — and `trend-scan` silently doing nothing —
because those default `[[schedule]]` prompts were passive "summarize / search"
instructions the local model could satisfy by inventing prose instead of
reading (empty) memory/journal. The reporting prompts now **name an explicit
first read** (recall memory + read the journal; *actually* web-search for
trend-scan), report **only what's genuinely found**, and say "nothing notable
to report yet" when the record is empty — never invent, infer, or pad.
Re-verified live: the digest now grounds in real memory instead of fabricating.
(A daemon-level "an aggregation routine that made no tool calls is a no-op"
backstop is noted as a follow-up.)
## [0.7.6] — 2026-06-28
### Added
- **Chapter Anchor — `aivyx daemon install` (run for days).** The bare daemon —
the default install — now installs as a first-class persistent **user**
service so a local-first agent meant to run for days actually stays running
(its scheduled routines firing, its loop available) across logout and reboot,
without hand-rolled `systemd`/`launchd` files.
- **`aivyx daemon install [--web-ui] [--no-start]`** + **`aivyx daemon
uninstall`**. Linux = a systemd user unit + `loginctl enable-linger` (no
root); macOS = a launchd `LaunchAgent`. Idempotent re-install.
- The unattended store passphrase is captured at install (`AIVYX_PASSPHRASE`
or a one-time hidden prompt) and kept owner-only at rest — a `0600`
`EnvironmentFile` the unit references on Linux (never in the unit itself),
the `0600` plist's `EnvironmentVariables` on macOS.
- `aivyx doctor` gains a **Service** section (installed / running), and
`aivyx init` points new users at `daemon install`. Documented in
`docs/INSTALL.md`. Live-verified end to end on the dogfood rig.
- **Chapter Passport — the identity & cross-boundary trust keystone (FED.1–5).**
The open-core trust **substrate** for agent-to-agent interaction across an
operator boundary — the one primitive `VISION.md` flags as impossible to
retrofit (*"a network of agents is a Nonagon team with the trust boundary
moved"*). New `crates/aivyx-federation`:
- **Identity** — an operator-owned Ed25519 keypair + signed, replay-guarded
request envelope; key-at-rest sealed with a subkey derived from the storage
`MasterKey`; key material never logged.
- **Trust** — a per-peer `TrustPolicy` (deny-by-default; allowed scopes in the
`Scope` vocabulary; a confirm-first autonomy ceiling) and the cross-operator
attenuation (`effective = asked ∩ policy ∩ host-ceiling`, autonomy floored) —
NT-02 generalized so a peer can never exceed what *both* operators allow, and
revocation narrows reach immediately.
- **Relay shape** — the `chat`/`task`/`search` verbs as wasm-clean
`aivyx-ipc::federation` types ("as if the peer is a stranger"), plus the
forensic `Crossing` shape for the audit chain.
- **Untrusted peer content** — provenance-tagged, payload-validated, runnable
only under its attenuated authority (never the host's broader caps).
- **Operator consent** — peer-initiated effect (a `task`, or any irreversible
scope) routes through the existing confirm-first gate; no cross-boundary
self-escalation.
Substrate only — no transport, peer discovery, reputation, or the Nexus
product (built last, on an installed base). Proven end-to-end by a
two-identity in-process integration test.
## [0.7.5] — 2026-06-28
### Added
- **Chapter Circuit — agentic-loop hardening (CI.0–CI.6).** A deliberate
audit + hardening pass over the autonomous loop, prompted by the v0.7.4
scope-floor bug.
- **CI.0** — the loop's `team.run` delegation tool is now granted to the
default role when the loop is armed (it was registered and offered in the
iteration prompt but, like `loop.*` before v0.7.4, never floor-granted, so
the "delegate to a team" branch was dead). `git.write` stays operator-opt-in
by design. Adds a prompt↔floor drift guard so the two can't silently
diverge again.
- **CI.1** — a **cross-iteration stall breaker**. The loop driver now stops a
run after `[loop] max_idle_iterations` consecutive iterations make no
progress — neither completing/delegating a story nor recording a fresh
progress note. This catches a loop spinning on an unrecoverable error (the
v0.7.4 bug burned all 25 iterations / 631k tokens re-failing identically)
instead of letting it exhaust the iteration/token caps. Default 3; set `0`
to disable. Distinct from Bridle's within-turn repeat breaker — this is
across fresh-context iterations.
- **CI.2** — the iteration prompt is now **task-agnostic**. It previously
hardcoded a software-dev flow (run the build/tests, commit with `git`),
which stranded everyday-PA stories — the model had to improvise past
instructions for work that has no project and no granted `git.write`. The
verification step now offers a fitting check per task type (gates for code,
re-read for research/writing, read-back for a file), and the persistence
step routes the result to its proper home (a commit for code *only if
committing is available*, memory for notes, the requested path for a file).
- **CI.3** — audit of gate verification and `team.run` delegation governance
(documentation, no behavior change). Clarified that the loop gate is a
tree-level regression guard, not a per-story completion verifier; and
decided to keep `team.run` available whenever the loop is armed (rather than
posture-gate it) while documenting that loop-delegated team missions are not
yet bounded by an aggregate budget — both captured as tracked follow-ups.
- **CI.4** — durability & re-entrancy audit. Fixed a wedge: if the loop driver
task panicked mid-run it left the run flagged `active` with no driver behind
it, so every later `loop start` silently no-op'd ("already running") until a
daemon restart — a new run-scoped guard now clears the flag on an abnormal
exit. Documented that loop run-state is in-memory only (a daemon restart
mid-run does not auto-resume; stories persist, the operator re-issues
`loop start`); opt-in auto-resume is a tracked follow-up.
- **CI.5** — observability. `aivyx loop status` now shows the stall-breaker
configuration (`stall breaker: stop after N idle iteration(s)` / `off`)
alongside the other run caps, and a live warning while a run is spinning
(`no progress for N of M iteration(s) before the stall breaker stops the
run`) — so a stalling run is legible without reading `journalctl`. The
stall threshold + live idle count ride the `LoopStatus` IPC / run-state.
- **CI.6** — live-verified the chapter end-to-end on the dogfood rig: research
stories complete through the task-agnostic path with no git noise (CI.2); an
unrecoverable story trips the stall breaker after 3 idle iterations instead
of burning the 25-iteration cap, and stays pending rather than being
false-completed (CI.1); `loop status` shows the stall-breaker config + stop
reason (CI.5). (The `team.run` grant is present and never scope-denied;
delegation dispatch wasn't observed because the small local model
consistently self-implements even when told to delegate.)
### Fixed
- **Flaky e2e store-path collision.** Four `aivyx-channel` integration suites
(`fs_tool_e2e`, `cli_e2e`, `audit_persistence_e2e`, `memory_tool_e2e`) built
their scratch `store.redb` path from `pid + nanos`, which is not unique across
the crate's parallel test threads — under CI load two tests could land on the
same clock tick, collide on the path, and fail the second `RedbStorage::open`
on the redb lock (this failed the v0.7.4 release's quality gate). Each path now
carries a uuid, matching the existing `storage_persistence_e2e` fix.
## [0.7.4] — 2026-06-28
### Fixed
- **The autonomous loop's tools were never granted to the default role.** Arming
`[loop]` (or an `[autonomy]` level that arms it) spawned the loop driver, but
the default role's backcompat scope floor granted `memory.*` / `fs.*` /
`net.*` / `shell.exec` / `workspace.*` / `ollama.*` / `mcp.call:*` and **never
`loop.*`** — so every iteration was denied at step one (`loop.next ... not
currently granted`) and the driver burned its full iteration cap re-failing at
the same wall. The autonomous-loop backlog mechanism was effectively dead on
arrival for the zero-config default role. The floor now grants
`loop.next` / `loop.complete` / `loop.note` when the loop is armed, mirroring
the existing ollama/MCP grants; self-escalation scopes are still withheld.
Found and verified on a live multi-day dogfood run.
## [0.7.3] — 2026-06-27
### Added
- **Default starter skills (Chapter Outfit).** A brand-new agent now ships with a
curated starter repertoire — `summarize-document`, `research-and-summarize`,
`draft-reply`, `daily-briefing`, `capture-note` — so it can do real work on
turn one instead of arriving with none. Each is a lightweight
`{name, trigger, procedure}` recipe whose steps compose the agent's own tools
and pillars (the structured-data readers, web search/extract, memory,
workspace, persona). Compiled-in and default-on, planted onto a fresh agent's
persona chain at first boot; an already-running agent is never retro-injected.
Opt out with `[skills] starter = false`.
- **Operator-initiated skill teaching (Chapter Tutor).** New
`aivyx skills teach | update | forget` commands let the operator author a skill
directly onto a **grown** agent's persona chain — the channel that was missing
(genesis seeding is empty-chain-only, and the agent's own `skills.teach` tool is
gated behind a scope an autonomous agent shouldn't hold). Operator authoring
routes over the local daemon socket (same authority as `aivyx persona revert`)
and is cleanly separated from agent self-teaching, so it needs no agent scope.
Signed, audited on the persona chain, and reversible via `aivyx persona revert`.
### Changed
- **Semantic memory works out of the box (Chapter Engram).** `aivyx init` now
configures an embedding provider — pulling `nomic-embed-text` on the local
Ollama path, or using OpenAI embeddings on the cloud path — and turns on the
full memory profile (`[memory] profile = "smart"`). Previously the whole
graph-augmented memory stack was dormant on a fresh install: the daemon only
builds the auto-recall pipeline when `[embedding]` is configured, and `init`
never wrote one, so a new agent did no semantic recall at all regardless of the
memory profile. (Existing installs are unchanged on upgrade — the new defaults
are written only into new configs, so the token-spending extraction sweeps are
never a surprise.) `aivyx doctor` gained a Memory section reporting the
embedding provider, the active profile, and — on the local path — whether the
embedding model is actually downloaded.
## [0.7.2] — 2026-06-27
### Changed
- **The default system prompt is now an operating charter (Chapter Keel).** The
out-of-box base layer was a single line ("a terse and thoughtful assistant
running in a local terminal"). It is now a compact (~270-token) operating
charter that states, in prose the model actually reads, what the dynamic
Profile/Persona/Tools/Skills layers don't: how the agent works (terse and
honest, tool-first, uses its memory and workspace, doesn't loop), its safety
posture (confirm-first on irreversible/outbound actions, never widens its own
authority/reach/autonomy, everything is recorded — mirroring
`docs/SECURITY_POSTURE.md`, previously stated only in code), and turn
discipline. It remains a compiled-in default, fully overridable via
`[agent] system_prompt`, a per-`[[role]]` `system_prompt`, or
`AIVYX_SYSTEM_PROMPT` — existing installs inherit it live and pick up future
refinements on upgrade. No new config is planted into `aivyx.toml`.
- **The Studio's Command Center reflects a live, working agent.** The home
dashboard now surfaces an agent-vitals rail (model · provider · context ·
autonomy · access, with a pulsing "live" dot when the daemon is online) and a
Routines panel showing each scheduled background routine's cadence, enabled
state, and last/next fire — the clearest "the agent is working on its own"
signal. Backed by a new read-only `GetSchedules` IPC; no new capability.
### Fixed
- **Eliminated a parallel-test flake in `aivyx-config`.** Two config-load tests
read the ambient environment without holding the test suite's env-guard,
occasionally racing parallel env-mutating tests (intermittent failures under
default test threads; green at `--test-threads=1`). Both now hold the guard,
and the invariant is enforced via a thread-local check so any future unguarded
config-load fails deterministically with an actionable message instead of
flaking.
## [0.7.1] — 2026-06-27
### Security
- **Bumped `quinn-proto` 0.11.14 → 0.11.15** (RUSTSEC-2026-0185): a remote
memory-exhaustion via unbounded out-of-order QUIC stream reassembly. A
transitive dependency (via `reqwest`'s HTTP/3 path); a lockfile-only patch
bump, no API change. `cargo audit` no longer reports any vulnerability.
- **Documented the `aivyx-desktop` GTK3 advisory cluster in `deny.toml`.** The
native desktop app's webview (tao/wry/webkit2gtk → the frozen gtk-rs GTK3
bindings, pinned at glib 0.18) pulls 11 `unmaintained` advisories
(RUSTSEC-2024-0370 + 0411–0420) plus the glib `VariantStrIter` unsoundness
(RUSTSEC-2024-0429 = GitHub Dependabot alert #6). All are transitive,
desktop-build-only (the daemon/core/CLI never link them), unfixable until the
webview ecosystem moves off GTK3, and not exploitable in the thin shell. The
unmaintained ones are now ignored with documented rationale (restoring
`cargo deny` to green); the glib unsoundness is recorded as the accepted
disposition for the Dependabot alert (dismiss as "tolerable risk").
### Changed
- **`aivyx init` ends on real next steps.** The completion message now surfaces
the three things a new operator actually needs — `aivyx` (terminal chat),
`aivyx daemon run --web-ui` to open the **Studio** at `http://127.0.0.1:7843`
(the web GUI a first-run user wouldn't otherwise discover), and `aivyx doctor`
to re-check — plus, on the local path, a pointer to `docs/LOCAL_HOSTING.md` for
sizing the model/context to a capable GPU. The Studio port comes from the
canonical `DEFAULT_WEB_UI_PORT` so it can't drift.
- **One `TurnSafety` choke point for the per-turn knobs.** The deadline + cycle
breaker were applied ad-hoc at ~7 `ConcreteAgent::new` sites — which is exactly
why three of them drifted and shipped unprotected. Now every agent-construction
path ends with `TurnSafety::<posture>(…).apply(agent)`: `interactive` inherits
the operator's `[agent]` config (REPL, voice, daemon, role-switch child),
`autonomous` forces the breaker floor (team lead + specialists), and the
standalone remote-channel builders route through `default()`. `SessionConfig` /
`AgentStackSpec` now carry a single `turn_safety` field instead of two. No
behaviour change — the wiring just can't drift across sites again.
### Added
- **`aivyx autonomy` — one dial for how autonomous your agent is (Chapter Reins).**
A new `[autonomy] level` (`manual` → `assisted` *(default)* → `supervised` →
`autonomous` → `unleashed`) composes the scattered autonomy knobs (access,
confirm-first, gate policy, loop arming, self-improvement adoption) into named
tiers, with per-domain `[[autonomy.override]]` exceptions ("autonomous at
shell, manual on email") and an `[autonomy.auto_approve]` reversible-scope
allowlist. `aivyx autonomy show` renders the resolved level and the posture it
expands to; `aivyx autonomy set <level>` rewrites the section
(`autonomous`/`unleashed` confirm first). The default `assisted` expands to
today's behavior byte-for-byte, so an absent `[autonomy]` section changes
nothing. Its **first runtime effect**: a `supervised`/`autonomous`/`unleashed`
level **arms the autonomous loop** even without an explicit `[loop] enabled`
(additive — it never disarms an explicitly-enabled loop). Arming only makes the
loop *available* (a run still needs `aivyx loop start`) and takes effect only
when a `[loop]` section exists (which carries the iteration/budget caps). The
remaining dimensions (gate policy, growth) are wired incrementally — see
`docs/AUTONOMY.md`. No new capability base; a composition front end to
primitives already enforced. Settable from the **Studio Settings** screen too
(an "Autonomy" section with a level picker over a new `SetAutonomyLevel` IPC,
server-side confirm-first on the autonomy-granting levels).
- **`aivyx --headless "<task>"` — one-shot unattended runs from the CLI.** The
headless execution mode (Chapter H) was reachable over IPC and from the
operator-absent drivers, but never from the command line. This wires the
missing entry: it connects to a **running daemon**, submits one turn that
*refuses* (records the reason on the audit chain) at any approval gate rather
than parking for an operator, streams the output, and maps the turn's outcome
onto a **process exit code** for cron / batch / autonomous callers — `0`
completed, `3` refused-at-a-gate, `1` any other non-completion. No daemon
running yields a clear "start `aivyx daemon run` first" error (there is no
in-process fallback — headless relies on the daemon's gate interception). No
new tool/base/dep; the interactive paths are byte-identical when the flag is
absent.
- **Small-cycle breaker (`[agent] cycle_detection`).** A loop-safety companion
to the consecutive-identical breaker (Chapter Bridle): it catches a repeating
*cycle* of tool calls (`A,B,A,B,…`) that the consecutive counter resets on —
the one runaway shape that previously ran until the 32-step cap or the 120s
deadline. A bounded ring of recent call signatures trips when the tail is
`min_repeats` back-to-back copies of a `2..=max_period` block, reusing the
existing `Looping` outcome (no new audit surface). **Default-off** (the turn
loop stays byte-identical); operators arm it with `[agent] cycle_detection =
true` — or toggle it from the **Studio Settings screen** (a new "Agent" section
with a Cycle-breaker toggle, over a `SetCycleDetection` IPC that rewrites the
`[agent]` section; `GetSettings` now reports the current state).
- **Nonagon team agents get the cycle breaker as a built-in safety floor.** The
lead and every specialist run autonomously inside a mission — no human watches
each turn to `/cancel` a runaway — so they always get the small-cycle breaker
(like `MAX_STEPS_PER_TURN` is always on), independent of the interactive
`[agent] cycle_detection` knob.
- **Default scheduled "starter routines" at first-run.** `aivyx init` now plants
a small set of read-only, self-contained `[[schedule]]` routines so a fresh
agent orients itself and stays useful unattended: a daily **environment-review**
(self-bootstrapping — first run builds a baseline of the accessible
environment, later runs diff it and journal changes), a nightly
**reflection** (consolidate the day's memory into the knowledge base), a
6-hourly **health-check** (silent unless something breaks), a Monday
**weekly-digest** (learnings + pending proposals), and an opt-in daily
**trend-scan** (web research across the operator's interests). Each prompt is
read-only and explicitly forbids destructive actions — they run unattended.
Gated by the wizard's existing answers: the four core routines are **enabled on
a local (Ollama) provider** and written **present-but-disabled on a cloud
provider** (discoverable, a one-line flip to enable, and cost-aware since
cloud schedules spend tokens); `trend-scan` additionally requires web search.
The wizard prints what it set up, so it is never surprise behavior. Verified
live on real hardware (all five fire and execute) before shipping. See
`docs/ROUTINES.md`.
### Fixed
- **Daemon no longer panics when a runaway turn ends in `MaxStepsExceeded` /
`Looping` with failure-learning on.** A `match` building the failure summary
for the skill auto-proposer handled `Failed`/`Cancelled`/`TimedOut`/`Escalated`
but fell through to `_ => unreachable!()` for the two outcomes a *local model*
is most likely to produce when it runs away (the step cap or the cycle
breaker) — so with `[skills.auto_propose] from_failed_turns = true`, exactly
that scenario killed the daemon mid-mission. The match is now **exhaustive**
(no `_` arm), so the compiler forces every present and future `TurnOutcome` to
be handled here — a single turn can never panic a 24/7 daemon. Surfaced by the
Pass A panic-resistance audit, which otherwise found the untrusted-input parse
boundaries (LLM responses, tool-call JSON, tool inputs, IPC frames, config,
channel ingestion) already defensive.
- **`[agent]` per-turn knobs now reach the daemon agent and the role-switch
child** (the Studio + persistent chat path, and role sub-sessions).
`turn_timeout_secs` and the new `cycle_detection` were applied only on the
REPL/voice paths (`build_agent_stack`); the daemon agent and the `role.switch`
child factory built raw `ConcreteAgent`s that skipped them, so they always ran
the 120s default and no cycle breaker — a latent gap on the primary path, now
closed (the child inherits the operator's config, like its parent).
- The `channel-voice-full` feature build: the voice `AgentStackSpec` literal had
drifted from the struct (it predated `turn_timeout`), so it failed to compile
under that feature. Restored, with the new cycle-detection knob wired in.
- **`access_level = full` granted nothing — the most permissive level was the
only broken one.** `full` resolves the sandbox root to `/`, and every
root-anchored scope grant was built as `format!("{root}/**")`, which at root
`/` produced `fs.read://**` (double slash) — a glob whose leading `//` matches
no single-rooted path. So a `full`-access agent could not read or write a
single file (it would narrate a permission refusal). Fixed with a `rooted_glob`
helper that collapses the trailing slash (`/` → `/**`), routed through all five
root-anchored grant sites (`fs.read`/`fs.write`/`fs.metadata` +
`shell.exec:cwd` + `fs.delete`). Found live on a real-hardware dogfood; the
needed scope was always correct, so it hid behind a model that politely
refused. Regression test added.
- **Local thinking-model output was verbose and off-style — the `think` flag was
inverted.** For a model advertising the `thinking` capability the Ollama
provider sent `think: false`, assuming that forces the answer into `content`.
On current Ollama (0.30) that is wrong for hybrid-reasoning models: e.g.
`qwen3:30b-a3b` ignores `think: false` and dumps its whole chain-of-thought
into `content`. `think: true` instead cleanly routes reasoning into the
separate `thinking` field (which the agent already discards) and leaves the
answer — or tool_calls — in `content`. Now sends `think: true` for
thinking-capable models (verified for qwen3:8b and qwen3:30b-a3b, with and
without tools). Test updated.
- **Configured MCP servers' tools were registered but un-callable by the default
role.** Each MCP tool call needs the scope `mcp.call:<server>:<tool>`, but the
default-role capability floor granted no `mcp.call` scope at all — so the
bundled web-search server the wizard's "Enable web search?" adds was dead on
arrival (the agent saw the tool, lacked the scope, and reported it as "not
authorized"). The floor now grants `mcp.call:<server>:*` per configured MCP
bridge (mirroring the existing `ollama.*` grant) — least-privilege per server.
Roles that declare their own `capability_scopes` still name `mcp.call:<server>`
grants explicitly. Scope-matching regression test added.
## [0.7.0] — 2026-06-24
The desktop & experience release. A native desktop app, a top-to-bottom Studio
overhaul, end-user documentation, and the first ecosystem seam for vertical
packs — all over the unchanged security core (no new P10 amendment, no new
capability surface).
### Added
- **Native desktop app (`aivyx-desktop`).** A thin `tao` + `wry` shell that
hosts the Studio in a system webview (reusing the exact WASM UI, no port),
with daemon lifecycle (attach or spawn), a **system tray** (Open Studio ·
Restart daemon · Start at login · Quit) and hide-to-tray, **native
approval-gate notifications** (a background WS client watches for missions
awaiting approval), a **global hotkey** (`Ctrl+Shift+A`) to summon the window,
and launch-on-login. Packaged with `cargo-bundle` (a `.deb` on Linux, a
`.app`/`.dmg` on macOS) via a dedicated release workflow. Linux runtime deps:
`webkit2gtk-4.1`, `libayatana-appindicator`, `xdotool`/`libxdo`.
- **`aivyx-vertical-sdk`** — a thin, semver-stable facade that re-exports only
the pack-facing slice of the engine, so vertical packs survive core refactors.
The Kitchen pack is re-pointed at it as the open reference example;
`crates/verticals-private/` (git-ignored, auto-joined via a workspace member
glob) is the home for commercial packs.
- **End-user guide** — a task-oriented `docs/guide/` (welcome, getting started,
the Genesis wizard, per-feature pages, the desktop app, troubleshooting),
surfaced as a **Guide screen** in the Studio (markdown rendered in-app via
`pulldown-cmark`) with working cross-page links.
- **Studio command palette** — `Ctrl/Cmd-K` to fuzzy-jump to any screen.
- **Deep-linking** — the active screen is mirrored in the URL hash, so screens
are bookmarkable/shareable and survive a reload, and back/forward navigate.
- **Contextual help** — a topbar "?" that opens the Guide to the page for the
current screen.
### Changed
- **Responsive Studio shell** — the sidebar collapses to a hamburger drawer
below tablet width, the nav is grouped into labeled sections, and the screen
grids stack on narrow viewports.
- **Loading skeletons** across every data-driven screen (Command Center, Memory,
Skills, MCP, Teams, Documents) instead of flashing empty/zero states.
- **UI polish** — a distinct icon per nav item, a single daemon-status indicator
(was duplicated), and a corrected version footer.
- **Windows** is documented as supported via WSL2 or the Docker appliance (no
native binary yet — the daemon's IPC is Unix-socket-only).
### Accessibility
- Visible `:focus-visible` keyboard focus rings (there were none), a
skip-to-content link, `main`/`nav` landmarks, and `aria-label`s on
previously-unlabeled inputs and icon-only buttons.
### Fixed
- The CI quality gate is green again: install the desktop crate's GTK/WebKit
system deps for the `--workspace` build, and clear a `clippy::type_complexity`
lint in `aivyx-web`.
## [0.6.0] — 2026-06-21
The toolbox release. Three chapters **widen what the agent can do** without
touching the security model: a pack of exact pure-compute utilities, readers
that turn operator files into legible structured content, and the wiring that
makes the whole MCP server ecosystem an operator-config story. None adds a P10
substrate amendment or a new capability surface beyond what each tier already
allows.
### Added
- **Utilities pack (Chapter Abacus).** Five exact, deterministic helpers a
language model is structurally bad at doing in its head, in the existing
`aivyx-toolkit` process: **`calc.eval`** (a hand-rolled, zero-dependency
arithmetic evaluator — `+ - * / % ^`, parens, `sqrt/abs/round/floor/ceil/
min/max`), **`convert.units`** + **`convert.time`** (length/mass/temperature/
volume/digital via a curated table, and IANA-timezone conversion via
`chrono-tz`), and **`date.diff`** + **`date.add`** (calendar-correct date
arithmetic). Three group bases (`calc.eval`, `convert.units`, `date.compute`)
— the **first toolkit surface reachable below the Trusted tier** (SemiTrusted),
because pure compute touches no network, filesystem, or operator data.
- **Structured-data readers (Chapter Sheaf).** Three tools in the new
`aivyx-dataread` crate that turn a file the agent can already reach into
legible structured content — the file analogue of `web.extract`:
**`data.csv`** (delimited text → rows), **`data.xlsx`** (a spreadsheet's
binary zip+XML, which `fs.read` cannot expose, via `calamine`), and
**`data.pdf`** (a PDF's text layer via `pdf-extract`, no OCR). Each **reuses
the existing `fs.read` capability and sandbox** — it only parses bytes the
agent could already read, so it adds no new capability base and no new I/O
reach (infrastructure tier, no P10 amendment).
- **MCP servers that actually work, especially keyed ones (Chapter Conduit).**
The MCP client could connect to servers but not *authenticate* to them, and a
misconfigured server failed silently. Conduit adds **`[[mcp_server]] env`**
(secrets to a stdio child — the GitHub MCP server's
`GITHUB_PERSONAL_ACCESS_TOKEN` was previously unconfigurable) and
**`[[mcp_server]] headers`** (e.g. `Authorization: Bearer` to a remote server,
never overriding protocol-reserved headers), both with **`${VAR}`
interpolation** resolved from the daemon environment so secrets stay out of
`aivyx.toml`. Stdio **stderr is now captured** (last 50 lines) instead of
discarded, and **`aivyx mcp status`** reports each configured server as
connected (with its tool count) or failed (with the reason and captured
stderr). No new capability base, P10 amendment, or dependency — the
GitHub/weather/Google-Tasks integrations are now operator config, not in-tree
builds.
## [0.5.0] — 2026-06-21
The local tool-calling release. A small local model (an in-process GGUF on the
mistral.rs engine) can now reliably **drive the agent loop to completion** —
emitting a valid, real-named tool call by construction, then finishing its turn
instead of looping. Both chapters are **opt-in and byte-identical by default**;
no new tool, capability base, P10 amendment, or dependency.
### Added
- **Reliable local tool-calling (Chapter Stencil).** Small local models are
fragile on the agent loop — they hallucinate tool names and emit malformed
arguments, and four prompt-substrate phases proved it can't be fixed from the
prompt. Stencil adds the lever the prompt can't reach: **grammar-constrained
decoding** on the in-process mistral.rs engine. With **`[mistralrs]
constrain_tool_calls = true`** (default off), the decoder is constrained to a
JSON-Schema grammar built from the registered tools, so a small GGUF emits a
valid, *real-named* tool call — or a `respond` text escape — **by
construction**, not by hoping. Live-proven on Qwen3-4B: it called real
`fs.read` + `memory.write` with schema-valid arguments where prior phases
never got it to invoke a write tool at all. No new tool, capability base, P10
amendment, or dependency; byte-identical when off.
- **Hardened local tool-calling (Chapter Bridle).** The harness that makes
Stencil's primitive usable in the wild — a grammar that forces a valid call is
necessary but not sufficient if the model then can't stop calling it (Stencil's
live run watched a constrained model loop on one tool call until the deadline).
Three guarded, default-safe fixes: **(A)** a turn-loop **repeated-call breaker**
that stops a turn after *N* consecutive identical tool calls (default 3) with a
distinct `[turn stopped: repeated tool call]` outcome — a generalizable safety
net for any local model, on by default because it only fires on identical
repeats; **(B)** a constrained-mode **`respond` preamble** that teaches the
model how to reply in plain text and finish (the root-cause fix for the loop);
and **(C)** an operator-configurable **`[agent] turn_timeout_secs`** so a
legitimately slow local backend can complete a turn (unset → the 120s default,
byte-identical). Live re-verified on Qwen3-4B: the looping scenario now ends
cleanly — one tool call, a plain-text answer, a completed turn. No new tool,
capability base, P10 amendment, or dependency.
## [0.4.0] — 2026-06-20
The memory + skills release. Everything below is **opt-in and byte-identical by
default** — nothing changes for an existing config until you enable it. The
agent's memory now compounds (graph-augmented recall + a knowledge-wiki + a
typed knowledge graph, all behind one `[memory] profile` switch), and its
skills now learn (they sharpen when they underperform, the agent authors new
specialized ones from its own knowledge, and a Studio Skills screen makes the
whole repertoire legible). Plus a pre-release cleanup sweep that cleared the
chapter deferrals and stabilized the test suite.
### Added
- **The Skills library (Chapter Repertoire).** A new Studio **Skills** screen
(the thirteenth) gives the agent's skills a home: every skill — operator-
taught, agent-authored (Praxis), agent-refined (Whetstone) — with its
effectiveness (the WH.2 decayed EWMA as a bar + bucket label), provenance
badge, `domain`, version, and lineage, and the full procedure on demand. A
banner links pending skill proposals to the Agents screen (where their
approval already lives). Read-only over a new `GetSkills` IPC; no new
capability base, tool, or storage domain.
- **Skills authored from knowledge (Chapter Praxis).** The agent now writes its
own **specialized skills** from what it has learned: on the reflection cadence
it finds a topic with rich, connected knowledge (a substantial wiki page + a
typed-graph neighbourhood) but no skill, and proposes a grounded specialized
skill synthesized from that page + those relations (tagged with the topic's
`domain`, agent provenance). Like Whetstone's refinements, it's a governed,
propose-only persona proposal in the existing Agents UI. Opt-in via
`[skill_authoring]`, off by default; reuses the wiki/graph stores — no new
agent tool, capability base, or storage domain.
- **Skills that sharpen (Chapter Whetstone).** Skills are no longer a static
list. Each turn folds its skill outcomes into a decayed per-skill
effectiveness ledger, and on the reflection cadence the agent **proposes a
refined version** of an underperforming skill — the operator's *or* its own —
as a governed supersession pair (provenance + lineage) that surfaces in the
existing Agents approve / edit / reject UI. Opt-in via `[skill_refinement]`,
off by default, propose-only; no new agent tool or capability base.
- **One-switch smart memory (Chapter Synapse).** The whole memory stack above
(graph-augmented recall + the knowledge-wiki and typed-graph layers + their
extraction sweeps) was opt-in and spread across ~14 knobs. **`[memory] profile
= "smart"`** now arms the coherent bundle with one line (explicitly-set knobs
still win; the default stays `off` ⇒ byte-identical). Plus the end-to-end
integration proof the layered arc lacked — the real memory + wiki synthesizer
+ graph extractor + `graph.query` + recall fusion, verified to compose into a
single turn — and an operator live-verify runbook.
- **Typed knowledge graph (Chapter Lattice).** A real, **directed, typed**
graph the agent extracts from memory: nodes are entities, edges are directed
`(subject)-[predicate]->(object)` relations (`deploy` —*depends-on*→ `ci`),
stored in a new encrypted `KnowledgeGraph` domain. The agent can **query** it
with a new **`graph.query`** tool (a multi-hop typed traversal — "what depends
on X?", "what did Y cause?"; gated by a new `graph.read` *infrastructure*
capability base, no P10 amendment); a new **Studio "Graph" screen** browses
the directed graph; and `recall_graph_typed_weight > 0` lets the typed
relations steer recall along *meaningful* edges (vs. mere co-occurrence).
Opt-in via `[graph].enabled` (a periodic extraction sweep); always derived
from memory; zero new dependencies. **Chapter Lexicon** then gives the graph
a **controlled relation vocabulary** — a curated set of canonical relation
types (`depends-on`, `causes`, `part-of`, …) that synonymous predicates fold
into, so `depends on` / `requires` / `needs` become one edge instead of three
(applied at extraction + query, with a sweep that merges existing synonyms);
unknown relations are kept as-is.
- **Knowledge-wiki layer (Chapter Codex).** A derived, browsable layer over
memory: the agent consolidates each topic's entries into a **`WikiPage`** —
an LLM-written summary plus co-occurrence **backlinks** — persisted in a new
encrypted `KnowledgeWiki` storage domain. A new **Studio "Wiki" screen**
browses the pages (index → summary + clickable backlinks + source-entry
count) over read-only IPC. Opt-in: `[wiki].enabled` arms a periodic
stale-page sweep on the maintenance cadence, and `recall_wiki_weight > 0`
lets a page summary compete in recall as a single high-signal unit. Pages are
always *derived* — memory stays the source of truth. Zero new dependencies.
- **`web.extract` + `git.commit` (Chapter Forge).** Two new substrate tools:
`web.extract` returns a page's readable article text (readability over the
existing `net.fetch` capability), and `git.commit` stages + commits in an
operator-allowed repo (a new `git.write` capability base, Trusted-tier only,
confirm-first). The substrate count moves 13 → 15 (**Amendment A13b**).
- **`tools.list` runtime tool introspection (Chapter Atlas).** A refinement
pass over the ~92-tool surface: a runtime `tools.list` tool, a drift-guarded
[`docs/TOOLS.md`](docs/TOOLS.md) catalog, and a tool-metadata quality guard.
- **Permissive voice (Chapter Timbre).** Swapped the GPL Piper TTS for
**Kokoro-82M (Apache-2.0)** + an espeak-free MIT G2P, closing the last GPL
door (`cargo deny check licenses` clean with no copyleft exception).
### Changed
- **Graph-augmented recall (Chapter Loom).** Auto-recall now fuses three signals
on one ranking via weighted Reciprocal Rank Fusion: **semantic** (vectors),
**lexical** (a real BM25 scorer, replacing the old substring match), and a
**multi-hop co-occurrence graph-walk** (the agent's topic-affinity graph,
promoted from a passive view to an active retrieval signal). Tunable per
source under `[embedding]` and proven by a `recall@k` eval harness; default
off ⇒ identical to prior recall.
## 0.3.0 — source-available (BUSL-1.1) (2026-06-19)
**The headline is the license.** Aivyx moves from **MIT** to the **Business
Source License 1.1 (BUSL-1.1)**: the whole public workspace is now
**source-available** — **free for personal, individual, and non-commercial use**,
with a **paid commercial license for business or production use** — and **every
released version auto-reverts to MIT four years after it ships.** BUSL-1.1 is
source-available, *not* OSI "open source," and we no longer call it that.
**v0.2.0 and every prior release remain MIT in perpetuity** — a license can't be
revoked; the relicense applies from this tag forward. See [`LICENSE`](LICENSE),
[`COMMERCIAL.md`](COMMERCIAL.md), and [`docs/LICENSING.md`](docs/LICENSING.md)
(model + FAQ). This release also lands three breadth chapters since the Studio:
Contacts, Genesis, and the Docker appliance (Harbor).
### Changed
- **Relicensed MIT → BUSL-1.1 (Chapter Charter).** `LICENSE` is the full
canonical BUSL-1.1 (Additional Use Grant = personal/non-commercial; Change
Date = 4 years per release; Change License = **MIT**, preserved at
[`LICENSES/MIT.txt`](LICENSES/MIT.txt)). A dependency-license audit (`cargo
deny check licenses`, all-features) confirmed no copyleft poisons the combined
work. Every "open source"/"MIT" claim about Aivyx's own code is now
"source-available under BUSL-1.1" (README, TRADEMARK, and DESIGN.md's
open-core deliverable via **Amendment A14**).
- **Contributing now requires a CLA** ([`CONTRIBUTING.md`](CONTRIBUTING.md) +
[`CLA.md`](CLA.md)), accepted via a `git commit -s` sign-off — necessary so the
free + commercial dual model can lawfully cover contributed code.
### Added
- **Google Contacts (Chapter Contacts).** A new `aivyx-contacts` tool process
(People API) with six tools over `contacts.read`/`contacts.write`
(`contacts.search`/`list`/`get`/`create`/`update`/`delete`); connect with
`aivyx connect contacts`. The fifth Google integration on the substrate
pattern. See [`docs/CONTACTS.md`](docs/CONTACTS.md).
- **Unified agent creation (Chapter Genesis).** One onboarding flow across CLI
and web: the Profile drafter is lifted into the daemon (`DraftProfile` IPC)
and a "Create your agent" flow (Profile → Persona → access) lands in the
Studio. See [`docs/ONBOARDING.md`](docs/ONBOARDING.md).
- **Docker server appliance (Chapter Harbor).** A `docker-compose` deployment of
the daemon + Studio in a container (distinct from the desktop local-first
install), with two opt-in web-exposure knobs (`web_ui_host`,
`web_ui_allowed_origins`); CI publishes the image to GHCR. See
[`docs/DOCKER.md`](docs/DOCKER.md).
- **Tool-call rate limits & quotas (Chapter Throttle).** A third dispatch gate —
after the capability + role gates, before execute — bounds *how often* tools
run, the sibling of Chapter K's dollar budgets for call counts. Opt-in
`[rate_limit]` config sets per-turn-per-tool, per-turn-total, and per-tool
sliding-window caps with an `alert` (warn + proceed) or `deny` (block) action;
a throttled call yields a forensically-distinct `ToolOutcome::RateLimited` and
a dedicated `RateLimited` audit record (separate from capability/role denials).
Bounds a runaway turn — autonomous loop, Nonagon mission, or interactive — from
hammering `web.fetch` / `shell.exec`. Uncapped by default, so existing configs
are unchanged. See [`docs/RATE_LIMITS.md`](docs/RATE_LIMITS.md); closes backend
audit finding **F2**.
### Fixed
- **Storage-domain count corrected (21, was documented as 20)** — the Phase-183
`Reminders` `KeyDomain` was never propagated to the docs. Added a
`key_domain_count_matches_docs` drift-guard test (audit **F7**).
### Security
- **The Studio `/ws` bridge now enforces an `Origin` check** (closes a
Cross-Site WebSocket Hijacking / DNS-rebinding vector). A loopback bind is
not a boundary against the browser — WebSockets are exempt from the
same-origin policy — so a malicious page the operator visited could
otherwise open `ws://127.0.0.1:7843/ws` and drive the already-unlocked
daemon, which since the Studio's write screens can change config and the
filesystem. The `/ws` upgrade is now accepted only when `Origin` is absent
(a non-browser client, already inside the trust boundary) or exactly matches
a loopback origin on the bound port; cross-site, rebinding, wrong-port, and
`null` origins are rejected with `403`. See `THREAT_MODEL.md` §4.11.
## 0.2.0 — the Studio (2026-06-16)
The headline is the **Studio** — the daemon's web GUI grew from two tabs into a
**complete** local-first mission-control surface, one screen per chapter (R–Z
plus Voice), every one live-verified in a real browser with its WASM bundle
committed. The entire screen inventory is now live: Command, Missions, Chat,
Memory, Settings, Agents, Teams, Documents, Voice.
### Added
- **The Studio web GUI is complete.** Every screen is live, offline, and
Stitch-styled, served from the daemon's embedded bundle on `:7843`:
- **Command Center** (S) — the default dashboard: stat cards, active missions,
a live audit-trail feed, agent status.
- **Memory** (T) — topic rail + entry cards + keyword/semantic search over the
self-learning memory, **plus a knowledge-graph view** (MG): a real weighted
graph (nodes = topics, edges = the co-occurrence ledger's pair scores) laid
out in-WASM with a deterministic force-directed simulation; click a node to
filter its entries.
- **Settings** (U) — the **first config-write surface**: edit the access level
(confirm-first, enforced server-side) and budgets; section-scoped `toml_edit`
rewrites with a `ConfigChanged` audit and an honest "restart to apply".
- **Agents** (V) — a direct **Profile** editor plus the self-learned **Persona**
governance loop (approve / edit / reject proposals, revert deltas), live.
- **Teams** (Y) — a read-only view of the active **Nonagon** roster.
- **Documents** (Z) — a **file browser and editor** over the agent workspace and
the access-scoped `fs_root`: read files, and (DW) edit + save, create
files/folders, rename, and delete.
- **Voice** — a `[voice]` config editor with a daemon-side readiness check
(ASR/TTS model + espeak data: present / missing / unset) and the launch
command. Voice itself is a host-local CLI loop (`aivyx --channel voice`), so
the screen configures it rather than doing browser audio.
- **Onboarding Persona/Skills seed (Chapters W–X).** The end user can give the
agent a starting Persona + Skills — by hand or by **describing it in words**
(the model drafts it) — at first launch (`aivyx init` → `[persona_seed]`,
planted on the signed chain at boot iff empty, adopted turn-one) or live from
the Studio. See [`docs/PERSONA_SEED.md`](docs/PERSONA_SEED.md).
### Security
- **Documents browsing never escapes its root.** `ListDir`/`ReadFile` reuse the
fs tools' lexical-resolve → canonicalize → `starts_with(root)` guard, so `..`
and symlink escapes are rejected over IPC; reads are size-capped + binary-aware;
the `fs` root is exactly the operator-granted access level.
- **Editable Documents is the most safety-sensitive surface, and gated to match
(DW).** Writes canonicalize the *parent* dir (a new file can't be canonicalized)
and re-check `starts_with(root)`; `WriteFile` carries an explicit `overwrite`
flag (no accidental clobber); rename/mkdir refuse existing targets; `DeleteFile`
is **empty-only, never recursive, and always requires `confirm: true`** (the web
shows a confirm modal). Writes are atomic (temp-file + rename), and **every
mutation is recorded as a signed `DocumentMutated` audit event.**
### Fixed
- The `web_asset_lookup` unit test asserted the bundle was *absent*, which has
been false since the bundle became committed in Chapter R; rewritten for the
shipped reality. The full workspace test suite is green (4,666 passing).
## 0.1.0 — first public pre-release (2026-06-13)
**This is an early pre-release.** Aivyx is a capable, actively-developed personal
agent, but `0.1.0` is its first published binary and has not yet had external
users. Expect rough edges, breaking changes between releases, and gaps in the
docs. Try it, file issues — but don't depend on it for anything critical yet.
### What Aivyx is
A local-first, capability-secured personal AI agent that runs as a daemon on
**your** machine. Its headline path is **"runs on your hardware, no API key"**
via [Ollama](https://ollama.com) — cloud providers (Anthropic, OpenAI) are
optional. The agent has a persistent, self-learning Profile/Persona/Soul, an
HMAC-chained audit log of everything it does, and a capability/trust-tier model
that bounds exactly what it can reach.
### Highlights in this release
- **Local-first on-ramp that just works.** Pick Ollama in `aivyx init` and you
get a working, tool-using first conversation with **zero manual config**:
context length is auto-detected from the model (no more starved `num_ctx`
one-token replies), the wizard recommends and offers to download a vetted
tool-capable model, and `aivyx doctor` verifies the whole path end to end.
- **Operator-chosen access levels.** *You* decide how far the agent reaches —
from a sandbox, to your home directory, to full-machine access — via
`aivyx access` and the init wizard. Irreversible filesystem operations are
confirm-first; remote channels are automatically attenuated.
- **The agent's own workspace.** A private, always-available directory
(`~/.aivyx/workspace`) the agent uses for its own thoughts, ideas, plans, and
projects, with optional proactive journaling.
- **Multi-agent teams in the daemon.** Goal → plan → delegated execution with a
live TUI feed and human approval gates.
- **A browser Mission-Control GUI.** A Rust→WASM (Dioxus) web client that speaks
the daemon's wire protocol directly — Missions and Chat in the browser.
- **Productivity integrations.** Gmail, Google Calendar, Google Drive, Notion,
Obsidian, n8n, and a web/task/health toolkit, each as an isolated tool process
with operator-provided OAuth.
### Install
```sh
curl --proto '=https' --tlsv1.2 -LsSf \
https://github.com/Aivyx-Agent/aivyx/releases/download/v0.1.0/aivyx-cli-installer.sh | sh
```
Then run `aivyx init` to set up your agent. See `docs/INSTALL.md` for building
from source and `docs/LOCAL_FIRST_RUN.md` for the local-model path.
### Platforms
Prebuilt binaries for Linux (x86_64, aarch64; musl-static) and macOS (x86_64,
aarch64). Windows is not yet supported (the daemon currently uses Unix domain
sockets). All platforms can build from source.