Class: Kward::Conversation
- Inherits:
-
Object
- Object
- Kward::Conversation
- Defined in:
- lib/kward/conversation.rb
Overview
Mutable transcript and runtime context for one agent session.
Conversation owns message ordering, system prompt refresh, read-before-write
state, memory prompt context, and persistence hooks. It intentionally stores
plain hashes because provider payload builders, session JSONL files, and RPC
normalizers all share the same transcript shape. Use MessageAccess when
reading messages so symbol/string key and legacy field compatibility stays in
one place.
Frontends should not mutate messages directly after attaching a
SessionStore::Session; use append/compact helpers so persistence callbacks
run and session trees stay consistent.
Constant Summary collapse
- DEFAULT_SYSTEM_MESSAGE =
Object.new.freeze
Instance Attribute Summary collapse
-
#compaction_system_message ⇒ Hash?
readonly
System prompt used when summarizing old context.
-
#last_memory_retrieval ⇒ Hash?
Metadata for the last memory retrieval attached to the session.
-
#memory_context ⇒ String?
Memory prompt context injected into refreshed system messages.
-
#messages ⇒ Array<Hash>
readonly
Ordered transcript entries sent to providers and persisted in sessions.
-
#model ⇒ String?
readonly
Model id captured for session/runtime prompts.
-
#on_append ⇒ Proc?
Persistence callback invoked after appending a message.
-
#on_compact ⇒ Proc?
Persistence callback invoked after compaction replaces history.
-
#on_runtime_update ⇒ Proc?
Callback invoked when runtime metadata should be persisted.
-
#on_tool_execution ⇒ Proc?
Callback invoked when a tool execution record should be persisted.
-
#plugin_registry ⇒ PluginRegistry?
Registry used to collect plugin prompt context.
-
#provider ⇒ String?
readonly
Provider captured for session/runtime prompts.
-
#read_paths ⇒ Set<String>
readonly
Resolved paths read by file tools during the active context.
-
#reasoning_effort ⇒ String?
readonly
Reasoning effort captured for session/runtime prompts.
-
#session_memories ⇒ Array<Hash>
readonly
Memories scoped to this conversation session.
-
#workspace_root ⇒ String
readonly
Canonical workspace root used for prompts and file guardrails.
Instance Method Summary collapse
- #append_assistant(message) ⇒ Object
- #append_tool(tool_call_id:, name:, content:) ⇒ Object
- #append_tool_execution(tool_call:, content:) ⇒ Object
-
#append_user(content, display_content: nil) ⇒ Object
Appends a user message and normalizes image attachment syntax.
-
#compact!(summary, compaction_summary: false, first_kept_entry_id: nil, tokens_before: nil, from_hook: false, details: {}, keep_messages: []) ⇒ Object
Replaces most transcript entries with a compaction summary and optional recent messages to keep.
-
#initialize(system_message: DEFAULT_SYSTEM_MESSAGE, messages: [], read_paths: [], on_append: nil, on_compact: nil, on_tool_execution: nil, on_runtime_update: nil, workspace_root: Dir.pwd, compaction_system_message: DEFAULT_SYSTEM_MESSAGE, provider: nil, model: nil, reasoning_effort: nil, memory_context: nil, session_memories: [], last_memory_retrieval: nil, plugin_registry: nil) ⇒ Conversation
constructor
A new instance of Conversation.
- #last_entry_compaction? ⇒ Boolean
- #last_file_change_result ⇒ Object
- #mark_last_entry_compaction! ⇒ Object
- #mark_read(path) ⇒ Object
- #persist_runtime_context! ⇒ Object
- #plugin_prompt_context ⇒ Object
-
#refresh_system_message! ⇒ Object
Rebuilds the system message from current config, memory, plugins, and workspace AGENTS.md state.
- #refresh_system_message_if_workspace_agents_changed! ⇒ Object
- #update_runtime_context!(provider: nil, model:, reasoning_effort:) ⇒ Object
Constructor Details
#initialize(system_message: DEFAULT_SYSTEM_MESSAGE, messages: [], read_paths: [], on_append: nil, on_compact: nil, on_tool_execution: nil, on_runtime_update: nil, workspace_root: Dir.pwd, compaction_system_message: DEFAULT_SYSTEM_MESSAGE, provider: nil, model: nil, reasoning_effort: nil, memory_context: nil, session_memories: [], last_memory_retrieval: nil, plugin_registry: nil) ⇒ Conversation
Returns a new instance of Conversation.
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/kward/conversation.rb', line 55 def initialize(system_message: DEFAULT_SYSTEM_MESSAGE, messages: [], read_paths: [], on_append: nil, on_compact: nil, on_tool_execution: nil, on_runtime_update: nil, workspace_root: Dir.pwd, compaction_system_message: DEFAULT_SYSTEM_MESSAGE, provider: nil, model: nil, reasoning_effort: nil, memory_context: nil, session_memories: [], last_memory_retrieval: nil, plugin_registry: nil) @workspace_root = ConfigFiles.canonical_workspace_root(workspace_root) @provider = provider @model = model @reasoning_effort = reasoning_effort @plugin_registry = plugin_registry @messages = [] if .equal?(DEFAULT_SYSTEM_MESSAGE) = .any? { || MessageAccess.role() == "system" } ? nil : Prompts.(workspace_root: @workspace_root, model: @model, reasoning_effort: @reasoning_effort, memory_context: memory_context, plugin_context: plugin_prompt_context) end @system_message_enabled = !!( || .find { || MessageAccess.role() == "system" }) if .equal?(DEFAULT_SYSTEM_MESSAGE) = @system_message_enabled ? Prompts.(workspace_root: @workspace_root, include_workspace_personality: false, model: @model, reasoning_effort: @reasoning_effort) : nil end @compaction_system_message = @workspace_agents_mtime = workspace_agents_mtime @last_entry_compaction = false @memory_context = memory_context @session_memories = Array(session_memories) @last_memory_retrieval = last_memory_retrieval @messages << unless .nil? @messages.concat() @read_paths = Set.new(read_paths) @on_append = on_append @on_compact = on_compact @on_tool_execution = on_tool_execution @on_runtime_update = on_runtime_update end |
Instance Attribute Details
#compaction_system_message ⇒ Hash? (readonly)
Returns system prompt used when summarizing old context.
31 32 33 |
# File 'lib/kward/conversation.rb', line 31 def @compaction_system_message end |
#last_memory_retrieval ⇒ Hash?
Returns metadata for the last memory retrieval attached to the session.
51 52 53 |
# File 'lib/kward/conversation.rb', line 51 def last_memory_retrieval @last_memory_retrieval end |
#memory_context ⇒ String?
Returns memory prompt context injected into refreshed system messages.
49 50 51 |
# File 'lib/kward/conversation.rb', line 49 def memory_context @memory_context end |
#messages ⇒ Array<Hash> (readonly)
Returns ordered transcript entries sent to providers and persisted in sessions.
25 26 27 |
# File 'lib/kward/conversation.rb', line 25 def @messages end |
#model ⇒ String? (readonly)
Returns model id captured for session/runtime prompts.
35 36 37 |
# File 'lib/kward/conversation.rb', line 35 def model @model end |
#on_append ⇒ Proc?
Returns persistence callback invoked after appending a message.
41 42 43 |
# File 'lib/kward/conversation.rb', line 41 def on_append @on_append end |
#on_compact ⇒ Proc?
Returns persistence callback invoked after compaction replaces history.
43 44 45 |
# File 'lib/kward/conversation.rb', line 43 def on_compact @on_compact end |
#on_runtime_update ⇒ Proc?
Returns callback invoked when runtime metadata should be persisted.
47 48 49 |
# File 'lib/kward/conversation.rb', line 47 def on_runtime_update @on_runtime_update end |
#on_tool_execution ⇒ Proc?
Returns callback invoked when a tool execution record should be persisted.
45 46 47 |
# File 'lib/kward/conversation.rb', line 45 def on_tool_execution @on_tool_execution end |
#plugin_registry ⇒ PluginRegistry?
Returns registry used to collect plugin prompt context.
53 54 55 |
# File 'lib/kward/conversation.rb', line 53 def plugin_registry @plugin_registry end |
#provider ⇒ String? (readonly)
Returns provider captured for session/runtime prompts.
33 34 35 |
# File 'lib/kward/conversation.rb', line 33 def provider @provider end |
#read_paths ⇒ Set<String> (readonly)
Returns resolved paths read by file tools during the active context.
27 28 29 |
# File 'lib/kward/conversation.rb', line 27 def read_paths @read_paths end |
#reasoning_effort ⇒ String? (readonly)
Returns reasoning effort captured for session/runtime prompts.
37 38 39 |
# File 'lib/kward/conversation.rb', line 37 def reasoning_effort @reasoning_effort end |
#session_memories ⇒ Array<Hash> (readonly)
Returns memories scoped to this conversation session.
39 40 41 |
# File 'lib/kward/conversation.rb', line 39 def session_memories @session_memories end |
#workspace_root ⇒ String (readonly)
Returns canonical workspace root used for prompts and file guardrails.
29 30 31 |
# File 'lib/kward/conversation.rb', line 29 def workspace_root @workspace_root end |
Instance Method Details
#append_assistant(message) ⇒ Object
95 96 97 98 |
# File 'lib/kward/conversation.rb', line 95 def append_assistant() = { role: "assistant", content: } if .is_a?(String) () end |
#append_tool(tool_call_id:, name:, content:) ⇒ Object
100 101 102 103 104 105 106 107 |
# File 'lib/kward/conversation.rb', line 100 def append_tool(tool_call_id:, name:, content:) ({ role: "tool", tool_call_id: tool_call_id, name: name, content: content }) end |
#append_tool_execution(tool_call:, content:) ⇒ Object
109 110 111 |
# File 'lib/kward/conversation.rb', line 109 def append_tool_execution(tool_call:, content:) @on_tool_execution&.call(tool_call, content) end |
#append_user(content, display_content: nil) ⇒ Object
Appends a user message and normalizes image attachment syntax.
display_content is transcript/UI text for cases where the model input is
expanded, decorated, or contains encoded attachment content.
88 89 90 91 92 93 |
# File 'lib/kward/conversation.rb', line 88 def append_user(content, display_content: nil) content = ImageAttachments.content_from_text(content) unless content.is_a?(Array) = { role: "user", content: content } [:display_content] = display_content.to_s unless display_content.nil? () end |
#compact!(summary, compaction_summary: false, first_kept_entry_id: nil, tokens_before: nil, from_hook: false, details: {}, keep_messages: []) ⇒ Object
Replaces most transcript entries with a compaction summary and optional recent messages to keep.
Compaction clears read-before-write state because file contents observed before the summary may no longer be represented exactly in the active context. Callers that need file mutation after compaction should read files again through the normal tools.
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
# File 'lib/kward/conversation.rb', line 163 def compact!(summary, compaction_summary: false, first_kept_entry_id: nil, tokens_before: nil, from_hook: false, details: {}, keep_messages: []) = if compaction_summary { role: "compactionSummary", summary: summary.to_s } else { role: "assistant", content: summary.to_s } end if compaction_summary [:first_kept_entry_id] = first_kept_entry_id if first_kept_entry_id [:tokens_before] = tokens_before if tokens_before [:from_hook] = from_hook [:details] = details || {} end @messages = @messages.select { |item| MessageAccess.role(item) == "system" } @messages << @messages.concat(Array()) @read_paths.clear @last_entry_compaction = true @on_compact&.call() end |
#last_entry_compaction? ⇒ Boolean
184 185 186 |
# File 'lib/kward/conversation.rb', line 184 def last_entry_compaction? @last_entry_compaction end |
#last_file_change_result ⇒ Object
192 193 194 195 196 |
# File 'lib/kward/conversation.rb', line 192 def last_file_change_result @messages.select do || MessageAccess.role() == "tool" && ["write_file", "edit_file"].include?(MessageAccess.name()) end.last end |
#mark_last_entry_compaction! ⇒ Object
188 189 190 |
# File 'lib/kward/conversation.rb', line 188 def mark_last_entry_compaction! @last_entry_compaction = true end |
#mark_read(path) ⇒ Object
145 146 147 |
# File 'lib/kward/conversation.rb', line 145 def mark_read(path) @read_paths << path end |
#persist_runtime_context! ⇒ Object
137 138 139 |
# File 'lib/kward/conversation.rb', line 137 def persist_runtime_context! @on_runtime_update&.call(provider: @provider, model: @model, reasoning_effort: @reasoning_effort) end |
#plugin_prompt_context ⇒ Object
149 150 151 152 153 154 |
# File 'lib/kward/conversation.rb', line 149 def plugin_prompt_context return nil unless plugin_registry context = PluginRegistry::Context.new(conversation: self, workspace_root: @workspace_root) plugin_registry.prompt_context(context) end |
#refresh_system_message! ⇒ Object
Rebuilds the system message from current config, memory, plugins, and workspace AGENTS.md state.
Conversations created with system_message: nil keep system prompts
disabled; this preserves tests, compaction summaries, and imported
transcripts that intentionally do not include runtime instructions.
119 120 121 122 123 124 125 126 127 128 |
# File 'lib/kward/conversation.rb', line 119 def return nil unless @system_message_enabled replacement = Prompts.(workspace_root: @workspace_root, model: @model, reasoning_effort: @reasoning_effort, memory_context: @memory_context, plugin_context: plugin_prompt_context) index = @messages.index { || MessageAccess.role() == "system" } index ? @messages[index] = replacement : @messages.unshift(replacement) @compaction_system_message = Prompts.(workspace_root: @workspace_root, include_workspace_personality: false, model: @model, reasoning_effort: @reasoning_effort) @workspace_agents_mtime = workspace_agents_mtime replacement end |
#refresh_system_message_if_workspace_agents_changed! ⇒ Object
141 142 143 |
# File 'lib/kward/conversation.rb', line 141 def if @system_message_enabled && workspace_agents_mtime != @workspace_agents_mtime end |
#update_runtime_context!(provider: nil, model:, reasoning_effort:) ⇒ Object
130 131 132 133 134 135 |
# File 'lib/kward/conversation.rb', line 130 def update_runtime_context!(provider: nil, model:, reasoning_effort:) @provider = provider unless provider.to_s.empty? @model = model @reasoning_effort = reasoning_effort end |