<h1>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/vshylov/mindfork-rs/raw/HEAD/assets/mindfork-wordmark-dark.svg?sanitize=true">
<img src="assets/mindfork-wordmark-light.svg" alt="mindfork" width="320">
</picture>
</h1>
[](https://mindfork.io)
[](https://github.com/vshylov/mindfork-rs/actions/workflows/ci.yml)
[](https://sonarcloud.io/summary/new_code?id=vshylov_mindfork-rs)
[](https://github.com/vshylov/mindfork-rs/actions/workflows/dependabot/dependabot-updates)
[](https://github.com/vshylov/mindfork-rs/releases)
[](https://crates.io/crates/mindfork)
[](LICENSE)
[](https://ratatui.rs/)
**A terminal AI chat written in Rust: local models via llama.cpp, or OpenAI,
Anthropic, Gemini, Grok and OpenRouter in the cloud, with persistent memory,
notes, RAG and tools.** One native binary for **Windows**, **Linux** and **macOS**
on Apple Silicon (a preview), built on [ratatui](https://ratatui.rs).
**No model yet?** `mindfork demo` opens the app with sample chats and a scripted
model in a throwaway folder — no download, no key, nothing written outside a
temporary directory.
> mindfork is not trying to be yet another LLM client. The premise is that a
> local Gemma or Qwen becomes **more self-aware and more interesting to talk
> to** once it is given room to reflect: it keeps notes about you, maintains a
> self-model it can revisit, reads and adjusts its own system message and
> sampling mid-conversation, and can delegate work to a subagent that has its
> own tools.
<p align="center">
<img src="assets/screenshots/chat-dark-en.png" alt="mindfork chat: a conversation with an expanded thoughts block, a GFM table, a Mermaid flowchart drawn as text graphics, a LaTeX line and a note_save tool card" width="900">
</p>
Every screenshot here is **generated from code** against a demo profile — never
captured from a real session — and a gate test fails the build the moment they
drift from what the app renders
([docs/history/demo-screenshots.md](docs/history/demo-screenshots.md)).
<details>
<summary><b>More screens</b> — the chat list, settings (model & tools), and the assistant's self-model</summary>
<p align="center">
<img src="assets/screenshots/chat-list-dark-en.png" alt="the full-screen chat list: search, sort, message counts, an active-chat marker" width="900">
<img src="assets/screenshots/settings-model-dark-en.png" alt="settings, Model/server: a managed llama-server with model path, context, GPU layers, FlashAttention and speculative decoding" width="900">
<img src="assets/screenshots/settings-tools-dark-en.png" alt="settings, Tools: the agentic loop, web search, the Python sandbox, video and file-access switches" width="900">
<img src="assets/screenshots/self-model-dark-en.png" alt="the self-model screen (F3): the assistant's summary, goals with statuses, its model of the user, and dated observations" width="900">
</p>
</details>
---
## What it does
- **Any engine, one contract.** A managed `llama-server` the app launches
itself, any OpenAI-compatible server you already run (vLLM, LM Studio, Ollama,
a gateway like LiteLLM), the **OpenAI**, **Gemini**, **Claude** and
**Grok** clouds, or the **OpenRouter** gateway — one key in front of every
vendor's models — each with its own settings, all configured at once, and
switching loses nothing.
- **Memory that persists.** A **self-model** the assistant maintains about
itself and about you (`F3`), notes it writes and links into a graph, and a
**knowledge base** with semantic retrieval over your own files. Isolated per
companion profile, all of it on your disk.
- **Real tools, behind switches you set.** Web search and page fetching, a
**Python sandbox** (Wasmer/WASIX — no host file access), file access jailed to
one directory you name, YouTube video understanding, and tools from any **MCP**
server. Everything off by default; an optional confirmation prompt shows
exactly what a call is about to do.
- **It can hand work off.** Subagents with their own tools and no chat history,
a staged dialogue between two personas, and both of those in the **background**
— `F7` lists every run across every chat, with what it is doing now or how it
ended.
- **Your code project, contained.** Attach a directory and the assistant can
list, read, search and change files inside it — never above it. `F4` shows
every change as a diff, with one key to put a file back; build/run/test are
exact command lines you set, which it cannot extend.
- **A real TUI.** Markdown with tables and LaTeX, Mermaid drawn as text
graphics, streamed "thoughts" in a foldable block, images in and out, search
across every conversation, spellcheck, emoji, mouse, themes — over your
terminal's own background, painted whole with the contrast held for you
(a theme of your own is a file of one colour or of twenty), or no colours at
all (`NO_COLOR` is honoured) — and an interface in English or Russian.
- **Private by construction.** No telemetry, no update check, no account.
Everything lives in a `data/` folder next to the binary — take the folder, or
the USB stick it is on, and it comes with you. API keys are encrypted and bound
to the machine; backups are a zip with optional AES-256.
The longer version, with the reasoning: [the manual](docs/manual.md) and the
articles at [mindfork.io](https://mindfork.io/articles/).
---
## Getting started
### 1. Install
Grab a build from the
[releases](https://github.com/vshylov/mindfork-rs/releases): a Windows
**installer** or zip archive, Linux **deb / rpm / pkg.tar.zst** packages or a
tar.gz. On Linux one line installs the portable build — and on a rented GPU box
or in a container, the same line again repairs what a restart lost
([install.md](docs/install.md) §1, §3.4):
```bash
On Windows, **Scoop** installs it and keeps it current:
```powershell
scoop bucket add mindfork https://github.com/vshylov/scoop-bucket; scoop install mindfork/mindfork
```
On a Mac with Apple Silicon, **Homebrew** does (a preview —
[install.md](docs/install.md) §1):
```bash
brew install vshylov/tap/mindfork
```
Or build from source with a recent stable Rust (edition 2024):
```bash
cargo build --release # binary lands in target/release/
```
> mindfork is a TUI and needs a **real terminal**. Started with its output
> redirected or with no console, it says so and exits; the line commands
> (`backup`, `import`, `llama setup`, …) work anywhere.
### 2. Connect a model
Three routes, and they can all stay configured side by side.
**A cloud provider.** Open settings (`Ctrl+P`, or `/settings`), pick the mode —
`openai` / `gemini` / `claude` / `grok` / `openrouter` — paste your API key right
there (stored encrypted and machine-bound, never shown back), and choose the
**model**: `Enter` on that field asks the provider what it serves and offers the
list, filtered as you type.
**The OpenRouter gateway** is the `openrouter` mode: one key serves the
assistant, impersonation, embeddings, speech and video, each with a model of
its own. Its model
list opens before a key is entered and shows, per model, the context window, the
price per million tokens in and out, and a mark on a model that takes no tools.
The key is checked when an engine in this mode is applied — the embedder
included — so a refused key is that engine's status rather than the first
request's error; the context window, the
sampling fields on offer and image support are read from the gateway's own entry
for the model. Requests to the gateway carry two headers that name the
application — never you — and a switch in the same section turns them off
([PRIVACY.md](PRIVACY.md) §3.1). `/tts` speaks through the gateway as well: the
Speech tab lists its speech models and each model's voices, and settles the
audio format with the model by itself ([install.md](docs/install.md) §4.3).
And a YouTube video can be watched through it: the video tool has a provider of
its own to choose — Google's API or the gateway — and through the gateway the
whole video is read and charged, even when a part of it is asked for
([install.md](docs/install.md) §4.4). Details:
[install.md](docs/install.md) §3.2.
**An external server.** Run any OpenAI-compatible server and point the app at it
(mode `external`, the URL includes `/v1`):
```bash
# --jinja is required for the chat template and tool calling
llama-server -m google_gemma-4-E4B-it-Q4_1.gguf \
--host 0.0.0.0 --port 8000 -ngl 99 -c 8192 --jinja
```
A gateway or an authenticated server takes its key in the field below the URL,
and its model name in the one above — that name is what a multi-model endpoint
routes on, while a single-model server ignores it. (OpenRouter works this way
too, as it always did; its own mode above is the better road to it.)
**A managed server.** The app launches and supervises `llama-server` itself, and
can fetch one for your machine:
```bash
mindfork llama backends # what llama.cpp publishes for your machine
mindfork llama setup --backend vulkan --set-binary # …and point the settings at it
```
Or everything in one line — llama.cpp, the Python sandbox, the model and its
context written into the settings, and a test start to see that it loads:
```bash
mindfork setup --llama vulkan --sandbox --model ~/models/chat.gguf --ctx 16384 --verify
```
The model itself is a **GGUF** file, which llama.cpp does not ship — Hugging Face
hosts them; search the model's name with `GGUF` and fetch it with `hf download`
(install.md §3.4). Whatever llama.cpp takes that the settings have no field for
goes in the section's **Extra arguments** — `--n-cpu-moe 20`, say, to fit a large
mixture-of-experts model on a smaller card (install.md §3).
Embeddings (memory, RAG, attachment search) use a **separate** server, configured
in the same section's Embeddings tab; without one those features decline politely
and everything else keeps working.
**[docs/install.md](docs/install.md)** has the long version: every setting, the
data paths, the Python sandbox, MCP servers, speech, backups and the environment
variables.
### 3. Learn the app
**[docs/manual.md](docs/manual.md)** — the screens, what it remembers, files and
your project, the tools and their switches, and the full list of keys and
commands. Inside the app, **`F1`** is the same reference, always current and
listed *by screen*.
---
## How it's built
The app is an HTTP client to an inference engine behind the **`EngineBackend`**
trait — it deliberately embeds no ML stack
([ADR 0004](docs/decisions/0004-engine-contract-multi-provider.md)). Six
providers live behind that one contract: a local OpenAI-compatible server, the
OpenAI Responses API, native Gemini `generateContent`, the Anthropic Messages
API, xAI (which needed no client of its own), and the OpenRouter gateway (the
same client again, writing the request in the gateway's dialect).
Decisions that shape the code: the **agentic loop is client-side** — tools return
a result plus effects, and the orchestrator, sole owner of `Chat`, applies them,
so there are no locks; the UI↔orchestrator flow is **unidirectional**
(`AppCommand` up, `AppEvent` down); storage is JSON + SQLite with sqlite-vec and
soft delete everywhere; the markdown renderer and the input box are the project's
own, for control over tables, LaTeX, theming and spellcheck underlines.
The structure follows **Feature-Sliced Design**, dependencies pointing strictly
downward: `app → screens → widgets → features → entities → shared`. The full code
map with its invariants is [docs/architecture.md](docs/architecture.md).
---
## Development
```bash
cargo test # unit tests — no server, no key needed
cargo clippy --all-targets -- -D warnings
cargo fmt --check
```
Anything that needs a real model is an `#[ignore]` smoke test, silently skipped
unless the matching environment variable points at a live server. No local GPU?
`cd docker && docker compose up --build` brings up a CPU stack, and
`python tools/e2e_hf.py run` rents a pair of ephemeral Hugging Face endpoints and
deletes them afterwards.
**[CONTRIBUTING.md](CONTRIBUTING.md)** is where to start; the full task workflow
(design doc → branch → live run → docs → PR) is **[AGENTS.md](AGENTS.md)**, the
traps worth knowing are [docs/lessons.md](docs/lessons.md), and security reports
go through [SECURITY.md](SECURITY.md).
---
## Documentation
**[docs/README.md](docs/README.md)** is the index. The short version:
| [docs/manual.md](docs/manual.md) | how to use the app |
| [docs/install.md](docs/install.md) | installing, engines, data, environment |
| [CHANGELOG.md](CHANGELOG.md) | what each release changed |
| [docs/roadmap.md](docs/roadmap.md) | what may come next |
| [spec.md](spec.md) · [docs/architecture.md](docs/architecture.md) | behaviour and code map — the engineering references |
---
## License, disclaimer and privacy
The software is under the **MIT License** ([LICENSE](LICENSE)) — the standard
text, unmodified.
It ships **no model**. Every word on screen is written by a model you chose and
obtained yourself, and the app applies no content filtering of its own. What that
means for warranty and liability, and for the tools a model can invoke on your
machine, is in **[DISCLAIMER.md](DISCLAIMER.md)**. What stays on your machine,
what leaves it and only on which setting of yours, and what reaches the author of
this software — which is nothing — is in **[PRIVACY.md](PRIVACY.md)**. Both are
also on the `F1` → "Legal" tab, and the Windows installer shows the privacy
policy during setup.
All three have a Russian translation ([docs/legal/](docs/legal/)), unofficial and
for convenience: the English originals are the texts with legal force.
---
## Project status
Actively developed, in small reviewed tracks; the original ten-milestone plan
([docs/history/plan.md](docs/history/plan.md)) is long finished. The suite stands
at **3693 unit tests** plus **218 `#[ignore]` smoke tests** that are run against
real stacks — a local `llama-server` and the live cloud APIs — before
provider-touching changes ship. See the [changelog](CHANGELOG.md) for what is new
and the [roadmap](docs/roadmap.md) for what may come next.