Experimenting With AI In Emacs
Over the last few years I’ve used most of the major LLMs in some capacity or another for a variety of tasks.
If I need to write a script to do something tedious my reflex is to first see if Claude can do it before writing it myself.
If I want to compare different products I’m thinking about buying I generally use Perplexity to round up info sources and make a table showing the contrasts.
Over at $DAY_JOB
we have an internal version of a Cursor clone (which is unfortunately not very good) that I dabble with for brain-dead sorts of code changes.
One thing I had never really done a deep dive on before was the state-of-the-art in coding assistance software.
This involves two different dimensions: the models generating the code and the software that puts things in a nice package (or at least tries to).
I had a pretty good understanding of the reputations of the different available models (which is no substitute for direct experience!) but I was less familiar with the different interfaces for interacting with them.
One of the things I was especially interested in was the potential for integration with Emacs.
I am one of those people (there are dozens of us! Dozens!) who more or less runs their digital life through Emacs.
One of the benefits of Emacs is that it is highly extensible and has a robust ecosystem of packages so whenever some new cool thing comes around a package integrating it with Emacs quickly follows.
After doing some further digging I stumbled upon a product called Aider.
Aider had a few things going for it that I found appealing.
First, the fact that it wasn’t bound to a particular model (and in fact has some interesting ways you can mix and match them) was a big plus.
Given the speed at which new and better models drop the ability to easily swap them out and experiment is really nice.
Second, the interaction model simply feels like the right approach.
The ability to have granular control over the contents of the model’s context is great and the UX around adding, removing, and communicating what files are included is easy to grok.
Installing Aider
Note: The following was done on my <1 month old Macbook Pro so based on your installed versions of things YMMV
I actually had a little bit of trouble with this due to the fact that Aider only supports python version 3.12 and the version I had installed via homebrew was 3.13.2. The way this manifested was that I got errors related to numpy being uninstallable but no real helpful info about why that was the case. It was only from reading the docs1 that I discovered 3.12 was required. I had never previously used homebrew to attempt to install multiple simultaneous python versions so I had some trepidation about potentially Ruining Everything™ but after some web searching the consensus seemed to be that it was well-handled so I went ahead and ran the following:
brew install python@3.12
After this was done I checked that python3 --version
still gave me the answer of 3.13.2 that I was hoping for and thankfully it did.
Once that was done I was able to run the following and ended up with a successfully installed and working Aider setup:
pipx install aider-chat --python /opt/homebrew/opt/python@3.12/bin/python3.12
Trying It Out
Before diving in to Emacs integration I wanted to try the CLI on it’s own. I had some code I wanted to reorganize and make a little more DRY and that is precisely the sort of thing LLMs are good at so I decided to use that endeavor to evaluate Aider’s UX flow. I already had API keys from OpenAI and Google and since gemini-2.5-pro just came out and I heard good things that was the model I opted to use. The code repo in question is a Django based site that has too many mega-files so I went about asking Aider to break things up in to smaller components on a topic-by-topic basis. In general giving LLMs tasks of the form “fix the world” will not go well so it’s always a good idea to make your instructions and goals as targeted and specific as possible. The results of this were somewhat surprising. The gemini-2.5-pro model turned out to be quite good at the refactoring tasks I gave it. For those who have never tried it the UX flow happens from the terminal. You are presented with a prompt like this:
To give you a compact end-to-end example of a prompt becoming a commit here is me telling Aider to modify my Emacs init file to ignore any Aider generated files in any repos I work with:
There are a couple of things to note here.
- The first time it is asked to operate in a new repo it asks to modify the .gitignore so that Aider stuff doesn’t clutter up your history.
- I refer to a file and I get explicitly asked if I want to add it to the model’s context.
It is worth pointing out that the change Aider shows you is in fact a git commit.
If you complain about the contents it will generally amend it.
If you think the whole thing is off base you can issue the /undo
command and it will nuke whatever was just done.
When I detected a new issue (which didn’t happen much to be fair) and communicated that the model understood and fixed it in one shot.
This all mostly went pretty well but the big stumbling block I ran in to while doing this is that I frequently hit rate limits. It’s unclear to me if this is an Aider problem of being spammy or a Google problem of having their newly-released model being too popular for them to handle. I don’t think this throttling was my fault as I was just giving directives and waiting for results which would generally take 10s of seconds. Either way there was a lot of exponential backoff from Aider whenever this happened that slowed things down a lot when it occurred.
Emacs Integration
After poking at Aider for a while and getting mostly good results I decided to proceed to attempt to integrate Aider with my Emacs setup. There are a few different packages out there that integrate with Aider in different ways but the one I found most compelling was aidermacs2. Having a transient3 based workflow is nice both from a muscle-memory point of view but is also helpful from a discoverability angle. Aider has a lot of commands available but unless you read through the docs you won’t know they exist. For those unfamiliar with Emacs and transient menus here is a screenshot from the aidermacs repo of them in action:
After poking around with the default aidermacs experience and looking at the various configuration options in the documentation this is what I ultimately landed on in and what now lives in my init.el
file:
(defun josh/read-gemini-api-key ()
(let ((key-file "~/.gemini_api_key"))
(if (file-exists-p key-file)
(with-temp-buffer (insert-file-contents key-file)
(setenv "GEMINI_API_KEY" (buffer-string))))))
(use-package aidermacs
:straight t
:bind (("C-c a" . aidermacs-transient-menu))
:config (josh/read-gemini-api-key)
:custom
(aidermacs-auto-commits t)
(aidermacs-show-diff-after-change t)
(aidermacs-default-model "gemini-2.5-pro"))
For those who are not familiar with Emacs or elisp this does the following:
- It defines a function that opens a file
~/.gemini_api_key
and loads its contents in to the environment variableGEMINI_API_KEY
which is what Aider is going to look for when using any gemini models. - If we have not done so already we find and fetch the aidermacs package and install it.
- We bind the main menu (what you see in the bottom of the previous screenshot) to the
Ctrl-c a
key chord (C-c a
is Emacs’ notation for this). - We load the gemini API key in to the environment variable when we load this package.
- When Aider finishes making changes it will put them in a git commit. This matches the behavior of the CLI and I prefer it to having to muck about with staged files. For those wondering in this mode (as with the CLI) if you tell Aider you don’t like some change it is smart enough to amend its commits rather than pollute history with commits like ‘fix’, ‘fix for the fix’, ‘fix part 3’, etc.
- When Aider finishes a requested piece of work it will show a diff view of the changes.
- Aider is going to use gemini-2.5-pro as the model it leverages to do stuff. At the time of this writing 2.5-pro is pretty new and had some impressive benchmark numbers on coding related tasks. Because of that combined with Google’s fairly generous free tier I decided to use this for a while and so far I’m pretty impressed.
Trying It Out
In one of my personal projects I had a backlog of refactorings I wanted to do for a while but had not gotten around to. The project in question is Django based and most of the work centered around breaking down html templates in to more manageable pieces. Instructing aidermacs to do this was very straightforward and gemini-2.5-pro did exactly what I wanted on the first try. To be fair this type of work is not hard but it would have been a boring time suck if I did this myself. Having aidermacs do it for me with no mistakes in a couple of minutes is a big win. The fact that this functionality is all contained in Emacs and one shortcut away is also great. Especially for non-work projects reducing the cost of otherwise tedious coding activities means that things I would be too lazy or unmotivated to tackle will now a) actually get done and b) will get done as they are identified rather than piling up over time.
Interesting Features I Have Not Tried Yet
Architect Mode
The basic idea behind this as I understand it is that you can enlist two different models to tackle tasks. One is designated the architect and the other is designated the editor. The architect is tasked with taking the user’s prompt and coming up with a plan for what to do to accomplish the provided objectives. Once the architect outputs that plan the editor then goes and actually generates the code to realize the architect’s plan. I’m not sure exactly what the interaction model between the two is but this notion of cooperating specialists sounds cool. Since most AI companies out there market their models as omni-experts who are awesome at everything it’s not immediately clear to me what the best model choice would be for each role. From personal experience with a lot of them I would feel pretty good if Claude or the above mentioned gemini-2.5-pro was my editor but I don’t have a clear answer as to who I should want my architect to be.
File Watching
If file watching is enabled when you add comments that will trigger aidermacs to do something when the file is saved containing comments with special AI
designators.
Examples:
# This comment's contents will be added to the model's context. AI
# This comment's instruction will trigger code changes. AI!
# This comment's question will be answered. AI?
This is one I’m probably going to want to experiment with at some point. The idea of triggering contextually aware activity inline and in the moment sounds pretty enticing.
Wrap Up
Having put all of this through its paces I’m pretty confident I will be using a lot of this functionality going forward. Automating the boring stuff is something I am almost always conspiring to do so having a low friction avenue to do this as it relates to coding stuff is awesome. The fact that Aider provides an abstraction layer such that I can swap out models as better ones become available gives me confidence that this will be a future-proof setup in at least the medium term. I’m well aware that the landscape of AI tooling and the Emacs integrations with that tooling is vast so it is entirely possible that a better setup is out there to be found but from the early returns this seems pretty solid. If anyone in the know has ideas about how I could improve upon this shoot me a DM.