________
╱ ╲
╱ ╱
╱ ╱
╲__╱__╱__╱
Magenta is for agents.
magenta.nvim
is a plugin for leveraging LLM agents in neovim. It provides a chat window where you can talk to your AI coding assistant, as well as tools to populate context and perform inline edits. It's similar to copilot agent, claude code, cursor compose, ampcode or windsurf.
(Developed by dlants.me. I was tempted by other editors due to lack of high-quality agentic coding support in neovim. I missed neovim a lot, though, so I decided to go back and implement my own. I now happily code in neovim using magenta, and find that it's just as good!)
(Note - I mostly develop using the Anthropic provider, so claude sonnet 3.7 or 4 are recommended. The OpenAI provider is supported, but with limitations. Contributions are welcome! See for example https://github.com/dlants/magenta.nvim/issues/82 and https://github.com/dlants/magenta.nvim/issues/84 )
I implemented sub-agents - a powerful feature that allows the main agent to delegate specific tasks to specialized sub-agents. Sub-agents can work in parallel and have their own specialized system prompts for tasks like learning codebases, planning implementations, or performing focused work. This enables complex workflows where multiple agents collaborate on different aspects of a problem.
I added support for images and pdfs. Magenta can now read these using the get_file tool for image-compatible openai and anthropic models.
I implemented thread compaction that intelligently analyzes your next prompt and extracts only the relevant parts of the conversation history. This makes it easier to continue long conversations without hitting context limits while ensuring all important information is preserved. I also updated the magenta header to give you an estimate of the token count for your current conversation.
I updated the architecture around context following. We now track the state of the file on disk, and the buffer, as well as the current view that the agent has of the file. When these diverge, we send just the diff of the changes to the agent. This allows for better use of the cache, and more efficient communication since we do not have to re-send the full file contents when a small thing changes.
I updated the architecture around streaming, so we now process partial tool calls, which means we can preview Insert and Replace commands gradually as they stream in. This makes the tool feel a lot more responsive. I also added support for anthropic web search and citations!
I made a significant architectural shift in how magenta.nvim handles edits. Instead of merely proposing changes that require user confirmation, the agent can now directly apply edits to files with automatic snapshots for safety. Combined with the recent PR that implemented robust bash command execution, this creates a powerful iteration loop capability: agents can now modify files, run tests through bash, analyze results, and make further changes - all without user intervention.
Make sure you have node installed, at least v20
:
node --version
The plugin uses profiles to configure provider access. Each profile specifies:
{
"dlants/magenta.nvim",
lazy = false, -- you could also bind to <leader>mt
build = "npm install --frozen-lockfile",
opts = {},
},
local vim = vim
local Plug = vim.fn['plug#']
vim.call('plug#begin')
Plug('dlants/magenta.vim', {
['do'] = 'npm install --frozen-lockfile',
})
vim.call('plug#end')
require('magenta').setup()
require('magenta').setup({
profiles = {
{
name = "claude-3-7",
provider = "anthropic",
model = "claude-3-7-sonnet-latest",
apiKeyEnvVar = "ANTHROPIC_API_KEY"
},
{
name = "gpt-4o",
provider = "openai",
model = "gpt-4o",
apiKeyEnvVar = "OPENAI_API_KEY"
}
},
-- open chat sidebar on left or right side
sidebarPosition = "left",
-- can be changed to "telescope" or "snacks"
picker = "fzf-lua",
-- enable default keymaps shown below
defaultKeymaps = true,
-- keymaps for the sidebar input buffer
sidebarKeymaps = {
normal = {
["<CR>"] = ":Magenta send<CR>",
}
},
-- keymaps for the inline edit input buffer
-- if keymap is set to function, it accepts a target_bufnr param
inlineKeymaps = {
normal = {
["<CR>"] = function(target_bufnr)
vim.cmd("Magenta submit-inline-edit " .. target_bufnr)
end,
},
}
})
The first profile in your profiles
list is used as the default when the plugin starts. You can switch between profiles using :Magenta pick-provider
(bound to <leader>mp
by default).
For example, you can set up multiple profiles for different providers or API endpoints:
profiles = {
{
name = "claude-3-7",
provider = "anthropic",
model = "claude-3-7-sonnet-latest",
apiKeyEnvVar = "ANTHROPIC_API_KEY"
},
{
name = "custom",
provider = "anthropic",
model = "claude-3-7-sonnet-latest",
apiKeyEnvVar = "CUSTOM_API_KEY_ENV_VAR",
baseUrl = "custom anthropic endpoint"
}
}
Currently supported providers are openai
, anthropic
, bedrock
, and ollama
. The model
parameter must be compatible with the SDK used for each provider:
anthropic
: Anthropic Node SDK - supports models like claude-3-7-sonnet-latest
, claude-3-5-sonnet-20240620
openai
: OpenAI Node SDK - supports models like gpt-4o
, o1
bedrock
: AWS SDK for Bedrock Runtime - supports models like anthropic.claude-3-5-sonnet-20241022-v2:0
ollama
: Ollama Node SDK - supports models like qwen3:14b
which have been insalled locally. (Ollama models)Any provider that has a node SDK and supports tool use should be easy to add. Contributions are welcome.
Magenta includes a security feature for the bash_command tool that requires user approval before running shell commands. To improve the workflow, you can configure a list of regex patterns that define which commands are pre-approved to run without confirmation.
The commandAllowlist
option takes an array of regex patterns. When the LLM tries to execute a shell command, it's checked against these patterns. If any pattern matches, the command runs without approval. Otherwise, you'll be prompted to allow or deny it.
Regex patterns should be carefully designed to avoid security risks. You can find the default allowlist patterns in lua/magenta/options.lua.
You can create project-specific configuration by adding a .magenta/options.json
file to your project root. This allows you to customize Magenta settings per project while keeping your global configuration unchanged.
The plugin will automatically discover and load project settings by searching for .magenta/options.json
starting from the current working directory and walking up the directory tree.
Common use cases include:
make
, cargo
, npm
commands)The merging works as follows:
sidebarPosition
)Create .magenta/options.json
in your project root:
{
"profiles": [
{
"name": "project-claude",
"provider": "anthropic",
"model": "claude-3-7-sonnet-latest",
"apiKeyEnvVar": "PROJECT_ANTHROPIC_KEY"
}
],
"commandAllowlist": [
"^make( [^;&|()<>]*)?$",
"^cargo (build|test|run)( [^;&|()<>]*)?$"
],
"autoContext": ["README.md", "docs/*.md"]
}
The project settings file supports all the same options as the global configuration, just in JSON format instead of Lua.
If default_keymaps
is set to true, the plugin will configure the following global keymaps:
local Actions = require("magenta.actions")
vim.keymap.set(
"n",
"<leader>mn",
":Magenta new-thread<CR>",
{silent = true, noremap = true, desc = "Create new Magenta thread"}
)
vim.keymap.set(
"n",
"<leader>mc",
":Magenta clear<CR>",
{silent = true, noremap = true, desc = "Clear Magenta state"}
)
vim.keymap.set(
"n",
"<leader>ma",
":Magenta abort<CR>",
{silent = true, noremap = true, desc = "Abort current Magenta operation"}
)
vim.keymap.set(
"n",
"<leader>mt",
":Magenta toggle<CR>",
{silent = true, noremap = true, desc = "Toggle Magenta window"}
)
vim.keymap.set(
"n",
"<leader>mi",
":Magenta start-inline-edit<CR>",
{silent = true, noremap = true, desc = "Inline edit"}
)
vim.keymap.set(
"v",
"<leader>mi",
":Magenta start-inline-edit-selection<CR>",
{silent = true, noremap = true, desc = "Inline edit selection"}
)
vim.keymap.set(
"v",
"<leader>mp",
":Magenta paste-selection<CR>",
{silent = true, noremap = true, desc = "Send selection to Magenta"}
)
vim.keymap.set(
"n",
"<leader>mb", -- like "magenta buffer"?
Actions.add_buffer_to_context,
{ noremap = true, silent = true, desc = "Add current buffer to Magenta context" }
)
vim.keymap.set(
"n",
"<leader>mf",
Actions.pick_context_files,
{ noremap = true, silent = true, desc = "Select files to add to Magenta context" }
)
vim.keymap.set(
"n",
"<leader>mp",
Actions.pick_provider,
{ noremap = true, silent = true, desc = "Select provider and model" }
)
In order to use fzf-lua as your selector for certain commands, like <leader>mp
for :Magenta provider
, you should
set it as the default selector for neovim, by running register_ui_select
at some point during initialization.
{
"ibhagwan/fzf-lua",
lazy = false,
config = function()
require("fzf-lua").setup({
-- ...
})
require("fzf-lua").register_ui_select()
end,
-- ...
}
<leader>mt
is for :Magenta toggle
. This will open a sidebar where you can chat with the model. You can add files to the context with Magenta context-files
(<leader>mf
), or paste your current visual selection with Magenta paste-selection
(<leader>mp
)Magenta now supports multiple concurrent chat threads:
<leader>mn
is for :Magenta new-thread
. This creates a new independent chat thread.-
to view the thread overview screen.*
in the thread list.Enter
on any thread in the overview to make it active.The LLM agent can now spawn specialized sub-agents to handle independent tasks more effectively. This allows the main agent to:
How it works:
The main agent uses spawn_subagent
and wait_for_subagents
tools to create and coordinate sub-agents. Each sub-agent operates independently with its own context and toolset, then reports results back to the main agent using yield_to_parent
.
Sub-agent system prompts (see full prompts):
learn
: System prompt focused on code discovery, understanding APIs, and analyzing existing implementationsplan
: System prompt specialized for strategic planning and breaking down complex implementationsdefault
: General-purpose system prompt with standard coding assistant behaviorExample workflows:
Learning workflow:
user: I want to refactor this interface
→ Main agent spawns a 'learn' sub-agent to analyze the interface and its usage
→ Sub-agent explores the codebase, finds all references, understands patterns
→ Sub-agent yields findings back to main agent
→ Main agent uses the focused analysis to safely perform the refactoring
Planning workflow:
user: I want to build a new authentication system
→ Main agent spawns a 'plan' sub-agent to create an implementation strategy
→ Sub-agent analyzes existing auth patterns, creates detailed plan in plans/auth-system.md
→ Sub-agent yields plan location back to main agent
→ Main agent responds: "Please review `plans/auth-system.md` and confirm before I proceed"
This architecture enables more sophisticated problem-solving by allowing the agent to gather focused context and work on multiple independent tasks simultaneously.
<leader>mi
is for :Magenta start-inline-edit
, or start-inline-edit-selection
in visual mode. This will bring up a new split where you can write a prompt to edit the current buffer. Magenta will force a find-and-replace tool use for normal mode, or force a replace tool use for the selection in visual mode.Inline edit uses your chat history so far, so a great workflow is to build up context in the chat panel, and then use it to perform inline edits in a buffer.
The display buffer is not modifiable, however you can interact with some parts of the display buffer by pressing <CR>
. For example, you can expand the tool request and responses to see their details, and you can trigger a diff to appear on file edits.
enter
on a [review] message to pull up the diff to try and edit initenter
on a tool to see the details of the request & result. Enter again on any part of the expanded view to collapse it.enter
on a context file to open itd
on a context file to remove itenter
on a diff to see a detailed side-by-side comparison between the original file snapshot and proposed changest
on a running bash command to terminate it (SIGTERM)Thread compaction allows you to retain relevant pieces of context as you shift focus to new tasks.
@compact
followed by your next prompt in the input bufferThis smart compaction ensures that only information specifically relevant to your next task is carried forward, while irrelevant parts of the conversation are summarized or removed.
Example usage:
@compact Now let's implement unit tests for the new feature we just discussed
When a bash command is running, you can press the t
key in the display buffer while your cursor is over the executing command to terminate it immediately. This is useful for long-running commands or commands that have entered an undesired state. When terminated, the command will display a message indicating it was terminated by user with SIGTERM.
See the most up-to-date list of implemented tools here.
:UpdateRemotePlugins
! (h/t wallpants).I think the closest plugins to this one are avante.nvim and codecompanion.nvim
Codecompanion has a single buffer, while magenta.nvim has separate input & display buffers. This makes it easier to add some interactivity to the display buffer (since it's not directly editable). I think this makes it nicer for situations when the LLM uses multiple tools at once. So for example, in codecompanion when the LLM needs permission to open a file, or proposes a change, this takes over your editor, which isn't a nice workflow when the tool needs to edit multiple files.
I think it's fairly similar. However, magenta.nvim is written in typescript and uses the sdks to implement streaming, which I think makes it more stable. I think the main advantage is the architecture is very clean so it should be easy to extend the functionality. Between typescript, sdks and the architecture, I think my velocity is pretty high.
magenta.nvim includes capabilities that neither plugin offers: