Class: Botiasloop::Conversation

Inherits:
Object
  • Object
show all
Defined in:
lib/botiasloop/conversation.rb

Overview

Conversation model - represents a chat conversation with messages Direct Sequel model with all database and business logic combined

Defined Under Namespace

Classes: Message

Constant Summary collapse

LABEL_REGEX =

Valid label format: alphanumeric, dashes, and underscores

/\A[a-zA-Z0-9_-]+\z/

Instance Method Summary collapse

Instance Method Details

#add(role, content, input_tokens: 0, output_tokens: 0) ⇒ Object

Add a message to the conversation

Parameters:

  • role (String)

    Role of the message sender (user, assistant, system)

  • content (String)

    Message content

  • input_tokens (Integer) (defaults to: 0)

    Input tokens for this message (prompt tokens sent to LLM)

  • output_tokens (Integer) (defaults to: 0)

    Output tokens for this message (completion tokens from LLM)



124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/botiasloop/conversation.rb', line 124

def add(role, content, input_tokens: 0, output_tokens: 0)
  Message.create(
    conversation_id: id,
    role: role,
    content: content,
    input_tokens: input_tokens || 0,
    output_tokens: output_tokens || 0,
    timestamp: Time.now.utc
  )

  # Update conversation token totals
  update_token_totals(input_tokens, output_tokens)
end

#archive!Conversation

Archive this conversation

Returns:



90
91
92
93
# File 'lib/botiasloop/conversation.rb', line 90

def archive!
  update(archived: true)
  self
end

#archived?Boolean

Check if this conversation is archived

Returns:

  • (Boolean)

    True if archived



74
75
76
# File 'lib/botiasloop/conversation.rb', line 74

def archived?
  archived == true
end

#before_createObject

Auto-generate human-readable ID before creation if not provided



48
49
50
51
# File 'lib/botiasloop/conversation.rb', line 48

def before_create
  self.id ||= HumanId.generate
  super
end

#compact!(summary, recent_messages) ⇒ Object

Compact conversation by replacing old messages with a summary

Parameters:

  • summary (String)

    Summary of older messages

  • recent_messages (Array<Hash>)

    Recent messages to keep



173
174
175
176
177
178
179
# File 'lib/botiasloop/conversation.rb', line 173

def compact!(summary, recent_messages)
  reset!
  add("system", summary)
  recent_messages.each do |msg|
    add(msg[:role], msg[:content])
  end
end

#historyArray<Hash>

Get conversation history as array of message hashes

Returns:

  • (Array<Hash>)

    Array of message hashes with role, content, timestamp



151
152
153
# File 'lib/botiasloop/conversation.rb', line 151

def history
  messages_dataset.order(:timestamp).map(&:to_hash)
end

#label=(value) ⇒ Object

Set the label for this conversation

Parameters:

  • value (String)

    Label value



81
82
83
84
85
# File 'lib/botiasloop/conversation.rb', line 81

def label=(value)
  # Allow empty string to be treated as nil (clearing the label)
  value = nil if value.to_s.empty?
  super
end

#label?Boolean

Check if this conversation has a label

Returns:

  • (Boolean)

    True if label is set



67
68
69
# File 'lib/botiasloop/conversation.rb', line 67

def label?
  !label.nil? && !label.to_s.empty?
end

#last_activityString?

Get the timestamp of the last activity in the conversation

Returns:

  • (String, nil)

    ISO8601 timestamp of last message, or nil if no messages



98
99
100
101
102
# File 'lib/botiasloop/conversation.rb', line 98

def last_activity
  return nil if messages.empty?

  messages_dataset.order(:timestamp).last.timestamp.utc.iso8601
end

#message_countInteger

Get the number of messages in the conversation

Returns:

  • (Integer)

    Message count



107
108
109
# File 'lib/botiasloop/conversation.rb', line 107

def message_count
  messages.count
end

#reset!Object

Reset conversation - clear all messages and reset token counts



162
163
164
165
166
167
# File 'lib/botiasloop/conversation.rb', line 162

def reset!
  messages_dataset.delete
  self.input_tokens = 0
  self.output_tokens = 0
  save
end

#system_promptString

Generate the system prompt for this conversation Includes current date/time and environment info

Returns:

  • (String)

    System prompt



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/botiasloop/conversation.rb', line 185

def system_prompt
  skills_registry = Skills::Registry.new

  prompt = <<~PROMPT
    You are Botias, an autonomous AI agent.

    Environment:
    - OS: #{RUBY_PLATFORM}
    - Shell: #{ENV.fetch("SHELL", "unknown")}
    - Working Directory: #{Dir.pwd}
    - Date: #{Time.now.strftime("%Y-%m-%d")}
    - Time: #{Time.now.strftime("%H:%M:%S %Z")}

    You operate in a ReAct loop: Reason about the task, Act using tools, Observe results.
  PROMPT

  if skills_registry.skills.any?
    prompt += <<~SKILLS

      Available Skills:
      #{skills_registry.skills_table}

      To use a skill, read its SKILL.md file at the provided path using the shell tool (e.g., `cat ~/skills/skill-name/SKILL.md`).
      Skills follow progressive disclosure: only metadata is shown above. Full instructions are loaded on demand.
    SKILLS
  end

  prompt += build_operator_section
  prompt += build_identity_section

  prompt
end

#total_tokensInteger

Get total tokens (input + output) for the conversation

Returns:

  • (Integer)

    Total token count



114
115
116
# File 'lib/botiasloop/conversation.rb', line 114

def total_tokens
  (input_tokens || 0) + (output_tokens || 0)
end

#update_token_totals(input_tokens, output_tokens) ⇒ Object

Update conversation-level token totals

Parameters:

  • input_tokens (Integer)

    Input tokens to add

  • output_tokens (Integer)

    Output tokens to add



142
143
144
145
146
# File 'lib/botiasloop/conversation.rb', line 142

def update_token_totals(input_tokens, output_tokens)
  self.input_tokens = (self.input_tokens || 0) + (input_tokens || 0)
  self.output_tokens = (self.output_tokens || 0) + (output_tokens || 0)
  save if modified?
end

#uuidString

Returns Human-readable ID of the conversation.

Returns:

  • (String)

    Human-readable ID of the conversation



156
157
158
159
# File 'lib/botiasloop/conversation.rb', line 156

def uuid
  # Return existing id or generate a new one for unsaved records
  self.id ||= HumanId.generate
end

#validateObject

Validations



54
55
56
57
58
59
60
61
62
# File 'lib/botiasloop/conversation.rb', line 54

def validate
  super

  return unless label && !label.to_s.empty?

  validates_format LABEL_REGEX, :label,
    message: "Invalid label format. Use only letters, numbers, dashes, and underscores."
  validates_unique :label, message: "Label '#{label}' already in use by another conversation"
end