Skip to content

v0.13.0

Choose a tag to compare

@github-actions github-actions released this 07 Oct 16:19
· 489 commits to main since this release
c1d2b87

Added

  • A subagent can work in a git worktree of its own. Give the
    subagent tool isolation: "worktree", or put isolation: worktree in
    an agent definition's frontmatter as in Claude Code's agent files (the
    call's value wins), and the subagent runs in a new worktree of your
    repository, on a branch krowk/<8 hex>, under
    ~/.local/share/krowk/worktrees (or $XDG_DATA_HOME/krowk/worktrees).
    Subagents started together no longer overwrite each other's files. The
    worktree starts from your files as they are, uncommitted changes
    included: modified, new and deleted files, and what you staged (ignored
    files only if you force-added them, nested repositories never), are put
    in one commit, krowk: working state for <hex>, on top of your HEAD, and
    the branch starts there; with nothing uncommitted it starts at HEAD.
    Your index, HEAD and files are not touched. When
    it finishes having changed nothing, the worktree and its branch are
    removed. When it changed something, its changes are applied to your
    working tree (see below). Outside a git repository the call fails
    with isolation: worktree needs a git repository. Without the field, or
    with "none", a subagent runs in your directory as before. Your
    repository's git hooks do not run when the worktree is made.

  • A subagent's work in its worktree comes back to your working tree.
    When a subagent with isolation: "worktree" finishes having changed
    something, krowk applies its commits and uncommitted changes, new,
    deleted and binary files included, to the working tree the parent
    agent works in, as uncommitted changes: your index and HEAD are not
    touched. The worktree and its branch are then removed, its final state
    kept as refs/krowk/snapshots/<hex> for 30 days, and the summary the
    parent gets ends with Changes applied to your working tree: <files>.
    Subagents finishing at once apply one after another. When a file
    doesn't apply cleanly (you, or a sibling, changed the same lines),
    nothing of it is applied, the worktree and branch are kept, and the
    summary ends with Changes not applied (conflicts in <files>). Worktree: <path>, branch krowk/<hex>. A subagent that changed a
    submodule is kept the same way, as krowk doesn't apply submodule
    changes. So is one that changed files inside a .git, .claude,
    .codex or .krowk directory, which the file tools only change with
    your say: its summary ends with Changes not applied (they touch protected files: <files>), the worktree and branch, and the
    krowk worktrees apply <hex> you run to apply them.
    krowk worktrees apply <hex|path> [--to <dir>] does the same for any
    kept worktree, protected files included, into your repository's main
    checkout by default: it is how you bring a --worktree session's work
    home. It refuses (exit 4) while that session is still running, or when
    a file conflicts, and refuses (exit 1) a --to that is a checkout of
    another repository.

  • A subagent's worktree has your submodules checked out. Before the
    subagent starts, krowk initialises every submodule of its worktree,
    submodules inside submodules too, at the commits the worktree records.
    One you have initialised in your main checkout is copied from there,
    with no download; its origin stays its own URL. One you haven't is
    cloned from its URL, with no terminal to prompt on and a 2-minute limit.
    A submodule that can't be initialised stays empty, and the subagent's
    first prompt says which and what git said. URLs from .gitmodules that
    would run a command (ext::) or copy a repository from a local path are
    refused. Your repository's config is not changed, so a submodule you
    deinitialised stays that way in your checkout. The agent can edit files
    in a submodule but not commit in it. An edit or a new file in any
    submodule, at any depth, keeps the worktree; an unchanged one is removed
    with its submodules' git data.

  • A subagent's worktree starts with your build output. On a file
    system that can clone files without copying their blocks (btrfs, XFS
    with reflink, APFS on macOS), krowk clones your main checkout's target
    and node_modules into a new worktree before the subagent starts, so its
    first cargo build finds nothing to compile and the clone takes almost
    no disk. target is skipped while a build is running in it, and
    node_modules unless the worktree's lockfile (package-lock.json,
    pnpm-lock.yaml, yarn.lock, bun.lock or bun.lockb) is the same as
    your checkout's. Anywhere else, and on Windows, nothing is copied. Choose
    the directories with worktrees.seed in config.json, for example
    {"worktrees": {"seed": ["target", ".venv"]}}; [] turns it off.
    What was skipped and why is in seed.log, beside the repository's
    worktrees.

  • Worktrees appear almost instantly on btrfs. When krowk's worktrees
    directory is on btrfs, on the same file system as your repository, and
    the btrfs program is installed, a new worktree for a subagent or a
    krowk --worktree session is a snapshot of a copy of your source tree
    that krowk keeps up to date, with your build output already in it:
    about a tenth of a second whatever the repository's size, and its first
    cargo build compiles only what differs from your last build. Twenty
    agents starting at once in one repository each get theirs in under half
    a second, rather than waiting on each other. Anywhere else, or if a
    snapshot fails, the worktree is checked out as before.

  • A subagent's worktree gets the ignored files you list in
    .worktreeinclude.
    As in Claude Code, put a .worktreeinclude at the
    top of your main checkout, in .gitignore syntax (.env*,
    config/local.yml), and every file it matches that git ignores is
    copied into a new worktree before the subagent starts, with its mode.
    They are copies, never links, so the agent can't change your real
    .env; on a file system that clones files they cost no disk. Tracked
    files are never copied over the worktree's, nothing is read through a
    symlink or written over a file the worktree has, and the
    worktrees.seed directories are left to seeding. A file that can't be
    copied is skipped and noted in seed.log. Without a
    .worktreeinclude, nothing is copied.

  • A subagent's worktree can run your project's setup command first.
    Set worktrees.setup to a shell command (npm ci, a code generator,
    a database script) and krowk runs it in each new worktree before the
    subagent starts, so the agent doesn't burn turns finding out the
    project isn't installed. Put it in ~/.krowk/config.json for yourself,
    or in a repository's .krowk/config.json, where it runs only once you
    have trusted the repository (the same question that turns its hooks
    on); a trusted repository's command is the one that runs. It runs in
    the sandbox: network on, the worktree writable, your main checkout and
    your home not, so a cloned repository's install can't touch anything
    else. On a machine with no sandbox it runs only for a trusted
    repository. It gets KROWK_PROJECT_ROOT (your main checkout),
    KROWK_WORKTREE_PATH and KROWK_PORT_BASE: each live worktree holds
    its own ten ports from 20000 up (20000, 20010, …), and the subagent's
    own commands see KROWK_PORT_BASE too, so dev servers in two worktrees
    don't collide. A build or install command waits for a build slot as the
    agent's would. Its output is in krowk-setup.log in the worktree's git
    directory. When it fails, or runs past worktrees.setupTimeout (600
    seconds by default), the subagent starts anyway and its first prompt
    says so, with the exit code or "timed out" and the last 50 lines of
    output.

  • krowk worktrees lists, removes and prunes the worktrees krowk
    kept.
    krowk worktrees shows every one, across repositories: its
    path, repository, branch, base commit, commits ahead of the base,
    whether it has uncommitted changes, the session that made it, its age,
    and whether that session is still running (--json for scripts).
    krowk worktrees remove <hex|path> removes one, and keeps its branch.
    It refuses while a running session uses it, and refuses one with
    uncommitted changes or commits ahead of its base unless you add
    --force. A forced removal first saves the uncommitted changes, new
    files included, as refs/krowk/snapshots/<hex>, and a HEAD its
    branch doesn't hold (the agent detached it, or switched branch, and
    committed) as refs/krowk/snapshots/<hex>-head, and prints the commands
    that bring the worktree back. Ignored files, such as build output and
    the copies .worktreeinclude made, don't count as changes and are
    deleted with it. krowk worktrees prune clears git's record of krowk's
    worktrees whose directory you deleted (only krowk's: your own
    worktrees' records are left alone, even when their drive isn't
    mounted), deletes worktree directories git no longer knows when their
    files match their branch (one with changes is left in place and
    named), and deletes snapshots older than 30 days. A repository that has
    moved, or whose drive isn't mounted, is skipped and named. Prune also
    runs by itself in the background, at most once a day, when a session
    starts. Worktrees made by an earlier krowk are
    listed too, with their base shown as unknown, so removing one of them
    needs --force.

  • krowk --worktree and krowk -p --worktree start a session in a git
    worktree of its own.
    Sessions you start in separate terminals no
    longer share one checkout. krowk makes the worktree from the repository
    you are in, your uncommitted changes included, on a branch
    krowk/<8 hex>, readies it as it does a subagent's (submodules, build
    output, .worktreeinclude, worktrees.setup), and starts the session
    there; the TUI's header shows the branch, and the agent's commands get
    KROWK_PORT_BASE. When the session ends having changed nothing, the
    worktree and its branch are removed. When it changed something, both
    are kept and krowk says Worktree kept: <path> (branch krowk/<hex>) on
    stderr, with the krowk worktrees apply <hex> that brings its changes
    into your checkout; -p --output-format json or stream-json names it in the
    result instead, as worktree: {path, branch, commits, uncommittedChanges}. Resuming such a session runs it in its worktree
    again; if that directory is gone, krowk says so (exit 2) and points to
    krowk worktrees. Outside a git repository --worktree is refused
    (exit 1). It can't be combined with --resume, or with -p --daemon:
    the session runs in krowk's own process, which holds the worktree and
    finishes it on the way out, and the TUI does the same rather than using
    the host daemon. A worktree is trusted as the repository it was made
    from. krowk sessions --worktree <path> filters the listing as before.

  • An agent can commit inside a worktree krowk made for it. When a
    sandboxed session runs in a worktree under
    ~/.local/share/krowk/worktrees (or $XDG_DATA_HOME/krowk/worktrees) on
    a krowk/… branch, git add and git commit now work there, and the
    commit shows up in your main checkout under your own name and email:
    krowk reads user.name and user.email for that worktree and passes
    them in, without exposing your .gitconfig. The agent can move only
    its own krowk/… branches. Your other branches, tags, replace refs and
    packed-refs stay read-only, and so do the repository's config, hooks
    and HEAD, and your other worktrees. So git tag fails in there. A
    symlink the agent leaves among those branches, their reflogs or the
    worktree's git files is removed after the command, and the command
    fails and says so.
    Every other repository, your own linked worktrees included, keeps
    .git read-only as before.

  • Builds and test runs take turns across every krowk on the machine.
    Agents spend most of their time waiting on the model, so one machine
    can run many of them, until several start cargo test or
    npm install at once and it runs out of memory. Now, when the agent's
    bash command runs a build or test program (cargo, make, npm,
    pnpm, yarn, go, pytest, gradle, mvn, dotnet, swift and
    the like, wherever the command line itself runs one, but not inside a
    script it calls or a bash -c "…"), it first waits for one of a fixed
    number of build slots that every krowk session and subagent on the
    machine shares, and holds it until the command ends. Other commands run
    straight away. While it waits, the command shows
    waiting for a build slot (N in use), and its result says how long it
    waited when that was over a second. Interrupting the turn stops the
    wait. A krowk that is killed or crashes frees its slot at once. A build
    the command starts in the background (cargo build &) is not held to a
    slot once the command returns. There are a quarter as many slots as
    cores by default (at least one); set "builds": {"slots": 2} in
    ~/.krowk/config.json to change that. A build also gets
    CARGO_BUILD_JOBS set to the cores divided by the slots, so even a
    build running alone gets only that share; set CARGO_BUILD_JOBS
    yourself, or raise builds.slots, to change it. Your own value is
    passed on in the sandbox too. This applies to krowk's own agent;
    Claude Code and Codex run their own shells.

  • Agents take turns across every krowk on the machine, and worktrees
    wait for disk.
    subagents.maxParallel limits one turn, so ten
    sessions each starting four subagents started forty at once. Now every
    subagent, and every --worktree session, holds one of a fixed number of
    agent slots that every krowk on the machine shares, for as long as it
    runs. When all are held, a subagent shows
    waiting for an agent slot (N in use) and starts when one frees up;
    interrupting the turn stops the wait. A --worktree session prints the
    same line before it starts, and Ctrl-C stops it. A krowk that is killed
    or crashes frees its slot at once. Sessions without --worktree never
    wait, and a --worktree session's subagents run on its slot rather
    than taking more, so a session holding the last slot never waits on
    its own subagents. The default is twice your cores or twice your memory in GiB,
    whichever is fewer, between 4 and 64; set
    "subagents": {"maxHost": 8} in ~/.krowk/config.json to change it (0 is
    refused). Before making a worktree, krowk also checks that its disk will
    have 4 GiB free beyond twice the size the worktree is expected to be
    (the last one krowk made of that repository, else your tracked files).
    When it would not, nothing is made, and the session or subagent fails
    with not enough disk for a worktree: <free> free, <needed> needed,
    pointing at krowk worktrees prune and krowk worktrees remove.

  • The README covers worktrees, and scripts/bench-worktrees measures
    them.
    The README's new Worktrees section sums up isolation, applying
    back, --worktree, .worktreeinclude, worktrees.setup, builds.slots,
    subagents.maxHost and krowk worktrees. In a checkout of krowk,
    scripts/bench-worktrees makes 20 worktrees of a scratch clone at once,
    runs cargo check in all of them behind the build slots, applies three
    subagents' changes back (one a conflict), removes everything and prints
    creation p50/p95, check time, peak memory, slot wait and leftovers. It
    calls no model.

  • A host says why a viewer stays on the relay. When a viewer offered
    the direct path never reaches it — a firewall on the host, or a tailnet
    access policy that doesn't allow the port, drops the connection before
    krowk sees it — krowk sync host now says so once per device, 30 seconds
    after the viewer joined:
    krowk: session …: device 0123abcd stays on the relay — nothing it sent reached this machine's direct addresses (100.64.0.1:51915, …); if it is on this tailnet, a firewall here or the tailnet's access policy may be refusing the port. A viewer that reached the direct path once and fell
    back is never named. The session goes on by the relay, as before.

  • A bare krowk link no longer lands in a pull request or an issue. On
    GitHub a bare krowk.com/a/… link doesn't unfurl. It shows up as a blue
    link that says nothing about the file. When an agent running in krowk
    calls gh pr create|edit|comment or gh issue create|edit|comment with
    one in the body, krowk refuses that one call and tells the agent to paste
    the krowk block instead, and where to get it (paste.markdown from
    krowk uploads show <link> --json). The agent rewrites the body and posts
    again without you stepping in. The body is checked whether it is given
    inline, through a heredoc, or with --body-file when the file can be
    read. A card link inside a krowk block is fine, and nothing else is
    touched: other gh commands, bodies without a card link, and posts to
    Slack or Basecamp, where a bare link unfurls. This isn't a permission
    rule, so it holds in every mode, bypassPermissions and unhinged
    included, on krowk's own models, Claude Code and Codex alike. On Codex,
    krowk passes the check as a hook, and the first time, has Codex record it
    as trusted in its config.toml (a hooks.state entry for that one
    command), because Codex runs only trusted hooks. krowk's Codex accounts
    share your ~/.codex/config.toml, so that is where the entry lands. On
    Windows, Codex sessions don't have the check yet. A code span or fenced
    block that quotes a card link is left alone.

  • The agent can ask you which way to go. When a choice is yours, the
    agent asks up to four questions with options to pick from, and you answer
    over the prompt: a number or Enter picks, Space toggles where several may
    be picked, Tab moves between questions, and typing gives your own answer
    instead. Esc declines and the agent carries on with its best judgment.
    The native agent gets an ask_user tool for this. Claude Code's
    AskUserQuestion and Codex's questions, which krowk used to refuse, now
    reach you the same way, in plan mode too. Questions are only asked where
    someone can answer: krowk -p doesn't offer the tool, and a backend that
    asks anyway is told to decide and say what it assumed. For protocol
    clients, an approval.requested frame can carry questions, and
    approve answers them with answers.

  • Paste a screenshot into the prompt. Ctrl-V (or Alt-V, where the
    terminal keeps Ctrl-V for itself) puts the clipboard's image in the prompt
    as [Image #1], and the model sees it with the text. Dragging an image
    file onto the terminal, or pasting its path, does the same, several at
    once included. A file is read the moment it's dropped, so a macOS
    screenshot dragged from its thumbnail still works. [Image #N] is one
    unit to the caret: Backspace takes it whole. Typing goes on while a large
    screenshot is read, and the image lands where you pasted it. PNG, JPEG,
    GIF and WebP are taken, scaled down to 2,000 pixels a side and under the
    providers' size limits as they're pasted, and kept beside the session's
    log in ~/.krowk/sessions/<id>/images/. Every provider and backend gets them
    (Anthropic, OpenAI, xAI and other Chat Completions servers, Claude Code
    and Codex), and a model the catalog says can't read images is refused
    with the prompt handed back. The clipboard is read with wl-paste or
    xclip on Linux (krowk names the one to install if neither is there),
    osascript on macOS, and PowerShell on Windows and under WSL. Ctrl-V with
    only text on the clipboard pastes the text.

  • On Linux, krowk -p now makes edits without asking, inside a sandbox.
    A -p run with no --permission-mode and no defaultMode used to refuse
    every edit; where bubblewrap works it now runs in acceptEdits, with its
    commands inside the workspace sandbox below, and inside a container in
    acceptEdits with the file tools held to the same fences. Pass
    --permission-mode default to keep the old behaviour. --sandbox workspace|read-only|strict|off puts the bash tool inside bubblewrap.
    The sandbox allows rather than lists what it hides: your home is replaced
    by an empty one, and only the workspace and the Rust toolchain's homes
    come back, so nothing else in it — ~/.ssh, .git-credentials, .netrc,
    agents' logins, ~/Documents — is in reach, and programs installed under
    the home (mise's shims, ~/.local/bin) are not on the sandbox's PATH. workspace writes
    the working directory and its added directories and keeps the network;
    read-only writes nothing and has no network, not even DNS; strict
    writes the workspace and has no network. Every .git in
    the workspace, nested repositories and gitdir files included, and every
    hooks directory a repository's core.hooksPath names there — found by
    searching the workspace once a turn and re-checking only what changed —
    stays
    read-only, as do .claude, .codex and .krowk; a .git a command
    creates is removed after the call. The file tools hold the same lines
    under every permission mode, and open exactly the path they checked, so a
    symlink swapped in meanwhile fails the call. A sandboxed command gets
    only PATH, TERM, the locale, USER, a private HOME and TMPDIR —
    no provider key, token or agent socket — no inherited file descriptor, and
    a session of its own. RUSTUP_HOME and CARGO_HOME are bound read-only
    with cargo's credentials.toml hidden, so cargo build of what is
    already fetched works and fetching a new dependency does not. PreToolUse
    hooks are your own and run outside the sandbox. A sandbox that cannot be
    enforced refuses the run with a fix instead of running unsandboxed:
    bubblewrap missing or blocked, a workspace too large to search for
    repositories, a Claude Code or Codex backend, --daemon, macOS (Seatbelt
    is not built yet) and Windows.

  • krowk's own agent uses your MCP servers. It reads the mcpServers
    you set up for Claude Code (~/.claude.json, its settings.json), those
    in ~/.krowk/config.json, and a repository's .mcp.json once you trust
    the repository, since a .mcp.json names programs to run. Stdio and
    streamable-HTTP servers work. The model gets two tools, mcp_search and
    mcp_call, instead of every server's tools, so fifty MCP tools cost each
    turn about 140 tokens. No server starts until the model searches.
    Mcp(server:tool) permission rules allow, ask about or deny each call,
    and search leaves out tools a deny rule covers. A repository's servers
    are asked about again when its .mcp.json changes after you trusted it,
    and no server inherits your provider keys unless its config sets them.

  • Idle sessions move off the machine as weekly vintages. krowk sessions archive takes every native session idle for more than 14 days
    (--older-than DAYS, or KROWK_ARCHIVE_AFTER_DAYS) and stores each ISO
    week's sessions in the registry as one vintage: zstd-compressed JSONL,
    sealed under the account key, so the registry only ever holds ciphertext.
    An existing vintage for the week is merged, never overwritten. --weekly
    runs only when a week has passed since the last run, so it can be put on
    a schedule. An archived session keeps its title, summary, directory,
    dates, models and cost on the machine, so krowk sessions still lists
    it, and krowk sessions show or krowk -p --resume fetches its week and
    restores it into krowk.db first. krowk sessions restore <id> does that
    on its own. krowk sessions pin <id> keeps a session from ever being
    archived, and unpin undoes it. It needs krowk sync set up on the
    machine.

  • The Claude Code plugin brings the MCP server. Installing krowk@krowk
    in Claude Code now adds the krowk MCP server (npx -y @krowk/mcp) beside
    the skill, so publishing works without a separate claude mcp add; if you
    added krowk-mcp that way, run claude mcp remove krowk in the project
    where you added it, or the model sees every krowk tool twice there. When
    krowk drives Claude Code as a backend the plugin's server is not started,
    and krowk's own MCP server is the only one. The plugin's version now
    moves with every release: 0.12.0 and 0.12.1 shipped without moving it, so
    Claude Code offered no update.

Changed

  • krowk's own git calls now run with repository hooks, fsmonitor and
    commit signing turned off.
    The git krowk runs itself — the branch on
    the status line, the commit a synced session is at, the files a search
    lists — looks for hooks in an empty directory in krowk's home
    (~/.krowk/no-hooks), runs no core.fsmonitor command and signs
    nothing. Filters such as git-lfs still run, as a checkout needs them.
    Git the agent runs through its bash tool is unchanged.
  • Uploads are now krowk artifacts, matching the API and the JSON.
    krowk artifacts create | list | show | attach | delete | claim is the
    full set, named as /v1/artifacts and data.artifacts already were.
    krowk push is the short form of artifacts create, and krowk claim
    of artifacts claim; both stay and work as before. krowk uploads …
    still runs every command it did, and krowk help uploads shows the
    artifacts help, but help no longer lists it. Breadcrumbs and error fixes
    now name krowk artifacts …, so an agent that copies them gets the new
    spelling.
  • krowk's messages read like sentences, and failures say what to try.
    An error leads with what went wrong in bold, puts the command that fixes
    it on a Try: line of its own, and ends with its code. It no longer drops
    the alternative a fix offers ("…, or upload anonymously"). A mistyped
    command, subcommand, flag or help topic gets a "did you mean". Flag errors
    are plain English (unknown flag --nope, --limit takes a whole number)
    rather than Go's flag provided but not defined: -nope, and point at the
    command's own help. Bare krowk runs names the subcommands it takes
    instead of calling runs unknown. Successes, warnings (a yellow !),
    labels and empty lists are capitalised, and krowk doctor shows a
    checklist instead of raw JSON. Colour follows NO_COLOR and TERM=dumb,
    and an error is coloured only when stderr is a terminal. The JSON
    envelopes are unchanged except for the fix wording.
  • krowk --help is the readable page even when piped. An agent running
    krowk --help used to get the whole command catalog as about 50 KB of JSON.
    It now gets the overview, which has an Examples section and points to the
    new krowk help agents page. That page explains the JSON envelope,
    breadcrumbs, exit codes and which command answers which task.
    krowk help --json, or --jq, still prints the catalog. Help headings
    are in title case and bold on a terminal.
  • krowk hosts lists your machines that host, with no Tailscale tag.
    It used to list only tailnet machines tagged tag:krowk-host, which took
    an edit to the tailnet policy in the Tailscale admin console. Now it lists
    every machine on your device list that hosts a synced session — from the
    registry's sessions, so it needs this machine's sync keys — with whether
    it hosts one now and whether this machine's Tailscale reaches it
    directly. A host names its own tailnet node, sealed, in each session it
    hosts, so a session hosted by an older krowk shows its host as reachable
    only once a newer one hosts it again. Machines that aren't yours, and
    yours that host nothing, aren't listed. tag:krowk-host is no longer
    read. In krowk hosts --json, name is now the machine's name on your
    device list rather than Tailscale's (dnsName is still Tailscale's), and
    rows also carry device, os, thisMachine, hosting, direct and
    sessions; online still means online on the tailnet.
  • The same-user check refuses to run on a tagged machine. Tailscale
    names every tagged machine of a tailnet as one shared user,
    tagged-devices, so on a tagged host KROWK_TAILSCALE_SAME_USER=1 would
    have let in any tagged machine and turned away your own. It now says so
    and offers no direct path; untag the machine, or leave the check off.
  • A synced session goes direct over Tailscale with no setup. With
    Tailscale running on both machines, krowk sync host offers the direct
    path by itself: it fetches the registry's relay ticket public keys
    (GET /v1/relay/ticket_keys) at every hosting, so a viewer's session
    moves off the relay with no KROWK_RELAY_TICKET_KEYS file to write, and
    a key the registry rotates in reaches the next session without a
    reinstall. It keeps them in ~/.krowk/cache/ for a day, used only when
    the registry can't be reached. Admission is unchanged: only the
    registry's ticket and the device's signed challenge get in, and being on
    the tailnet admits nobody. When there is no direct path, the host says
    why in one line on stderr, such as krowk: no direct path (Tailscale isn't running: it is Stopped); the session goes by the relay, and the
    session goes by the relay. KROWK_RELAY_TICKET_KEYS still names the keys
    by hand, and wins over the fetched ones, for a stand-in registry.
  • The Gemini extension, the MCP registry entry and @krowk/mcp describe
    Krowk as a coding agent harness.
    They used to describe a permalink
    uploader. Each now says that it's Krowk's publishing, for Gemini CLI or
    for any MCP client, and what it turns into links. With
    the Claude Code plugin's, they no longer say links unfurl in GitHub and
    Linear, which don't unfurl links. Those links unfurl in Slack and Basecamp;
    in GitHub and Linear, images show inline. The server.json here was over the
    MCP registry's 100-character description limit and didn't validate against
    its schema; it's within it now. make release-check and the release
    workflow now hold it to that limit, and the other descriptions to one line.
  • What you say in a session is shown in markdown, as answers are.
    `code` is in the code colour, **bold** is bold, and links, lists,
    quotes and fenced blocks look as they do in an answer, all on the band of
    your prompt. Ctrl-Y still copies the prompt as you typed it.
  • Reading your synced sessions is signed by this machine's device key.
    krowk sync sessions, attaching, --resume and the host now sign the
    calls that list a session, show it and list its chunks, as they already
    signed every write. Once the registry requires it, a copy of your API key
    without the device's key reads none of your sessions, not even their
    sizes and times. Two reads signed in the same millisecond on one machine
    are told apart by signing the second again.
  • The new-release notice speaks up only when the release is worth it.
    A major release (before 1.0, a new middle number, like 0.12 → 0.13) is
    mentioned once and once more a week later, a minor release once, and a
    patch release never — unless a release you skipped has a security fix,
    which is mentioned once a day until you upgrade. In the agent it's one dim
    Update: row under the session header, never a banner, and Ctrl-O's
    session details always say whether a newer release is out. The agent
    never waits on the check: it uses the last answer and asks for the next
    one in the background. After other commands the line on stderr follows
    the same rules. KROWK_NO_UPDATE_CHECK=1 still turns it all off.

Fixed

  • A Mac with the Tailscale app can host over the direct path. The app
    has no tailscaled socket: its LocalAPI is on 127.0.0.1 behind a token.
    On macOS, krowk sync host and krowk hosts now try the open-source
    tailscaled's sockets, then the App Store app's sameuserproof file in
    its group container, then the standalone app's in /Library/Tailscale.
    KROWK_TAILSCALE_LOCALAPI names the app's LocalAPI by hand
    (http://127.0.0.1:<port> with KROWK_TAILSCALE_LOCALAPI_TOKEN, or the
    sameuserproof file's path); only loopback is taken.
    KROWK_TAILSCALE_SOCKET still names a socket, and wins over it.

  • A synced host no longer hangs on a relay link that stopped working
    without saying so.
    If the relay kept answering heartbeats but acked none
    of what the host sent, viewers stopped receiving updates while the host
    kept writing into the void. The host now drops such a link once eight
    batches have gone unacked for ten seconds, joins the relay again, and
    resends what was missed.

  • krowk devices add no longer says "nothing was added" when the new
    device may have your key.
    If the answer to the step that sends the key
    was lost and checking on it failed too, the pairing used to end as if
    nothing had happened, although the new device might hold your key
    without being on your list. Now the laptop goes on: it waits for the new
    device to confirm and adds it, or, if it never confirms, still lists it so
    krowk devices remove can take it off.

  • krowk sync host notices a device removed while it runs. It used to
    check your device list only when it started, so a long-running host went
    on as before after you removed a device. It now reads the list again every
    minute. If another device was removed, it hosts the session again from
    the top, under the new list; if this device was removed, or the list was
    started over, it stops.

  • The installer says when another krowk would run instead. If a krowk
    earlier on your PATH (an older install, or a build from source) would
    answer before the one just installed, the installer now names it, says
    its version, and how to fix it. Before, krowk --version quietly showed the
    old one.

  • Pairing refuses a malformed key exchange outright. If the other side
    of krowk devices add / krowk sync join sends a degenerate SPAKE2 point
    (one of small order, or one spelled non-canonically), the pairing ends at
    once instead of carrying on. An honest device never sends one; this only
    closes the door on a misbehaving peer.

  • Running krowk sync init --start-over again says what it did. While
    the old keys from a start-over are kept, running it again carries on that
    start-over (sealing any sessions left under the new list) and never begins
    a new one. It now says so, and that a new start-over needs
    krowk sync recovery discard-old first.

  • A new device can't take a name that only looks like one already on your
    list.
    Adding or pairing a device is refused when its name reads like
    another device's: a Cyrillic а for a Latin a, full-width letters, or
    1 or I for l. Each name in a removal or recovery review now names
    exactly one device.

  • A synced session moves to a new key when you host it again after
    removing a device.
    A session published before you removed a device used
    to go on under the session key that device held, however often it was
    hosted again. Now the host first moves it to a new key, sealed under your
    current user key, which the removed device never had; a start-over does
    the same for the sessions it brings along. Everything written before
    still reads on all your devices. A host already running when you remove
    the device keeps its key until the session is next hosted. A session that
    was never moved is stored exactly as before and still opens on older
    krowk versions; one that was needs this version, and a viewer that
    opened it before the move has to open it again.

  • Copying from the TUI no longer drags the layout along. Text in the
    transcript starts at the first column: a mouse selection of your prompt,
    an answer, a list or a code block comes without the two-column margin,
    the ▎ bar before your prompts, the padding inside code blocks or the
    spaces that filled a band to the edge. Code blocks are left for the
    terminal to wrap, so a long line of code copies as one line, the block's
    language sits on the row above the code, and tabs in code are four
    spaces rather than one. Prose still wraps between words, so a selection
    across a wrapped paragraph has a line break where each row ended:
    Ctrl-Y now offers the last answer, each code block in it and your last
    prompt, and copies the one you choose exactly as written, tabs included.
    In Ghostty, and in herdr, which is built on it, text from the first
    screen of a session used to copy with a break at every row the terminal
    had wrapped, because the prompt was kept at the bottom by moving the
    rows above it; the prompt now sits at the bottom from the start, the
    conversation filling the screen down to it, and nothing printed is ever
    moved.

  • Two quick Ctrl-Cs right after a prompt still print the resume line.
    Leaving that fast could beat the session's start to the TUI, so krowk
    exited 130 without krowk --resume <id> although the host had already
    started the session. The TUI now reads what the host already sent before it leaves.

  • The prompt stays on the bottom row when the window grows taller, in
    Ghostty and other terminals that add the new rows at the bottom.

  • What you said survives a narrower window. Each row is banded only as
    wide as its text, so the band no longer wraps onto rows of its own; a row
    wider than the new window still wraps, as any text does.

Install

# npm — the bare `krowk` name is blocked by npm's typosquat filter
npx @krowk/cli push screenshot.png

# The installer: the full build on a workstation, the lean one in CI
# and containers (or with --lean)
curl -fsSL https://krowk.com/install | bash

# Direct download — no runtime, no package manager. krowk_… is the
# full build (the agent, its TUI, sessions); krowk-lean_… the lean
# agent-container build.
curl -sSfL https://github.com/krowkcom/krowk/releases/download/v0.13.0/krowk_0.13.0_$(uname -s | tr A-Z a-z)_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz \
  | tar -xz -C /usr/local/bin krowk krowk-mcp

# From source
cargo install --locked --git https://github.com/krowkcom/krowk --tag v0.13.0 --features harness krowk

checksums.txt covers every archive on this release, and
checksums.txt.sigstore.json is its signature. To verify a
download (with cosign):

cosign verify-blob checksums.txt --bundle checksums.txt.sigstore.json \
  --certificate-identity https://github.com/krowkcom/krowk/.github/workflows/release.yml@refs/tags/v0.13.0 \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com
sha256sum --check --ignore-missing checksums.txt

The .cdx.json files are CycloneDX SBOMs of the full and lean
builds, each signed the same way. SECURITY.md in the repository says
how to rebuild a release and compare the bytes; this release's
reproduce job already did it once for Linux x86-64.