README.org
September 26, 2025 · View on GitHub
#+TITLE: acp.el #+AUTHOR: Álvaro Ramírez
- This project needs your funding
As you pay for those useful LLM tokens, consider [[https://github.com/sponsors/xenodium][sponsoring]] development and maintenance of this project.
=acp.el= enables us to build better integrations and tools into our beloved Emacs text editor. With your help, I can make this effort more [[https://github.com/sponsors/xenodium][sustainable]].
Thank you!
[[https://xenodium.com/][Alvaro]]
- acp.el
acp.el implements the Agent Client Protocol (ACP) for Emacs as per [[https://agentclientprotocol.com][agentclientprotocol.com]].
ACP is a standardized protocol for communicating with LLM agents like Gemini CLI, Claude Code, etc.
Note: This package is in the very early stages, isn't yet API stable, and is bound to change.
- Install
** Prerequisites
Install an LLM agent command-line tool like:
- Gemini CLI: =gemini-cli= - [[https://github.com/google-gemini/gemini-cli][github.com/google-gemini/gemini-cli]]
- Claude Code: =claude-code-acp= - [[https://www.anthropic.com/claude-code][anthropic.com/claude-code]]
** Package Installation Clone the repository and add to your Emacs load path:
#+begin_src emacs-lisp (add-to-list 'load-path "/path/to/acp.el/") (require 'acp) #+end_src
- Sample Usage
** Initialization
#+begin_src emacs-lisp :exports both (setq client (acp-make-client :command "gemini" :command-params '("--experimental-acp") :environment-variables (when api-key (list (format "GEMINI_API_KEY=%s" "your-api-key")))))
(acp-send-request :client client :request (acp-make-initialize-request :protocol-version 1) :on-success (lambda (response) (message "Initialize success: %s" response)) :on-failure (lambda (error) (message "Initialize failed: %s" error))) #+end_src
Initialize success:
#+RESULTS:
: ((protocolVersion . 1)
: (authMethods . [((id . oauth-personal)
: (name . Log in with Google)
: (description . :null))
: ((id . gemini-api-key)
: (name . Use Gemini API key)
: (description . Requires setting the GEMINI_API_KEY environment variable))
: ((id . vertex-ai)
: (name . Vertex AI)
: (description . :null))])
: (agentCapabilities (loadSession . :false)
: (promptCapabilities (image . t)
: (audio . t)
: (embeddedContext . t))))
Typically, you'd need to send a sequence of requests before you can send the agent prompts to interact with:
- =acp-make-initialize-request=
- =acp-make-authenticate-request= (not needed for Claude Code)
- =acp-make-session-new-request=
You may now send the agent prompts with:
- =acp-make-session-prompt-request=
- Subscriptions
#+begin_src emacs-lisp :lexical no :exports both (acp-subscribe-to-requests client (lambda (request) (message "Received request: %s" request)))
(acp-subscribe-to-notifications client (lambda (notification) (message "Received notification: %s" notification))) #+end_src
- Cleanup
#+begin_src emacs-lisp :lexical no (acp-shutdown client) #+end_src
- Logging
Logging can be enabled via:
#+begin_src emacs-lisp :lexical no (setq acp-logging-enabled t) #+end_src
Look out for =acp log= and =acp traffic= buffers.
The =acp traffic= is particularly useful to inspecting incomng or outgoing traffic.
[[file:traffic.gif]]
- APIs
#+BEGIN_SRC emacs-lisp :results table :colnames '("Function" "Type" "Description") :exports results (let ((rows)) (mapatoms (lambda (symbol) (let ((name (symbol-name symbol))) (when (and (string-match "^acp-" name) (not (string-match "^acp--" name)) (not (string-match "^acp-traffic" name)) (not (string-match "^acp-fakes" name)) (fboundp symbol)) (push `(,name ,(or (car (split-string (or (documentation symbol t) "No documentation") "\n")) "No documentation")) rows))))) (sort rows (lambda (a b) (string< (car a) (car b))))) #+END_SRC
#+RESULTS: | acp-logs-buffer | Get CLIENT logs buffer. | | acp-make-authenticate-request | Instantiate an "authenticate" request. | | acp-make-client | Create an ACP client. | | acp-make-error | Create a JSON-RPC error object. | | acp-make-fs-read-text-file-response | Instantiate a "fs/read_text_file" response. | | acp-make-fs-write-text-file-response | Instantiate a "fs/write_text_file" response. | | acp-make-initialize-request | Instantiate an "initialize" request. | | acp-make-session-cancel-notification | Instantiate a "session/cancel" request. | | acp-make-session-new-request | Instantiate a "session/new" request. | | acp-make-session-prompt-request | Instantiate a "session/prompt" request. | | acp-make-session-request-permission-response | Instantiate a "session/request_permission" response. | | acp-reset-logs | Reset CLIENT log buffers. | | acp-send-notification | Send NOTIFICATION from CLIENT. | | acp-send-request | Send REQUEST from CLIENT. | | acp-send-response | Send a request RESPONSE from CLIENT. | | acp-shutdown | Shutdown ACP CLIENT and release resources. | | acp-subscribe-to-errors | Subscribe to agent errors using CLIENT. | | acp-subscribe-to-notifications | Subscribe to incoming CLIENT notifications. | | acp-subscribe-to-requests | Subscribe to incoming CLIENT requests. |
- FAQ
** Why not use [[https://github.com/emacs-mirror/emacs/blob/master/lisp/jsonrpc.el][jsonrpc.el]]?
That was my initial intention, though it doesn't seem possible with [[https://github.com/emacs-mirror/emacs/blob/1d6ec2a0406c8a53fcf793b05453dbcc7e809d76/lisp/jsonrpc.el#L586][Content-Length automatically appended]] to requests sent. If you do know of a way, I'd love to know.