Class: Session
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- Session
- Defined in:
- app/models/session.rb
Overview
A conversation session — the fundamental unit of agent interaction. Owns an ordered stream of Event records representing everything that happened: user messages, agent responses, tool calls, etc.
Sessions form a hierarchy: a main session can spawn child sessions (sub-agents) that inherit the parent’s viewport context at fork time.
Defined Under Namespace
Classes: MissingSoulError
Constant Summary collapse
- VIEW_MODES =
%w[basic verbose debug].freeze
Instance Method Summary collapse
-
#activate_skill(skill_name) ⇒ Skills::Definition
Activates a skill on this session.
-
#activate_workflow(workflow_name) ⇒ Workflows::Definition
Activates a workflow on this session.
-
#assemble_system_prompt(environment_context: nil) ⇒ String
Assembles the system prompt: soul first, then environment context, then skills/workflow, then goals.
-
#deactivate_skill(skill_name) ⇒ void
Deactivates a skill on this session.
-
#deactivate_workflow ⇒ void
Deactivates the current workflow on this session.
-
#goals_summary ⇒ Array<Hash>
Serializes active goals as a lightweight summary for ActionCable broadcasts and TUI display.
-
#messages_for_llm(token_budget: Anima::Settings.token_budget) ⇒ Array<Hash>
Builds the message array expected by the Anthropic Messages API.
-
#next_view_mode ⇒ String
Cycles to the next view mode: basic → verbose → debug → basic.
-
#promote_pending_messages! ⇒ Integer
Promotes all pending user messages to delivered status so they appear in the next LLM context.
-
#recalculate_viewport! ⇒ Array<Integer>
Recalculates the viewport and returns IDs of events evicted since the last snapshot.
-
#schedule_analytical_brain! ⇒ void
Enqueues the analytical brain to perform background maintenance on this session.
-
#snapshot_viewport!(ids) ⇒ void
Overwrites the viewport snapshot without computing evictions.
-
#sub_agent? ⇒ Boolean
True if this session is a sub-agent (has a parent).
-
#system_prompt(environment_context: nil) ⇒ String?
Returns the system prompt for this session.
-
#viewport_events(token_budget: Anima::Settings.token_budget, include_pending: true) ⇒ Array<Event>
Returns the events currently visible in the LLM context window.
Instance Method Details
#activate_skill(skill_name) ⇒ Skills::Definition
Activates a skill on this session. Validates the skill exists in the registry, adds it to active_skills, and persists.
133 134 135 136 137 138 139 140 141 142 |
# File 'app/models/session.rb', line 133 def activate_skill(skill_name) definition = Skills::Registry.instance.find(skill_name) raise Skills::InvalidDefinitionError, "Unknown skill: #{skill_name}" unless definition return definition if active_skills.include?(skill_name) self.active_skills = active_skills + [skill_name] save! definition end |
#activate_workflow(workflow_name) ⇒ Workflows::Definition
Activates a workflow on this session. Validates the workflow exists in the registry, sets it as the active workflow, and persists. Only one workflow can be active at a time — activating a new one replaces the previous.
163 164 165 166 167 168 169 170 171 172 |
# File 'app/models/session.rb', line 163 def activate_workflow(workflow_name) definition = Workflows::Registry.instance.find(workflow_name) raise Workflows::InvalidDefinitionError, "Unknown workflow: #{workflow_name}" unless definition return definition if active_workflow == workflow_name self.active_workflow = workflow_name save! definition end |
#assemble_system_prompt(environment_context: nil) ⇒ String
Assembles the system prompt: soul first, then environment context, then skills/workflow, then goals. The soul is always present — “who am I” before “what can I do.”
190 191 192 |
# File 'app/models/session.rb', line 190 def assemble_system_prompt(environment_context: nil) [assemble_soul_section, environment_context, assemble_expertise_section, assemble_goals_section].compact.join("\n\n") end |
#deactivate_skill(skill_name) ⇒ void
This method returns an undefined value.
Deactivates a skill on this session. Removes it from active_skills and persists.
148 149 150 151 152 153 |
# File 'app/models/session.rb', line 148 def deactivate_skill(skill_name) return unless active_skills.include?(skill_name) self.active_skills = active_skills - [skill_name] save! end |
#deactivate_workflow ⇒ void
This method returns an undefined value.
Deactivates the current workflow on this session.
177 178 179 180 181 182 |
# File 'app/models/session.rb', line 177 def deactivate_workflow return unless active_workflow.present? self.active_workflow = nil save! end |
#goals_summary ⇒ Array<Hash>
Serializes active goals as a lightweight summary for ActionCable broadcasts and TUI display. Returns a nested structure: root goals with their sub-goals inlined.
199 200 201 |
# File 'app/models/session.rb', line 199 def goals_summary goals.root.includes(:sub_goals).order(:created_at).map(&:as_summary) end |
#messages_for_llm(token_budget: Anima::Settings.token_budget) ⇒ Array<Hash>
Builds the message array expected by the Anthropic Messages API. Includes user/agent messages and tool call/response events in Anthropic’s wire format. Consecutive tool_call events are grouped into a single assistant message; consecutive tool_response events are grouped into a single user message with tool_result blocks. Pending messages are excluded — they haven’t been delivered yet.
212 213 214 |
# File 'app/models/session.rb', line 212 def (token_budget: Anima::Settings.token_budget) ((token_budget: token_budget, include_pending: false)) end |
#next_view_mode ⇒ String
Cycles to the next view mode: basic → verbose → debug → basic.
35 36 37 38 |
# File 'app/models/session.rb', line 35 def next_view_mode current_index = VIEW_MODES.index(view_mode) || 0 VIEW_MODES[(current_index + 1) % VIEW_MODES.size] end |
#promote_pending_messages! ⇒ Integer
Promotes all pending user messages to delivered status so they appear in the next LLM context. Triggers broadcast_update for each event so connected clients refresh the pending indicator.
221 222 223 224 225 226 227 228 |
# File 'app/models/session.rb', line 221 def promoted = 0 events.where(event_type: "user_message", status: Event::PENDING_STATUS).find_each do |event| event.update!(status: nil, payload: event.payload.except("status")) promoted += 1 end promoted end |
#recalculate_viewport! ⇒ Array<Integer>
Recalculates the viewport and returns IDs of events evicted since the last snapshot. Updates the stored viewport_event_ids atomically. Piggybacks on event broadcasts to notify clients which messages left the LLM’s context window.
95 96 97 98 99 100 101 102 |
# File 'app/models/session.rb', line 95 def new_ids = .map(&:id) old_ids = evicted = old_ids - new_ids update_column(:viewport_event_ids, new_ids) if old_ids != new_ids evicted end |
#schedule_analytical_brain! ⇒ void
This method returns an undefined value.
Enqueues the analytical brain to perform background maintenance on this session. Currently handles session naming; future phases add skill activation, goal tracking, and memory.
Runs after the first exchange and periodically as the conversation evolves, so the name stays relevant to the current topic.
53 54 55 56 57 58 59 60 61 62 |
# File 'app/models/session.rb', line 53 def schedule_analytical_brain! return if sub_agent? count = events..count return if count < 2 # Already named — only regenerate at interval boundaries (30, 60, 90, …) return if name.present? && (count % Anima::Settings.name_generation_interval != 0) AnalyticalBrainJob.perform_later(id) end |
#snapshot_viewport!(ids) ⇒ void
This method returns an undefined value.
Overwrites the viewport snapshot without computing evictions. Used when transmitting or broadcasting a full viewport refresh, where eviction notifications are unnecessary (clients clear their store first).
111 112 113 |
# File 'app/models/session.rb', line 111 def (ids) update_column(:viewport_event_ids, ids) end |
#sub_agent? ⇒ Boolean
Returns true if this session is a sub-agent (has a parent).
41 42 43 |
# File 'app/models/session.rb', line 41 def sub_agent? parent_session_id.present? end |
#system_prompt(environment_context: nil) ⇒ String?
Returns the system prompt for this session. Sub-agent sessions use their stored prompt. Main sessions assemble a system prompt from active skills and current goals.
122 123 124 |
# File 'app/models/session.rb', line 122 def system_prompt(environment_context: nil) sub_agent? ? prompt : assemble_system_prompt(environment_context: environment_context) end |
#viewport_events(token_budget: Anima::Settings.token_budget, include_pending: true) ⇒ Array<Event>
Returns the events currently visible in the LLM context window. Walks events newest-first and includes them until the token budget is exhausted. Events are full-size or excluded entirely.
Sub-agent sessions inherit parent context via virtual viewport: child events are prioritized and fill the budget first (newest-first), then parent events from before the fork point fill the remaining budget. The final array is chronological: parent events first, then child events.
77 78 79 80 81 82 83 84 85 86 87 |
# File 'app/models/session.rb', line 77 def (token_budget: Anima::Settings.token_budget, include_pending: true) own_events = select_events(own_event_scope(include_pending), budget: token_budget) remaining = token_budget - own_events.sum { |e| event_token_cost(e) } if sub_agent? && remaining > 0 parent_events = select_events(parent_event_scope(include_pending), budget: remaining) trim_trailing_tool_calls(parent_events) + own_events else own_events end end |