pi-agent-rb
Ruby client for the pi coding agent.
Spawns pi --mode rpc and speaks its JSONL protocol from Ruby. Designed for
building interactive agent UIs (web, TUI) on top of pi.
Not officially maintained by the pi project.
Requirements
- Ruby 3.3+
pionPATH(install vianpm i -g @earendil-works/pi-coding-agent)- This gem is pinned against pi
0.76.0; other versions may work but are not verified.
Installation
Install the gem:
gem install pi-agent-rb
Or add it to your Gemfile:
gem "pi-agent-rb"
then run bundle install.
Quick start
require "pi_agent"
PiAgent.session do |session|
session.prompt("Write a haiku about Ruby") do |event|
print event.delta if event.type == :message_update
end
end
A pi RPC process hosts one session, so there is no create/select step —
PiAgent.session spawns pi --mode rpc and the session is that process.
prompt yields each Event until the agent
finishes (agent_end). Without a block it returns an Enumerator:
PiAgent.session do |session|
events = session.prompt("List three primes")
text = events.filter_map(&:delta).join
puts text
end
For a single-shot call, run submits a prompt, drains the stream, and
returns the final assistant text — pi's print mode:
PiAgent.session do |session|
puts session.run("What's 2 + 2?") # => "4"
end
Other session methods:
- Prompting:
steer,follow_up,abort - Model:
set_model,cycle_model,available_models,set_thinking - State:
get_state,messages,last_assistant_text,session_stats - Context:
compact - Sessions:
new_session,switch_session,fork,clone_session,set_session_name
set_model accepts either set_model("anthropic/claude-sonnet-4-5") or
set_model("anthropic", "claude-sonnet-4-5").
Images
prompt, steer, and follow_up accept an images: array. Entries
may be PiAgent::Image objects, file path strings, or raw
ImageContent hashes, mixed freely:
PiAgent.session do |session|
session.prompt("What's in these?", images: [
"screenshot.png", # path
PiAgent::Image.from_file("diagram.jpg"), # Image object
PiAgent::Image.from_bytes(blob, mime_type: "image/webp")
]) { |e| ... }
end
Supported formats: png, jpeg, gif, webp.
For low-level RPC access (raw request/notify/subscribe), use
PiAgent.open, which yields a PiAgent::Client.
Extension UI
pi extensions can request user interaction (confirm, select, input,
editor) mid-run. Pass an extension_ui handler to answer them:
handler = lambda do |req|
case req.method
when :confirm then true # confirmed
when :select then req.options.first # pick an option
when :input then "default" # entered text
when :editor then req.prefill # edited text
# fire-and-forget (:notify, :set_status, ...) — return value ignored
end
end
PiAgent.session(extension_ui: handler) do |session|
session.prompt("Refactor the parser") { |e| ... }
end
Returning nil from a dialog handler cancels it. With no handler,
dialogs are auto-cancelled so the agent never hangs. Handlers run on
their own thread and never block the event stream.
Forking
PiAgent.session do |session|
session.prompt("Add a feature") { |e| ... }
# Branch from an earlier message
forkable = session.fork_messages # [{ "entryId" =>, "text" => }]
session.fork(forkable.first["entryId"]) # => { "text" =>, "cancelled" => }
session.clone_session # duplicate the active branch
session.set_session_name("feature-work")
end
fork/clone_session return cancelled: true (rather than raising) if
a pi extension vetoes the operation — that is an expected outcome, not
an error.
Errors
- A failed RPC command (
success: false) raisesPiAgent::CommandError, which carries the failing#commandname. - Agent-side errors arrive in the event stream, not as exceptions —
inspect them with
Event#error?,#error_message, and#error_reason("aborted"vs"error"). This coversextension_errorevents and errored assistant turns. The gem does not abort your iteration on agent errors; you decide how to react.
Protocol reference
The wire protocol is documented upstream in
packages/coding-agent/docs/rpc.md.
Development
bin/setup # bundle install
bundle exec rspec
License
MIT