rails_agent_server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 67c2f8f4c1a4d5f62de047f40c1bf076549b7f303b1dad62b8459919cd73ed87
4
+ data.tar.gz: 4ae4c453471fb620d461742684b379fc0ba4e28d62b77d662f704bc039b10a54
5
+ SHA512:
6
+ metadata.gz: 414387d2240dd9736ae51d8149949e0adb72d75eac81b39dcd50da9cbf03ff29424cbc281d4477fe10ab211541e45b6d7e48b5d512d3ce20389f50508e768f45
7
+ data.tar.gz: 325d73826e71519c811aac9b91206d1f1f702006d578ddc40618abac28d18ca7411da1dc5db54d8bc13bba68036f0bc2ad96491bf9bb3f0c621122ddef29f33a
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Andy Waite
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,192 @@
1
+ # Rails Agent Server
2
+
3
+ A persistent Rails server for AI agents that avoids boot overhead for repeated queries. Intended for AI agents like Claude Code that need fast Rails runner access without waiting for Rails to boot on every request.
4
+
5
+ ## Why This Gem?
6
+
7
+ When using AI coding assistants or automation tools with Rails applications, the agent often needs to run many small queries via `bin/rails runner` to understand the runtime behaviour or state. Using `bin/rails runner` for each query means booting Rails every time, which can typically take 5-10 seconds per query.
8
+
9
+ Rails Agent Server starts a persistent background server that keeps Rails loaded in memory. The first request takes the normal Rails boot time, but subsequent requests are instant.
10
+
11
+ ### Why Not `bin/rails console`?
12
+
13
+ AI agents can't easily interact with `bin/rails console` because:
14
+
15
+ - **Interactive TTY requirement**: Rails console expects an interactive terminal (TTY) and won't accept input from standard pipes
16
+ - **No request/response protocol**: There's no simple way to send a command and receive just its result back
17
+ - **Session complexity**: Managing an interactive console session requires handling readline, prompt detection, and terminal control sequences
18
+ - **Output parsing**: Console output includes prompts, formatting, and IRB metadata that's difficult to parse programmatically
19
+
20
+ Rails Agent Server provides a simple request/response interface over Unix sockets, making it trivial for AI agents to execute code and get clean results.
21
+
22
+ ### Why Not Spring?
23
+
24
+ Spring is Rails' official application preloader and is a viable alternative for this use case. However, some projects prefer to avoid Spring for various reasons:
25
+
26
+ - **Simplicity**: Spring can sometimes cause confusion with stale code or require manual intervention (`spring stop`)
27
+ - **Compatibility**: Some projects have experienced issues with Spring in certain environments or with specific gems
28
+
29
+ If Spring works well for your project, you can use `bin/spring rails runner` instead. Rails Agent Server is for teams that prefer an alternative approach or have disabled Spring.
30
+
31
+ ### Why Not MCP (Model Context Protocol)?
32
+
33
+ MCP servers provide a structured way for AI agents to interact with systems through defined tools and resources. While MCP is excellent for complex, multi-step workflows and standardized interfaces, Rails Agent Server is preferable when:
34
+
35
+ - **Simplicity**: You just need to run Rails code quickly without defining MCP tools and schemas
36
+ - **Flexibility**: AI agents can execute arbitrary Rails code without being limited to predefined tool operations
37
+ - **Setup**: No need to configure MCP server definitions, transport layers, or client-server communication
38
+ - **Performance**: Direct command execution is faster than MCP's request/response protocol overhead
39
+ - **Token efficiency**: MCP can consume many tokens for structured tool schemas and responses
40
+ - **Existing workflows**: Works with agents that already know how to run shell commands
41
+
42
+ Rails Agent Server is a lightweight alternative that lets AI agents treat your Rails app like a fast REPL, while MCP is better suited for building formalized integrations with specific capabilities.
43
+
44
+ ## Installation
45
+
46
+ Add this line to your application's Gemfile:
47
+
48
+ ```ruby
49
+ gem 'rails_agent_server', group: :development
50
+ ```
51
+
52
+ And then execute:
53
+
54
+ ```bash
55
+ bundle install
56
+ ```
57
+
58
+ Or install it yourself as:
59
+
60
+ ```bash
61
+ gem install rails_agent_server
62
+ ```
63
+
64
+ ## Agent Setup
65
+
66
+ Add this section to your project's `CLAUDE.md` or equivalent:
67
+
68
+ ```markdown
69
+ ## Rails Console Access
70
+
71
+ This project uses `rails_agent_server` for fast Rails runner access without boot overhead.
72
+
73
+ When you need to query the database or run Rails code:
74
+ - Use `rails_agent_server 'YourCode.here'` instead of `bin/rails runner`
75
+ - First request auto-starts a persistent server (takes ~5 seconds)
76
+ - Subsequent requests are instant (no Rails boot time)
77
+ - Server stays running in background until you run `rails_agent_server stop`
78
+
79
+ Examples:
80
+ rails_agent_server 'User.count'
81
+ rails_agent_server 'Post.where(published: true).count'
82
+ rails_agent_server 'User.find_by(email: "test@example.com")&.name'
83
+ ```
84
+
85
+ ## Usage
86
+
87
+ ### Basic Commands
88
+
89
+ These commands are designed to be used by AI agents (like Claude Code) or automation tools, and not intended for manual use.
90
+
91
+ ```bash
92
+ # Run a Ruby expression (auto-starts server if needed)
93
+ rails_agent_server 'User.count'
94
+
95
+ # Run code that prints output
96
+ rails_agent_server 'puts User.pluck(:email).join(", ")'
97
+
98
+ # Run a script file
99
+ rails_agent_server /path/to/script.rb
100
+
101
+ # Server management
102
+ rails_agent_server status # Check if server is running
103
+ rails_agent_server start # Manually start the server
104
+ rails_agent_server stop # Stop the background server
105
+ rails_agent_server restart # Restart the background server
106
+ rails_agent_server help # Show help
107
+ ```
108
+
109
+ ### Examples
110
+
111
+ ```bash
112
+ # Database queries
113
+ rails_agent_server 'User.count'
114
+ rails_agent_server 'Post.where(published: true).pluck(:title)'
115
+ rails_agent_server 'User.find_by(email: "test@example.com")&.name'
116
+
117
+ # Inspect schema
118
+ rails_agent_server 'ActiveRecord::Base.connection.tables'
119
+ rails_agent_server 'User.column_names'
120
+
121
+ # Complex operations
122
+ rails_agent_server 'User.group(:status).count'
123
+ rails_agent_server 'Rails.cache.clear; "Cache cleared"'
124
+ ```
125
+
126
+ ## How It Works
127
+
128
+ 1. **First Request**: When you run `rails_agent_server` for the first time, it:
129
+ - Spawns a background server process
130
+ - Loads your Rails environment once
131
+ - Creates a Unix socket for communication
132
+ - Stores the PID for management
133
+
134
+ 2. **Subsequent Requests**: Each request:
135
+ - Connects to the existing Unix socket
136
+ - Sends code to execute
137
+ - Receives the result instantly
138
+ - No Rails boot time required
139
+
140
+ 3. **Server Management**: The server:
141
+ - Runs in the background until explicitly stopped
142
+ - Captures both printed output and expression results
143
+ - Handles errors gracefully
144
+ - Cleans up socket and PID files on exit
145
+
146
+ ## File Locations
147
+
148
+ By default, the server creates these files in your Rails application:
149
+
150
+ - **Socket**: `tmp/rails_agent_server.sock` - Unix socket for communication
151
+ - **PID file**: `tmp/pids/rails_agent_server.pid` - Process ID for management
152
+ - **Log file**: `log/rails_agent_server.log` - Server output and errors
153
+
154
+ If not in a Rails directory, files are created in `/tmp/`.
155
+
156
+ ## Performance
157
+
158
+ - **First request**: ~5-10 seconds (Rails boot time)
159
+ - **Subsequent requests**: ~50-200ms (no boot overhead)
160
+ - **Memory**: One Rails process running in background (~200-500MB depending on your app)
161
+
162
+ ## When to Restart
163
+
164
+ You should restart the server when:
165
+ - You've changed model files or schema
166
+ - You've updated initializers
167
+ - You've modified environment configuration
168
+ - The server is returning stale data
169
+
170
+ ```bash
171
+ rails_agent_server restart
172
+ ```
173
+
174
+ ## Limitations
175
+
176
+ - The server may need to be restarted to pick up some code changes
177
+ - Only one server runs per Rails application (shared socket file)
178
+ - Requires Unix sockets (macOS, Linux, WSL)
179
+
180
+ ## Development
181
+
182
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
183
+
184
+ To install this gem onto your local machine, run `bundle exec rake install`.
185
+
186
+ ## Contributing
187
+
188
+ Bug reports and pull requests are welcome on GitHub at https://github.com/andyw8/rails_agent_server.
189
+
190
+ ## License
191
+
192
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "minitest/test_task"
5
+
6
+ Minitest::TestTask.create
7
+
8
+ require "standard/rake"
9
+
10
+ task default: %i[test standard]
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "rails_agent_server"
5
+
6
+ RailsAgentServer::CLI.new(ARGV).run
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "server"
4
+
5
+ module RailsAgentServer
6
+ class CLI
7
+ attr_reader :server
8
+
9
+ def initialize(argv = ARGV)
10
+ @argv = argv
11
+ @server = Server.new
12
+ end
13
+
14
+ def run
15
+ case @argv[0]
16
+ when "stop"
17
+ server.stop
18
+ when "restart"
19
+ server.restart
20
+ when "status"
21
+ server.status
22
+ when "start"
23
+ server.start
24
+ puts "Rails agent server started"
25
+ when "--help", "-h", "help", nil
26
+ print_help
27
+ else
28
+ execute_code
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def execute_code
35
+ code = if @argv[0] && File.exist?(@argv[0])
36
+ File.read(@argv[0])
37
+ else
38
+ @argv.join(" ")
39
+ end
40
+
41
+ if code.empty?
42
+ $stderr.puts "Error: No code provided"
43
+ print_help
44
+ exit 1
45
+ end
46
+
47
+ begin
48
+ puts server.execute(code)
49
+ rescue Errno::ENOENT
50
+ $stderr.puts "Error: Could not connect to Rails agent server"
51
+ exit 1
52
+ rescue => e
53
+ $stderr.puts "Error: #{e.message}"
54
+ exit 1
55
+ end
56
+ end
57
+
58
+ def print_help
59
+ puts <<~HELP
60
+ Rails Agent Server - A persistent Rails server for AI agents
61
+
62
+ Usage:
63
+ rails_agent_server 'User.count' # Run a Ruby expression
64
+ rails_agent_server /path/to/script.rb # Run a script file
65
+ rails_agent_server start # Start the server
66
+ rails_agent_server stop # Stop the server
67
+ rails_agent_server restart # Restart the server
68
+ rails_agent_server status # Check server status
69
+ rails_agent_server help # Show this help
70
+
71
+ The server auto-starts on first use if not already running.
72
+
73
+ Examples:
74
+ rails_agent_server 'User.count'
75
+ rails_agent_server 'puts User.pluck(:email).join(", ")'
76
+ rails_agent_server 'ActiveRecord::Base.connection.tables'
77
+ rails_agent_server script.rb
78
+
79
+ For Claude Code or AI agents, add this to your CLAUDE.md:
80
+
81
+ ## Rails Console Access
82
+
83
+ This project uses rails_agent_server for fast Rails console access without boot overhead.
84
+
85
+ When you need to query the database or run Rails code:
86
+ - Use `rails_agent_server 'YourCode.here'` instead of `bin/rails runner`
87
+ - First request auto-starts a persistent server (takes ~5 seconds)
88
+ - Subsequent requests are instant (no Rails boot time)
89
+ - Server stays running in background until you run `rails_agent_server stop`
90
+
91
+ Examples:
92
+ rails_agent_server 'User.count'
93
+ rails_agent_server 'Post.where(published: true).count'
94
+ rails_agent_server 'User.find_by(email: "test@example.com")&.name'
95
+ HELP
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,227 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "socket"
4
+ require "fileutils"
5
+ require "stringio"
6
+
7
+ module RailsAgentServer
8
+ class Server
9
+ attr_reader :socket_path, :pid_path, :log_path
10
+
11
+ def initialize(socket_path: nil, pid_path: nil, log_path: nil)
12
+ @socket_path = socket_path || default_socket_path
13
+ @pid_path = pid_path || default_pid_path
14
+ @log_path = log_path || default_log_path
15
+ end
16
+
17
+ def start
18
+ return if running?
19
+
20
+ puts "Starting Rails agent server (this will take a few seconds)..."
21
+
22
+ pid = spawn(
23
+ RbConfig.ruby, "-r", "rails_agent_server/server",
24
+ "-e", "RailsAgentServer::Server.new(socket_path: '#{socket_path}', pid_path: '#{pid_path}', log_path: '#{log_path}').run",
25
+ out: log_path, err: log_path
26
+ )
27
+ Process.detach(pid)
28
+
29
+ wait_for_server
30
+ end
31
+
32
+ def stop
33
+ if File.exist?(pid_path)
34
+ pid = File.read(pid_path).strip.to_i
35
+ begin
36
+ Process.kill("TERM", pid)
37
+ puts "Stopped Rails agent server (PID: #{pid})"
38
+ rescue Errno::ESRCH
39
+ puts "Rails agent server is not running (stale PID file)"
40
+ cleanup_files
41
+ end
42
+ else
43
+ puts "Rails agent server is not running"
44
+ end
45
+ end
46
+
47
+ def restart
48
+ stop if running?
49
+ cleanup_files
50
+ sleep 0.5
51
+ start
52
+ puts "Rails agent server restarted"
53
+ end
54
+
55
+ def status
56
+ if running?
57
+ pid = File.read(pid_path).strip
58
+ puts "Rails agent server is running (PID: #{pid})"
59
+ true
60
+ else
61
+ puts "Rails agent server is not running"
62
+ false
63
+ end
64
+ end
65
+
66
+ def running?
67
+ return false unless File.exist?(pid_path)
68
+
69
+ pid = File.read(pid_path).strip.to_i
70
+ Process.kill(0, pid)
71
+ true
72
+ rescue Errno::ESRCH, Errno::EPERM
73
+ false
74
+ end
75
+
76
+ def run
77
+ load_rails_environment
78
+
79
+ FileUtils.rm_f(socket_path)
80
+ server = UNIXServer.new(socket_path)
81
+ File.write(pid_path, Process.pid.to_s)
82
+
83
+ $stdout.puts "Rails agent server listening on #{socket_path} (PID: #{Process.pid})"
84
+
85
+ setup_signal_handlers
86
+ at_exit { cleanup_files }
87
+
88
+ loop do
89
+ client = server.accept
90
+ handle_client(client)
91
+ end
92
+ rescue => e
93
+ $stderr.puts "Server error: #{e.class}: #{e.message}"
94
+ $stderr.puts e.backtrace.join("\n")
95
+ cleanup_files
96
+ raise
97
+ end
98
+
99
+ def execute(code)
100
+ start unless running?
101
+
102
+ socket = UNIXSocket.new(socket_path)
103
+ socket.write(code)
104
+ socket.close_write
105
+ response = socket.read
106
+ socket.close
107
+ response
108
+ end
109
+
110
+ private
111
+
112
+ def default_socket_path
113
+ if defined?(Rails) && Rails.root
114
+ Rails.root.join("tmp", "rails_agent_server.sock").to_s
115
+ else
116
+ "/tmp/rails_agent_server.sock"
117
+ end
118
+ end
119
+
120
+ def default_pid_path
121
+ if defined?(Rails) && Rails.root
122
+ Rails.root.join("tmp", "pids", "rails_agent_server.pid").to_s
123
+ else
124
+ "/tmp/rails_agent_server.pid"
125
+ end
126
+ end
127
+
128
+ def default_log_path
129
+ if defined?(Rails) && Rails.root
130
+ Rails.root.join("log", "rails_agent_server.log").to_s
131
+ else
132
+ "/tmp/rails_agent_server.log"
133
+ end
134
+ end
135
+
136
+ def wait_for_server(timeout: 30)
137
+ (timeout * 2).times do
138
+ return if File.exist?(socket_path)
139
+ sleep 0.5
140
+ end
141
+
142
+ abort "Timed out waiting for Rails agent server to start. Check #{log_path}"
143
+ end
144
+
145
+ def cleanup_files
146
+ FileUtils.rm_f(socket_path)
147
+ FileUtils.rm_f(pid_path)
148
+ end
149
+
150
+ def setup_signal_handlers
151
+ trap("INT") { exit }
152
+ trap("TERM") { exit }
153
+ end
154
+
155
+ def load_rails_environment
156
+ return if defined?(Rails)
157
+
158
+ rails_root = find_rails_root
159
+ abort "Not in a Rails application directory" unless rails_root
160
+
161
+ environment_path = File.join(rails_root, "config", "environment.rb")
162
+ abort "Rails environment not found at #{environment_path}" unless File.exist?(environment_path)
163
+
164
+ # Ensure tmp/pids directory exists
165
+ FileUtils.mkdir_p(File.dirname(pid_path))
166
+
167
+ require environment_path
168
+ end
169
+
170
+ def find_rails_root
171
+ current_dir = Dir.pwd
172
+ until current_dir == "/"
173
+ config_path = File.join(current_dir, "config", "environment.rb")
174
+ return current_dir if File.exist?(config_path)
175
+ current_dir = File.dirname(current_dir)
176
+ end
177
+ nil
178
+ end
179
+
180
+ def handle_client(client)
181
+ code = client.read
182
+
183
+ output = StringIO.new
184
+ result = nil
185
+ error = nil
186
+
187
+ begin
188
+ old_stdout = $stdout
189
+ $stdout = output
190
+ result = eval(code, TOPLEVEL_BINDING) # rubocop:disable Security/Eval
191
+ $stdout = old_stdout
192
+ rescue => e
193
+ $stdout = old_stdout
194
+ error = format_error(e)
195
+ end
196
+
197
+ response = build_response(output.string, result, error)
198
+ client.write(response)
199
+ client.close
200
+ rescue => e
201
+ client.write("Server error: #{e.class}: #{e.message}")
202
+ client.close
203
+ end
204
+
205
+ def format_error(exception)
206
+ message = "#{exception.class}: #{exception.message}"
207
+ if exception.backtrace
208
+ backtrace = exception.backtrace.first(5).join("\n ")
209
+ message += "\n #{backtrace}"
210
+ end
211
+ message
212
+ end
213
+
214
+ def build_response(printed_output, result, error)
215
+ response = +""
216
+ response << printed_output unless printed_output.empty?
217
+
218
+ if error
219
+ response << error
220
+ elsif printed_output.empty?
221
+ response << result.inspect
222
+ end
223
+
224
+ response
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAgentServer
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "rails_agent_server/version"
4
+ require_relative "rails_agent_server/server"
5
+ require_relative "rails_agent_server/cli"
6
+
7
+ module RailsAgentServer
8
+ class Error < StandardError; end
9
+ end
@@ -0,0 +1,4 @@
1
+ module RailsReplServer
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails_agent_server
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Andy Waite
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: Rails Agent Server provides a persistent background server for running
13
+ Rails code without the overhead of booting Rails for each request. Perfect for AI
14
+ agents and automation tools that need fast Rails console access.
15
+ email:
16
+ - andyw8@users.noreply.github.com
17
+ executables:
18
+ - rails_agent_server
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - LICENSE.txt
23
+ - README.md
24
+ - Rakefile
25
+ - exe/rails_agent_server
26
+ - lib/rails_agent_server.rb
27
+ - lib/rails_agent_server/cli.rb
28
+ - lib/rails_agent_server/server.rb
29
+ - lib/rails_agent_server/version.rb
30
+ - sig/rails_repl_server.rbs
31
+ homepage: https://github.com/andyw8/rails_agent_server
32
+ licenses:
33
+ - MIT
34
+ metadata:
35
+ homepage_uri: https://github.com/andyw8/rails_agent_server
36
+ source_code_uri: https://github.com/andyw8/rails_agent_server
37
+ rdoc_options: []
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: 3.2.0
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ requirements: []
51
+ rubygems_version: 3.6.9
52
+ specification_version: 4
53
+ summary: A persistent Rails server for AI agents that avoids boot overhead for repeated
54
+ queries
55
+ test_files: []