Skip to content

Commit fcfcea9

Browse files
committed
WIP
1 parent 25796f8 commit fcfcea9

File tree

4 files changed

+49
-36
lines changed

4 files changed

+49
-36
lines changed

doc/Index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ To learn more about debugging with IRB, see [Debugging with IRB](#label-Debuggin
9595

9696
### Agent Mode (Experimental)
9797

98-
`binding.irb(agent: true)` starts a non-interactive IRB session designed for AI agents and scripts. Instead of opening a REPL, it exposes an IRB session over a Unix socket using a simple request/response protocol.
98+
`binding.agent` starts a non-interactive IRB session designed for AI agents and scripts. Instead of opening a REPL, it exposes an IRB session over a Unix socket using a simple request/response protocol.
9999

100100
The behavior depends on the `IRB_SOCK_PATH` environment variable:
101101

lib/irb.rb

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -746,31 +746,7 @@ class Binding
746746
#
747747
# See IRB for more information.
748748
#
749-
# When +agent+ is true, the session is designed for non-interactive use by
750-
# AI agents or scripts (experimental). Instead of opening an interactive REPL,
751-
# it starts a Unix socket server that accepts one command per connection. See
752-
# IRB::RemoteServer for the full protocol and workflow.
753-
def irb(show_code: true, agent: false)
754-
if agent
755-
require_relative "irb/remote_server"
756-
sock_path = ENV['IRB_SOCK_PATH']
757-
758-
# Phase 1 (discovery): no socket path set, so print instructions
759-
# teaching the agent how to connect, then exit immediately.
760-
# No IRB.setup needed since we're not starting a session.
761-
unless sock_path
762-
IRB::RemoteServer.print_instructions(self)
763-
exit(0)
764-
end
765-
766-
# Phase 2 (debug session): socket path set, start a request/response
767-
# server that the agent can send commands to.
768-
IRB.setup(source_location[0], argv: []) unless IRB.initialized?
769-
server = IRB::RemoteServer.new(self, sock_path: sock_path)
770-
server.run
771-
return
772-
end
773-
749+
def irb(show_code: true)
774750
# Setup IRB with the current file's path and no command line arguments
775751
IRB.setup(source_location[0], argv: []) unless IRB.initialized?
776752

@@ -801,4 +777,38 @@ def irb(show_code: true, agent: false)
801777
binding_irb.debug_break
802778
end
803779
end
780+
781+
# Opens a non-interactive IRB session designed for AI agents and scripts
782+
# (experimental). Instead of opening a REPL, it exposes an IRB session over a
783+
# Unix socket using a simple request/response protocol.
784+
#
785+
# The behavior depends on the +IRB_SOCK_PATH+ environment variable:
786+
#
787+
# - *Not set* (Phase 1 — discovery): prints instructions explaining the
788+
# workflow, then exits. This lets the agent discover the breakpoint and
789+
# learn the protocol.
790+
# - *Set* (Phase 2 — debug session): starts a Unix socket server at the
791+
# given path. Each connection accepts one command, evaluates it, returns
792+
# the result, and closes. The IRB session state persists across
793+
# connections. Send +exit+ to end the session and resume app execution.
794+
#
795+
# See IRB::RemoteServer for the full protocol and workflow.
796+
def agent
797+
require_relative "irb/remote_server"
798+
sock_path = ENV['IRB_SOCK_PATH']
799+
800+
# Phase 1 (discovery): no socket path set, so print instructions
801+
# teaching the agent how to connect, then exit immediately.
802+
# No IRB.setup needed since we're not starting a session.
803+
unless sock_path
804+
IRB::RemoteServer.print_instructions(self)
805+
exit(0)
806+
end
807+
808+
# Phase 2 (debug session): socket path set, start a request/response
809+
# server that the agent can send commands to.
810+
IRB.setup(source_location[0], argv: []) unless IRB.initialized?
811+
server = IRB::RemoteServer.new(self, sock_path: sock_path)
812+
server.run
813+
end
804814
end

lib/irb/remote_server.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ module IRB
77
# A request/response server for agent-driven IRB sessions over a Unix socket
88
# (experimental).
99
#
10-
# When <tt>binding.irb(agent: true)</tt> is called, the behavior depends on
10+
# When <tt>binding.agent</tt> is called, the behavior depends on
1111
# the +IRB_SOCK_PATH+ environment variable:
1212
#
1313
# - *Not set* (Phase 1 — discovery): prints instructions explaining how to
@@ -123,6 +123,8 @@ def print_instructions(binding_context)
123123
124124
No IRB_SOCK_PATH set — exiting without starting a debug session.
125125
126+
Add breakpoints with: require "irb"; binding.agent
127+
126128
To debug this breakpoint:
127129
128130
1. Run the app in the BACKGROUND with a socket path:

test/irb/test_remote.rb

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ def test_phase1_prints_instructions_and_exits
1010
write_ruby <<~'RUBY'
1111
require "irb"
1212
puts "BEFORE"
13-
binding.irb(agent: true)
13+
binding.agent
1414
puts "AFTER"
1515
RUBY
1616

@@ -20,6 +20,7 @@ def test_phase1_prints_instructions_and_exits
2020

2121
assert_include output, "IRB agent breakpoint hit at"
2222
assert_include output, "IRB_SOCK_PATH"
23+
assert_include output, 'require "irb"; binding.agent'
2324
assert_include output, "BEFORE"
2425
# exit(0) means AFTER should not print
2526
assert_not_include output, "AFTER"
@@ -28,7 +29,7 @@ def test_phase1_prints_instructions_and_exits
2829
def test_phase2_basic_eval
2930
write_ruby <<~'RUBY'
3031
require "irb"
31-
binding.irb(agent: true)
32+
binding.agent
3233
RUBY
3334

3435
output = run_agent_session do |sock_path|
@@ -46,7 +47,7 @@ class Potato
4647
attr_accessor :name
4748
def initialize(name); @name = name; end
4849
end
49-
Potato.new("Russet").instance_eval { binding.irb(agent: true) }
50+
Potato.new("Russet").instance_eval { binding.agent }
5051
RUBY
5152

5253
output = run_agent_session do |sock_path|
@@ -64,7 +65,7 @@ def test_phase2_show_source_command
6465
class Potato
6566
def cook!; "done"; end
6667
end
67-
Potato.new.instance_eval { binding.irb(agent: true) }
68+
Potato.new.instance_eval { binding.agent }
6869
RUBY
6970

7071
output = run_agent_session do |sock_path|
@@ -78,7 +79,7 @@ def cook!; "done"; end
7879
def test_phase2_error_handling
7980
write_ruby <<~'RUBY'
8081
require "irb"
81-
binding.irb(agent: true)
82+
binding.agent
8283
RUBY
8384

8485
output = run_agent_session do |sock_path|
@@ -92,7 +93,7 @@ def test_phase2_error_handling
9293
def test_phase2_multiline_expression
9394
write_ruby <<~'RUBY'
9495
require "irb"
95-
binding.irb(agent: true)
96+
binding.agent
9697
RUBY
9798

9899
output = run_agent_session do |sock_path|
@@ -108,7 +109,7 @@ def test_phase2_multiline_expression
108109
def test_phase2_session_state_persists
109110
write_ruby <<~'RUBY'
110111
require "irb"
111-
binding.irb(agent: true)
112+
binding.agent
112113
RUBY
113114

114115
output = run_agent_session do |sock_path|
@@ -125,7 +126,7 @@ def test_phase2_resumes_execution_after_exit
125126
write_ruby <<~'RUBY'
126127
require "irb"
127128
puts "BEFORE"
128-
binding.irb(agent: true)
129+
binding.agent
129130
puts "AFTER"
130131
RUBY
131132

@@ -140,7 +141,7 @@ def test_phase2_resumes_execution_after_exit
140141
def test_phase2_help_command
141142
write_ruby <<~'RUBY'
142143
require "irb"
143-
binding.irb(agent: true)
144+
binding.agent
144145
RUBY
145146

146147
output = run_agent_session do |sock_path|

0 commit comments

Comments
 (0)