Emacs, Agent Harnesses, and Malleable Software

An elegant weapon for a more civilized age

Although for the last several years I’ve used a combination of VS Code and Cursor for day-to-day work, I would use Emacs if it were entirely up to me. My coworkers have teased or tolerated me praising its virtues over the years, but Emacs is something I keep returning to and learning from. I think I used Emacs for the first time something like 30 years ago (30!). I picked a side in the already decades-old Editor Wars without really knowing what I was doing; all I knew was that I just wanted to be able to type, and vi continually tripped me up. Now I feel that the eternal Unix editor dichotomy is a false one and that they’re really two different types of software altogether. Vi is a lightweight text-editing katana. Emacs is a whole-ass Lisp REPL posing as an IDE. These are not the same. The magic of Emacs is that the entire state of the running Emacs process is always at your programmatic disposal. All of it! At any point, in any type of file, you can write one-off Lisp code (e.g. (+ 1 1) or (setq some-variable value)) and evaluate it right there and then. There are special affordances for using this to manipulate the text in front of you: every key press is actually triggering a Lisp function, which you can record and turn into rerunnable macros if you like. I know of no other software quite like Emacs. It blurs the line between environment and tool, user and author. It has made me realize that we are typically just guests in other people’s software.

gptel: an agent harness inside Emacs

I recently found my way back to Emacs because I was reviving a laptop that is too old to run Claude Code, Codex, Cursor, or other open-source harnesses. I technically got an old version of Claude Code running, but its auto-updater made the setup unusable. Agents had become crucial to the way I use a computer, so I needed an alternative.

Of course, Emacs runs on anything, and it also has libraries for calling LLMs. There are several, but the one with the most traction is probably gptel, along with its associated lightweight harness, gptel-agent.

There are a few basic ways of invoking an LLM with gptel (all with customizable keyboard shortcuts):

  1. Send a selected region to the model and insert its response afterward
  2. Rewrite a selected region in place with a quick instruction, similar to ⌘-K in Cursor
  3. Send a whole file, allowing a Markdown document with metadata to serve as a persistent chat

gptel also exposes a nice, idiomatic low-level primitive that generalizes all of these. You could easily use it to, e.g., auto-generate commit messages within Magit (a truly lovely version control porcelain):

(gptel-request
 "my prompt"                                ; the text sent to the model
 ;; Everything below is optional
 :buffer   output-buffer                    ; where the response should go
 :system   "Instructions for the model"     ; behavior for this request
 :position insertion-point                  ; where to insert the response
 :context  (list "any other info")          ; extra data for the callback
 :callback (lambda (response info) ...))    ; handle the response when it arrives
                                            ; by default, insert it at :position

These interactions are useful, but they become much more powerful when the model can repeatedly use tools. Agents (that is, LLMs calling tools in a loop to do a job) are more than simple chatbots. A growing body of work on “harness engineering” makes the point that an agent is the model plus the software invoking it (1, 2, 3, 4, 5). The surrounding runtime determines how context is assembled, which tools are available, how subagents divide work, where intermediate artifacts live, what actions require permission, and how the whole process can be observed, evaluated, and improved.

Some harnesses are also making that surrounding machinery extensible. Pi exposes hooks for tools, commands, events, context handling, and UI, allowing an agent to write a TypeScript extension, reload it, and continue working. OpenCode exposes much of its behavior through plugins and uses those same interfaces internally, while DeepSeek Harness treats models, tools, sessions, agent loops, and UI components as lifecycle-managed plugins. These systems are applying live extensibility to the agent harness itself.

You can probably see where I’m going with this: Emacs is unusually well suited to serving as an LLM harness, but its advantage is deeper than that. Emacs is a live environment where not only the harness, but also the editor, commands, keybindings, processes, state, and interface are all inspectable and modifiable. I’ve used gptel-agent, a library built on gptel, as the foundation for this work. It provides the tools you would expect: planner/inspector/executor subagents; web search and fetching; regular-expression searches across files; file reading and writing; to-do management; and MCP support. What makes the setup unique, however, is its Eval tool, which lets the agent execute Elisp directly, whether to consult documentation and source code, inspect state, or modify Emacs on the fly. The result is incredible: the agent can extend its own working environment without interrupting its work.

An agent session in Emacs, shown at 4× speed.

To make this more concrete, here are some changes I made to gptel using this setup:

  1. Added an AskQuestion tool to match a common UX pattern in other harnesses. The agent wrote the tool and then used it in the same chat (!)
  2. Split a Researcher subagent into WebResearcher and Scholarly-Researcher subagents to handle web and scientific searches more effectively
  3. Instrumented tool calls, then used the resulting traces to improve the system prompt and reduce unnecessary discovery calls (meta!)
  4. Automatically configured my Semantic Scholar MCP server

A big missing piece for me was persistent chat management. gptel makes a strongly Emacs-flavored choice: conversations are Markdown or Org files with metadata recording roles and configuration. I’m a bit mixed on this (admittedly idiomatic) choice. A chronological, append-only JSONL stream would separate canonical history from presentation more cleanly, but it would also require a separate rendering and editing layer. gptel instead reuses everything Emacs already knows how to do with text.

How those files are organized is deliberately left to the user. I use Obsidian on iOS, store my vault in iCloud, and write in Emacs, so I decided to keep conversations in a chats directory and name them using a YYYY-MM-DD--title.chat.md convention. Once that convention existed, I asked the agent to help build an Emacs major mode for browsing them:

An Emacs chat-history list showing chat status, dates, titles, models, and project context.

My chat-history list in Emacs, with status indicators and recency sorting.

I liked it enough that it is now the first screen I see when Emacs starts. This is just one example, but Emacs can build and host any TUI you could imagine.

The history list has a rename command that uses the same gptel-request primitive shown earlier to scan a conversation and suggest a new title. It’s also possible to ask, in the chat, “Please suggest three better names for this chat.” It will fire off the AskQuestion tool, then use the Eval tool to rename the file and edit its metadata. This is an example of how Emacs can blur the line between your work and the environment in which it happens.

Beyond the harness

Models will continue to improve, but I don’t think better models will make the harness irrelevant. The harness determines what the model can see, what it can do, what persists between calls, what requires permission, and how its behavior can be inspected and improved.

But the opportunity is larger than agent harnesses. Jeremy Morrell argues for software with a small, accountable core and stable extension points that LLMs can use to satisfy the long tail of individual needs. This resembles Ink & Switch’s “malleable software”: software that people can adapt at the point of use, creating a gradual path from using a tool to reshaping it. Emacs embodied this philosophy in the 1970s; secretaries famously extended it to automate their workflows. Andy Matuschak makes the complementary point: coding agents let domain experts adapt software, but they need a stable, composable substrate to produce more than isolated, one-off apps.

This is why Emacs seems like such a compelling environment for exploring and prototyping agentic work. Prose, references, code, data, computation, and communication can coexist in a persistent and inspectable environment, and the user and agent can reshape it together as the work evolves. I’m particularly interested in applying this approach to scientific research, including tools that let agents reason about uncertainty in a Bayesian manner, but that’s another topic for another post. I’m excited to see where it goes.

Finally, a huge thank-you to Karthik Chikmagalur and the other contributors. Their work on gptel and gptel-agent made all of this possible, and their thoughtful, Emacs-native design has been rewarding to extend.

References

  1. Schluntz, E. and Zhang, B. (2024). Building effective agents. Anthropic Engineering.
  2. Yang, J. et al. (2024). SWE-agent: Agent-Computer Interfaces Enable Automated Software Engineering. Advances in Neural Information Processing Systems 37.
  3. Hu, S., Lu, C. and Clune, J. (2025). Automated Design of Agentic Systems. International Conference on Learning Representations.
  4. Hadfield, J. et al. (2025). How we built our multi-agent research system. Anthropic Engineering.
  5. Weng, L. (2026). Harness Engineering for Self-Improvement.