Neovim client for Rummy, an AI agent service that turns your editor into a multi-model code synthesis environment. All inference, orchestration, and state management lives server-side — the plugin is a thin, vim-native interface over JSON-RPC 2.0.
Note
This plugin requires a running Rummy server. See the server repo for setup instructions.
- Three interaction modes — Ask (
?), Act (:), Run (!) with visual selection - Run tabs — each run gets a tab with an XML waterfall log and chat input
- Native vimdiff resolution — proposed edits in a split with accept/reject/accept-edits
- Multi-run sessions — multiple concurrent runs, buffers track their associated run
- Streaming shell output — sh/env commands stream within the run tab
- Model picker creates runs — select a model and the run exists immediately, no limbo
- Yolo mode — auto-accept all proposed changes and commands
- Real-time statusline — shows the current buffer's run: model, turn, tokens, cost, context
- Server-managed personas and skills — toggle from picker, server loads the files
- File visibility controls — active, read-only, ignored with sign column indicators
- Neovim >= 0.10
- plenary.nvim
- A running Rummy server (default
localhost:3044)
-- lazy.nvim
{
"possumtech/rummy.nvim",
dependencies = { "nvim-lua/plenary.nvim" },
config = function()
require("rummy").setup()
require("rummy").apply_default_keymaps()
end,
}With custom port
{
"possumtech/rummy.nvim",
dependencies = { "nvim-lua/plenary.nvim" },
config = function()
require("rummy").setup({ port = 3045 })
require("rummy").apply_default_keymaps()
end,
}Tip
All keybindings include descriptive labels.
which-key.nvim surfaces the
full <leader>a tree automatically.
- Start the Rummy server
- Open Neovim
:AI? What does this function do? " Ask a question
:AI: Fix the null check in parse() " Request an edit
:AI! make test " Run and analyze output
<leader>ar " Pick/switch run
On your first prompt, you'll pick a model. This creates a run, opens a run tab, and associates the buffer with that run.
The first character determines the mode:
| Prefix | Mode | Description |
|---|---|---|
? |
Ask | Inquiry — no code changes |
: |
Act | Surgical edits via unified diff |
! |
Run | Execute shell command, send output to model |
... |
BTW | Inject a message into this buffer's run |
Repeat the prefix to modify behavior:
| Modifier | Example | Effect |
|---|---|---|
| Double | :AI?? |
New run (ignores this buffer's current association) |
| Triple | :AI??? |
No repo context (bare prompt) |
| Quadruple | :AI???? |
Fork (branch from this buffer's run) |
Slash subcommands route to plugin commands: :AI/stop, :AI/clear, :AI/model, etc.
Strict buffer-per-run. Every AI action originates from a buffer, and that buffer's run association is the only source of truth — there is no global "current run" that commands can fall back to.
- Each run is a tab with a waterfall log and a chat input buffer.
- Source buffers carry their association in
vim.b.rummy_run. - First prompt from a source buffer with no association opens a run picker (existing runs + "New Run"). Your choice is attached to that buffer going forward.
- Subsequent prompts from that buffer automatically target its run.
<leader>aropens the picker to switch or create.- Actions invoked from a scratch / no-name buffer refuse with "No buffer — open a file first" rather than guessing.
- Multiple run tabs coexist with independent state and telemetry; notifications update per-alias state without touching any buffer's association.
When the model proposes edits, a split opens within the run tab:
| Key | Action |
|---|---|
do / dp |
Obtain / put individual hunks |
]c / [c |
Jump between changes |
<leader>ay |
Accept the proposal |
<leader>ae |
Accept your manual edits (left buffer) |
<leader>an |
Reject the change |
<leader>a] / a[ |
Navigate queued diffs |
Toggle with <leader>aY. All proposed diffs and commands auto-accept without prompting.
Select text, then use the same keybindings. The selection is captured as a <selection> block with file path and position metadata.
All keymaps live under <leader>a. Polite — only applied via apply_default_keymaps(), never overwrites existing mappings.
| Key | Action | Key | Action | |
|---|---|---|---|---|
aa |
Toggle run tab | af |
Fork run | |
a? |
Ask | aN |
New run | |
a: |
Act | ad |
No context | |
a! |
Run | ab |
BTW inject | |
ax |
Stop run | aX |
Abort & clear | |
am |
Select model | ap |
Select persona | |
ar |
Pick/switch run | aR |
Rename run | |
as |
Toggle skills | aY |
Toggle yolo | |
ay |
Accept diff | an |
Reject diff | |
ae |
Accept edits | a] a[ |
Next/prev diff | |
aA |
Set active | aO |
Set read-only | |
aI |
Set ignored | aD |
Drop override | |
aF |
List files | aS |
File status | |
at |
Set/run test | aT |
Auto-send test | |
a↑ a↓ |
Temperature | a→ a← |
Context window |
Visual mode: a?, a:, a! capture the selection.
require("rummy").setup({
host = "127.0.0.1", -- Server host
port = 3044, -- Server port
})All options are optional. Logs are written to vim.fn.stdpath("log"). Personas and skills are managed server-side via :RummyPersona and :RummySkill.
-- lualine
require("lualine").setup({
sections = { lualine_x = { require("rummy").statusline } },
})
-- native
vim.o.statusline = "%f %m %= %{%v:lua.require('rummy.statusline').native()%} "Shows the current buffer's run. Empty until a buffer has an associated run. Segments: model [turn]icon tokens cost context%
make test # Unit tests (headless)
make e2e # Live tests against running server
make scenarios # Full scenario suite
lua/rummy/
├── state.lua # Per-run state buckets
├── transport.lua # JSON-RPC send/receive, background job
├── dispatch.lua # Response/notification routing
├── client.lua # Thin facade, autocmds
├── commands.lua # User commands, AI metacommand
├── resolve.lua # Proposed item resolution, prompt queue
├── run_tab.lua # Tab lifecycle, waterfall buffer
├── input.lua # Chat input buffer
├── stream.lua # Streaming sh/env output
├── diff.lua # Vimdiff resolution (udiff)
├── patch.lua # Unified diff parser/applicator
├── statusline.lua # Per-run statusline provider
├── hud.lua # Signs, virtual text, toasts
├── selection.lua # Visual selection capture
├── keymaps.lua # Default keybindings
├── config.lua # Configuration
├── health.lua # :checkhealth
└── background_client.lua # Headless WebSocket bridge
MIT