Class: Ask::Tools::Repl
- Inherits:
-
Ask::Tool
- Object
- Ask::Tool
- Ask::Tools::Repl
- Defined in:
- lib/ask/tools/shell/repl.rb
Overview
The kernel runs with the caller's permissions (like Code). It is a durable control environment, not a security sandbox.
Evaluate Ruby code in a persistent, long-lived subprocess.
Unlike Code (which spawns a fresh ruby -e per call), Repl keeps a
kernel process alive across calls and evaluates every snippet into the
same binding — so state (locals, requires, defined methods) survives
between calls. This is the RLM (recursive language model) pattern: the
model composes capabilities as code against a persistent environment.
Sessions are named and shared process-wide: calling with the same
session name from any tool instance reaches the same kernel. A
session is closed by reset: true, Repl.close_session, or after
Repl.idle_timeout seconds without use.
Defined Under Namespace
Classes: Kernel
Constant Summary collapse
- DEFAULT_EVAL_TIMEOUT =
Timeout for a single evaluation, in seconds.
30- DEFAULT_IDLE_TIMEOUT =
Sessions idle longer than this are closed on next access.
300- BUNDLER_ENV =
Env vars that would drag the parent's bundler context into the kernel subprocess (RUBYOPT=-rbundler/setup restricts $LOAD_PATH to the parent's Gemfile). Nil overrides remove them at spawn so the session is plain ruby, like the one-shot Code tool's sandbox.
%w[ RUBYOPT RUBYLIB BASH_ENV GEM_PATH GEM_HOME BUNDLE_GEMFILE BUNDLE_PATH BUNDLE_BIN_PATH BUNDLER_SETUP BUNDLER_VERSION BUNDLE_WITHOUT BUNDLE_FROZEN BUNDLE_DEPLOYMENT BUNDLE_LOCKFILE BUNDLE_APP_CONFIG ].freeze
Class Attribute Summary collapse
-
.eval_timeout ⇒ Integer
Seconds a single evaluation may take.
-
.idle_timeout ⇒ Integer
Seconds a session may sit unused before it is closed on next access.
Class Method Summary collapse
-
.close_all ⇒ void
Close every session kernel.
-
.close_session(session) ⇒ void
Close and forget a session's kernel.
-
.kernel_for(session, reset: false) ⇒ Kernel
The kernel for
session, spawning one if needed (or after reset). -
.sessions ⇒ Array<String>
Active session names.
Instance Method Summary collapse
Class Attribute Details
.eval_timeout ⇒ Integer
Returns seconds a single evaluation may take.
64 65 66 |
# File 'lib/ask/tools/shell/repl.rb', line 64 def eval_timeout @eval_timeout end |
.idle_timeout ⇒ Integer
Returns seconds a session may sit unused before it is closed on next access.
61 62 63 |
# File 'lib/ask/tools/shell/repl.rb', line 61 def idle_timeout @idle_timeout end |
Class Method Details
.close_all ⇒ void
This method returns an undefined value.
Close every session kernel. Called at exit; call it explicitly to free subprocesses early.
111 112 113 114 115 116 117 |
# File 'lib/ask/tools/shell/repl.rb', line 111 def close_all @registry_mutex.synchronize do @registry.each_value(&:close) @registry.clear end nil end |
.close_session(session) ⇒ void
This method returns an undefined value.
Close and forget a session's kernel.
99 100 101 102 103 104 105 |
# File 'lib/ask/tools/shell/repl.rb', line 99 def close_session(session) @registry_mutex.synchronize do kernel = @registry.delete(session) kernel&.close end nil end |
.kernel_for(session, reset: false) ⇒ Kernel
The kernel for session, spawning one if needed (or after reset).
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/ask/tools/shell/repl.rb', line 79 def kernel_for(session, reset: false) @registry_mutex.synchronize do close_session(session) if reset kernel = @registry[session] if kernel.nil? || kernel.dead? kernel = Kernel.new(session: session) @registry[session] = kernel elsif kernel.idle_seconds > idle_timeout kernel.close kernel = Kernel.new(session: session) @registry[session] = kernel end kernel end end |
.sessions ⇒ Array<String>
Returns active session names.
120 121 122 |
# File 'lib/ask/tools/shell/repl.rb', line 120 def sessions @registry_mutex.synchronize { @registry.keys } end |
Instance Method Details
#execute(code:, session: "default", reset: false) ⇒ Object
127 128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/ask/tools/shell/repl.rb', line 127 def execute(code:, session: "default", reset: false) build_result(eval_code(session, code, reset: reset), session) rescue Kernel::TimeoutError => e Ask::Result.error(message: "REPL session '#{session}' timed out (#{e.timeout}s); session state was lost") rescue Kernel::DeadError => e # The session died (crash, external kill, closed stdin). Respawn a # fresh kernel and retry once; only give up if it dies again. begin build_result(eval_code(session, code), session) rescue Kernel::DeadError Ask::Result.error(message: "REPL session '#{session}' died repeatedly: #{e.}") end end |