mindfork mindfork

mindfork 0.9.9 — files in, charts out, work in the background

mindfork 0.9.9 is out. It is a release about keeping the conversation moving: the assistant's code gets your files and hands its own back with no copy-paste in between, a delegation no longer holds the chat while it runs, and the engine is one command away instead of a build away.

Your files reach the code, and its files reach you

Until now the sandboxed Python could compute, but only on what the assistant typed into the script. Now a call names the chat's files it needs — by the number /file list shows or by name — and each is copied into the sandbox before the code runs: an attachment as its text, a stored file or a picture as itself. The copies are the sandbox's own, so nothing here changes under the code's hands, and only what it saves into /w/out comes back. That folder is the other half: a matplotlib chart, a CSV or a workbook saved there is kept with the chat, listed by /file list, packed by backups — up to ten files and 50 MB a call — and a PNG or JPEG is shown to the model, so it can check the chart it drew. Measured before shipping on Qwen 3.6 27B and Gemma 4 31B: a plot colour only the image carries was named nearly every time with the picture and never without — and blind, both models described a chart they had not seen, which is why every image the assistant is not shown is now said in the result, in words a small model does not talk past.

/file attach stops refusing a binary: a workbook or a zip is kept with the chat, readable by code alone, and a PDF, DOCX or web page keeps its original beside the text the assistant reads — one entry in the list, removed together, never your own file. /file open <name|#N> hands a file to whatever your system opens it with, and /file folder opens the chat's folder. Only document types open directly; a script, a shortcut or an HTML page the assistant wrote opens the folder it sits in instead, so nothing the assistant produced can run by being opened — and on Windows a workbook or document the code wrote carries the same mark as a download, so Office opens it in Protected View.

The same contract holds when Python runs on your own machine instead of in the sandbox: the files named in the call are copied in, what the code saves comes back, and a new per-process memory limit (Windows, off by default) turns a runaway allocation into a MemoryError instead of taking the machine's memory. The sandbox itself gained sympy, networkx, lxml, pyyaml, regex, feedparser, openpyxl, pypdf, tabulate, pillow and matplotlib — mindfork sandbox setup fetches only what is missing — and its packages are now packed into an image the code cannot alter, so one call can no longer leave a file that runs inside every later one. An existing sandbox needs that command run once more; the tool names it when it refuses.

Work that goes on without you

A delegation used to hold the conversation until it landed. Switch on Background runs in Settings → Tools and the assistant gains start_subagent: a delegation that returns at once — the assistant keeps answering you, past the end of its own reply — and whose result arrives later as a task notification in the chat. When the chat is open and idle the assistant reads it and reports by itself; otherwise the note waits for your next message. The run is a row of the chat list while it is out, its transcript opens as it streams, and a result that lands while you are looking elsewhere marks its chat unread. A staged dialogue (below) can run the same way. A background run never asks you to confirm a tool call — switch off the tools you would not let run unattended.

F7 (or /tasks) is the screen for all of it: every subagent and dialogue run across all your chats — the ones running, with the round and tool they are in; the ones that landed, with how they ended — and the app's own quiet work (reflection, consolidation, history compaction) as running, waiting or idle. Enter opens a run's transcript, F6 stops one — the app's own tasks included — and /tasks stop <kind> does the same from a terminal where F6 never arrives. A reflection stopped before it wrote anything gives its window back, so nothing is skipped and nothing is written twice.

Things can also happen at once. Subagent: parallel runs lets the delegations of one reply run together; Parallel tool calls lets the reads and page fetches of one reply run together — only tools that change nothing, four at a time on a cloud engine by default, one after another on a local engine until you raise it; and a new Parallel sessions setting on every engine mode says how many request streams the app may keep open. A managed llama-server is then launched with -np N --kv-unified, its slots sharing the one context pool, no extra memory. Parallel runs and sessions both default to 1, so nothing changes until you turn them up. Measured on one RTX 4090: two sessions gain little on a 31B model, four gain 2.9× on Qwen 3.6 27B. Every stream reserves what it will occupy in the pool and waits for room rather than overfilling it, so two subagents no longer die together with "Context size has been exceeded" after a minute of visible progress.

The app's own requests learnt to yield. A title, a reflection or a history compaction that is streaming when your message arrives is cancelled and made again after your reply, rather than holding your message until it ends — measured, a message that waited 46 s on a CPU-only server now waits about 24 s, one that waited 5.6 s on a GPU under two. A CPU-only host runs a smaller prompt batch for the same reason, and the app reads the server's own timing of every prompt: once per server session it says in the feed how fast prompts are processed and the one setting to change when that is slow.

A scene of two characters

run_dialogue stages a conversation between two personas the assistant composes, and the assistant directs it as it unfolds — a private stage direction to one character, a retake, a line rewritten outright — and ends it when the scene reaches its ending. The script is a child chat under the conversation, each side under its character's name, the director's interventions visible as notes; open it while it runs and each line streams in on its speaker's side. One engine session, one request at a time, so no extra VRAM on a local model; a run-time limit (30 minutes by default) bounds a scene that will not end. Gemma 3's chat template, which refuses two same-role turns in a row, is handled — for the director's memory of its verdicts, and for Ctrl+U impersonation, which used to answer with an engine error on every chat you had opened yourself.

The engine, downloaded

mindfork llama backends lists the llama.cpp llama-server builds published for your OS and architecture — cpu, vulkan, cuda-13.3, rocm-10.0, … — with their download sizes, and mindfork llama setup --backend <id> fetches one into data/llama/. Every file is checked against the sha256 the release publishes, an interrupted download resumes instead of starting over, and a CUDA build brings the CUDA runtime with it — without it the backend does not load and the server quietly runs on the CPU. Once unpacked, the binary is run and reports its build number and the compute devices it found, so a GPU backend with a missing driver says so instead of pretending. --set-binary writes the path into the settings; mindfork llama installed and mindfork llama remove <id> manage what is on disk, and the remove refuses while a setting still points at the build.

Better still, the llama-server binary field may now be left empty: the app takes the build installed last, or a llama-server sitting next to the application — so downloading one, or unpacking a llama.cpp archive beside mindfork, is enough to run in managed mode with nothing typed.

Pick up where it stopped

/continue resumes an interrupted reply in place — one cut by Esc, by a dropped connection or by the length limit: the model picks up exactly at the cut, the text grows inside the same message, and a turn interrupted between tool calls resumes its tool loop. It works on the local and external engines, on Gemini, and on Claude models up to the 4.5 generation; the providers that cannot resume a partial reply (OpenAI, Grok, current Claude models) are told apart, and the command says so instead of guessing. A reply that hit the length limit finally gets a note at all.

An external server's model is now named on screen even when you did not type it — the app asks the server what it is running — and the "Model (opt.)" field, which had never actually been sent, now reaches the server: llama-server in router mode, LM Studio, LiteLLM and OpenRouter all pick the model from it.

Off until you say so

  • The web tools start off. web_search, fetch_url and youtube_watch are disabled in a fresh installation: the search engines behind them are picked by the app and the query is built from your conversation, so nothing goes to them until you switch the tools on in Settings → Tools. An existing installation keeps whatever its settings already say.
  • A search key is only used where you put it. A TAVILY_API_KEY sitting in your environment for some other program is no longer picked up on its own. Name the variable, or enter the key, in Settings → Tools.
  • The sandbox's largest download is verified too — the Python distribution wasmer fetches on the app's behalf, until now the one file not checked against a pinned checksum.
  • A privacy policy, readable in the app: F1 → the tab that used to be "Disclaimer" is now Legal and carries both documents, in the interface language. The Windows installer shows it and every archive ships it. It says what stays on your machine, what leaves it and which setting has to be on first, and what reaches the author — nothing.
  • The spellcheck dictionaries say where they come from, with their licences installed beside them; en_GB moves to its author's current word list, about 14 000 more stems; and an installed build gets a dictionaries/ folder of your own in the data directory, with a note on how to add one.

Also in this release

  • Files in an older encoding are read — and edited — as they are. A windows-1251, KOI8-R or cp866 text, or Notepad's "Unicode", used to reach the assistant with every letter replaced by the replacement character, and an edit in an attached project rewrote the whole file that way. Files are read in their own encoding now, an edit is written back in it — refused with nothing written when the encoding cannot hold the new text — and a web page in an older encoding is read as it declares itself, or recognised from its text.
  • A fetched page is named after the page, not its site — every chapter of the Rust Book used to be attached as "The Rust Programming Language".
  • Spellcheck no longer stumbles over a stress mark: a word carrying one is judged whole, stressed text is left alone, and a real typo under the mark is still underlined.
  • The assistant can name its language model. get_llm_name answers "which model are you?" with the model actually generating the reply, and get_llm_history lists the profile's dated history of model changes — filled from your existing chats on the first launch, as far back as they go.
  • The self-model screen (F3) reads as two named halvesAssistant and User, or the names from the profile — with the newest observation first; a setting flips the order.
  • Every key hint names only keys that work, sits in the same place on every screen and follows what you have selected, and F1 is among the hints everywhere. The status bar no longer stacks its hints into a column while a turn runs, and says cancel for Esc while a reply is generating.
  • An API-key field says whose key it isOpenAI API key, Tavily API key (env, opt.) — everywhere one is entered.
  • The chat list counts messages the way the conversation reads: your questions and the assistant's replies, not every tool call's result.
  • A long title no longer ends mid-word: it is cut where it is drawn, to the columns that screen has, always with "…" — and titles an older limit had cut on disk are repaired.
  • A tool round on OpenAI no longer fails when the model reasons in stages, and a server error inside an open stream ends the reply as an error — with the retry that was always meant to run — instead of passing for a finished reply cut mid-word.
  • In KDE Konsole the footer names the line-break chord the terminal can actually deliver (Alt+Enter), instead of one it silently swallows.
  • mindfork.exe calls itself mindfork in Task Manager and Explorer, with the author and copyright it was missing; the installer carries the real version instead of 0.0.0.0.
  • The Python sandbox installs and runs again. The Python package is pinned to an exact version, after a build published in the Wasmer registry in late August could not be compiled by the runtime the app pins. A sandbox installed in that window needs mindfork sandbox setup --force.

One note on data: chats gain a list of stored files, and the data folder a files/ directory beside chats/; chat files move to schema 4 — a version stamp for dialogue transcripts, then the repair of titles an older limit had cut. Both migrations happen automatically on the first start, after a pre-migration backup, and nothing is removed; an older build refuses to open migrated data rather than misread it.

The full list is in the changelog. Downloads — Windows installer and archive, Linux archive, .deb, .rpm and .pkg.tar.zst — are on the releases page.