<!-- Copyright 2026 NX1X. Licensed under the Apache License, Version 2.0. -->
<!-- Generated from public/docs.html by scripts/docs-to-md.py. Do not edit by hand. -->

# OpenMila documentation

Everything you need to install OpenMila on Linux or Windows and use it day to day, followed by a quickstart for an AI agent that wants to read your transcripts over MCP. Every claim here is drawn from the port's own repository, and nothing on this page goes further than [the parity table](https://github.com/NX1X/OpenMila/blob/main/docs/port/PARITY.md) does.

**No release has been published yet.** OpenMila is in early development. The first Linux beta will appear on the [releases page](https://github.com/NX1X/OpenMila/releases). Everything below describes the files that release will contain and how to use the app, so you can read it before you need it. Linux is the development platform. Windows is built and tested on a `windows-2025` runner in CI, where the core, the CLI, the MCP helper, the tests, the WinUI app and the portable zip all pass, but nobody has run any of it on a Windows desktop yet.

## Install OpenMila

Three packages: an AppImage and a `.deb` for Linux, and a portable zip for Windows. On a Mac, use the original, [Mila](https://github.com/island-io/mila). OpenMila does not build for macOS and does not try to.

### Linux: the AppImage

One file, no installation. Tested on Ubuntu 26.04 (GNOME, on both Wayland and X11); other current desktops with GTK 4 should work. Download `OpenMila-<version>-x86_64.AppImage` and the `.sha256` file next to it, then check it:

```
sha256sum -c OpenMila-<version>-x86_64.AppImage.sha256
```

Make it executable and run it:

```
chmod +x OpenMila-<version>-x86_64.AppImage
./OpenMila-<version>-x86_64.AppImage
```

If it complains about FUSE, run it with `--appimage-extract-and-run`, or install `libfuse2t64`. The AppImage updates itself: it checks the published SHA-256 and replaces the running file, and it has a beta channel with a pre-release guard.

### Linux: the Debian package

`openmila_<version>_amd64.deb` is built from the same payload as the AppImage and ships its own `.sha256` file. Check it, then install it with the package manager so the dependencies come with it:

```
sha256sum -c openmila_<version>_amd64.deb.sha256
sudo apt install ./openmila_<version>_amd64.deb
```

The package puts its payload in `/opt/openmila/` and three launchers on your `PATH`: `openmila` for the app, `openmila-cli` for the headless harness, and `openmila-mcp` for the [MCP server](https://openmila.nx1xlab.dev/docs#agents). A `.deb` install does not update itself: updating belongs to the package manager, so the app points you back at the releases page.

### Windows: the portable zip

Windows 11, and Windows 10 22H2, on x64. Download `OpenMila-<version>-win64.zip` and check it against the `.sha256` file beside it:

```
(Get-FileHash OpenMila-<version>-win64.zip -Algorithm SHA256).Hash
```

Unblock and extract it anywhere, then run `openmila.exe`. Nothing else needs installing: the Swift runtime and the transcription engine travel with the app. `openmila-cli.exe` and `openmila-mcp.exe` sit beside it in the same folder. The zip has no self-install, so an update is a fresh download.

### What each system needs present

- **Linux, required**
  GTK 4 and libadwaita, libsecret, GLib, zlib, and PipeWire or PulseAudio for audio. The `.deb` declares these as `libgtk-4-1`, `libadwaita-1-0`, `libsecret-1-0`, `libglib2.0-0`, `libc6` and `zlib1g`.
- **Linux, to record one application's audio**
  PipeWire's own tools, the `pipewire-bin` package, which provides `pw-dump` and `pw-record`. Most PipeWire installs already have them. Without them the source picker still offers the whole-system monitors, so you can record everything the machine plays but not one application on its own.
- **Linux, for global dictation hotkeys**
  Nothing extra. `XGrabKey` is used wherever an X display answers, including XWayland, and the XDG `GlobalShortcuts` portal on a pure Wayland session; the two paths pick themselves. See [Dictation](https://openmila.nx1xlab.dev/docs#dictation).
- **Linux, for dictation that pastes by itself**
  `xdotool` or `wtype` to paste at the cursor, plus `wl-clipboard` or `xclip` for the clipboard. On a Wayland session OpenMila can use the `RemoteDesktop` portal instead, once you grant it. With none of that the dictated text still lands on the clipboard and a notification tells you to press Ctrl+V.
- **Linux, optional**
  `ffmpeg`, for compact `.m4a` recordings, the remote transcription backend, and importing audio that is not WAV. GStreamer is used where ffmpeg is absent, which a GNOME or KDE install almost always has. `libnotify-bin` for desktop notifications.
- **Windows**
  Nothing. Everything the app needs travels in the zip.

### Verify what you downloaded

Every release publishes a single `SHA256SUMS` file covering all of its files, alongside the per-file `.sha256` files. Download it from the release and check the files you have:

```
sha256sum --ignore-missing -c SHA256SUMS
```

The release pipeline also attests what it built, so you can confirm a file came from this repository's workflow and not from somewhere else. With the [GitHub CLI](https://cli.github.com/) installed:

```
gh attestation verify OpenMila-<version>-x86_64.AppImage --repo NX1X/OpenMila
```

The same command works for the other files, on Windows too:

```
gh attestation verify .\OpenMila-<version>-win64.zip --repo NX1X/OpenMila
```

If a hash or an attestation does not check out, do not run the file, and please [report it privately](https://github.com/NX1X/OpenMila/security/advisories/new).

## First launch

### Download a model

The Whisper models are large and are not bundled, so the first thing to do is fetch one. Open **Settings > Models** and download the one you need:

- OpenAI `large-v3-turbo` for English and other languages, 1.6 GB.
- ivrit.ai `large-v3` for Hebrew, 3.0 GB.

Each download shows a percentage while it runs and is checked against a pinned SHA-256 before it is used. How long it takes is your connection's business rather than the app's: no timing figure is published, because none has been measured. Budget for a 1.6 GB or a 3.0 GB transfer and leave it running. Deleting a model later frees the whole install and keeps the remaining choice sane. A recording's language decides which model is used, so you only need the one you record in.

### Where transcription runs: GPU when there is one, CPU otherwise

Mila on a Mac has two accelerators it can count on, the GPU through Metal and the Neural Engine through a CoreML encoder, because Apple ships them in every machine it supports. Linux and Windows have no equivalent that is always present, so the port's rule is to always work on the CPU and use a GPU when the machine turns out to have a usable one.

The release packages for both systems carry whisper.cpp's Vulkan backend, which covers NVIDIA, AMD and Intel through one driver-level API. A discrete, integrated or virtual GPU gets the model. A software Vulkan device (llvmpipe, lavapipe, SwiftShader) is refused, because running the model through a CPU pretending to be a GPU is slower than whisper.cpp's own CPU backend. No Vulkan at all is not an error: the CPU path is the fallback and the common case. `openmila-cli gpu` prints what the probe found, **Settings > Models** shows it, and a switch there forces the CPU for a driver that misbehaves, as does `OPENMILA_DISABLE_GPU=1`.

**No speed has been measured.** The decision logic is tested and the backend is compiled in, but no transcription has been timed on a real GPU on either system, so "Vulkan works" means built, probed and wired, and no figure is claimed. There is no CoreML or Neural Engine path off macOS at all. The reasoning and what is still unmeasured are in [docs/port/HARDWARE.md](https://github.com/NX1X/OpenMila/blob/main/docs/port/HARDWARE.md).

If your machine is slow at this, the way out today is a server of your own: **Settings > Models > Backend** can point at any OpenAI-compatible transcription endpoint. Audio leaves the machine while a remote backend is active, and the app says so in that screen.

## Recording

The Home screen has a source picker, a language picker and a Record button. There are three sources:

- **Microphone**
  Your input device, with a live level meter and an elapsed clock. Pick a device in **Settings > Audio**, or leave it on the system default. Adaptive gain for quiet microphones is a toggle in the same place.
- **System audio**
  On Linux, choosing this reveals a second picker listing what is currently playing: OpenMila asks PipeWire to link that one application's output to a capture stream, so you record the meeting and not your music. The whole-system monitors are offered in the same list. Windows has the same two choices, through WASAPI process loopback bound to the chosen process tree; that code compiles in CI and has never been run on a Windows machine.
- **Meeting (mic + system)**
  Both, mixed into one mono 16 kHz WAV file. If OpenMila notices a meeting application starting, it offers to start a meeting recording for you, and offers to stop when the application ends. Native applications are found by process name on every session. A meeting in a browser tab is found in the window title, which X11 and XWayland expose and a pure Wayland session does not, by design: there, a tab meeting is missed. Windows places no such restriction, so a tab meeting is detectable on every Windows session.

Live transcription runs while you record, in the Live transcript pane, and you can edit and copy it there.

### Pause and resume

While a recording is running the Record button becomes Pause, and Stop sits beside it. A paused span is absent from the audio and from the timer, so pausing for a side conversation leaves no gap of silence to sit through later.

### Where the file goes

Stopping saves the recording, queues it for transcription, and shows a post-recording sheet where you can accept a suggested name and see the summary and action items if AI features are on. The audio and its transcript land in the recordings directory, which is `~/.local/share/Mila/` on Linux and `%APPDATA%\Mila\` on Windows, and is configurable in **Settings > Storage** along with a storage cap. See [Where everything lives on disk](https://openmila.nx1xlab.dev/docs#files).

## Dictation

Dictation transcribes a short burst of speech and pastes it where you were typing. Two hotkeys, one per language: Ctrl+Alt+2 for English and Ctrl+Alt+3 for Hebrew by default. Change them in **Settings > General**, where you type a combination as modifiers plus a key (Ctrl, Alt, Shift, Super, and a letter, digit, F-key, Space, Return, Escape or Tab). The app tells you if another application has already taken the combination you asked for.

**On X11, and in XWayland windows**, the hotkey works anywhere: press it, speak, press it again, and the text is pasted into whichever window had focus.

**On a pure Wayland session**, the desktop owns global shortcuts, so OpenMila asks for them through the XDG `GlobalShortcuts` portal instead of grabbing them. Your desktop will ask you to confirm the binding once. The two paths choose themselves; you do not configure which. A key press through the portal has not yet been confirmed by a human at a Wayland desktop, so if it does not fire, the **Dictate** buttons on the Home screen do the same job.

Getting the text back to the cursor has three routes, tried in that order: `xdotool` or `wtype` where they are installed, the `RemoteDesktop` portal on a Wayland session once you grant it (`openmila-cli grant-typing`, or the button in Settings), and the clipboard with a notification otherwise. Dictation never waits on the consent dialog: without permission it falls back in a tenth of a second.

**On Windows**, hotkeys are registered with the operating system and dictated text is pasted into whichever window has focus. That code is written and passes in CI, but like everything else on Windows it has not been run on a real desktop yet.

Dictations are saved under **Dictations** in the sidebar, and they respect the storage cap like any other recording.

## Speakers

Speaker diarization names who said what, live during a recording and afterwards, with a colour per speaker. It is **off by default**, as it is in Mila.

The first time you turn it on, the app downloads PyTorch for the CPU, about 200 MB, into the Python runtime the packages ship. That download happens once. Turning diarization on or off mid-recording takes effect immediately.

Naming a speaker is a click on the speaker label in a transcript: a sheet opens with a text field, suggestions from the names you have used before, a **Save** button and an **Un-name** button. **Giving two labels the same name merges them**, which is how you fix diarization splitting one person into `SPEAKER_01` and `SPEAKER_04`. The names you have used are listed in **Settings > Speakers**, where you can remove any of them. Speakers you have not named stay as `SPEAKER_NN`.

**Recognising the same person across recordings is separate and opt-in.** The toggle is *Recognise the same speaker across recordings* in **Settings > Speakers**. With it on, a voice you have named once is labelled automatically in later recordings. Beside it is a **Delete everything learned about voices** button, which does what it says.

## AI features

**Nothing is sent anywhere until you configure a provider.** OpenMila ships no API keys and has no provider of its own, and every AI feature is inert until **Settings > AI Provider** names something to talk to. Transcription itself never needs any of this.

### Choose a provider

In **Settings > AI Provider**, pick one of the command-line tools OpenMila can shell out to, the Claude, Cursor or Gemini CLI, or an OpenAI-compatible endpoint. A CLI can be found on your `PATH` or given an explicit executable path. An OpenAI-compatible endpoint takes a base URL, a model name and an API key, and the key is kept the way this system keeps secrets rather than in a settings file (see [Secrets](https://openmila.nx1xlab.dev/docs#files)). Every invocation is logged with credentials redacted.

#### Ollama (local): the option where nothing leaves the machine

The OpenAI-compatible endpoint has presets, and one of them is **Ollama (local)**. It points at `http://localhost:11434/v1`, the default address of [Ollama](https://ollama.com/) running on your own computer, and it is the one preset that asks for no API key, because there is nobody to authenticate to. Install Ollama, pull a model (`ollama pull llama3.1` matches the preset's default model name), pick the preset, and every AI feature on this page - suggested names, summaries, action items, Live AI, Send to LLM - runs without a single outbound request.

This has not been exercised end to end on OpenMila: the parity table marks the provider list *done* on Linux on the strength of a run through the Claude CLI, and *written* on Windows. The preset itself is upstream code that the port compiles unchanged. If it misbehaves, please [say so](https://github.com/NX1X/OpenMila/issues).

### Summaries and action items

**Settings > AI Features** turns on suggested recording names and an automatic summary after each recording, and holds the prompt each feature uses, which you can edit. A summary can be regenerated from a recording's **Actions** menu, and recordings from before you turned the feature on can be backfilled. Action items carry where they came from: a `voice_command` item is one somebody dictated out loud, and an `inferred` item is one Live AI derived from the conversation. Those are not the same claim.

### Live AI

Live AI keeps a rolling summary and a list of action items while the meeting is still going, next to the live transcript, with a context box you fill in per recording so the model knows what the meeting is about. Running transcription and an AI pass concurrently is the heaviest thing the app does, so it is gated on what the machine can actually manage: OpenMila reads the memory and processor count and turns Live AI off on hardware that cannot keep up.

### Send to LLM

Send to LLM takes one recording's transcript and a prompt of your own to the provider you configured, for the questions a fixed summary prompt does not answer.

## Folders, exports and team setup

### Watched folders

Mila watches the folder iCloud syncs iPhone Voice Memos into. OpenMila watches any folder you sync recordings into, a Syncthing or Nextcloud directory or a mounted phone, and imports new audio files automatically. Point it at a folder in **Settings > Watched Folders**, then choose which subfolders to include and whether to take files sitting directly in the folder. Only files newer than the start date are imported, and they land in a **Voice Memos** folder in the library.

### Obsidian export

With an Obsidian vault configured, every finished recording is also written into it as a Markdown note carrying the title, date, duration, summary, action items and transcript. It is off until you choose a vault, and the vault can be committed and pushed for you using `git` from your `PATH`. The parity table marks this done on Linux.

### Team setup with .milaconfig

A `.milaconfig` file carries a ready-made configuration, the transcription server, model, language and more, so one person can set a team up in a step. OpenMila shows you exactly what will change before applying it, and settings the file does not name are left alone. Import one from **Settings > General > Import a .milaconfig file**, or open it from a terminal:

```
openmila team.milaconfig
```

Double-clicking such a file does not work yet, on either system: the desktop file association is not installed on Linux and there is no file association on Windows. A `.milaconfig` written for Mila works in OpenMila and the other way round. Note that the file holds its API key in plain text, so hand it to your team over a channel you trust.

### SRT export

**Actions > Export SRT** on a recording writes a subtitle file with the per-segment timestamps. It can also be exported mid-recording, from the live transcript, without waiting for the recording to finish. The same menu copies the transcript with its speaker labels.

### Playback speed

A recording plays back between 0.5x and 2x, in steps, and the transcript follows along; clicking a line seeks to it. The speed change goes through a WSOLA time stretcher, so voices keep their natural pitch rather than turning into chipmunks. The stretched audio was checked by transcribing it again at 0.75x and 1.5x and getting the same words, which is the intelligibility test a frequency measurement cannot give.

## Where everything lives on disk

*File locations per system*
| What | Linux | Windows |
| --- | --- | --- |
| Recordings, transcripts, models, settings sidecars | `~/.local/share/Mila/` | `%APPDATA%\Mila\` |
| Logs | `~/.local/state/openmila/logs/openmila.log` | `%LOCALAPPDATA%\OpenMila\logs\openmila.log` |
| Settings | `~/.config/`, managed by the app | managed by the app |
| MCP consent flag | `~/.local/share/Mila/mcp-access.json` | `%APPDATA%\Mila\mcp-access.json` |

The data folder is named `Mila` on purpose: the MCP helper and other shared code look for it by that name.

### Secrets

API keys and tokens are never written into the settings file. On **Linux** they go to your desktop keyring through the Secret Service, which is GNOME Keyring, KWallet or KeePassXC depending on your desktop. Where no keyring answers, they fall back to files under `~/.local/share/Mila/secrets/` with owner-only permissions. On **Windows** they live in `%APPDATA%\Mila\secrets\`, encrypted with DPAPI for your Windows account.

## Troubleshooting

### No microphone in the picker

Check that PipeWire or PulseAudio is running and that the device appears to the rest of the desktop. The command-line harness lists exactly what the app can see, which separates an OpenMila problem from an audio-stack problem:

```
openmila-cli devices
```

If the device is listed there but recording fails, the error names the device and the log file has the detail. On Windows, the microphone privacy setting is the operating system's own; there is no per-application permission gate on either system.

### No sound from the application you chose

Recording one application's audio on Linux needs PipeWire's tools, the `pipewire-bin` package, which provides `pw-dump` and `pw-record`. Without them the picker offers only the whole-system monitors. Install the package, then check that the application is actually playing when you start: the link is made to the output stream that exists at that moment.

```
openmila-cli app-audio
```

On Windows the same picker is drawn from WASAPI process loopback. Nothing there has been run on a real machine, so if it does not offer what you expect, choose the whole-system loopback and mute what you do not want recorded, and please report it.

### Hotkeys do not fire on Wayland

On a pure Wayland session the hotkeys go through the XDG `GlobalShortcuts` portal, which your desktop has to grant. Confirm the binding when the desktop asks; if you dismissed that prompt, check your desktop's own shortcut settings for OpenMila. This path has not yet been confirmed by a human at a Wayland desktop, so if it stays silent, use the **Dictate** buttons on the Home screen and [tell us](https://github.com/NX1X/OpenMila/issues). If the dictated text is not typed at the cursor, install `xdotool` or `wtype`, or grant the `RemoteDesktop` portal with `openmila-cli grant-typing`; without either, the text is put on the clipboard and you paste it yourself.

### The model download fails

The error is shown beside the model in **Settings > Models**. A download is verified against a pinned SHA-256 and a failed check is refused, so a truncated or tampered file is thrown away rather than used. Retry the download; a fresh attempt starts cleanly. If it keeps failing, check that you can reach the network at all and that the disk has room for the 1.6 GB or 3.0 GB file. A network that filters where the weights are served from will show up as a download that never starts, so it is worth trying the connection you would normally install software over. If it still fails, attach a diagnostic report to an issue.

### Diarization cannot install torch

Enabling speaker diarization downloads PyTorch for the CPU, about 200 MB, into the Python runtime the packages ship. Two things make this fail: the runtime not being in the package, which happens on Windows today because the Windows diarization bundle has never been built, and the network refusing the download. Leave diarization off in the first case; it is off by default and everything else works without it. In the second case, retry with the network that would normally let you install Python packages. The log file records what the attempt did.

### Export a diagnostic report

**Settings > General > Export diagnostic report** writes a zip and tells you where it saved it. Inside are system information, your settings with credentials redacted and prompts reduced to their length, the shape of your recordings with no titles or paths, and the log files. It is built to be safe to attach in public, and it is the single most useful thing you can put on a bug report.

### Where to report a bug

- Bugs and feature requests: [the repository's issues](https://github.com/NX1X/OpenMila/issues), with a diagnostic report attached. If something is marked done in the parity table and does not work for you, that is a bug worth reporting.
- Questions rather than bugs: [Discussions](https://github.com/NX1X/OpenMila/discussions).
- Security problems: do not open a public issue. Use [private vulnerability reporting](https://github.com/NX1X/OpenMila/security/advisories/new) on the repository.
- Anything else: the contact form and social links at [nx1xlab.dev/contact](https://nx1xlab.dev/contact).

## For AI agents: the MCP server

This section is for an AI agent, and for the person configuring one. OpenMila ships the same MCP server Mila has, built from the same source, as a helper called `openmila-mcp`. It speaks MCP over stdio and lets a session search and read your transcripts, including the live transcript of a meeting in progress. It reads OpenMila's own on-disk store; nothing leaves the machine unless the agent is asked to do something with it.

On Linux this has been exercised end to end through Claude Code, refused with consent off and listing recordings with it on. On Windows the helper builds and ships in the zip, and nothing has run it there.

### What the server exposes

*The four tools, and what each returns*
| Tool | What it returns |
| --- | --- |
| `list_recordings` | A list of recordings, filtered by speaker display name, by text in the title, application or folder, by source, or by date range, and sorted by date, duration or title. Trashed recordings are not included. |
| `get_transcript` | One recording's full transcript with speaker names resolved, plus its summary and action items. Omit the id and you get the latest completed recording. Each action item carries a `source`, either `voice_command` (somebody dictated it out loud) or `inferred` (Live AI derived it from the conversation); those are not the same claim, so do not present the second as the first. Trashed recordings are not reachable, by id or otherwise. |
| `search_transcripts` | Full-text matches over titles and transcripts, with context snippets, sorted by relevance or by date. Relevance is the default and scores every recording, so it always finds the best match and costs more as the library grows. Sorting by `created_at` stops at the limit, which is cheaper and usually the better question when you want recent mentions. |
| `get_live_transcript` | The in-progress recording's transcript, with a polling cursor for cheap deltas: nothing new gives a tiny unchanged reply, new content gives only the new segments. A status field says whether to keep polling, and hands off to `get_transcript` when the recording completes. |

Nothing is cached. Every call reads the store as it is at that moment, which is what makes the answers trustworthy while a recording is still running.

### The consent gate

**Access is off by default.** Turn it on in **Settings > Storage > Allow MCP access to transcriptions**. While it is off every tool call is refused, and the refusal explains itself by pointing back at that toggle, so an agent that hits it can tell the person what to do rather than guessing that the transcripts do not exist. The setting takes effect immediately: turning it off cuts an already-running session without restarting anything.

The flag lives in `mcp-access.json` in the data folder, `~/.local/share/Mila/mcp-access.json` on Linux and `%APPDATA%\Mila\mcp-access.json` on Windows. Every call re-reads it. Missing, unreadable or malformed all mean denied: the gate fails closed on anything it does not positively understand, and a failed attempt to revoke access escalates to deleting the file so that revocation cannot fail open.

**It is a consent control, not a privilege boundary.** The helper runs as your user and reads the same files you can, so anything already running as you could read the store directly whatever this setting says. What the toggle prevents is the realistic accident: an MCP client that happens to be configured quietly reading meeting transcripts and sending them to a cloud model because the tools were simply there. It does not, and cannot, defend against a hostile program already running as you.

### Register it with Claude Code

One command, once. Use the line that matches how you installed OpenMila.

From the AppImage, which carries the helper inside it:

```
claude mcp add openmila -- /path/to/OpenMila-<version>-x86_64.AppImage --mcp
```

From the Debian package, which puts the helper on your `PATH`:

```
claude mcp add openmila -- /usr/bin/openmila-mcp
```

From the Windows zip, using the folder you extracted it into:

```
claude mcp add openmila -- C:\path\to\OpenMila\openmila-mcp.exe
```

From a source build:

```
claude mcp add openmila -- /path/to/OpenMila/.build/debug/openmila-mcp
```

### The same thing as JSON

Other MCP clients take a configuration file instead. The shape is the same everywhere: a named server with a command to run. Replace the path with the one for your install from the section above.

```
{
  "mcpServers": {
    "openmila": {
      "command": "/usr/bin/openmila-mcp"
    }
  }
}
```

The helper takes no arguments of its own and needs no environment. It finds the store through a pointer file the app rewrites on every launch, so it follows the recordings folder if you move it.

### What to ask it

- Read my last transcription with a named person and summarise it.
- Search my transcripts for a decision I half remember, and show me the lines around each match.
- List everything I recorded last week, longest first.
- Pull the action items out of yesterday's meeting, and say which ones somebody asked for out loud rather than the ones that were inferred.
- Follow my current meeting: poll `get_live_transcript` with the cursor every fifteen to twenty seconds, and when something new lands, suggest in a sentence or two what I should say next. When the status becomes completed, fetch the final transcript and summarise it.

### Reading the whole site

An agent that wants the rest of the site, not just this page, should read [/llms.txt](https://openmila.nx1xlab.dev/llms.txt), which summarises what OpenMila is, where the port stands, what it does, its privacy properties and its known limits, with links to every page. This page itself is available as Markdown at [/docs.md](https://openmila.nx1xlab.dev/docs.md), generated from this HTML on every build so the two cannot disagree.
