v1.0.49
@veyyon/catalog
Added
- Refreshed the bundled catalog from models.dev: 127 rows across 22 providers that upstream already served, including
gemini-3.7-flashon Google, Vertex, GitHub Copilot, OpenRouter, Kilo, NanoGPT, OpenCode Zen and Vercel AI Gateway, each carrying the declaredlow/medium/higheffort ladder. The bundle had drifted far enough that a model released the day before was absent from every provider that serves it. A row missing from the bundle still recovers its ladder at runtime through the models.dev fallback, so what this closes is the cold-start and offline window before that fetch lands, not a broken lookup.
Removed
- Dropped
novita/inclusionai/ling-3.0-tinyandumans/umans-deepseek-v4-flash-0731-lab, which upstream no longer lists.
@veyyon/coding-agent
Fixed
- A recursive delete through a variable nobody set no longer stops a
yolosession.rm -rf "$BUILD_DIR",rm -rf "$CARGO_TARGET_DIR",rm -rf "$WORKTREE"andrm -rf "$checkout"were allcritical, the one verdict/yolocannot lift and no standing grant can cover, so the most ordinary cleanup an agent writes ended an unattended run — and the reason given was that the variable MIGHT hold/. It does not: an unset name expands to nothing, so the guard was refusing a command on a guess about a value that does not exist, at the only severity that cannot be overridden. A floor that fires there is a floor an operator switches off, and then nothing is left for the realrm -rf /. An unsettled expansion now earnsdestroysonly where no assumption is needed — the EMPTY reading, which is what an unset variable already is, sorm -rf "$OUT"/*(the July 2026 incident,direxpanded to nothing),rm -rf "$D"/andrm -rf "$D/lib"all still stop evenyolo— while a barerm -rf "$D", catastrophic only if the name turns out to be/or the home directory, drops todangerous: still a prompt at every rung belowyolo, no longer a claim to be as certain asrm -rf /. A word that spells a protected component itself keeps the floor whatever the prefix expands to, sorm -rf "$D/.ssh"is refused exactly asrm -rf ~/.sshis. - Two words the guard reported as "unknown" carry opposite evidence, and reading them as one shape is a fail-open hole. A variable NOBODY SET expands to nothing. A variable whose value the scan READ and declined to paste is going to become a real path:
rm -rf $VwithV="/*"globs to every top-level entry,${NOPE:-/}carries/in its own text,cd / && rm -rf $PWDnames a variable the shell maintains and which is/by the time it runs, and~usernames another account's home directory. Each of those was measured deleting the root with no prompt at any rung before the guard read the environment at all, soExpandedWordnow records WHY a word is unknown and every one of them keeps thedestroysfloor. Only the genuinely-unset case is treated as speculative. - A recursive delete through a variable is judged by the path it can become, instead of being refused for holding a variable at all.
rm -rf "$DST/facet"wascritical, which is the one verdict/yolocannot lift and no standing grant can cover, so on a long unattended run every variable-shaped cleanup stopped the agent dead — and the reason given, "an expansion whose value is not knowable from the command text", was true of an ordinary staging command whose worst possible reading is/facet, a top-level path the literal spellingrm -rf /facethas always been allowed to delete. An unsettled expansion is now instantiated with the three values that make a path dangerous (empty, the root, the home directory) and each concrete result goes through the same classifier a literal path does, so the refusal names the reading it is about: "rm would recursively remove a protected system directory (/) when the expansion this command line does not settle is empty". Every incident shape is still refused, including the July 2026 one: the bare word reads as/,"$dir"/*reads as/,"$dir"/libreads as/lib, and"$D/.ssh"reads as the credentials directory. A value that CLIMBS is the residual this accepts, and it is named where the rule is written. - A glob is judged as the directory it reads, which closes the literal half of the same hole.
rm -rf /*,rm -rf ~/*,rm -rf /var/*andrm -rf ~/.config/*were all allowed, because the text/var/*equals no protected root, is not an ancestor of the home directory, and sits under none of the protected directories — so each of them destroys a directory whose glob-free spelling has always been refused. A component holding a glob is now dropped together with everything below it and the directory the glob reads is judged instead. A glob inside an ordinary directory (rm -rf ./dist/*,rm -rf /var/log/nginx/*) is untouched. rm -rf ""no longer reports the working directory as its target. A word an expansion collapsed to nothing was resolved against the working directory like any relative path, so a cleanup run from the home directory was refused as a delete of the home directory itself.- The re-root nudge waits for a habit and then says it once.
cwd-rerootfired on the FIRST call naming a path outside the working directory — one glance at one file in another project — and its own body then told the reader to ignore it if the read was a one-off, which is a paragraph of advice arriving before the behavior it is about exists. It repeated after a gap of eight messages on top of that, so a session that reads across two projects heard it several times about work that had already moved on. It now declareswarmupMatches: 3andrepeatMode: per-compact: silent until three separate calls have reached outside the working directory, and then silent until the transcript is replaced. - A rule may declare
warmupMatchesin its frontmatter: how many distinct streams it has to match in before it fires at all, default 1 (the old behavior, unchanged for every rule that says nothing). The unit is the stream rather than the match, which is the whole difficulty — one tool call is re-matched on every delta it streams, so a warm-up counted in matches clears inside the first call and the rule fires exactly as early as it did before, with nothing reporting it. A count is set aside when the reminder is claimed and restored if that claim is released undelivered, so an aborted turn costs neither the reminder nor the evidence for it; delivery starts the count over. veyyon updatereplaces the binary that is running, instead of whichever copy of the name PATH resolves first. Measured end to end: a 1.0.47 binary installed under a sandbox home reportedNew version available: 1.0.48,Checksum verifiedand✓ Updated to 1.0.48, yet stayed at 1.0.47 — while a different install nobody had named was overwritten with the 1.0.48 release asset. Both halves are silent, and the machine that produces them is ordinary: the installer prints a warning by name whenever it installs somewhere that is not first on PATH. The target now comes from the running executable whenever this process is the shipped binary, and an install with nothing on PATH at all no longer fails with "Could not resolve veyyon binary path in PATH". A source checkout still resolves its launcher through PATH, because bun sits at the executable path there and a checkout updates by advancing itself.
Removed
buildRuleFromMarkdownincapability/rule.ts, a second copy of the rule-frontmatter parser that nothing imported. Every provider, the TTSR CLI and the generated-rule path all go through the one indiscovery/helpers.ts, so the copy's only remaining effect was to look like the place a new frontmatter field belongs: a field added there parses in nothing and the rule silently ships without it.parseRuleConditionAndScope, which helpers.ts does import, stays.
@veyyon/simulations
Added
- New family
src/cache-sim/, which prices a prompt-cache change before anyone makes one. It drives the shipped Anthropic request builder to capture the real wire body and the real breakpoints, then bills the result against a modelled provider cache (longest prefix of the arriving request wins, entries expire on the retention they were written with, published read/write multipliers). Scenarios: every counterfactual arm sends byte-identical content so a delta is never a comparison of two different prompts; a system block that changes each turn is measured against the shipped anchor and against a deeper one; a retention switch is priced across gap lengths and shown to have exactly one crossover; and a rewritten earlier message is shown to forfeit the whole history behind it while rewriting the newest message does not. src/cache-sim/also runs a fleet: several sessions billed against one shared cache, interleaved by simulated time, which is the only shape in which the shipped anchor's justification can be measured. It prices the trade between anchoring the first system block (so a subagent can read the harness its parent cached) and anchoring one block deeper (so a parent with a changing system tail stops re-reading everything in between), and reports the fan-out at which the shallow anchor breaks even. It also shows that an entry is invisible to another session unless its marker carriesscope: "global", which no code path sets, so today that break-even is never reached at any fan-out.- The fleet scenario also prices its own recommendation under an adverse assumption: no published number says what a shared cache write costs, so the modelled cache takes a
globalWritePremiumand the scenario re-runs the switch as if a shared write cost the dearest write in the table. Sharing still wins from one subagent, and scoping every system marker rather than the anchor loses outright, because the deepest system marker sits on a block that changes every turn. src/cache-sim/now covers the implicit-cache surface too, which a scan of the local corpus says is where the tokens are actually lost: of 152,120 judgeable turn pairs, the misses arriving too fast to be expiry whose shape is consistent with a rewritten history forfeit at most 18.5M tokens on implicit-cache providers, against 1.5M on the Anthropic path where every placement effect lives. It drivesbuildTransformedCodexRequestBody— the same builder the shipped Codex provider calls — and bills it against a modelled implicit cache (1024-token floor, 128-token matching increments, no write price, keyed byprompt_cache_key). It prices what a caller on that surface actually controls: an append-only run reads most of its prompt, a re-serialized early item forfeits every item behind it on a prompt of identical size, the same edit to the newest item costs nothing, and a fresh key per turn forfeits everything however identical the bytes are. It also asserts the surface carries no breakpoint of any kind, since that premise is what makes prefix hygiene the only lever. The scenario header states what those corpus figures bound and what they cannot prove.src/cache-sim/now sweeps the shipped Codex builder for prefix stability, which is the question the corpus measurement raises and the priced scenarios cannot answer: whether this repository is the author of the rewrite it attributes tokens to. It drivesbuildTransformedCodexRequestBodyover a conversation that grows one step at a time, on both replay paths (a payload the model minted, and the block re-encode a mid-session model switch leaves behind), and asserts every block of turn N comes back byte-identical in turn N+1. It does, so the loss is provider-side or above the builder rather than in it. The content-part variant space is derived from the message unions in@veyyon/ai/typesrather than listed, so a new kind fails the type check until someone records a decision for it, and the kinds that cannot reach a Codex request are pinned by exact equality. The scenario also names the same class living unpriced on the Google path, where the signature-retention settings are a distance-from-the-end window whose boundary moves as history grows.
What changed
16 commits since v1.0.48.
Fixes
- fix(bash-guard): keep yolo's floor for what is certain, not what is guessed
- fix(simulations): take the zeroed usage from its one owner
- fix(update): replace the binary that is running, not the first one on PATH
- fix(rules): make a nudge wait for a habit and speak once per compaction
- fix(bash-guard): judge an unsettled expansion by the path it can become
Documentation
- docs(simulations): correct the corpus figures the implicit scenario cites
- docs(simulations): say which two surfaces cache-sim covers
- docs(internal): re-stamp the two rulebook docs the warm-up change edited
Tests
- test(bash-guard): sweep the yolo floor under a recursive delete
- test(simulations): ask whether this repo is the author of the rewrite it measured
- test(simulations): price the surface where the tokens are actually being lost
- test(simulations): price the shared-cache switch under the assumption it is wrong
- test(simulations): price the shipped cache anchor against the subagents it was chosen for
- test(simulations): price a prompt-cache change before anyone makes one
Chores
- chore: bump version to v1.0.49
- chore(catalog): refresh bundled models from models.dev