A small Neovim bridge for keeping your editor context close to an agent running in a native Herdr pane.
The plugin owns neither the agent terminal UI nor the agent conversation. Neovim produces context, Herdr owns the pane and agent lifecycle, and the agent keeps its normal terminal interface.
This project is inspired by and builds on the workflow and context model pioneered by
folke/sidekick.nvim. In particular, it adopts Sidekick's context-template
vocabulary, prompt-oriented UX, and {this} / {position} / {selection} semantics.
- When Sidekick is installed, the bridge builds directly on its public
sidekick.cli.renderandsidekick.cli.promptAPIs. Sidekick remains the source of truth for prompts, custom context variables, diagnostics, quickfix entries, and Tree-sitter text objects. - Without Sidekick, the built-in provider offers an independent, dependency-free compatibility implementation of the core context workflow.
- Sidekick's terminal and multiplexer layer is not embedded here. Herdr owns pane and agent lifecycle, while this project supplies the adapter and safety boundary between Neovim context and Herdr transport.
Sidekick is licensed under the Apache License 2.0. This project is independently distributed under MIT and does not bundle Sidekick source code.
Important
This project is an early preview. It is tested with Herdr 0.8.2 and Codex CLI on macOS. Linux is covered by unit tests; Windows integration has not been verified yet.
- Starts, reveals, or focuses one agent in the current Herdr tab.
- Toggles between Agent View and Code View without stopping the agent or rebuilding its pane.
- Creates a right-hand agent pane at 30% width by default.
- Sends the current file, cursor/range, or visual selection without nesting tmux inside Herdr.
- Focuses an agent instead of typing when the latest Herdr discovery response reports it as
blocked. - Treats Herdr's startup
agent_not_readyresponse as a recoverable interactive state. - Resolves the live pane, tab, and workspace before each action, so moving a pane does not leave discovery on stale launch-time context.
- Works without Sidekick through a built-in Neovim context provider.
- Optionally reuses Sidekick's context renderer and prompt library.
- Neovim 0.10 or newer.
- Herdr 0.8.2 or newer. The bridge relies on 0.8.2's
calling-pane resolution for
pane current --currentand uses native pane layout and zoom commands for Code View. - An agent executable supported by Herdr, such as
codex, installed and authenticated normally. - Neovim must run inside a Herdr pane so
HERDR_ENVandHERDR_PANE_IDare available. The live tab and workspace are resolved from that caller pane for every action; inherited workspace and tab variables are launch-time information only.
The optional Sidekick provider follows Sidekick's own requirements, currently Neovim 0.11.2 or newer.
With lazy.nvim:
{
"WorksOnMyVM/herdr-agent-bridge.nvim",
main = "herdr_agent",
lazy = false,
opts = {
context = { provider = "auto" },
pane = { size = 0.30 },
keymaps = {
toggle = "<leader>aa",
-- open = "<leader>ao", -- optional: always reveal and focus the agent
send_this = "<leader>at",
send_file = "<leader>af",
send_selection = "<leader>av",
prompt = "<leader>ap",
},
},
}For local development, replace the repository string with:
{
dir = "/path/to/herdr-agent-bridge",
main = "herdr_agent",
lazy = false,
opts = { --[[ same options ]] },
}No keymaps are installed unless explicitly configured.
keymaps.toggle is recommended for <leader>aa. keymaps.open remains available when you always want to reveal and
focus the agent.
| Mapping | Context | Result |
|---|---|---|
<leader>aa |
— | Create/focus an agent if needed; otherwise toggle Agent View / Code View |
<leader>at |
{this} |
Cursor/range reference in a file; this plus selection in a non-file buffer |
<leader>af |
{file} |
Current file reference, such as @lua/example.lua |
<leader>av |
{selection} |
Dedented visual selection |
<leader>ap |
Prompt library | Select a rendered prompt and stage it in the agent input |
Code View uses Herdr's tab-local pane zoom: only Neovim is shown. All sibling panes in that tab are temporarily hidden, but their processes and the underlying split layout remain intact. Each toggle reads the live Herdr layout instead of caching a hidden-state flag. Returning to Agent View restores the layout and focuses the selected agent.
open() and :HerdrAgentOpen remain deterministic: they always reveal and focus the current tab's agent. After a
context action, text is pasted into the agent input, the layout is revealed, and the agent pane is focused. It is
deliberately not submitted automatically, so you can inspect or extend it before pressing Enter.
Agent reuse is scoped to the caller's Herdr tab. If another tab in the same workspace already has a Codex pane, using the bridge from the current tab creates or focuses a separate pane here instead of switching tabs. View-changing actions run in invocation order, so rapid shortcut presses cannot race each other or create duplicate agent panes.
context.provider accepts "auto", "builtin", "sidekick", or a custom provider table.
The built-in provider has no plugin dependency and supports:
{file},{position},{line},{this},{selection}{buffers},{diagnostics},{diagnostics_all},{quickfix}- configurable prompt templates rendered through
vim.ui.select
When folke/sidekick.nvim is installed, "auto" uses its public sidekick.cli.render and sidekick.cli.prompt
interfaces. This preserves Sidekick prompts, custom context variables, diagnostics, quickfix entries, and Tree-sitter
text objects while Herdr remains the transport.
The adapter contains a tested compatibility shim for Sidekick's first lazy load. All Sidekick-specific knowledge is
isolated in lua/herdr_agent/context/sidekick.lua.
The built-in provider intentionally follows Sidekick's {this} behavior: it resolves to a cursor or visual-range
reference in a real file, and to this plus {selection} in a non-file buffer. Use {selection} / <leader>av when
the selected source text itself must be sent from a file.
require("herdr_agent").setup({
context = {
provider = {
render = function(template)
return template == "{file}" and "@custom-context.lua" or nil
end,
select_prompt = function(callback)
callback("Review @custom-context.lua")
end,
},
},
}):HerdrAgentToggle— switch between Agent View and Code View, creating an agent when needed.:HerdrAgentOpen— always reveal and focus the current tab's agent.:HerdrAgentSendThis— stage the current file range or non-file selection.:HerdrAgentSendFile— stage the current file reference.:HerdrAgentSendSelection— stage the active visual selection.:HerdrAgentPrompt— select and stage a rendered prompt.:checkhealth herdr_agent— check Herdr and context-provider availability.
require("herdr_agent") exposes setup, toggle, open, send_this, send_file, send_selection, prompt, and
context_provider. toggle is the user-facing view switch; open always reveals and focuses the agent. The last
function returns the active provider name ("builtin", "sidekick", or a custom provider's name) and is useful for
diagnostics.
require("herdr_agent").setup({
executable = nil, -- HERDR_BIN_PATH, then "herdr"
agent = {
kind = "codex",
-- globally unique string matching [a-z][a-z0-9_-]{0,31},
-- function(workspace_id, tab_id),
-- or a globally unique name generated from the current tab ID
name = nil,
},
pane = {
direction = "right",
size = 0.30, -- share assigned to the new agent pane
},
context = {
provider = "auto",
sidekick = { plugin = "sidekick.nvim" },
},
prompts = {
-- merged with the built-in prompt library
},
keymaps = {
toggle = false,
open = false,
send_this = false,
send_file = false,
send_selection = false,
prompt = false,
},
max_context_bytes = 64 * 1024,
})Herdr agent names are globally unique across all live agents. The default name is derived from the current tab ID. If
you override agent.name and use several tabs or workspaces at once, prefer a function(workspace_id, tab_id) that
derives a distinct valid name. Existing one-argument functions still run because workspace_id remains the first
argument, but Herdr will reject a second live agent if that function returns the same name for multiple tabs.
If an automatically named agent pane is moved to another tab, the bridge adds a short numeric suffix when rebuilding the original tab's agent. Explicit string or function names are never changed automatically.
- The plugin does not accept Codex directory trust, approvals, or interactive questions for you.
- An agent reported as blocked by the latest discovery snapshot is focused instead of sent staged context.
- Existing Herdr agents and panes are reused only within the current tab; the plugin does not migrate tmux sessions.
- View-changing actions are serialized, and multi-agent selections are revalidated against the live tab before focus or terminal input.
- Code View changes presentation only: sibling panes are temporarily hidden by Herdr zoom, while their processes and split layout remain intact.
- Context is limited to 64 KiB by default.
- Context containing terminal control bytes is rejected before raw pane transport.
- The project does not inspect nested tmux sessions.
Staging is intentionally implemented with Herdr's raw pane surface because agent prompt submits immediately, while
this plugin leaves the text editable in the agent input. State can change after discovery, so this check is a safety
guard rather than an atomic approval boundary; Herdr and the agent remain responsible for interactive approvals.
make test
stylua --check lua plugin testsTests use only Neovim's headless Lua runner; no Lua test framework is required.
The module boundaries and transport safety decisions are documented in docs/architecture.md.
This is an independent community project. It is not affiliated with or endorsed by Herdr, Sidekick, or OpenAI.
Special thanks to Folke Lemaitre and the Sidekick contributors. Sidekick established the Neovim context and prompt workflow that this bridge adapts to a Herdr-native agent pane.
MIT