Architecture
Terminal Emacs (emacs-no-x) on a Talos II under Guix. Single user, single host, no CI. The config repo (~/.emacs.d/) is itself the source of truth for both the human reader and the local LLM tools — the config is the documentation.
Two package managers cohabit, with non-overlapping scope:
- Guix Home installs
emacs-no-xand every Emacs package available in the official Guix channel. These load with plain(require ...)oruse-package— nostraight-use-packageform needed. - straight.el installs only the Drew Adams emacsmirror ecosystem. Those packages are not in Guix, and only the upstream emacsmirror checkouts will do.
When adding a new package: check Guix first (guix search emacs-FOO). Fall back to straight.el only if it is not in Guix or only an upstream emacsmirror checkout works. See the Drew Adams page for the full straight.el surface.
Layout
init.el is a bootstrap only. It sets load-path, then requires each lisp/init-*.el in a deliberate order. Real configuration does not belong in init.el — every concern gets its own module ending with (provide 'init-<name>).
| File | Purpose | Packages |
|---|---|---|
init-display.el | Theme, faces, e-ink colors, UI | built-in |
init-straight.el | Drew Adams ecosystem via straight.el | emacs-straight |
init-gnus.el | Email + RSS + local mail | built-in |
init-gptel.el | LLM interface + tool definitions | emacs-gptel, emacs-llm-tool-collection |
init-org.el | Org-mode + Denote + capture | emacs-org, emacs-denote |
init-magit.el | Magit + auto-commit on save | emacs-magit, emacs-git-auto-commit-mode |
init-dictionary.el | StarDict CLI wrapper + corpus rg | sdcv |
init-vocabulary.el | Controlled tag vocabulary for Org | built-in |
init-mastodon.el | Mastodon client | emacs-mastodon |
init-browsing.el | Web browsers (eww, elpher), tor toggles | built-in, emacs-elpher |
init-editing.el | In-buffer completion, navigation, region, undo | corfu, avy, ace-window, expand-region, wgrep, vundo, paredit |
init-completion.el | Minibuffer completion stack | vertico, orderless, marginalia, consult |
init-quail.el | Input methods (SKK, pyim) | built-in |
init-scheme.el | Scheme REPL (Guile) | emacs-geiser, emacs-geiser-guile |
init-tramp.el | HPC TRAMP | built-in |
The .gitignore in this repo is denylist-then-allowlist: it ignores everything, then re-includes .gitignore itself, init.el, the lisp/ directory, and the bookmarks file. Anything else (straight/, elpa/, Gnus state, autosaves) is intentionally excluded — none of it is reproducibly mine.
Save = publish
Every save in the config repo is a commit and a push. git-auto-commit-mode runs on save for org, elisp, scheme, HTML, and CSS buffers, with gac-automatically-push-p set to t. A second hook commits the bookmarks file whenever bookmark+ saves it.
Consequences I've internalized:
- The git log is dominated by
Auto-commitentries — that's normal, not noise. - A normal save is a publish event. There is no buffer of half-finished work waiting for cleanup before commit; the commit already happened.
- For named, intentional changes, save once and follow up with
git commit --amendor a real follow-up commit.
Owned keybinding prefixes
The global keymap has a few prefixes I've claimed for project-internal commands. Avoid stomping these when adding new global bindings.
| Prefix | Used for |
|---|---|
C-c b * | Browsing — eww, elpher, history, tor toggles |
C-c d * | Dictionary lookup (rg over the text corpus + sdcv fallback) |
C-c n * | Notebook — capture (c), find (f), heading search (h), corpus ripgrep (g), tag query (t), recent (r) |
C-c z | Denote / zettel capture |
C-c l | Input method picker (also denote-insert-link in init-org.el — but init-quail.el loads after init-org.el, so the input-method binding wins) |
C-c h | TRAMP jump to HPC home |
C-x X * | highlight.el commands (Drew Adams) |
The C-c n prefix is the notes / RAG query map. Capture lives at C-c n c rather than the bare C-c n so the prefix can hold the rest. See the RAG page for what each query does and why.
External assumptions
Hard defaults the config does not try to detect or fall back from. They're set for one host:
- Notes corpus is
~/notes;org-directoryanddenote-directorylive under the same root. - Plain-text dictionaries under
~/ref/text/dicts/, with entry format@ headword [POS]produced by external converter scripts. See the RAG page. - Local LLM is a llama.cpp endpoint serving
gpt-oss-120bover an OpenAI-compatible HTTP interface; the Anthropic key is read from auth-source. See the gptel page. - The Python sandbox for gptel is a venv under
~/.local/share/— see the Talos page for the venvs convention. - Mail is Fastmail IMAP/SMTP, eweka NNTP, with a local maildir; auth in
~/.authinfo.gpg. - Tor toggles in
init-browsing.elassume a system tor daemon on127.0.0.1:9050.
This is the right tradeoff for one host: simplicity over portability. Any of these can be changed in one place and re-applied with a single guix home reconfigure.