Unreleased
next release
Added
- The models page on veyyon.dev lists every provider and model veyyon supports, read live from the bundled catalog and auto-refreshed from the repository; bun run site:build regenerates website/models-data.json from packages/catalog/src/models.json.
- A bare interactive launch replays the previous launch's card from a cache before the CLI's import graph is evaluated, then adopts those rows and corrects only what changed. bun scripts/bench-startup.ts --runs 5 --bin packages/coding-agent/dist/vey reports the card's first byte at a 34-35ms median composed and a 15ms median replayed. The recording is discarded unless the terminal size, the environment this process received and the binary's path, size and modification time all still match, and it ages out after 24 hours; a launch whose composed card disagrees with the replayed rows drops the recording so the next launch records a fresh one.
- bun scripts/bench-startup.ts gains a replay arm and keeps the first-frame recording inside its scratch directory. The recording resolves its path from os.homedir(), which Bun fixes at process start, so the bench's seeded HOME did not reach it and a run read and overwrote the operator's own cache.
- VEYYON_REPLAY_DEBUG names a file the launch appends its replay decision to. A rejected recording is otherwise indistinguishable from a slow launch, and the logger does not exist yet at that point.
- /rephrase asks for the reply on screen again in plainer prose, and refuses unless the conversation is resting on a finished reply.
- /autoswarm opens a setup console for the goal, breadth, attempts and certification, then runs autoresearch with breadth: each iteration builds several candidate arms, rejects the ones that are empty, out of scope, unreadable or duplicates, has the survivors cross-review each other, and keeps at most one; /autoresearch is unchanged and still serial.
- Autoresearch and autoswarm have handbook pages.
- Machine-wide resource limits cap CPU, memory, disk writes and process count across every veyyon process at once, beside the existing per-session limits, in /settings under Resources; both scopes default to no limit.
- The two resource-limit scopes share one definition of each cgroup control-file format, with no user-visible change: the duplicate the machine scope carried while unreleased could write a freeze quota for a very small CPU budget.
- bun run test:cgroup-proof drives both resource-limit scopes against a real kernel outside the test sandbox and reports each cap as held or not, refusing with a named reason on a host that cannot delegate cgroups rather than passing having proved nothing.
Changed
- The hidden messages a session injects and the gates that fire them live in session/nudges.ts rather than as file-private literals in agent-session.ts, and the mid-run todo and prewalk gates key their tool tables through the TOOL registry so renaming a tool now fails to compile instead of silently disabling the nudge.
- The subagent model resolver reads the shared-model switch once and builds the layers of whichever scope that selects, instead of gating each layer against a second reading of it, and the unread SUBAGENT_MODEL_SCOPE_VERSION constant is gone from the rejected-setting log record. No change to what any subagent runs.
- The compaction policy vocabulary (check outcomes, the bar a pass is measured against, the truncation edge budget, the prune cache window and idle flush, and the recovery band) lives in session/compaction-policy.ts rather than interleaved with shutdown timeouts and credential backoffs in agent-session.ts, with no behavior change.
- The thinking display's hidden-code fence marker is named hiddenCodeMarker, no longer colliding with the unrelated elisionMarker in @veyyon/utils/byte-truncate; the marker text is unchanged.
- The read tool's URL routing, internal-URL routing, notebook reading, document conversion and structural summarizing each live in their own named method instead of inline branches of one 758-line execute, with no change to what any read returns.
- The doc comments on the agent registry's conversation scope and address check, and on the subagent model and thinking-level resolvers, state the precedence and the boundary rule without the surrounding narrative; no behavior changes.
- The queued-message predicates and the retry.fallbackChains selector vocabulary live in session/queued-message.ts and session/retry-fallback.ts instead of as file-private helpers in agent-session.ts; RestoredQueuedMessage is imported from the first of those.
- The ACP permission gate's argument reading — which tools need consent, the prompt each call earns and the file locations it points at — lives in session/permission-intent.ts instead of as file-private helpers in agent-session.ts, which also drops that file's private copy of getStringProperty in favour of the @veyyon/utils owner. No change to what is gated or prompted.
- The launch card paints about 8ms sooner: the bundled themes are embedded as text and parsed on the ask instead of building all 98 before the first frame, and the card path no longer evaluates node:assert/strict, node:crypto, node:inspector, node:child_process or node:zlib for calls it does not make.
- The status row reads its truncation limits from tools/render-limits, a leaf that imports nothing, rather than tools/render-utils, which drops the tool renderers, path helpers and image resizing from the launch card's import graph; first-frame time is unchanged, because those modules only declare functions.
- The home screen hero drops the recent-session row; /welcome still lists recent sessions.
- The launch card paints the whole status row from config instead of a hand-written path and branch, so the profile, model, approval rung, branch and context gauge are on screen with the first frame; measured on a pty against the built binary, the row lands at 47-48ms rather than 1067-1083ms and the frame is editable at 49-50ms.
- The status row's segment gathering and row fitting live in one module, modes/components/status-line/quiet-row.ts, which the live footline and the launch card both render through, so the two rows cannot carry different segments or order them differently.
- A status-line segment reads a flat SessionFacts value block rather than the AgentSession itself, which is what lets the same segment table render before a session exists. No change to the mounted row.
- bun scripts/bench-startup.ts renames its statusline arm to statusrow and times the status row being on screen; the old arm timed a segment only a session supplied and now reports nothing, because the card supplies the row.
- bun scripts/bench-startup.ts times its statusrow and replay:statusrow arms against the approval rung on the row rather than the context gauge, which is the row's last segment and is shed on the bench's eighty-column pty: both arms reported no samples at all. They now read a 39ms median composed and 15ms replayed against the built binary.
- The autoswarm setup console and the autoresearch experiment tool clamp their breadth and attempt counts through the shared clamp rather than local copies. No behavior change.
- VEYYON_TIMING reports the window between process start and the launch card instead of hiding it: the tree now starts at the CLI entry and carries spans for the command load, the launch-card import, the prologue, settings, the theme and the paint, leaving only Bun's own start and the entry's static imports under (before instrumentation).
- The launch card arrives in about half the time. The binary is now code-split, so the standalone loader links the CLI entry and the launch card instead of the bytecode of every subcommand, tool and agent-runtime module before the first statement runs, and whitespace and syntax minification are on. Measured warm on a pty, the card's first byte goes from 138-151ms to 57-72ms, the first keystroke echoes at 111ms instead of 188-207ms, and the binary is 231.7MB instead of 296.9MB. Function names are still kept, so a stack trace is unchanged.
- The six modules on the launch path that reached for the @veyyon/utils barrel now import the subpath that owns what they use, so painting the card no longer evaluates the YAML parser, Handlebars and the prompt-variable layer that the barrel re-exports. The launch card's import graph drops from 311 modules to 268, its import span from 32.5ms to 20.2ms, and the card's first byte to 50-58ms.
- The launch shell no longer evaluates a workspace barrel. The launch card, the editor and the status row reached @veyyon/tui, @veyyon/utils and @veyyon/agent-core for a handful of symbols, and each barrel is export * from over its whole package; every such edge now names the leaf that owns the symbol. Three modules were split along the same line: internal-urls/resolve-sync.ts takes the URL resolver out of the OSC 8 writer, modes/image-reference-markers.ts takes marker rendering out of the blob store, and modes/magic-keyword-notices.ts takes the three hidden notices out of keyword detection. Module evaluation for the three shell entries drops from 147ms to 63ms, the editor from 36ms to 3.6ms and the status row from 78ms to 6.5ms.
- The status row reads its premium-request formatter from @veyyon/utils/format instead of @veyyon/stats/format, so painting the card no longer evaluates the stats package. No visible change to the row.
- The status row's non-message token accounting lives in session/non-message-tokens.ts, so painting the launch card no longer evaluates the compaction layer or the tokenizer through session/context-usage.ts, and config/inline-tool-descriptors-mode.ts reads modelFamilyToken from @veyyon/catalog/identity/family instead of the identity barrel. No change to the counts the row reports.
- The host capability probe and the environment it measures against moved out of the session budget module into session/cgroup-host.ts, and the capabilities a probe reports no longer carry the field it used to pick a cgroup parent. No behavior change.
- The first-frame recording is written by startup/first-frame-recorder.ts through the shared atomicWriteFileSync instead of a hand-rolled temp-and-rename, so the cache file is created owner-readable only; the replay reader on the boot path still reaches node builtins only.
- The launch card paints the working directory on the status row instead of leaving it blank until the session mounts. Measured on a pty, the row named the directory at 59-62ms with the card rather than at 1067-1083ms; the model, mode and context gauge still arrive with the session, to the right of it.
- The status row's location is rendered by one owner, modes/components/status-line/location.ts, which the live row and the launch card both call, so the two cannot drift. shortenPath and sanitizeStatusText moved to their own modules, re-exported from tools/render-utils.ts and modes/shared.ts; defaultDisplayRoots and resolveDisplayRoots are now imported from the location module rather than from segments.ts.
- The launch card paints the git branch on the status row beside the working directory. Measured on a pty, the branch appeared at 59-66ms with the card rather than at 1067-1083ms.
- The status row's branch is rendered by one owner, modes/components/status-line/branch.ts, which the live row and the launch card both call, so the two cannot drift.
- Which segments a status-line preset shows is resolved by one function, resolvePresetSegments, so the launch card honors the same preset and git.enabled rules the mounted row does. No behavior change to the mounted row.
- The branch a repository is on can be read from .git alone through utils/git-head.ts, without running git; utils/git.ts composes that reader with the git symbolic-ref fallback a reftable repository needs, rather than keeping a second copy of the file parsing.
- StatusLineComponent.watchBranch is watchGitState: one repaint request for every git read the row is painted from, rather than a name that described only the HEAD watcher. No user-visible change.
- The model and effort a subagent runs are chosen on one of two exclusive scopes. Subagents → Same Model for All Subagents is off by default, and each agent's page under Roster decides; on, Shared Model and Shared Effort decide for every agent while the per-agent rows are hidden and keep what they hold for when the switch goes off. An agent naming neither runs the profile's default model role at medium effort, and subagent.modelByDepth decides nothing in either scope.
- A subagent definition that names a tool nobody recognizes is reported with the file and the unknown names instead of loading an agent with no tools and a prompt pruned of everything.
- User-authored subagents are discovered from ~/.veyyon/subagents/, shared across profiles, and enabled per profile.
- The compaction loader names the engine on every pass, not only a remote one: "Compacting context... (local compaction)", "(openai remote compaction)", "(azure remote compaction)" or "(codex remote compaction)". A codex or azure server-side pass used to be announced as openai, and a local pass was not announced at all.
- The model the advisor runs is asked in one place: Model → Advisor → Advisor Model, directly under Enable Advisor, hidden while the advisor is off. advisor is gone from the Roles table, which was a second surface for the same slot under a different name; the slot itself is unchanged, so @advisor and any existing modelRoles.advisor keep working.
- The Advisor rows are contiguous in the Model tab: the Prewalk rows were declared between them.
- The resolved-model snapshot a warm launch restores is verified against the bytes it was written from instead of being parsed and serialized back to check itself. The file is now a header line then the stage payload, and reading it costs 14.6ms instead of 27.6ms measured on a 9.2MB snapshot; a snapshot written by an earlier version misses and is rebuilt.
- The launch card paints the real composer instead of a drawing of one. Typing at the card edits the editor the session goes on to use, so nothing has to be replayed into it at handover, and a submit typed before the session exists no longer discards the draft. Measured warm on a pty, a keystroke at the card echoes 3.2ms after the card's first byte.
- The settings store holds no database handle. AgentStorage.forAgentDir is the one owner of the run's agent.db and opens it on first use, config/legacy-agent-db-settings.ts owns the first-run read of the pre-config.yml settings table, and the launch card no longer evaluates bun:sqlite or the SQLite credential store to read a setting. Measured warm on a pty, the card's first byte goes from 50.5ms to 42.4ms and a keystroke is echoed at 45.6ms instead of 53.6ms; a database that will not open now costs usage statistics with a logged reason rather than failing the launch.
- The three hidden magic-keyword notices are in session/magic-keyword-notices.ts rather than under modes/, which was the one edge from the session into the UI directory with no drawing behind it. No behavior change.
- The status row no longer carries the secrets segment. The secrets id is gone from every preset and from statusLine.segments, and a configuration naming it is rejected; /secret list states what a session has masked.
- The roster states that an operator may write an agent, and names docs/features/subagents-authoring as the instructions.
- The subagent authoring page states which frontmatter key spellings are read: thinkingLevel and thinking-level reach the same field, an underscore does not, and the bundled definitions use the dashed form.
- The rewind and checkpoint entry readers and the side-channel reply bound live in session/rewind-checkpoint.ts and session/ephemeral-reply.ts instead of as file-private helpers in agent-session.ts; the doc comment describing the Anthropic request metadata payload is attached to the function it describes rather than to the tool-order check below it. No behavior change.
- session/content-text.ts is gone; the session modules that flattened content blocks call the @veyyon/utils owner, which now carries the options that copy held. Two implementations of the same flattening each documented themselves as the only one.
- The MCP command controller races its OAuth login and its connection wait through withTimeout and raceWithTimeout in @veyyon/utils rather than a file-private copy, and the protocol probe's truecolor bar converts hue through hsvToRgb in @veyyon/utils/color rather than a second implementation of the same conversion. Both emit what they emitted before, byte for byte.
Fixed
- A session on a model whose thinking blocks are bound to their conversation prefix, such as Claude 5.1 and later, leaves an already-sent tool result in place instead of superseding it mid-history, which invalidated every thinking block recorded after it and drew a 400 on the next turn.
- The installer refuses to replace a binary whose only ownership record is a pre-identity v1 receipt, instead of moving it aside. That receipt vouches for the path alone, so a user who deleted the installed binary and put their own file at the name left exactly one behind, and it was being read as permission to displace their file.
- veyyon agents unpack writes to ~/.veyyon/subagents, the directory subagent discovery reads, instead of the profile's agent/agents dir, where an unpacked definition was reported as written and then never loaded.
- veyyon config set and veyyon config reset no longer exit 1 with an ENOENT on a profile that has never been launched: the save now creates the profile's agent directory before it takes the config file's lock, which refused a parent directory that did not exist yet.
- A /btw or /omfg reply long enough to be truncated no longer ends in a replacement character when the cut lands inside an emoji: the trim counts UTF-8 bytes but removes UTF-16 code units, so it could stop on half a surrogate pair.
- A malformed content block no longer opens a blank line in a session listing or in rendered history. A text block whose text is absent or is not a string contributed an empty part, so the join placed a separator beside it, while a thinking or tool block carrying just as little text was skipped outright.
- The composer hairline and the transcript rules no longer change shade about half a second after the launch card appears: the card mixes them out of the background this terminal reported on the previous launch instead of a static token, and tui.paintGround on auto decides the paint from that same recorded background rather than repainting the whole window when the terminal answers. The background is recorded per terminal in cache/launch-facts.json, whose shape version is now 4.
- The launch card's context gauge no longer jumps when the session mounts in a project it has never measured: the reading filed under the model is now that reading with the measuring project's context files subtracted, so a card seeded in a heavy repository no longer states 77% left where the session settles at 88%. A project's own reading is unchanged and still wins where it exists.
- The session mount no longer forces a full-viewport repaint over the launch card, so the screen no longer flashes and darkens at handover; the mount now writes only the rows whose content changed.
- The launch card states the model name, git state and context percentage recorded at the end of the last launch instead of placeholders, so the status row and hero do not change when the session mounts; each fact falls back to its placeholder when the release, the model or the project changed.
- The launch card records what a project knows and what a model knows separately, so working in two projects no longer erases both and leaves the context gauge reading ? on every start, and a project opened for the first time states the model's display name and effort instead of a raw id. The file is versioned, each map is bounded to its 24 most recently written entries, and a copy written by a previous shape is discarded rather than read.
- The launch card states the context gauge in a project it has never measured, using the at-rest reading the configured model last took anywhere, so a project opened for the first time draws a real bar instead of ? left and an empty one; the project's own reading still wins where it has one, and the gauge falls back to ? only until that model has idled once.
- The launch card states the effort the last launch ran at, so the status row no longer grows a @high tail when the session mounts and shifts every segment beside it; the rung is dropped when the model changes and erased when the row stops printing one.
- The launch card prints the configured model id's last path segment rather than the whole qualified id, so a namespaced id no longer costs the status row its context gauge on the first launch of a project.
- The launch hero states the configured model instead of no model yet · /login when no display name has been recorded yet.
- The context percentage the next launch states is recorded only while the session is running the configured default model, so a session started with --model, switched with /model before anything was sent, or fallen back to another model no longer leaves the card a gauge measured against a window its model does not have.
- The context gauge renders every percentage at one width, so a reading that arrives or changes no longer shifts the status row beside it.
- The launch card lays its status row out against the same width the live row uses, so a narrow terminal no longer shows the card keeping a segment the running session immediately drops.
- The screen no longer shakes while an answer streams into a viewport it has not filled: each streamed chunk repaints its own block alone, and the anchor fill above it is now resized in that same frame rather than reused at the previous frame's height, which composed one row past the viewport and moved the window per row of the answer.
- The composer no longer lifts off the bottom row for a frame when content collapses (a tool card closing, the working indicator retiring): the anchor is sized from the children about to render rather than from the frame that already composed, so the placement no longer needs a second paint to correct it.
- Context budgeting is unchanged from 1.3.0: the unreleased reserve for the model's output allocation is withdrawn, because subtracting it from the usable window moved every model's compaction threshold, roughly doubling how often compaction fired and invalidating the prompt cache on each pass.
- A turn too large for compaction to summarize is truncated in the middle, keeping the head and the tail, instead of pausing automatic maintenance; the removed text is written to a recovery artifact the notice names. A session whose newest turn was a single oversized message could previously make no progress, and rewinding the tree did not clear it.
- A payload the outbound secret scan refuses for its size is treated as a context overflow, so the session compacts and retries instead of stopping at "the provider request exceeds the confidentiality scan byte limit" on every attempt. The scan runs before the request is sent, so nothing else had reported the turn as too large.
- An image a kitty-protocol terminal cannot be handed reports the format as the reason instead of claiming images are switched off, and a picture whose conversion is still running no longer prints a placeholder that a moment later becomes the picture.
- A key pressed before the launch card appears is drawn into the card's composer about a millisecond later instead of 156ms later, so the composer no longer sits on screen ignoring what is typed into it while the main module loads.
- The status row's dirty marker appears when git status answers instead of waiting for whatever redraws next, which in a resting session is the next keystroke; the row had been showing a clean branch over a tree nothing had looked at.
- The status row keeps the dirty marker the launch card painted instead of dropping it for the width of its own git status, so a handover on a dirty tree no longer shows the branch change colour twice; a scan that finds the tree really did move still repaints it once.
- A turn that ends on text after a tool call is recognized as finished even while a session subscriber is still running, so the todo reminder, the rewind pass and the session-stop hooks no longer skip a turn whose final message arrived behind a slow subscriber.
- A memory limit pins the capped subtree's swap to zero, so the cap bounds the whole anonymous footprint; while unreleased a 256 MB machine cap let a single process reach 5,520 MB by swapping.
- The machine limit requires a parent that delegates two cgroup levels, so a host that delegates one — a container whose cgroup root holds processes — reports per-session limits held and the machine tier unheld, instead of reporting a machine cap the kernel never applies.
- The CPU-limit probe and the limiter resolve one environment, so the probe can no longer report support for a cgroup path the limiter does not write to.
- A probe directory left behind by a killed veyyon no longer makes the only usable cgroup parent look unusable, which dropped the machine to no budget at all until it was deleted by hand.
- /cpu-limit status reports whether the kernel is holding the machine-wide limit, naming the resource it refused, instead of printing the configured cores on a host that holds none of them.
- /cpu-limit states that a session CPU cap above the machine cap is bounded by it, rather than printing the two numbers with nothing relating them.
- A background bash job joins its session's CPU and memory budget instead of running outside every cap.
- A model whose compaction route answers 404 reports the downgrade to local compaction once, instead of once for the 404 and again in different wording on the next compaction.
- A session that never enabled goal mode no longer reports "Goal mode stopped driving" after three consecutive provider-killed turns; the failed-turn counter and its stand-down warning now require a running goal.
- A goal now starts with its full failed-turn tolerance instead of inheriting the failures an earlier goal in the same session left behind, which stood it down on its first error rather than its third.
- A turn-ending provider error too long to render inline now reports how many lines were dropped instead of ending without a trace.
- The installer asks what is already installed before downloading, so a machine already on the released version finishes in seconds instead of fetching the whole binary to discard it.
- The installer repairs an install whose binary was replaced since it was written — a local build copied over it, or a write interrupted mid-swap — by moving that file aside and installing, instead of refusing and leaving the machine on the old version; a file at a path the installer has never installed to is still refused untouched.
- A completed goal now reports the tokens the turn that completed it spent after the goal tool ran, including a subagent that returned in the same batch, instead of stopping its count at the tool call.
- /cpu-limit no longer sets a budget: it reports both scopes and lifts this session's CPU cap, and points at /settings under Resources for configuration.
- An ACP client following a tool-call location now opens the file, not a name ending in the read tool's line range.
- The settings screen states that left returns to the category list, and no longer expands a row that has no description, which consumed the next left with nothing on screen to show for it.
Removed
- subagent.sharedModel, subagent.model, subagent.thinkingLevel and subagent.modelByDepth decided the model and effort for every subagent at once and are rejected; a config still holding one is reported once, naming the agent page that replaces it.
- The --subagent-model launch flag, which set the model for every subagent in the session.
- The --project flag on veyyon agents unpack, which wrote definitions to ./.veyyon/agents; there is no project scope for subagent discovery, so those files were never loaded.
- The tagline under the wordmark on the session welcome hero, which the launch card and the mounted hero each printed.