--- id: NVIM-RESEARCH-KZYYEFY_ type: research title: Pi and Neovim capabilities for pivi --- # Pi and Neovim capabilities for pivi ## Sources Installed documentation only; no remembered API behavior. | Source | Version | Location | | --- | --- | --- | | Pi docs (`rpc.md`, `extensions.md`, `usage.md`, `sessions.md`, `json.md`) | 0.85.1 | `$PI_CONTEXT_DOCS_PATH` | | Neovim runtime docs (`api.txt`, `lua.txt`, `undo.txt`, `editing.txt`, `options.txt`) | 0.12.5 | `$VIMRUNTIME/doc` | ## Pi facts ### Transport - `pi --mode rpc` speaks JSON commands on stdin, responses and events on stdout. - Strict JSONL, LF only; clients strip a trailing `\r`; Unicode separators must not split records. - Optional `id` on commands correlates responses; most events carry no `id`. - Startup flags relevant to a client: `--provider`, `--model`, `--name`, `--session `, `--session-dir`, `--no-session`, `-e `, `--tools`, `--append-system-prompt`. ### Commands that map to pivi surfaces | pivi need | RPC command | Notes | | --- | --- | --- | | Submit prompt | `prompt` | During streaming requires `streamingBehavior: steer \| followUp`, else error | | Queue while running | `steer`, `follow_up`, `clear_queue` | Extension commands rejected here | | Stop | `abort`, `abort_retry`, `abort_bash` | `abort` waits for idle | | Status | `get_state`, `get_session_stats` | Model, thinking, streaming, queue, context usage | | Transcript | `get_messages`, `get_entries`, `get_tree` | `get_entries since` is a durable cursor; `leafId` detects branch moves | | Sessions | `new_session`, `switch_session`, `fork`, `clone`, `set_session_name` | Switch/fork cancellable by extensions | | Slash commands | `get_commands` | Returns extension, prompt-template, and skill commands; invoked as `/name` through `prompt` | | Model/thinking | `set_model`, `cycle_model`, `get_available_models`, `set_thinking_level`, `get_available_thinking_levels` | | | Compaction | `compact`, `set_auto_compaction` | | Built-in TUI commands such as `/settings` and `/hotkeys` are absent from `get_commands` and do not execute over RPC. ### Concurrency semantics ```mermaid stateDiagram-v2 [*] --> Idle Idle --> Streaming: prompt Streaming --> Streaming: steer / follow_up queued Streaming --> Streaming: extension command executes immediately Streaming --> Idle: agent_settled Streaming --> Idle: abort ``` - A plain `prompt` while streaming is rejected without `streamingBehavior`. - An extension command (`/name`) sent through `prompt` executes immediately even during streaming and manages its own LLM interaction. - `agent_end` marks one low-level run; `agent_settled` marks full settlement after retry, compaction, and queued continuations. - Therefore a truly ephemeral question that never enters session history needs either an extension command or a separate process (`pi -p` / `pi --mode json` / a second `--mode rpc --no-session` child). ### Events Streamed as JSONL: `agent_start`, `agent_end`, `agent_settled`, `turn_start`, `turn_end`, `message_start`, `message_update`, `message_end`, `tool_execution_start|update|end`, `bash_execution_update`, `queue_update`, `compaction_start|end`, `auto_retry_start|end`, `summarization_retry_*`, `extension_error`. - `message_update` carries only deltas; clients assemble partial state by `contentIndex`; `message_end.message` is authoritative. - Tool-call streaming exposes `toolcall_start` (`id`, `toolName`), `toolcall_delta` (argument chunks), `toolcall_end` (complete call). This is the only source for progressive edit intent, and argument chunks are partial JSON text. - `tool_execution_update.partialResult` is cumulative, not a delta. ### Extension UI over RPC | Method | RPC behavior | | --- | --- | | `select`, `confirm`, `input`, `editor` | Request/response sub-protocol with matching `id`; optional `timeout` auto-resolves agent-side | | `notify`, `setStatus`, `setWidget`, `setTitle`, `set_editor_text` | Fire-and-forget requests | | `custom` | Returns `undefined` | | `setWorkingMessage`, `setWorkingIndicator`, `setFooter`, `setHeader`, `setEditorComponent`, `setToolsExpanded` | No-ops | | `getEditorText`, `getToolsExpanded`, theme getters/setters | Degraded or empty | `ctx.mode === "rpc"` and `ctx.hasUI === true`; TUI-only capability must be guarded on `ctx.mode === "tui"`. ### Extension hooks a companion extension can use - `pi.registerTool` at load or at runtime; new tools are callable without `/reload`. - Built-in tools `read`, `bash`, `powershell`, `edit`, `write`, `grep`, `find`, `ls` can be overridden by registering the same name; result and `details` shapes must match exactly; omitted renderers fall back to built-ins; `promptSnippet`/`promptGuidelines` are not inherited. - Built-in tools also accept pluggable operations (`ReadOperations`, `WriteOperations`, `EditOperations`, `BashOperations`, …) via `createReadTool` and siblings, plus a `spawnHook` for shell tools. This delegates I/O without reimplementing tool semantics. - `tool_call` can mutate arguments or block; `tool_result` can post-process. - `input` event sees raw text with `source` of `interactive | rpc | extension` and can `continue`, `transform`, or `handled`. - `before_agent_start` exposes and can extend `systemPrompt` and structured `systemPromptOptions`. - `pi.registerCommand` supports `getArgumentCompletions`; `pi.getCommands` mirrors `get_commands`. - Tool output must be truncated; built-in limits are 50KB and 2000 lines. ## Neovim facts ### Transport - `vim.system(cmd, { stdin = true, stdout = fun(err, data), stderr = fun(err, data) })` gives streaming callbacks plus `SystemObj:write(data)` and `obj:write(nil)` to close stdin; `obj:kill()` stops the child. - Output callbacks run in fast contexts; API calls must be deferred through `vim.schedule` (`api-fast`, `vim.in_fast_event()`). - `jobstart`/`nvim_chan_send` remain the alternative channel API. ### Buffers and edits | Need | Mechanism | Caveat | | --- | --- | --- | | Precise edit | `nvim_buf_set_text`, `nvim_buf_set_lines` | Each call opens a new undo block | | One undoable change | `:undojoin` before the next change | `E790` after undo/redo; must not be used blindly | | Change detection | `nvim_buf_attach` `on_lines`, `b:changedtick` | Callbacks are fast-context | | Diffing model output against buffer | `vim.text.diff` with `result_type = 'indices'` | Available in 0.12 | | Disk coherence after buffer edits | buffer write plus `:checktime`; `'autoread'` defaults on | Unwritten buffers stay invisible to filesystem tools | ### Presentation - Extmarks provide `virt_text` with `virt_text_pos = "inline"` for ghost text, `virt_lines` (with `virt_lines_above`, `virt_lines_overflow`), highlights, and signs. - `nvim_set_decoration_provider` with `ephemeral` marks renders per redraw without persistent state. - `vim.diagnostic` namespaces keep agent findings separate from LSP and compiler diagnostics. - Quickfix and location lists use `setqflist`/`setloclist` and normal navigation. ### Integration surface - `nvim_exec_autocmds` emits `User` events with a `data` payload (`event-data`), which fits publishing cached pivi state. - `nvim_create_user_command` supplies subcommands, `nargs`, and custom completion for `:Pi help` and `:Pi command ...`. - `` mappings expose behavior without claiming global keys. - `vim.ui.select` and `vim.ui.input` can back RPC dialog methods. ## Spec claims versus evidence | Spec claim | Support | Risk | | --- | --- | --- | | Non-terminal JSONL subprocess channel | `pi --mode rpc` plus `vim.system` stdin/stdout | Low | | Semantic status (queue, run, tool, retry, compaction) | `get_state`, `get_session_stats`, event stream | Low | | Slash commands via `:Pi command ...` | `get_commands` plus `prompt` with `/name` | Low; TUI-only commands unavailable | | `:Pi help` runnable during an active run | Extension command executes during streaming, or a separate ephemeral process | Medium; not ephemeral if routed through the working session | | Loaded buffers authoritative for reads and edits | Built-in tool override or injected `ReadOperations`/`EditOperations`/`WriteOperations` | Medium; result and `details` shapes must match exactly | | Progressive ghost text from streaming tool arguments | `toolcall_delta` plus inline `virt_text` | Medium; requires tolerant partial-JSON parsing | | One coherent undoable change per edit | `nvim_buf_set_text` plus `:undojoin` | Medium; `E790` and multi-hunk edits | | Edits visible to later shell work without watchers | Explicit buffer write plus `:checktime` | Medium; unwritten buffers diverge from disk | | Extension dialogs mapped to native surfaces | `extension_ui_request`/`extension_ui_response` | Low | | Multiple sessions, resume, switch | `--session`, `switch_session`, `new_session`, `fork`, `clone` | Low | ## Open questions - Does `:Pi help` use a second short-lived Pi process, or a companion extension command inside the working session that suppresses history? - Can two Pi processes safely target one session file, or must ephemeral help always run with `--no-session`? - Is buffer authority better implemented as operations injection into built-in tools than as full tool overrides, given the exact `details` shape requirement? - How should partial `toolcall_delta` arguments be parsed safely enough to show ghost text without ever mutating a buffer? - When must pivi write a modified buffer to disk so that agent shell commands observe agent edits?