Skip to content

Releases: jpzk/koto

koto 1.7.4

Choose a tag to compare

@jpzk jpzk released this 26 Oct 12:32
Immutable release. Only release title and notes can be modified.

koto 1.7.4

A maintenance release. No new features and no intended behaviour change. It holds one small daemon refactor with new tests, a docs update, a README change, and 114 lines of dead code removed after a static analysis pass. The guest image (rootfs, kernel, firecracker) is unchanged from 1.7.3, and no dependency versions changed.

Daemon: one shared trailer for peer content in ctl tail and report (#84)

  • When main gets a peer's text, from a report turn or from {"cmd":"tail"}, the daemon quotes it with > and closes it with a trailer line that the daemon writes. The trailer says the block is peer-authored data, not operator instructions. Until now, report and tail each wrote their own copy of this trailer.
  • Both now build it from one helper (peerTrailer, daemon/report.go), so the two cannot drift apart. The trailer text that main sees is unchanged.
  • New tests for ctl tail output (daemon/ctl_tail_test.go). A forged report header or trailer inside a peer's log stays inside the quote. Control characters, bidi characters and NUL are stripped. The real trailer is the single last line. An ordinary multi-line log keeps every line, up to n.
  • Docs: docs/ctl-reference.md and the harness prompt prompts/global.md now say that tail output comes back quoted and fenced like a report body, and that the same rules apply to it. The prompt is not part of the guest image. koto install copies it from the release source tree into the state dir, and the daemon reads it on the host for every turn. Upgrading is enough to get the new text.

README (#85)

  • The walkthrough video was removed from the README.

Dead code removal after a SAST pass (#87)

  • A static analysis pass ran on all four modules (protocol, daemon, tui, fcguest). The tools were pinned and built with the same go1.26.10 image as the release: staticcheck, deadcode, gosec, errcheck, ineffassign, unparam, nilaway, golangci-lint and govulncheck. govulncheck found no reachable vulnerabilities in any module.
  • Only code that was confirmed unreachable was deleted: 114 lines in 8 files, 6 small commits (95afbc6..48e68b5), deletions only. Daemon: fcMachineCfg, fcNetSubnetNet, groupEffortName, groupConfigString, podmanPath, copyIfAbsent, a logtail inode helper, setupCtx.interactive and prefixWriter. TUI: clearGroupUnread.
  • There is no behaviour change. go.mod and go.sum are unchanged. A real-VM end-to-end run used firecracker microVMs on an isolated dev daemon. It ran the PR build and its base build (develop before #87) side by side, and both passed the same scenarios: doctor, up, tail, non-main tail, sched, notify, report and respawn.

Upgrade

  • From 1.7.2 or 1.7.3: koto update.
  • From 1.7.1 or earlier: run the installer once, curl -fsSL https://kotovm.com/install.sh | sh. Installers before 1.7.2 cannot read the signed # koto-version: line at the top of SHA256SUMS, so koto update from those versions fails. After this one-time step, koto update works again.

Assets

  • koto: rebuilt (#84, #87). Same pinned go1.26.10 build image as 1.7.0 to 1.7.3.
  • koto-tui: rebuilt (#87). Same pinned go1.26.10 build image.
  • rootfs.img (fedora44), vmlinux 6.1.188, firecracker 1.17.0: the 1.7.3 assets, reused byte-for-byte. They were downloaded from release 1.7.3 and checked against its signed SHA256SUMS. Nothing that goes into the guest image changed since 1.7.3 (fcguest/, sidecar/, protocol/, the Makefile and every go.mod/go.sum are identical to tag 1.7.3), so the guest is identical.
  • No dependency changes since 1.7.3. Every go.mod and go.sum is identical to tag 1.7.3.

Tests

  • On release/1.7.4 @2631ad4 (901ffe5 = develop with #84, #85 and #87, plus the DIST_VERSION bump) with the go1.26.10 image: gofmt is clean, and build and vet pass for all 4 modules. go test for tui, protocol, fcguest and daemon passes on the first full run (daemon 142.9s, tui 23.5s, fcguest 5.5s), with no flaky reruns. The only later commit is 02a6dec, the artifacts.sha256 manifest update, which changes no code.
  • Reused assets: rootfs, vmlinux and firecracker are byte-identical to the 1.7.3 release assets. They were verified against the signed 1.7.3 SHA256SUMS before reuse, and their SHA256SUMS lines match 1.7.3 exactly (rootfs 620f1c56…, vmlinux de3cd729…, firecracker 6083c11a…). Only koto (ca52e76b…) and koto-tui (aea69d73…) were rebuilt.
  • Upgrade path: the installer embedded in the installed 1.7.3 koto (byte-identical to the 1.7.3 install.sh) was run against the 1.7.4 candidate assets. It accepted the # koto-version: 1.7.4 line, verified all 5 asset hashes, and unpacked bytes that match artifacts.sha256. It refused a SHA256SUMS that says 1.7.3 (rollback check) and a key with the wrong fingerprint.
  • End-to-end smoke run on the 1.7.4 candidate (koto 54ecd808, koto-tui c7b5b3b3, installed rootfs unchanged): claude-wan, claude-none and minimax all pass on the first try. koto tui connects to the daemon with the client certificate and shows the group tree. koto ctl list returns ok after the daemon restart. 1.7.4 is installed and running.

Known issues

  • Shared terminal keystroke p99 is about 3ms under the load benchmark, above the 2ms goal (#79). The typical keystroke is about 0.3ms.
  • The goal-finished notification banner is not session-scoped. The handoff turn goes to the origin session, but the operator banner is raised against the group as a whole (goalNotify passes no session, daemon/goals.go:1665).
  • TestStopDisarmsTheReportWindow fails rarely, with no message, under full-suite ordering. It passes in isolation. Suspected cause: a race between tests over a TempDir or global path.
  • TestFcGrowWorkspaceImgOnlyTruncates depends on the host disk being under 90% full, because the disk-admission guard is not stubbed in that test.
  • TestOtelUnreachableReceiverNeverBlocks is slow (about 2 minutes of the daemon test run). It measures wall-clock p99 latency, so it can flake on a heavily loaded host.
  • Guest boot has a fixed 30s limit. On a heavily loaded host a guest can miss it and the send fails with a boot timeout. Retrying once the load drops works.

🤖 Generated with Claude Code

koto 1.7.3

Choose a tag to compare

@jpzk jpzk released this 25 Oct 18:02
Immutable release. Only release title and notes can be modified.

koto 1.7.3

A TUI-only release: typing is fast again, in both the input prompt and the shared terminal. No daemon or guest changes, and no dependency version changes.

TUI: keystroke latency fast paths (#79)

  • A keystroke used to rebuild the whole screen: the transcript, the agents tree, the status bar and the shell pane were all redrawn for every key, even though only the focused pane changed. Under a busy fleet that made typing feel slow.
  • The input prompt and the shared terminal pane now have a keystroke fast path. Off-focus regions are kept in a frame cache and redrawn only when their own inputs change (stream deltas, ticks, tree scroll offset, resizes). Background stream deltas and clean flushes no longer throw the cached frame away.
  • A new load benchmark (BenchmarkFocusInput, BenchmarkFocusShell: many groups and sessions, streaming events, spinner ticks, pty output; idle and burst) measures keystroke to frame cost. Before (8c09f31) and after (ed8fae5) were run back to back on the same VM (about 20.8% CPU steal on average). Medians of 5 runs:
    • input prompt, typical keystroke: 1.6ms -> 0.075ms (about 21x). p99 idle: 15.6ms -> 0.76ms. p99 during a stream burst: 16.1ms -> 1.6ms.
    • shared terminal, typical keystroke: 8.0ms -> 0.31ms (about 26x). p99 idle: 26ms -> 3.5ms. p99 during a stream burst: 25ms -> 2.9ms.
    • allocations per keystroke: input 702 -> 64, shell 2531 -> 120.
  • The goal was p99 at or under 2ms in both panes. The input prompt meets it. The shared terminal does not: its p99 is still about 3ms (see Known issues). Typical keystrokes there are well under 1ms.
  • Rendered output is unchanged. New equality tests compare fast-path frames with full redraws, including a scrolled agents tree.

Upgrade

  • From 1.7.2: koto update.
  • From 1.7.1 or earlier: run the installer once, curl -fsSL https://kotovm.com/install.sh | sh. Installers before 1.7.2 cannot read the signed # koto-version: line at the top of SHA256SUMS, so koto update from those versions fails. After this one-time step, koto update works again.

Assets

  • koto: rebuilt (version bump only, no daemon code change). Same pinned go1.26.10 build image as 1.7.0 to 1.7.2.
  • koto-tui: rebuilt (#79). Same pinned go1.26.10 build image.
  • rootfs.img (fedora44), vmlinux 6.1.188, firecracker 1.17.0: the 1.7.2 assets, reused byte-for-byte. They were downloaded from release 1.7.2 and checked against its signed SHA256SUMS. Nothing outside tui/ changed since 1.7.2, so the guest is identical.
  • No dependency version changes since 1.7.2. go.sum, go.work and package*.json are identical to tag 1.7.2. The only go.mod change is in tui/go.mod: github.com/charmbracelet/ultraviolet (same version, already in the build) is now imported directly, so its // indirect marker was dropped.

Tests

  • On release/1.7.3 @d104d23 (d9276ef = develop with #79, plus the DIST_VERSION bump) with the go1.26.10 image: gofmt is clean on every .go file changed since 1.7.2, and build and vet pass for all 4 modules. go test for tui, protocol, fcguest and daemon passes on the first full run (daemon 126.6s, tui 11.0s, fcguest 2.4s). The only later commit is d2e1884, the artifacts.sha256 manifest update, which changes no code.
  • Reused assets: rootfs, vmlinux and firecracker are byte-identical to the 1.7.2 release assets. They were verified against the signed 1.7.2 SHA256SUMS before reuse, and their SHA256SUMS lines match 1.7.2 exactly (rootfs 620f1c56…, vmlinux de3cd729…, firecracker 6083c11a…). Only koto (415a837a…) and koto-tui (1225e524…) were rebuilt.
  • End-to-end smoke run on the 1.7.3 candidate (koto 84d63120, koto-tui d04d8ecc, installed rootfs unchanged): claude-wan, claude-none and minimax all pass on the first try, koto-tui starts, and koto ctl list returns ok after the daemon restart. 1.7.3 is installed and running.

Known issues

  • Shared terminal keystroke p99 is about 3ms under the load benchmark, above the 2ms goal (#79). The typical keystroke is about 0.3ms.
  • The goal-finished notification banner is not session-scoped. The handoff turn goes to the origin session, but the operator banner is raised against the group as a whole (goalNotify passes no session, daemon/goals.go:1665).
  • TestStopDisarmsTheReportWindow fails rarely, with no message, under full-suite ordering. It passes in isolation. Suspected cause: a race between tests over a TempDir or global path.
  • TestFcGrowWorkspaceImgOnlyTruncates depends on the host disk being under 90% full, because the disk-admission guard is not stubbed in that test.
  • TestOtelUnreachableReceiverNeverBlocks is slow (about 2 minutes of the daemon test run). It measures wall-clock p99 latency, so it can flake on a heavily loaded host.
  • Guest boot has a fixed 30s limit. On a heavily loaded host a guest can miss it and the send fails with a boot timeout. Retrying once the load drops works.

🤖 Generated with Claude Code

koto 1.7.2

Choose a tag to compare

@jpzk jpzk released this 25 Oct 15:51
Immutable release. Only release title and notes can be modified.

Important

Upgrading from 1.7.1 or earlier: koto update fails on this release with SHA256SUMS names koto-version:, which is not a x86_64 asset. The 1.7.2 SHA256SUMS is now bound to its version by a # koto-version: 1.7.2 line, and installers before 1.7.2 cannot parse that line. Nothing is installed when it fails. Upgrade once with the 1.7.2 installer, pinned to the tag:

curl -fsSL https://raw.githubusercontent.com/jpzk/koto/1.7.2/install.sh | sh -s -- --version 1.7.2

From 1.7.2 on, koto update works again.

koto 1.7.2

Security hardening (#76, #78)

  • This release has security hardening across the daemon, proxy, installer, TUI and guest agent. Advisories will be published after the release. Upgrade recommended.
  • Details are held back until the advisories are public.

Installer: refuses releases older than 1.7.2 (#76)

  • From now on, make dist writes a # koto-version: X.Y.Z line at the top of SHA256SUMS, so the signature covers the version number.
  • install.sh (and therefore koto update) refuses a SHA256SUMS that does not name exactly the version you asked for. Releases signed before 1.7.2 have no such line, so this installer refuses them. That is intentional: it is rollback protection. To go back to an older release, use that release's own installer.
  • latest is resolved to a concrete tag before anything is downloaded. A mirror (KOTO_BASE_URL) now needs an explicit --version.
  • A release must contain all five assets (koto, koto-tui, firecracker, vmlinux, rootfs.img), each exactly once, with koto and koto-tui at the requested version. Otherwise nothing is installed.

Workspace resize happens inside the guest (#78)

  • When a workspace disk is enlarged, the host now only extends the sparse image file. The guest grows its own filesystem online after /workspace is mounted (best effort, with a timeout; on failure boot continues at the old size and the error is logged).
  • The host no longer needs e2fsprogs resize tools for this. mkfs.ext4 is still used when a workspace is first created.
  • Needs the 1.7.2 rootfs. A guest still on an older rootfs will not grow its filesystem after a resize. koto install with 1.7.2 installs the matching rootfs.

TUI: Nerd Font glyphs for every pictograph (#75)

  • One icon table now drives the tree dots, transcript glyphs, notification bell, queue badge, goal/schedule lifecycle lines and ok/err marks, extending the session-row icons from 1.7.1.
  • Nerd Font forms keep the same cell width as the plain forms. KOTO_TUI_NERDFONT=0 gives output byte-identical to 1.7.1's plain glyphs, and black-and-white mode folds every Nerd Font codepoint.

TUI: agents tree scrolls with its cursor (#77)

  • On short terminals the tree used to be cut off at the bottom of the pane, and the cursor could move onto rows that were never drawn, so part of the fleet could not be reached.
  • The tree pane now shows a window that moves only when the cursor would leave it (pager style, no recentering). Dim ↑ N more / ↓ N more markers show hidden rows. Clicks, resizes and folds stay in sync with what is drawn.
  • Also fixes the fleet view's height calculation, which disagreed with the height the layout gave the tree.

Assets

  • koto: rebuilt (#76, #78). Same pinned go1.26.10 build image as 1.7.0/1.7.1.
  • koto-tui: rebuilt (#75, #76, #77). Same pinned go1.26.10 build image.
  • rootfs.img (fedora44): rebuilt, because the guest agent changed (#78). The rootfs recipe (Dockerfile.rootfs, build scripts, pinned base image, pinned RPMs and agent tarballs) is unchanged since 1.7.0. The main package install resolves from the Fedora repos at build time, so package builds may differ from the 1.7.1 rootfs; see Tests for the comparison.
  • vmlinux 6.1.188, firecracker 1.17.0: the 1.7.1 assets, reused byte-for-byte. They were downloaded from release 1.7.1 and checked against its signed SHA256SUMS.
  • No dependency changes since 1.7.1: go.mod, go.sum, go.work and package*.json are identical to tag 1.7.1.

Tests

  • On release/1.7.2 @3ae74a3 (d06c5f0 = develop with #75, #76, #77, #78, plus the DIST_VERSION bump) with the go1.26.10 image: gofmt is clean on every .go file changed since 1.7.1, and build and vet pass for all 4 modules. go test for tui, protocol, fcguest and daemon passes on the first full run (daemon 248.7s, tui 21.9s, fcguest 7.0s). The only later commit is the artifacts.sha256 manifest update, which changes no code.
  • rootfs check: the built rootfs contains resize2fs and the go1.26.10 guest agent (checked with debugfs). Its package set is identical to the 1.7.1 rootfs (278 packages). The end-to-end smoke run (claude-wan, claude-none, minimax) was skipped for this release on operator decision. The candidate is installed and the daemon is up (koto ctl list ok).

Known issues

  • The goal-finished notification banner is not session-scoped. The handoff turn goes to the origin session, but the operator banner is raised against the group as a whole (goalNotify passes no session, daemon/goals.go:1665).
  • TestOtelUnreachableReceiverNeverBlocks is slow (about 2 minutes of the daemon test run). It measures wall-clock p99 latency, so it can flake on a heavily loaded host.
  • TestFcGrowWorkspaceImgOnlyTruncates depends on the host disk being under 90% full, because the disk-admission guard is not stubbed in that test.
  • TestStopDisarmsTheReportWindow fails rarely, with no message, under full-suite ordering (1 of 5 full runs). It passes 5/5 in isolation and in the other 4 full runs. Suspected cause: a race between tests over a TempDir or global path.
  • Guest boot has a fixed 30s limit. On a heavily loaded host a guest can miss it and the send fails with a boot timeout. Retrying once the load drops works.
  • GO-2026-5932 (golang.org/x/crypto/openpgp) still shows up in dependency scans. govulncheck finds the vulnerable code is not reachable, and there is no fixed version upstream.
  • Carried over from 1.6.0: koto ctl history can return a partially written reply if it is polled while the reply is still being written. Accept a reply only once it is stable across two polls.

🤖 Generated with Claude Code

koto 1.7.1

Choose a tag to compare

@jpzk jpzk released this 25 Oct 10:04
Immutable release. Only release title and notes can be modified.

koto 1.7.1

TUI: Nerd Font icons for session rows (#58)

  • Session rows in the tree show a Nerd Font icon instead of the hollow ◦: a git-branch icon (nf-oct-git_branch, U+F418) for regular sessions and a bullseye (nf-fa-bullseye, U+F140) for goal runs (goal-<name>, including -judge).
  • The icon stays gray. Unread rows still show the pink ●. Both icons are one cell wide, like ◦, so row width and padding do not change.
  • Opt-out: set KOTO_TUI_NERDFONT=0 (or false/no/off) to get ◦ back on terminals without a Nerd Font. It is read once, at startup.
  • In black-and-white mode the icons fold to - and O, one cell each.

TUI: ctrl+x on a session row destroys that session, not the group (#59)

  • On a session row, ctrl+x opens its own confirm dialog: "destroy session <s> of <g>? its conversation and transcript are deleted". y calls the daemon's existing Clear RPC for that one session.
  • On a group row, ctrl+x is unchanged: it destroys the group after the existing confirm dialog.
  • Goal rows (including -judge) and main are refused with a status line. Job rows still target their group.
  • The TUI refuses to send an empty, -, default, goal or main session name, because Clear with an empty session wipes the whole group.
  • After a confirmed destroy the session leaves the tree. If it was the active session, the view falls back to the group's default session. Nothing changes locally until the daemon answers.
  • The help cheatsheet and palette now say "destroy group / session". TUI-only change; no daemon or protocol changes.

Goals: remember the origin session (#60)

  • A goal started from a named session records that session (origin_session in goals.json; it survives a daemon restart).
  • The goal-finished and goal-terminated handoff turns go back to that session while it is still registered. Otherwise they go to the group's default session.
  • In the TUI tree the goal's worker row, its -judge row and their jobs nest under the origin session's row. Goals started from the default session stay at group level.
  • Jobs whose session no longer has a row (finished goal, cleared session) no longer vanish. They show under the group's default row, tagged @<session>.
  • How to pass the session: the gRPC GoalSet takes session; ctl goal_set takes an optional "session" (only when a group targets itself; main→peer ignores main's session); koto ctl goal set -session <name>; and the TUI's /goals set and goal dialogs pass the active session. Default aliases and malformed, reserved (goal-*) or unregistered names fold to the default session. That is logged, not an error.
  • Unchanged: job_done callbacks that name a goal-* session still go to the default session (audit M42).
  • Needs both sides on 1.7.1. The origin session is carried by new protocol fields (GoalSetReq.session, GoalItem.origin_session, GroupInfo.session_origins). With a 1.7.0 daemon or a 1.7.0 TUI, goals behave as in 1.7.0: they hand off to the default session and show at group level. Upgrade the daemon (koto install) and koto-tui together.

Assets

  • koto: rebuilt (#60 daemon changes). Same pinned go1.26.10 build image as 1.7.0.
  • koto-tui: rebuilt (#58, #59, #60). Same pinned go1.26.10 build image.
  • rootfs.img (fedora44), vmlinux 6.1.188, firecracker 1.17.0: the 1.7.0 assets, reused byte-for-byte. Nothing under fcguest/ has changed since 1.7.0. They were downloaded from release 1.7.0 and checked against its signed SHA256SUMS before they were added here.
  • No dependency changes since 1.7.0: go.mod, go.sum, go.work and package*.json are identical to tag 1.7.0.

Tests

  • On release/1.7.1 @cff6300 (#58, #59, #60 merged) with the go1.26.10 image: gofmt is clean on every .go file changed since 1.7.0, and build and vet pass for all 4 modules. go test for tui, protocol and daemon passes (daemon 121.7s). The first full run hit two flaky tests whose files are unchanged since 1.7.0 (TestStateHashChangeDetection, TestStopDisarmsTheReportWindow). Both passed 20/20 in a focused rerun, and the full rerun passed.
  • The final release candidate (release/1.7.1 @d513c3d: koto 438fc4b8, koto-tui 61b433db, plus the 1.7.0 rootfs e389ba0c, vmlinux and firecracker) was installed and passed the claude-wan, claude-none and minimax end-to-end cases, each on the first try (EXIT=0). In claude-wan the guest reached the internet; in claude-none it had no egress, as expected; minimax answered PONG. koto-tui started and drew its screen.

Known issues

  • The goal-finished notification banner is not session-scoped. The handoff turn goes to the origin session (#60), but the operator banner is still raised against the group as a whole (goalNotify passes no session, daemon/goals.go:1665).
  • TestOtelUnreachableReceiverNeverBlocks is slow (about 2 minutes of the daemon test run). It measures wall-clock p99 latency, so it can flake on a heavily loaded host.
  • Guest boot has a fixed 30s limit. On a heavily loaded host a guest can miss it and the send fails with a boot timeout. Retrying once the load drops works.
  • GO-2026-5932 (golang.org/x/crypto/openpgp) still shows up in dependency scans. govulncheck finds the vulnerable code is not reachable, and there is no fixed version upstream.
  • Carried over from 1.6.0: koto ctl history can return a partially written reply if it is polled while the reply is still being written. Accept a reply only once it is stable across two polls.

🤖 Generated with Claude Code

koto 1.7.0

Choose a tag to compare

@jpzk jpzk released this 24 Oct 19:24
Immutable release. Only release title and notes can be modified.

koto 1.7.0

TUI: sessions (#49)

  • Sessions are now rows in the tree, nested under their group (main included). The group row itself is the default session; selecting a session row switches the active session.
  • alt+n opens a new-session dialog. The name is validated inline; Enter creates the session and switches to it, Esc cancels.
  • New daemon RPC SessionCreate (ACL verb session_create) registers a named session in sessions.json before its first send, so it survives a TUI restart and is visible to every client. It refuses empty/default names, goal-* names, invalid names and unregistered groups.
  • Fix: /session <name> for a session the daemon had not listed yet used to snap back to the default session. The TUI now keeps locally created sessions in the tree until the daemon lists them.
  • Needs both sides on 1.7.0. The new-session dialog calls SessionCreate; a 1.7.0 TUI against a 1.6.0 daemon cannot create sessions. Upgrade the daemon (koto install) and koto-tui together.

TUI: keymap (#51 + #49)

  • alt+n: new session (new)
  • ctrl+n: new group (unchanged)
  • ctrl+x (tree): destroy the hovered group, with a confirm dialog (new)
  • ctrl+s (tree): stop the hovered group's VM, with a confirm dialog
  • ctrl+v: toggle mouse mode (wheel scroll vs native select/copy); with a draft in the input bar it pastes
  • ctrl+o: jump to any group or session (fuzzy)
  • ctrl+t: fold/unfold jobs on the current row
  • When more than one dialog could be open, the order is: destroy confirm, then stop confirm, then the new-session dialog.
  • An earlier draft of #49 put mouse mode on ctrl+x. That binding was dropped; ctrl+x is destroy only.
  • The help cheatsheet and command palette list the new keys.

Security: Go toolchain go1.26.9 → go1.26.10 (#53)

  • Fixes GO-2026-6809 (crypto/x509), which govulncheck found reachable in all four modules (protocol, tui, fcguest, daemon).
  • go1.26.10 came out 2026-10-19, so it is less than 6 weeks old. The operator approved an exception to the 6-week dependency age rule for this security fix on 2026-10-24. The toolchain and build image are pinned by hash.

Security: undici 8.12.2 + integer-conversion bounds checks (#54)

  • undici 8.10.2 → 8.12.2 in the guest's pi install, set through an npm overrides entry. This fixes 3 Dependabot alerts:
    • GHSA-xjj6-f33q-929c (high): denial of service from an unhandled error when a response has too many content-encodings.
    • GHSA-4qv8-2p9j-2685 (medium): denial of service from unbounded memory use in the EventSource parser.
    • GHSA-f72v-6w45-q472 (medium): Subresource Integrity bypass with non-lowercase algorithm tokens.
  • 8.12.2 is the oldest patched version. It came out on 2026-10-15, so it is less than 6 weeks old. The operator approved an exception to the 6-week rule for this security fix.
  • The only change to package-lock.json is the undici entry (version, resolved and integrity). We checked the tarball's integrity against the registry and confirmed with npm ci --ignore-scripts in a hash-pinned node container that every copy of undici resolves to 8.12.2. The rootfs build checks the lockfile hash and the undici version.
  • Fixed 7 CodeQL alerts (go/incorrect-integer-conversion: a strconv.Atoi result converted to int32 without an upper bound check). Each site now checks bounds instead of silently wrapping:
    • Ports in the ctl replies and gRPC spawn replies must be between 0 and 65535. Any other value returns an error.
    • Word counts and the host memory cap are clamped to the int32 range.
    • The TUI's limit and max_iterations reject NaN, ±Inf and out-of-range values before the RPC is sent.
    • Every site has unit tests for the max, min and overflow values.
  • The Dependabot and CodeQL alerts close once the default branch (develop) has the fix. That happens when release/1.7.0 is merged back into develop after the release is published.

Assets

  • koto: rebuilt (daemon changes + toolchain).
  • koto-tui: rebuilt (TUI changes + toolchain).
  • rootfs.img (fedora44): rebuilt for two reasons. (1) The guest agent (fc-agent) is now compiled with go1.26.10; the 1.6.0 rootfs had it compiled with go1.26.9, and GO-2026-6809 is reachable in fcguest. (2) The pi install inside the guest now ships undici 8.12.2 (#54). The rootfs build checks the OS (fedora44), the fc-agent toolchain, the undici version and that the pi lockfile hash matches the committed package-lock.json (a67466a8…).
  • vmlinux 6.1.188: the 1.6.0 asset, reused byte-for-byte. fcguest/build-kernel.sh is unchanged since 1.6.0.
  • firecracker 1.17.0: the 1.6.0 asset (itself reused from 1.5.2), byte-for-byte. fcguest/build-firecracker.sh is unchanged since 1.6.0.
  • Both reused assets were downloaded from release 1.6.0 and checked against its signed SHA256SUMS before they were repackaged.

Tests

  • On release/1.7.0 @37d5f21 (#54 merged) with the go1.26.10 image: build and vet for all 4 modules; go test for tui, protocol, fcguest and daemon all pass, including the new integer-conversion tests.
  • The final release candidate (koto cf2c75ef, koto-tui 4bbf41ee, rootfs e389ba0c, 1.6.0 vmlinux and firecracker) was installed and passed the claude-wan, claude-none and minimax end-to-end cases, each on the first try (EXIT=0). In claude-wan the guest reached the internet; in claude-none it had no egress, as expected; minimax answered PONG. koto-tui started and drew its screen.

Known issues

  • Guest boot has a fixed 30s limit. On a heavily loaded host a guest can miss it and the send fails with a boot timeout; retrying once load drops works. The fix (fix/guest-boot-timeout) is planned for the next release.
  • GO-2026-5932 (golang.org/x/crypto/openpgp) still shows up in dependency scans. govulncheck finds the vulnerable code is not reachable, and there is no fixed version upstream.
  • Carried over from 1.6.0: koto ctl history can return a partially written reply if it is polled while the reply is still being written. Accept a reply only once it is stable across two polls.

🤖 Generated with Claude Code

1.6.0

Choose a tag to compare

@jpzk jpzk released this 20 Oct 09:28
Immutable release. Only release title and notes can be modified.

koto 1.6.0

Fix: koto install keeps all of koto.env (#45)

  • Upgrading with koto install used to rewrite koto.env from a whitelist, so keys such as OTEL_* and custom KOTO_* settings were dropped. Every existing key is now kept.

Proxy: nested KOTO_UPSTREAM

  • When koto runs inside another koto, the proxy now forwards a provider's traffic to KOTO_UPSTREAM only if the outer koto actually serves that provider.

OpenAI: default model

  • provider=openai always passes a model to codex. The default is gpt-6.1-sol.

Dependency upgrades (#44)

The 6-week dependency age gate was waived for this release, so some pins are less than 6 weeks old. All of them are pinned by hash.

Guest kernel

  • amzn microvm-kernel 6.1.186-50.374 → 6.1.188-56.385 (commit 0dcff8409289). Includes the backport for CVE-2026-99999, the virtio_net_hdr checksum / ip_do_fragment bug. Kernel config unchanged.

Harnesses (rootfs)

  • codex 0.152.1 → 0.163.0, with rustls 0.23.45 and webpki 0.103.15
  • pi 0.85.1 → 0.86.0. The undici override was dropped because pi now ships 8.10.2; the brace-expansion override is now 5.0.12
  • npm CLI 10.9.9 → 11.21.1 (major version)
  • claude-code: unchanged

Rootfs packages (Fedora 44, signed)

  • curl / libcurl 8.18.0-14
  • openssl-libs 3.5.9-1
  • sudo 1.9.17-10.p2
  • tar 1.35-9
  • libevent 2.1.13-1

Daemon (Go)

  • gvisor-tap-vsock v0.8.9 (pre-release build) → v0.9.0. This is the userspace network stack that handles guest traffic. gVisor itself is unchanged. Test-only dependencies (ginkgo, gomega, go-mockdns) were pulled up with it.
  • OpenTelemetry SDK: unchanged

Unchanged: firecracker 1.17.0, koto-tui

Known vulnerabilities in dependency scans, before → after

  • npm: 20 → 0
  • Fedora security updates pending: 8 → 0
  • codex crates: 61 → 51
  • Go: 1 → 1 (same issue; the vulnerable code is not reachable)
  • Kernel 6.1 LTS: CVEs continue to arrive; this release picks up 6.1.188

Tests

  • scripts/e2e-send.sh PROVIDER [NETWORK]: an end-to-end send for any provider. The network probe now reads its result from a marker file written inside the guest.
  • The e2e-otel harness unsets OTLP TLS and header variables before it sends to the plain-http receiver.
  • Before this draft was made, the release candidate passed claude-wan, claude-none and minimax end to end.

Assets

  • rootfs.img (fedora44) is new. It was built from fcguest/ at b7edf11, and fcguest/ and sidecar/ have not changed between b7edf11 and this tag.
  • vmlinux 6.1.188 is new.
  • firecracker 1.17.0 is the 1.5.2 asset, reused byte-for-byte.

Known issue

  • koto ctl history can return a partially written reply (for example "P" when the model answered "PONG") if it is polled while the reply is still being written. Cause: daemon/logtail.go readStreamHistory parses the unterminated last log line as a finished event. This code predates 1.5.2; the new rootfs writes replies in smaller chunks, so the race shows up more often. Workaround for clients: accept a reply only once it is stable across two polls. The fix is planned for a follow-up.

koto 1.5.2

Choose a tag to compare

@jpzk jpzk released this 18 Oct 18:34
Immutable release. Only release title and notes can be modified.

koto 1.5.2

Fix: provider=minimax turns now run on pi (#42)

  • In 1.5.0 and 1.5.1, a group with -provider minimax sent its turns to the claude CLI harness inside the guest. The proxy's allowlist refused those requests, so every MiniMax turn failed.
  • The guest agent (fcguest) now runs MiniMax turns on the bundled pi harness, as intended. Clearing a session's context also removes the pi session id.
  • New scripts/e2e-minimax-send.sh: a full koto ctl send turn on provider minimax through a running daemon. It spawns a group, waits for the reply, fails on any proxy refusal, and always destroys the group. This release candidate passed it before the draft was made.

Upgrade note

  • rootfs.img is new. The guest agent is built into the rootfs, so the fix needs a new image. It is the image built from fcguest/ at b4276a2 (the same as this tag) and tested end to end with the script above.
  • firecracker 1.17.0 and vmlinux 6.1.186 are the 1.5.1 release assets, reused byte-for-byte. Their SHA256SUMS lines and artifacts.sha256 entries are the same as in 1.5.1. No new kernel.

koto 1.5.2 for linux/x86_64.

component version
koto 1.5.2
koto-tui 1.5.2
firecracker 1.17.0
vmlinux 6.1.186
rootfs.img fedora44

Install: curl -fsSL https://kotovm.com/install.sh | sh -s -- --version 1.5.2,
or from a clone of this tag: make fetch && make install && make wizard.

SHA256SUMS is signed by A6E69ED6BC4779F3281721484CC1AFDE15B64EA3 (SHA256SUMS.asc); both routes
verify that signature, and the unpacked bytes against the artifacts.sha256
committed at this tag, before anything is installed.

🤖 Generated with Claude Code

koto 1.5.1

Choose a tag to compare

@jpzk jpzk released this 18 Oct 14:53
Immutable release. Only release title and notes can be modified.

koto 1.5.1

koto venice-login / koto minimax-login, with a key check (#41)

  • Two new commands. koto venice-login writes creds/venice.key, and koto minimax-login writes creds/minimax.key. Both files are 0600 and live in the state dir, and the key never enters a guest.
  • The key comes from --key-file F, --api-key-stdin, or a hidden terminal prompt. It is never taken from argv.
  • Key check before storing. Each key is tested with one request that needs auth before it is written:
    • Venice: GET /api/v1/api_keys/rate_limits (Bearer)
    • MiniMax: GET /anthropic/v1/models (x-api-key)
    • A 2xx stores the key.
    • A 401/403 refuses it, exits 1, and leaves any existing key file untouched.
    • A network error, a 15s timeout or a 5xx stores the key with a warning.
  • --no-verify skips the check. --status only reports whether a key is stored.
  • Error messages show the status code or the transport error, never the key.
  • The setup wizard's auth step now uses the same check for Venice and MiniMax. The daemon re-reads the key on every request, so no restart is needed.

Upgrade note

  • Only koto and koto-tui are rebuilt.
  • firecracker 1.17.0, vmlinux 6.1.186 and rootfs.img (fedora44) are the 1.5.0 release assets, reused byte-for-byte. Their SHA256SUMS lines and their artifacts.sha256 entries are the same as in 1.5.0. No new rootfs, no new kernel.

koto 1.5.1 for linux/x86_64.

component version
koto 1.5.1
koto-tui 1.5.1
firecracker 1.17.0
vmlinux 6.1.186
rootfs.img fedora44

Install: curl -fsSL https://kotovm.com/install.sh | sh -s -- --version 1.5.1,
or from a clone of this tag: make fetch && make install && make wizard.

SHA256SUMS is signed by A6E69ED6BC4779F3281721484CC1AFDE15B64EA3 (SHA256SUMS.asc); both routes
verify that signature, and the unpacked bytes against the artifacts.sha256
committed at this tag, before anything is installed.

🤖 Generated with Claude Code

koto 1.5.0

Choose a tag to compare

@jpzk jpzk released this 18 Oct 14:03
Immutable release. Only release title and notes can be modified.

koto 1.5.0

pi coding agent in the guest, Venice + MiniMax through the proxy (#37, #40, #35)

  • The rootfs ships pi 0.84.4 at /opt/pi. It's pinned by sha256, runs offline (PI_OFFLINE=1, no telemetry, no version check), and fcguest/verify-rootfs.sh checks it.
  • Venice (#40): the proxy accepts pi's chat-completions route from the guest and relays it to the Venice API, using the host's Venice key. Budget key: venice/apikey.
  • MiniMax (#35): there's a new provider=minimax, served by an Anthropic-compatible proxy leg (POST /anthropic/v1/messages). The default model is MiniMax-M3. The host key in creds/minimax.key is billed as minimax/apikey (sk-api- keys) or as minimax/subscription (M Plan). For M Plan, the 5h and weekly windows are read from token_plan/remains and exported as koto.llm.ratelimit.*. Set KOTO_MINIMAX_REGION=cn for the mainland endpoints.
  • The rootfs ships a pi template at /usr/local/share/koto/pi/models.minimax.json. It points at the in-guest proxy and holds only a placeholder key.
  • In both cases the real key stays on the host. The guest sends a placeholder, and the proxy swaps it for the real key.
  • Scope: pi is installed and routed, but the guest agent turn doesn't launch pi automatically yet (runPi is follow-up work). Today pi runs from the group's shell or as a tool.

Venice account balance (#34)

  • Shows the Venice account balance as reported by Venice response headers. The balance is account-wide, not per key, and the USD/DIEM buckets are passed on as Venice sends them. Only the inference key is used: no admin key and no billing API call.
  • Shown in <koto-context> and the TUI as a credits line.
  • OTel exports it as koto.llm.balance, plus request-limit gauges.
  • There's no per-key spend tracking.

Verification before release

Each leg ran a fresh nested end-to-end test on a group with network=none:

  • pi made a bash tool call and gave its final answer through the proxy.
  • The daemon logged the proxied provider call and its budget key.
  • The repo's own OTLP receiver got the koto.llm.* datapoints.
  • A hash probe of the guest env and filesystem found no copy of the key.

Upgrade note

  • koto, koto-tui and rootfs.img are rebuilt. The rootfs is byte-identical to the image both e2e runs used.
  • firecracker 1.17.0 and vmlinux 6.1.186 are byte-identical to 1.4.0.
  • New groups need the new rootfs to get pi.

koto 1.5.0 for linux/x86_64.

component version
koto 1.5.0
koto-tui 1.5.0
firecracker 1.17.0
vmlinux 6.1.186
rootfs.img fedora44

Install: curl -fsSL https://kotovm.com/install.sh | sh -s -- --version 1.5.0,
or from a clone of this tag: make fetch && make install && make wizard.

SHA256SUMS is signed by A6E69ED6BC4779F3281721484CC1AFDE15B64EA3 (SHA256SUMS.asc); both routes
verify that signature, and the unpacked bytes against the artifacts.sha256
committed at this tag, before anything is installed.

🤖 Generated with Claude Code

koto 1.4.0

Choose a tag to compare

@jpzk jpzk released this 14 Oct 17:03
Immutable release. Only release title and notes can be modified.

koto 1.4.0

OpenAI provider (#24, #29, #31)

Groups can now run on provider=openai. The guest runs the Codex CLI (0.152.1, pinned by sha256, update check disabled). The credential stays on the host: koto openai-login (ChatGPT subscription) or creds/openai-api-key. The proxy injects it, and the guest only ever sees a placeholder.

  • Code mode for gpt-5.6 models: the rootfs ships codex-code-mode-host and enables it.
  • ChatGPT streams /responses with no Content-Type. The proxy now detects the SSE from the body, relays it verbatim and records usage.

Budget per provider and auth (#30)

  • Budget is tracked per (provider, auth), where auth is subscription, apikey, upstream or env. It records rate-limit windows, credits, and spend: tokens always, and USD only for models with a configured price.
  • Operator caps in budget.json (0600): tokens_per_day, usd_per_day, usd_per_month, max_window_used.<window>. A capped key gets a 429 from the proxy before anything goes upstream.
  • koto budget [set|unset|price] (admin-only).
  • <koto-context> shows rate-limit (provider/auth): ....
  • OTel adds koto.llm.spend.tokens and koto.llm.budget.refused, and budget series carry an auth attribute.

TUI (#22, #23, #26, #27, #28)

  • Ctrl+N: new-group dialog with a model picker fed by the daemon.
  • Ctrl+E: edit-group dialog with Save and Save + restart.
  • The provider select offers only providers that have a login or credential configured. The Providers RPC carries no secrets.
  • Daemon log lines wrap to the pane width instead of being clipped.

Setup and proxy (#25, #12, #20)

  • The setup wizard lets you choose the default provider (claudesdk, venice, openai).
  • KOTO_UPSTREAM overrides the Anthropic upstream origin, which lets a nested koto chain to an outer koto's proxy.
  • OTel export errors are rate-limited per signal.

Upgrade note

  • koto, koto-tui and rootfs.img are rebuilt.
  • firecracker 1.17.0 and vmlinux 6.1.186 are byte-identical to 1.3.1.
  • New groups need the new rootfs to use provider=openai.

koto 1.4.0 for linux/x86_64.

  • koto 1.4.0
  • koto-tui 1.4.0
  • firecracker 1.17.0
  • vmlinux 6.1.186
  • rootfs.img fedora44

Install: curl -fsSL https://kotovm.com/install.sh | sh -s -- --version 1.4.0,
or from a clone of this tag: make fetch && make install && make wizard.

SHA256SUMS is signed by A6E69ED6BC4779F3281721484CC1AFDE15B64EA3 (SHA256SUMS.asc). Both routes
verify that signature, and the unpacked bytes against the artifacts.sha256
committed at this tag, before anything is installed.

🤖 Generated with Claude Code