Skip to content

feat: add persistent RPC subprocess mode#18

Closed
h0rv wants to merge 1 commit into
pablopunk:mainfrom
h0rv:persistent-rpc
Closed

feat: add persistent RPC subprocess mode#18
h0rv wants to merge 1 commit into
pablopunk:mainfrom
h0rv:persistent-rpc

Conversation

@h0rv

@h0rv h0rv commented May 28, 2026

Copy link
Copy Markdown

Add optional long-lived pi --mode rpc subprocess that replaces the per-request process spawn when rpc.persistent = true.

=== Config

require("pi").setup({
  rpc = {
    persistent = true,  -- default: false
    start = "lazy",     -- "lazy" | "setup"
  },
})

=== New commands

  • PiWarm — start the persistent process
  • PiStop — stop it
  • PiRestart — restart it
  • PiStatus — show whether it's running

=== Architecture

  • lua/pi/rpc.lua: owns the persistent process handle, exposes start/stop/restart/is_running/send/prompt/abort. Sends new_session before each prompt.
  • lua/pi/process.lua: shared primitives (feed_lines, spawn) used by both runner.lua and rpc.lua.
  • Cancellation sends RPC abort instead of SIGTERM; process stays alive.
  • Process cleaned up on VimLeavePre.

=== Compatibility

  • Default behavior unchanged (rpc.persistent = false)
  • Existing commands preserved
  • Refactored runner.lua uses process.lua helpers but no behavioral change

Summary by CodeRabbit

  • New Features

    • Added persistent RPC mode to run a long-lived background process for prompt handling.
    • New user commands to manage the RPC lifecycle: :PiWarm, :PiStop, :PiRestart, :PiStatus.
    • Configuration options to enable/disable persistent RPC and choose startup behavior.
  • Documentation

    • Updated README with persistent RPC configuration details, startup modes, and command reference.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown

Walkthrough

This PR adds persistent RPC process support to pi.nvim, allowing a long-lived subprocess to remain active between editor prompts. The implementation introduces a new configuration schema for rpc.persistent and rpc.start modes, creates a process abstraction layer and RPC controller module, refactors the runner to use the new abstraction, updates the main module to support dual-mode execution (persistent RPC or inline runner), and exposes lifecycle commands for users to warm up, stop, restart, or query the RPC process.

Sequence Diagram(s)

sequenceDiagram
  participant User as NeovimUser
  participant Main as pi.init (M.run)
  participant RPC as pi.rpc
  participant Child as "pi --mode rpc"
  participant Runner as pi.runner

  User->>Main: invoke run (prompt)
  Main->>RPC: rpc.is_running() / rpc.start(cmd) (if persistent)
  Main->>RPC: rpc.prompt(payload, handlers)
  RPC->>Child: write payload (stdin)
  Child->>RPC: stdout lines (events)
  RPC->>Main: handlers.on_event / handlers.on_error
  Note right of Main: or, when non-persistent:
  Main->>Runner: runner.start(session, payload)
  Runner->>Child: spawn process (per-session)
  Child->>Runner: stdout lines -> on_event
Loading

Possibly related PRs

  • pablopunk/pi.nvim#14: Both PRs modify lua/pi/init.lua by refactoring/expanding the public M.run session-start logic; this PR adds persistent RPC routing logic while the referenced PR exposes M.run as a downstream API.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add persistent RPC subprocess mode' clearly and accurately summarizes the main change: adding a persistent RPC subprocess feature. It is concise, specific, and directly reflects the primary objective of the pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lua/pi/rpc.lua`:
- Around line 84-87: In M.stop(), avoid touching the internal
process._state.stdin; close stdin using the public API by calling
process:write(nil) (or the equivalent public close/write-nil on the process
handle) instead of accessing process._state.stdin. In M.prompt(), add a
reentrancy guard that checks is_busy at the start and returns (or queues) if
true before sending the {"type":"new_session"} message and before swapping
current_handlers so a concurrent call cannot orphan handlers; ensure M.abort()
still observes is_busy and can cancel the active session. Reference M.stop,
M.prompt, M.abort and the current_handlers/is_busy symbols when making the
changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ab303433-244f-437d-8350-a49a4460c8ab

📥 Commits

Reviewing files that changed from the base of the PR and between 4ba6db0 and 5f9a7ab.

📒 Files selected for processing (7)
  • README.md
  • lua/pi/config.lua
  • lua/pi/init.lua
  • lua/pi/process.lua
  • lua/pi/rpc.lua
  • lua/pi/runner.lua
  • plugin/pi.nvim.lua

Comment thread lua/pi/rpc.lua Outdated
@h0rv h0rv force-pushed the persistent-rpc branch 2 times, most recently from 7d5335f to 6e5940d Compare May 28, 2026 21:34
Add optional long-lived pi --mode rpc subprocess that replaces the per-request process spawn when rpc.persistent = true.

New lua/pi/rpc.lua owns the persistent process handle and exposes start/stop/restart/is_running/send/prompt/abort. Sends new_session before each prompt to reset in-memory session state.

New lua/pi/process.lua extracts shared primitives (feed_lines, spawn) used by both runner.lua and rpc.lua, removing duplicated newline-splitting and spawn boilerplate.

New config: rpc.persistent (default false), rpc.start (default lazy).
New commands: PiWarm, PiStop, PiRestart, PiStatus.
Persistent mode cancellation sends RPC abort instead of SIGTERM.
Process cleaned up on VimLeavePre.
@h0rv h0rv force-pushed the persistent-rpc branch from 6e5940d to 513c924 Compare May 28, 2026 22:50

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lua/pi/rpc.lua`:
- Around line 15-30: feed_stdout is currently treating undecodable stdout as an
error by calling current_handlers.on_error, which forces persistent sessions to
finish; change feed_stdout so undecodable lines do NOT invoke
current_handlers.on_error directly — instead, if current_handlers provides a new
non-fatal hook (e.g., on_stdout_unparseable or on_raw_stdout) call that with the
raw line, otherwise silently ignore the line; update feed_stdout (and any
callers of runner.decode_event) to prefer current_handlers.on_event for decoded
events and only mark is_busy/handle done/error for normalized events, leaving
session-finishing logic (finish_session) untouched.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9a148a9a-a27b-4294-b188-a1f26c022379

📥 Commits

Reviewing files that changed from the base of the PR and between 5f9a7ab and 513c924.

📒 Files selected for processing (7)
  • README.md
  • lua/pi/config.lua
  • lua/pi/init.lua
  • lua/pi/process.lua
  • lua/pi/rpc.lua
  • lua/pi/runner.lua
  • plugin/pi.nvim.lua
✅ Files skipped from review due to trivial changes (1)
  • README.md
🚧 Files skipped from review as they are similar to previous changes (5)
  • plugin/pi.nvim.lua
  • lua/pi/config.lua
  • lua/pi/process.lua
  • lua/pi/runner.lua
  • lua/pi/init.lua

Comment thread lua/pi/rpc.lua
Comment on lines +15 to +30
local function feed_stdout(chunk)
stdout_tail = proc.feed_lines(stdout_tail, chunk, function(line)
local event = runner.decode_event(line)
if event then
local normalized = runner.normalize(event)
if normalized and current_handlers and current_handlers.on_event then
current_handlers.on_event(normalized)
if normalized.type == "done" or normalized.type == "error" then
is_busy = false
end
end
elseif current_handlers and current_handlers.on_error then
current_handlers.on_error("unparseable RPC event: " .. line)
end
end)
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect runner.lua: exports of decode_event/normalize and how it treats undecodable stdout lines.
fd 'runner.lua' --exec sh -c 'echo "== {} =="; cat -n "{}"'
echo "== decode_event / normalize usage =="
rg -nP --type=lua -C3 '\b(decode_event|normalize)\b'

Repository: pablopunk/pi.nvim

Length of output: 6791


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and inspect persistent call path wiring in init.lua
fd 'init.lua' --exec sh -c 'echo "== {} =="; cat -n "{}"'

# Inspect rpc.lua around M.start/M.prompt/on_error/finish_session interactions
fd 'rpc.lua' --exec sh -c 'echo "== {} =="; rg -n --context=3 -S "finish_session|on_error|current_handlers|prompt|abort|new_session" "{}"; echo "---"; sed -n "1,260p" "{}"'

Repository: pablopunk/pi.nvim

Length of output: 27027


Handle undecodable RPC stdout without failing the session

  • Persistent mode (lua/pi/rpc.lua feed_stdout) treats any stdout line that runner.decode_event can’t parse as on_error, and lua/pi/init.lua’s on_error ends the session with finish_session(..., "error", ...).
  • Non-persistent mode (lua/pi/runner.lua) ignores undecodable stdout lines because the stdout path passes on_error=nil to feed_stream.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lua/pi/rpc.lua` around lines 15 - 30, feed_stdout is currently treating
undecodable stdout as an error by calling current_handlers.on_error, which
forces persistent sessions to finish; change feed_stdout so undecodable lines do
NOT invoke current_handlers.on_error directly — instead, if current_handlers
provides a new non-fatal hook (e.g., on_stdout_unparseable or on_raw_stdout)
call that with the raw line, otherwise silently ignore the line; update
feed_stdout (and any callers of runner.decode_event) to prefer
current_handlers.on_event for decoded events and only mark is_busy/handle
done/error for normalized events, leaving session-finishing logic
(finish_session) untouched.

@h0rv h0rv closed this May 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant