A modern, extensible Neovim plugin for managing multiple AI agent sessions (e.g. Claude Code, terminal agents, LLM CLI sessions) with floating windows, session switching, and lifecycle management.
demo_720p.mp4
- β‘ Multi-Session Management: Run and track multiple background AI agent processes.
- π Interactive Session Tab Bar: Browser-like tab bar at the top of the window showing all active sessions, status icons (β‘/π’/βͺ), and highlighting the active session.
- π― Targeted Prompting & Session Dispatch: Send prompts or commands to specific agents by friendly name or interactive picker without switching contexts.
- π·οΈ Session Renaming & Role Tagging: Rename sessions easily to assign clear roles (e.g.
architect,coder,tester,reviewer). - ποΈ Left Sidebar Session Explorer: Interactive side drawer list (like Neo-tree / Aerial) to view, launch, rename, prompt, and manage sessions.
- π Background Task Notifications: Receive automatic notifications (
vim.notify/ nvim-notify / Snacks) when unfocused background agent sessions complete tasks (idle) or exit. - πͺ Floating & Split Windows: Toggle floating modal terminals or splits seamlessly.
- π Zoom & Center Full View Toggle: Instantly switch between right-side split (compact view) and centered full-screen float (large reading view) with
zor:AgentSessionZoom. - π Universal Picker Integration: Switch sessions easily using
vim.ui.select(supports Telescope, Snacks, fzf-lua, dressing.nvim). - π§© Lazy.nvim & LazyVim & AstroNvim Ready: Zero-boilerplate setup and keymapping configuration.
Using lazy.nvim / LazyVim / AstroNvim
Add the following spec to your plugin configuration (e.g. lua/plugins/agent-session.lua):
return {
"yoch/agent-session.nvim",
cmd = {
"AgentSession",
"AgentSessionToggle",
"AgentSessionZoom",
"AgentSessionToggleZoom",
"AgentSessionNext",
"AgentSessionPrev",
"AgentSessionGoto",
"AgentSessionSidebar",
"AgentSessionTree",
"AgentSessionNew",
"AgentSessionList",
"AgentSessionSelectAgent",
"AgentSessionPrompt",
"AgentSessionSendCommand",
"AgentSessionPipe",
"AgentSessionRename",
"AgentSessionDelete",
"AgentSessionSendLine",
"AgentSessionSendLineTo",
"AgentSessionSendFile",
"AgentSessionSendFileTo",
},
keys = {
{ "<leader>at", "<cmd>AgentSessionToggle<cr>", mode = { "n", "t" }, desc = "Toggle Agent Session Window" },
{ "<leader>az", "<cmd>AgentSessionZoom<cr>", mode = { "n", "t" }, desc = "Toggle Center Full / Side View" },
{ "<leader>ae", "<cmd>AgentSessionSidebar<cr>", mode = { "n", "t" }, desc = "Toggle Agent Explorer (Sidebar)" },
{ "<leader>an", "<cmd>AgentSessionNew<cr>", desc = "New Agent Session (Interactive)" },
{ "<leader>aa", "<cmd>AgentSessionSelectAgent<cr>", desc = "Select & Launch Agent" },
{ "<leader>al", "<cmd>AgentSessionList<cr>", desc = "List Active Sessions" },
{ "]a", "<cmd>AgentSessionNext<cr>", mode = { "n", "t" }, desc = "Next Agent Session" },
{ "[a", "<cmd>AgentSessionPrev<cr>", mode = { "n", "t" }, desc = "Previous Agent Session" },
{ "<leader>ap", "<cmd>AgentSessionPrompt<cr>", desc = "Prompt / Command Target Session" },
{ "<leader>aP", "<cmd>AgentSessionPipe<cr>", mode = { "n", "v" }, desc = "Pipe Output to Target Session" },
{ "<leader>ar", "<cmd>AgentSessionRename<cr>", desc = "Rename Session" },
{ "<leader>as", "<cmd>AgentSessionSendLine<cr>", mode = { "n", "v" }, desc = "Send Line/Selection Ref to Session" },
{ "<leader>ab", "<cmd>AgentSessionSendFile<cr>", desc = "Send File Ref to Session" },
},
opts = {
default_agent = "agy", -- "claude" | "agy" | "codex" | "gemini" | "sh"
agents = {
claude = { cmd = "claude", args = {}, env = {} },
agy = { cmd = "agy", args = {}, env = {} },
codex = { cmd = "codex", args = {}, env = {} },
gemini = { cmd = "gemini", args = {}, env = {} },
sh = { cmd = vim.o.shell, args = {}, env = {} },
},
ui = {
position = "vsplit", -- "float" | "split" | "vsplit"
width = 0.35, -- 35% screen width or fixed column count
height = 0.8,
border = "rounded",
title = " Agent Session ",
terminal_mappings = {
enabled = true,
escape = "<C-\\><C-\\>", -- Double Ctrl-\ to exit terminal mode back to normal mode safely
},
},
sidebar = {
position = "auto", -- "auto" (bottom-left under neo-tree), "left", "bottom-left"
width = 0.20, -- 20% screen width (if neo-tree not open)
height = 0.35, -- 35% height under neo-tree (in bottom-left)
},
idle_timeout = 800, -- ms of silence before switching from running to idle
notify_on_idle = true, -- notify when a background session finishes task (idle)
notify_on_exit = true, -- notify when a background session process exits
notifications = {
enabled = true,
on_idle = true,
on_exit = true,
idle_delay = 2500, -- ms session must remain idle before notifying (avoids subagent / tool pause flickers)
cooldown = 5000, -- minimum ms between notifications for the same session
},
status_icons = {
running = "β‘",
idle = "π’",
stopped = "βͺ",
},
},
}You can display the active agent session status in your statusline:
-- Lualine component
{
function()
return require("agent-session").status()
end,
cond = function()
return require("agent-session.session").get_current() ~= nil
end,
}require("agent-session").setup({
session_dir = vim.fn.stdpath("data") .. "/agent-sessions",
default_agent = "claude",
agents = {
claude = {
cmd = "claude",
args = {},
env = {},
},
},
ui = {
position = "float", -- "float", "split", "vsplit"
width = 0.85,
height = 0.8,
border = "rounded",
title = " Agent Session ",
terminal_mappings = {
enabled = true,
escape = "<C-\\><C-\\>", -- Double Ctrl-\ to exit terminal mode back to normal mode safely
},
},
idle_timeout = 800, -- Milliseconds of silence before marking session as idle
notify_on_idle = true, -- Notify when a background session finishes task
notify_on_exit = true, -- Notify when a background session process exits
notifications = {
enabled = true,
on_idle = true,
on_exit = true,
idle_delay = 2500, -- Milliseconds session must remain idle before notifying (avoids subagent flickers)
cooldown = 5000, -- Minimum ms between notifications for the same session
},
status_icons = {
running = "β‘",
idle = "π’",
stopped = "βͺ",
},
hooks = {
on_session_start = nil, -- function(session)
on_session_exit = nil, -- function(session, exit_code)
on_status_change = nil, -- function(session, new_status, old_status)
},
})| Command | Description |
|---|---|
:AgentSession / :AgentSessionToggle |
Toggle current active session window |
:AgentSessionZoom / :AgentSessionToggleZoom |
Toggle between center full (float) screen and side split view |
:AgentSessionNext / :AgentSession next |
Switch to next agent session (chronological order) |
:AgentSessionPrev / :AgentSession prev |
Switch to previous agent session |
:AgentSessionGoto [N] / :AgentSession [N] |
Jump directly to agent session by tab index number |
| `:AgentSessionNew [agent | name] [agent]` |
:AgentSessionSelectAgent |
Open interactive picker to choose which agent to launch |
:AgentSessionList |
Open interactive session picker to switch active session |
:AgentSessionPrompt [target] [prompt] |
Send prompt/command to a specific session (interactive picker if omitted) |
:AgentSessionSendCommand [target] [prompt] |
Alias for :AgentSessionPrompt |
:AgentSessionPipe [source] [target] [instruction] |
Pipe output from one session into another session with optional instruction |
:AgentSessionRename [name] [target] |
Rename current session or specified session |
:AgentSessionDelete [target] |
Terminate and remove current or specified session |
:AgentSessionSendLine |
Send @file:line (normal mode) or @file:start-end (visual mode) to active session |
:AgentSessionSendLineTo [target] |
Send line/selection reference directly to a chosen target session |
:AgentSessionSendFile |
Send @file (whole current buffer) to active session |
:AgentSessionSendFileTo [target] |
Send whole buffer reference directly to a chosen target session |
| `:AgentSession status [idle | running]` |
When typing inside an Agent Session window:
<C-\><C-\>: Exit Terminal Mode directly to Normal Mode (100% safe, never sendsEsc, never stops running agents)- Global keymaps (e.g.
<C-t>/<leader>az) configured withmode = { "n", "t" }can be triggered directly in 1 step from terminal mode!
When in Normal mode inside an Agent Session window:
z/Z/<C-w>z/<C-w>m: Toggle Zoom (Center Full Float β· Side Split)q/<Esc>: Hide / Close session window]b/]s/]a: Cycle to Next Agent Session[b/[s/[a: Cycle to Previous Agent Session1~9/1gt~9gt/]1~]9: Jump directly to Session Tab 1 ~ 9R: Rename current sessioni/a/<CR>: Enter Terminal Input Mode
You can test the plugin in an isolated environment without affecting your main Neovim config:
nvim -u tests/minimal_init.luaagent-session.nvim/
βββ doc/
β βββ agent-session.txt # Vimdoc help file (:help agent-session)
βββ lua/
β βββ agent-session/
β βββ init.lua # Public API entry point
β βββ config.lua # Default configuration & options
β βββ session.lua # Session model & process manager
β βββ ui.lua # Floating window & vim.ui.select picker
βββ plugin/
β βββ agent-session.lua # User commands & autocommands
βββ tests/
β βββ minimal_init.lua # Lazy.nvim isolated repro & test harness
βββ .luarc.json # Lua Language Server settings
βββ .stylua.toml # StyLua formatting config
βββ .gitignore
βββ README.md