A Neovim plugin for orchestrating multi-agent AI workflows. Claude acts as a coordinator — decomposing tasks into a dependency graph, routing subtasks to specialized agents, running them in parallel, and synthesizing results back into your editor.
- Neovim 0.9+
- Claude CLI (
claudebinary in PATH) — or anANTHROPIC_API_KEYfor HTTP fallback - Optional: Ollama, LM Studio, or any OpenAI-compatible endpoint for local models
- Optional (for pickers): mini.pick, telescope.nvim, or fzf-lua
Add to your vimpack.lua (or wherever you call vim.pack.add):
vim.pack.add({
"https://github.com/curtisault/agentflow.nvim",
})Then call setup in your config (e.g. lua/curtis/init.lua):
require("agentflow").setup(){
"curtisault/agentflow.nvim",
config = function()
require("agentflow").setup()
end,
}:AgentFlow refactor the selected function to use early returns
Or via keymap: <leader>ap → type your prompt → <Enter>
require("agentflow").setup({
backend = {
primary = "cli", -- "cli" | "http"
cli_path = "claude", -- path to claude binary
api_key_env = "ANTHROPIC_API_KEY",
},
orchestrator = {
model = "claude-sonnet-4-20250514",
max_turns = 20,
},
agents = {
{ name = "sonnet", model = "claude-sonnet-4-20250514", backend = "cli", role = "subagent" },
-- Add more agents for routing, e.g. a local model for simple tasks:
-- { name = "local", model = "qwen2.5-coder:7b", backend = "ollama" },
},
routing = {
rules = {
-- Route analysis tasks to a cheaper/faster agent:
-- { match = { task_type = "analysis" }, agent = "local", priority = 1 },
{ match = { fallback = true }, agent = "sonnet", priority = 99 },
},
},
context = {
max_tokens_per_agent = 12000,
include_buffers = true,
include_treesitter = true,
include_git_diff = "staged", -- "staged" | "all" | false
include_lsp_symbols = true,
include_file_tree = true,
},
ui = {
approve_mode = "manual", -- "manual" | "auto" | "auto-safe"
picker = nil, -- nil = auto-detect (mini.pick > telescope > fzf-lua > vim.ui.select)
},
concurrency = {
max_parallel_agents = 4,
max_depth = 5,
max_total_agents = 200,
max_children_per_agent = 20,
timeout_ms = 30000,
},
keymaps = { enabled = true },
log = { level = "info", file = nil },
})Place a .agentflow.json file in your project root to override any settings for that project:
{
"concurrency": { "max_parallel_agents": 2 },
"context": { "include_git_diff": "all" }
}You can also add project-specific agents here. They are merged with (not replace) your global agent list:
{
"agents": [
{ "name": "rust-expert", "model": "claude-opus-4-6", "backend": "cli", "role": "subagent" }
]
}For a cleaner setup, place individual agent definition files under .agentflow/agents/ in your project root. AgentFlow automatically discovers and registers them at startup — no changes to your Neovim config needed.
my-project/
├── .agentflow/
│ └── agents/
│ ├── rust-expert.json
│ └── test-runner.json
└── ...
Each file defines one agent:
{
"name": "rust-expert",
"model": "claude-opus-4-6",
"backend": "cli",
"role": "subagent",
"max_tokens": 8192
}Supported fields mirror the agents entries in setup(). Project-local files are registered last and win on name conflict, so they can override globally configured agents for a specific project.
| Command | Description |
|---|---|
:AgentFlow [prompt] |
Open hub (no args) or start a workflow |
:AgentChat |
Open chat pane |
:AgentTree |
Open agent tree view |
:AgentRoster |
Open agent roster |
:AgentDash |
Open dashboard view |
:AgentGrid |
Open grid (heat-map) view |
:AgentReview |
Open review panel |
:AgentLog [name] |
Show log buffer (optionally filter by agent name) |
:AgentKill {name} |
Terminate an agent and its subtree |
:AgentAdd {name} {model} |
Register a new agent at runtime |
:AgentPick |
Picker to select and inspect an agent |
:AgentSessions |
Browse and resume saved sessions |
:AgentSave |
Save current session to disk |
| Key | Action |
|---|---|
<leader>af |
Open hub |
<leader>ap |
Prompt → start workflow |
<leader>as |
Open agent tree |
<leader>ac |
Open config panel |
<leader>ar |
Open review panel |
Disable with keymaps = { enabled = false } in setup.
| Key | Action |
|---|---|
t |
Tree view |
d |
Dashboard |
g |
Grid view |
r |
Roster |
s |
Sessions browser |
l |
Log buffer |
? |
Help |
p |
Prompt → start workflow |
q / <Esc> |
Close |
| Key | Action |
|---|---|
<CR> |
Accept artifact (apply diff / write file) |
x |
Reject |
e |
Edit artifact in buffer |
r |
Retry with agent |
]r |
Next artifact |
[r |
Previous artifact |
1 |
Diff tab |
2 |
Raw output tab |
3 |
Agent log tab |
4 |
Context tab |
<Tab> |
Cycle tabs |
q / <Esc> |
Close |
| Key | Action |
|---|---|
zo |
Expand node |
zc |
Collapse node |
zR |
Expand all |
zM |
Collapse all |
<CR> |
Open dashboard for agent |
f |
Filter (picker) |
d |
Set depth limit |
<C-k> |
Kill agent + subtree |
<Tab> |
Go to hub |
q / <Esc> |
Close |
| Key | Action |
|---|---|
<CR> |
Drill into child agent |
<BS> |
Go up to parent |
f |
Pick agent (jump) |
<Tab> |
Go to hub |
q / <Esc> |
Close |
| Key | Action |
|---|---|
h/j/k/l or arrows |
Navigate cells |
<CR> |
Open dashboard for selected agent |
/ |
Search / filter agents |
<Tab> |
Go to hub |
q / <Esc> |
Close |
| Key | Action |
|---|---|
<CR> |
Show agent details |
d |
Deregister agent |
q / <Esc> |
Close |
| Key | Mode | Action |
|---|---|---|
<CR> |
normal / insert | Send message |
<C-c> |
normal / insert | Close |
<C-l> |
normal / insert | Clear chat |
q / <Esc> |
normal | Close |
| Backend | Config value | Notes |
|---|---|---|
| Claude CLI | "cli" |
Requires claude binary. Falls back to HTTP if unavailable. |
| Anthropic HTTP | "http" |
Requires ANTHROPIC_API_KEY (or api_key_env). |
| Ollama | "ollama" |
Local. Default endpoint: http://localhost:11434. |
| LM Studio | "lmstudio" |
Local. Default endpoint: http://localhost:1234/v1. |
| OpenAI-compat | "openai_compat" |
Any OpenAI-compatible API. Requires endpoint. |
require("agentflow.extensions").register_backend("my-backend", function(agent_config)
return {
complete = function(self, messages, opts)
-- return result_string, nil (or nil, error_string)
end,
}
end)local ext = require("agentflow.extensions")
-- Custom context source (appended to every agent's context)
ext.register_context_provider("jira", function(task, config)
return "Current Jira ticket: " .. get_current_ticket()
end)
-- Custom result parser (tried before built-in parser)
ext.register_result_parser("my-format", function(content, task)
-- return artifacts[] or nil to fall through
end)
-- Inject a routing rule at runtime
ext.add_routing_rule({ match = { task_type = "test" }, agent = "local", priority = 1 })
-- Subscribe to events
ext.on("agent:completed", function(data)
print("Agent done: " .. data.agent.name)
end)AgentFlow fires User autocommands for all major events:
autocmd User AgentFlowAgentCompleted lua print(vim.g.agentflow_event_data)| Autocommand | Fired when |
|---|---|
AgentFlowPlanCreated |
Orchestrator produces a task plan |
AgentFlowAgentStarted |
An agent begins execution |
AgentFlowAgentCompleted |
An agent finishes successfully |
AgentFlowAgentFailed |
An agent errors out |
AgentFlowAgentRetrying |
An agent is retrying after failure |
AgentFlowSynthesized |
Orchestrator synthesizes final result |
AgentFlowReviewAccepted |
User accepts an artifact |
AgentFlowReviewRejected |
User rejects an artifact |
AgentFlowReviewRetry |
User requests a retry from review |
The orchestrator decomposes each request into tasks and routes them to registered agents. Two mechanisms control which agent handles which task.
Rules are evaluated in priority order (lower number = higher priority). The first matching rule wins.
routing = {
rules = {
{ match = { file_pattern = "%.lua$" }, agent = "lua-expert", priority = 1 },
{ match = { task_type = "analysis" }, agent = "reviewer", priority = 2 },
{ match = { task_type = "planning" }, agent = "planner", priority = 3 },
{ match = { fallback = true }, agent = "sonnet", priority = 99 },
},
}Match conditions (all optional; omitted conditions always match):
| Condition | Type | Matches when |
|---|---|---|
fallback |
boolean | Always — use as catch-all |
task_type |
string | Task type equals value ("analysis", "implementation", "test", etc.) |
filetype |
string | Current buffer filetype equals value |
file_pattern |
string | Any file in the task's required files matches the Lua pattern |
agent_hint |
boolean | Orchestrator set an explicit agent hint on the task |
Task types are assigned by the orchestrator's planner when it decomposes the request.
The orchestrator can assign a specific agent to a task directly via agent_hint in the plan JSON. This takes precedence over all routing rules. To enable this, the orchestrator needs to know what agents are available — inject them at runtime with add_routing_rule or by ensuring the agent names are mentioned in your orchestrator system prompt override.
You can also inject a rule at runtime without restarting:
require("agentflow.extensions").add_routing_rule({
match = { task_type = "test" },
agent = "reviewer",
priority = 1,
})The orchestrator is surfaced as a root agent node in the tree and dashboard. Its internal phases map to agent states so you can see exactly what is happening during a run:
| State | Phase |
|---|---|
running |
Planning — decomposing the user request |
assigned |
Plan ready — tasks queued for delegation |
running |
Delegating — task agents executing in parallel |
running |
Synthesizing — combining results into a final response |
completed |
Workflow finished |
failed |
An unrecoverable error occurred |
Open :AgentTree or :AgentDash at any point during a workflow to see the orchestrator and its child task agents live.
Add the current agent status to your statusline:
-- lualine example
require("lualine").setup({
sections = {
lualine_x = { _G.AgentFlowStatus },
},
})AgentFlowStatus() returns a spinner + elapsed time while agents are running, empty string otherwise.
Workflows are auto-saved to .agentflow/sessions/ in your project root after synthesis. Each session stores:
conversation.json— full orchestrator message historyplan.json— task plan and execution statemeta.json— model, cost, timestampslogs/— per-agent log files
Browse and resume sessions with :AgentSessions or s in the hub.
:checkhealth agentflow
Checks: Neovim version, Claude CLI, API key, curl, Ollama, LM Studio, treesitter parsers, picker plugins, git, and config validation.
Requires plenary.nvim.
# All tests
make -C lua/agentflow test
# Single file
make -C lua/agentflow test-file FILE=tests/test_planner.lua