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).
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
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.
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_attachon_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 ....
<Plug> 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
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?
---
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 <path|id>`, `--session-dir`, `--no-session`, `-e <ext>`, `--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 ...`.
- `<Plug>` 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?