Class: Nexo::Session
- Inherits:
-
Object
- Object
- Nexo::Session
- Defined in:
- lib/nexo/session.rb
Overview
A continuing, addressable session: a remembering instance of an Agent,
keyed by (agent_name, instance_id) and resumable across separate
invocations. It is the memory half of Nexo's model — the mirror of the
fire-and-finish Workflow.
session = Nexo::Session.resume(Assistant, "user-42")
session.prompt("My name is Mac.")
# ... a later request / process ...
Nexo::Session.resume(Assistant, "user-42").prompt("What is my name?")
# => "...Mac..." — the persisted thread carried the earlier turn
A Session adds ONLY memory + addressability. Message persistence is RubyLLM's
acts_as_chat (a Session defines no message table and serializes nothing);
the agent supplies the sandbox, permissions, skills, MCP servers, and fetch
allow-list unchanged. Opening or resuming a session therefore never widens
what the agent is allowed to do.
Rails-optional
Durable persistence requires ActiveRecord. Backend selection guards on
defined?(::ActiveRecord::Base), mirroring RunStore.default:
- Rails — the thread is a row on the host
acts_as_chatmodel (Nexo.config.session_chat_model, a String class name, constantized here at resume time so no compile-time AR reference exists).find_or_create_byon(agent_name, instance_id)loads-or-creates it; +acts_as_chat+'s callbacks persist every new message. The host app owns the schema (viarails g ruby_llm:install) plus the two addressing columns and their unique composite index. - Plain Ruby — no AR reference at all. A process-wide MemoryStore holds a
live
RubyLLM::Chatper pair; the thread lives only for the process and is documented as non-durable.
Lifecycle
A long-lived session accumulates context and any MCP/stdio/SSE connections the agent holds. Call #close when done to tear those down; see the README's retention/PII note.
Defined Under Namespace
Classes: MemoryStore
Instance Attribute Summary collapse
-
#agent_name ⇒ Object
readonly
The addressing pair for this session: the agent's class name and the caller-supplied instance id.
-
#instance_id ⇒ Object
readonly
The addressing pair for this session: the agent's class name and the caller-supplied instance id.
Class Method Summary collapse
-
.resume(agent_class, instance_id, **agent_opts) ⇒ Object
Loads-or-creates the persisted thread for
(agent_class.name, instance_id)and returns a Session whose #prompt appends to it.
Instance Method Summary collapse
-
#close ⇒ Object
Releases any MCP/fetch resources the agent holds.
-
#initialize(agent, instance_id) ⇒ Session
constructor
Wraps a built
agentandinstance_id, then hydrates (finds-or-creates) the persisted thread for the(agent_name, instance_id)pair. -
#prompt(text, max_turns: 25, &on_event) ⇒ Object
Appends
textto the thread, runs the agent's loop over the hydrated chat, and returns the response (respond to#content).
Constructor Details
#initialize(agent, instance_id) ⇒ Session
Wraps a built agent and instance_id, then hydrates (finds-or-creates)
the persisted thread for the (agent_name, instance_id) pair. Prefer the
Session.resume entry point over calling this directly.
58 59 60 61 62 63 |
# File 'lib/nexo/session.rb', line 58 def initialize(agent, instance_id) @agent = agent @agent_name = agent.class.name @instance_id = instance_id.to_s @chat = hydrate end |
Instance Attribute Details
#agent_name ⇒ Object (readonly)
The addressing pair for this session: the agent's class name and the caller-supplied instance id.
53 54 55 |
# File 'lib/nexo/session.rb', line 53 def agent_name @agent_name end |
#instance_id ⇒ Object (readonly)
The addressing pair for this session: the agent's class name and the caller-supplied instance id.
53 54 55 |
# File 'lib/nexo/session.rb', line 53 def instance_id @instance_id end |
Class Method Details
.resume(agent_class, instance_id, **agent_opts) ⇒ Object
Loads-or-creates the persisted thread for (agent_class.name, instance_id)
and returns a Session whose #prompt appends to it. Extra keyword arguments
are forwarded to agent_class.new so a session accepts the same construction
inputs an agent does where they don't conflict with addressing (e.g. cwd:).
47 48 49 |
# File 'lib/nexo/session.rb', line 47 def self.resume(agent_class, instance_id, **agent_opts) new(agent_class.new(**agent_opts), instance_id) end |
Instance Method Details
#close ⇒ Object
Releases any MCP/fetch resources the agent holds. Delegates to Agent#close; idempotent and safe when there is nothing to release.
76 77 78 |
# File 'lib/nexo/session.rb', line 76 def close @agent.close if @agent.respond_to?(:close) end |
#prompt(text, max_turns: 25, &on_event) ⇒ Object
Appends text to the thread, runs the agent's loop over the hydrated chat,
and returns the response (respond to #content). New messages are persisted
by acts_as_chat (Rails) or accumulate on the live in-memory chat (plain
Ruby). The optional &on_event block receives the same
(:tool_call | :tool_result | :done, payload) events as Loops::RubyLLM.
70 71 72 |
# File 'lib/nexo/session.rb', line 70 def prompt(text, max_turns: 25, &on_event) @agent.loop.run(agent: @agent, prompt: text, max_turns: max_turns, chat: @chat, &on_event) end |