Skip to content

MateoGreil/agent-fleet.nvim

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

70 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

agent-fleet.nvim

Run a fleet of coding agents from Neovim — pi, Claude Code, or any agent CLI — each in its own native terminal, listed and switchable from a clean board.

agent-fleet.nvim — launch real pi agents from the board, watch them run in native terminals, then rename, mark done and archive across the fleet

Inspired by claude agents, but with one hard rule:

The agent runs as its real CLI, in a native nvim terminal. Nothing reimplements the agent's UI.

So the whole conversation lives in a buffer you can scroll, search and yank with your own keybindings.

Features

  • Launch agents into a native terminal, with an optional initial prompt.
  • Persist & resume — a roster on disk; reopen a past agent (focus it if live, else relaunch it via its CLI's resume flag in its original cwd).
  • List & switch the agents of the current directory, live ones merged with on-disk sessions (pi and Claude) and deduped.
  • Live board (:AgentsBoard) — a dedicated buffer grouping agents into running / idle / done / archived sections, with colored state and relative last-activity times, refreshing on a timer.
  • Lifecycle — mark done, archive (soft, never deletes a session), rename, stop; bulk actions over a visual selection.
  • Background auto-naming (opt-in) — name an agent from its launch prompt, optionally via a one-shot LLM namer.

See ROADMAP.md for what's shipped and what's planned. Today pi and Claude Code are supported end-to-end; other CLIs launch but aren't yet persisted/resumed.

Requirements

  • Neovim 0.9+
  • At least one agent CLI on your PATH (pi, claude, …)

Installation

With lazy.nvim:

{
  "MateoGreil/agent-fleet.nvim",
  keys = {
    { "<leader>aa", "<cmd>Agent<cr>", desc = "Agent Fleet: launch agent" },
    { "<leader>al", "<cmd>Agents<cr>", desc = "Agent Fleet: list & switch" },
    { "<leader>ab", "<cmd>AgentsBoard<cr>", desc = "Agent Fleet: board" },
    { "<leader>ad", "<cmd>AgentDone<cr>", desc = "Agent Fleet: done" },
    { "<leader>ax", "<cmd>AgentArchive<cr>", desc = "Agent Fleet: archive" },
    { "<leader>as", "<cmd>AgentSend<cr>", desc = "Agent Fleet: send selection to agent" },
    { "<leader>as", ":AgentSend<cr>", mode = "x", desc = "Agent Fleet: send selection to agent" },
  },
  config = function()
    require("agent-fleet").setup({
      agents = {
        pi = {},
        claude = {},
      },
      -- optional: background auto-naming via LLM
      auto_name = {
        enabled = true,
        model = "anthropic/claude-haiku-4-5",
      },
    })
  end,
}

With packer.nvim:

use({
  "MateoGreil/agent-fleet.nvim",
  config = function()
    require("agent-fleet").setup({
      agents = {
        pi = {},
        claude = {},
      },
      -- optional: background auto-naming via LLM
      auto_name = {
        enabled = true,
        model = "anthropic/claude-haiku-4-5",
      },
    })

    local map = vim.keymap.set
    map("n", "<leader>aa", "<cmd>Agent<cr>", { desc = "Agent Fleet: launch agent" })
    map("n", "<leader>al", "<cmd>Agents<cr>", { desc = "Agent Fleet: list & switch" })
    map("n", "<leader>ab", "<cmd>AgentsBoard<cr>", { desc = "Agent Fleet: board" })
    map("n", "<leader>ad", "<cmd>AgentDone<cr>", { desc = "Agent Fleet: done" })
    map("n", "<leader>ax", "<cmd>AgentArchive<cr>", { desc = "Agent Fleet: archive" })
    map("n", "<leader>as", "<cmd>AgentSend<cr>", { desc = "Agent Fleet: send selection to agent" })
    map("x", "<leader>as", ":AgentSend<cr>", { desc = "Agent Fleet: send selection to agent" })
  end,
})

setup() is required, and you must declare at least one agent — there are no default agents. pi and claude are recognized keys that auto-fill their command and session settings, so pi = {} / claude = {} is enough; pass extra fields to override. See Configuration.

Usage

:Agent           " launch the default agent, prompting for an initial message (like the board's i)
:Agent fix auth  " launch the default agent with "fix auth" as the initial prompt
:AgentResume     " reopen a past agent of this directory (focus if live, else resume)
:Agents          " list & switch agents of this directory (focus if live, else resume)
:AgentsBoard     " open the live board buffer (sections, colors, per-row keymaps)
:AgentDone       " mark an agent done (✓)
:AgentArchive    " archive / unarchive an agent (hidden from :Agents by default)
:AgentRename foo " rename the current agent (or pick one) to "foo"
:AgentRename     " rename via a prompt (current agent, or pick one)
:AgentSend       " send the current line / visual selection to an agent as a file:line ref

:AgentSend doesn't send file content — it sends a path:line (or path:line1-line2 in visual mode) reference into the current or last-focused agent's terminal input, without moving focus or pressing Enter for you. That means you can fire it from a few different spots to stack up several references in the agent's input, then switch over yourself, add your question, and press Enter.

The visual-mode mapping must be :AgentSend<cr> (not <cmd>AgentSend<cr>): a <Cmd> mapping does not pass the visual selection's line range to the command, so it would only send the cursor line. The leading : lets Neovim insert the '<,'> range for the selection.

Inside an agent terminal: <C-\><C-n> to enter Normal mode, then move / scroll / yank with your usual nvim keys. i / a to type to the agent again.

The board (:AgentsBoard)

A dedicated, non-terminal buffer that opens in the current window and lists this directory's agents in lifecycle sections — running, idle, done, and (when toggled) archived — each row showing a live/dead marker, the derived state, the name, and a relative last-activity time. It re-renders on a timer while visible and reacts to agents exiting. Move with your normal nvim keys (j/k///gg); the per-row actions are:

Key Action
<CR> switch to the agent under the cursor (focus its terminal if live, else resume it via its CLI)
d mark done (✓)
x archive / unarchive
r rename (prompt)
s stop — kill the live terminal without marking it done (still resumable)
a launch a new agent
i type a prompt, then launch a new agent started with it
A toggle the archived section
R / gr refresh now

Switching, a and i hand the board's window to the agent (the board buffer is wiped; reopen with :AgentsBoard). Action keys are no-ops on section headers. d, x and s also work over a visual line selection (V): select a span of rows and the action applies to every agent in it at once. There is intentionally no q binding — leave the board with your usual buffer navigation.

Configuration

require("agent-fleet").setup({
  default_agent = "pi", -- which agent `:Agent` launches with no argument
  agents = {            -- registry of agents: key -> { cmd = "<command>" }
    pi = {},
    claude = {},
  },
  window = "enew",      -- where the agent terminal opens (see below)
  start_insert = true,  -- drop straight into terminal insert mode
  follow_output = true, -- keep unfocused agent terminals scrolled to the bottom
  board = {             -- the :AgentsBoard buffer
    refresh_ms = 2000,  -- how often the open board re-renders
  },
  auto_name = {         -- background auto-naming (off by default; see below)
    enabled = false,
    model = nil,        -- e.g. "openai/gpt-4o-mini"; required to do anything
  },
})
Option Default Description
default_agent (required when multiple agents declared) Agent launched by :Agent with no argument. Implicit when exactly one agent is declared; required when two or more are declared; with zero agents declared the plugin notifies an error and commands no-op.
agents (required — none by default) Registry of declared agents (key -> { cmd = … }). You must declare at least one; pi and claude are recognized keys that auto-fill their presets ({} suffices).
window "enew" Ex command that opens the agent window.
start_insert true Enter terminal insert mode after launching.
follow_output true Auto-scroll an agent's terminal to the bottom on new output even when its window is not focused.
board.refresh_ms 2000 How often (ms) the open :AgentsBoard re-renders.

Registering agents

The agents registry maps a key to a command. pi and Claude Code are fully supported end-to-end (persist, resume, board listing with live state). Other CLIs will launch in a terminal but not appear on the board or be resumable unless they provide a session backend.

agents = {
  pi = { cmd = "pi" },
  claude = { cmd = "claude" },
}

default_agent chooses which of these :Agent launches; the agent type is not a command argument (all of :Agent's arguments become the new agent's name).

cmd is split on spaces into an argv list and executed directly without a shell, so each token becomes a separate argument — no quoting, pipes, or VAR=val env prefixes. If you need shell features, point cmd at a wrapper script.

Declaring pi = {} or claude = {} pulls in that agent's built-in preset (command, session flags, and on-disk session location); any field you set overrides the preset. A key with no matching preset (e.g. aider = { cmd = "aider" }) launches and is tracked while live, but is only persisted/resumed if you also give it a session = { id_flag, name_flag, resume_flag } block.

window — where the agent opens

window is run as a plain Ex command right before the buffer becomes a terminal, so any window-opening command works. Common choices:

Value Result
"enew" current window (default)
"botright vnew" new vertical split on the right
"topleft vnew" new vertical split on the left
"topleft new" new horizontal split on top
"botright new" new horizontal split on the bottom
"tabnew" new tab

Power users can pass any Ex command, e.g. window = "botright 80vnew" for a fixed-width split.

auto_name — background auto-naming

When an agent (pi or Claude) is launched with an initial prompt but without a name (board i, or :Agent <prompt>), the plugin can rename it from that prompt: it runs a lightweight one-shot namer for that agent's backend (pi with tools/session/extensions off, or claude -p with --tools "") on the prompt text, sanitizes the reply to a short name, and applies it. There is no polling — the prompt we launched with is used directly. The agent stays machine-named, so a manual :AgentRename always takes precedence and is never overwritten.

Independently of this LLM namer, an agent launched with a prompt but no name already gets a sensible default: the first line of the prompt (char-aware truncated to 40 chars), instead of the numbered <kind>-<n> name. An agent launched without a prompt still falls back to <kind>-<n>.

The LLM namer is OFF by default and does nothing unless you both set auto_name.enabled = true and provide a model.

auto_name = {
  enabled = false,        -- master switch
  model = nil,            -- model the namer runs with (required, e.g. "openai/gpt-4o-mini")
  thinking = "off",       -- pi --thinking value for the namer
  namer_timeout_ms = 30000,  -- kill the namer subprocess after this long
  max_chars = 2000,          -- cap the prompt text sent to the namer
}
Option Default Description
enabled false Master switch for background auto-naming.
model nil Model the one-shot namer runs with. Required.
thinking "off" pi --thinking value for the namer.
namer_timeout_ms 30000 Kill the namer subprocess after this long.
max_chars 2000 Cap on the prompt text sent to the namer.

The namer is the only subprocess agent-fleet spawns itself; it runs via jobstart with an argv list (no shell), and it never passes --name or touches the session file.

Board highlight groups

The board defines these highlight groups, each linked to a standard group by default so it follows your colourscheme; override any with vim.api.nvim_set_hl(0, "<group>", { … }):

Group Default link Used for
AgentFleetWorking DiagnosticInfo a working agent's state
AgentFleetIdle Normal an idle agent's state
AgentFleetStopped Comment a stopped agent's state
AgentFleetError DiagnosticError an errored agent's state
AgentFleetNew DiagnosticHint a brand-new agent's state
AgentFleetUnknown NonText state older than the tail read
AgentFleetArchived Comment archived rows (dimmed)
AgentFleetHeader Title section headers and the empty-state title
AgentFleetTime Comment the relative last-activity column

About

Run a fleet of agent coding agents from Neovim

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors