swarm_sdk 2.7.13 → 3.0.0.alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_sdk/ruby_llm_patches/chat_callbacks_patch.rb +43 -22
  3. data/lib/swarm_sdk/ruby_llm_patches/init.rb +6 -0
  4. data/lib/swarm_sdk/ruby_llm_patches/mcp_ssl_patch.rb +144 -0
  5. data/lib/swarm_sdk/ruby_llm_patches/tool_concurrency_patch.rb +3 -4
  6. data/lib/swarm_sdk/v3/agent.rb +1165 -0
  7. data/lib/swarm_sdk/v3/agent_builder.rb +533 -0
  8. data/lib/swarm_sdk/v3/agent_definition.rb +330 -0
  9. data/lib/swarm_sdk/v3/configuration.rb +490 -0
  10. data/lib/swarm_sdk/v3/debug_log.rb +86 -0
  11. data/lib/swarm_sdk/v3/event_stream.rb +130 -0
  12. data/lib/swarm_sdk/v3/hooks/context.rb +112 -0
  13. data/lib/swarm_sdk/v3/hooks/result.rb +115 -0
  14. data/lib/swarm_sdk/v3/hooks/runner.rb +128 -0
  15. data/lib/swarm_sdk/v3/mcp/connector.rb +183 -0
  16. data/lib/swarm_sdk/v3/mcp/mcp_error.rb +15 -0
  17. data/lib/swarm_sdk/v3/mcp/server_definition.rb +125 -0
  18. data/lib/swarm_sdk/v3/mcp/ssl_http_transport.rb +103 -0
  19. data/lib/swarm_sdk/v3/mcp/stdio_transport.rb +135 -0
  20. data/lib/swarm_sdk/v3/mcp/tool_proxy.rb +53 -0
  21. data/lib/swarm_sdk/v3/memory/adapters/base.rb +297 -0
  22. data/lib/swarm_sdk/v3/memory/adapters/faiss_support.rb +194 -0
  23. data/lib/swarm_sdk/v3/memory/adapters/filesystem_adapter.rb +212 -0
  24. data/lib/swarm_sdk/v3/memory/adapters/sqlite_adapter.rb +507 -0
  25. data/lib/swarm_sdk/v3/memory/adapters/vector_utils.rb +88 -0
  26. data/lib/swarm_sdk/v3/memory/card.rb +206 -0
  27. data/lib/swarm_sdk/v3/memory/cluster.rb +146 -0
  28. data/lib/swarm_sdk/v3/memory/compressor.rb +496 -0
  29. data/lib/swarm_sdk/v3/memory/consolidator.rb +427 -0
  30. data/lib/swarm_sdk/v3/memory/context_builder.rb +339 -0
  31. data/lib/swarm_sdk/v3/memory/edge.rb +105 -0
  32. data/lib/swarm_sdk/v3/memory/embedder.rb +185 -0
  33. data/lib/swarm_sdk/v3/memory/exposure_tracker.rb +104 -0
  34. data/lib/swarm_sdk/v3/memory/ingestion_pipeline.rb +394 -0
  35. data/lib/swarm_sdk/v3/memory/retriever.rb +289 -0
  36. data/lib/swarm_sdk/v3/memory/store.rb +489 -0
  37. data/lib/swarm_sdk/v3/skills/loader.rb +147 -0
  38. data/lib/swarm_sdk/v3/skills/manifest.rb +45 -0
  39. data/lib/swarm_sdk/v3/sub_task_agent.rb +248 -0
  40. data/lib/swarm_sdk/v3/tools/base.rb +80 -0
  41. data/lib/swarm_sdk/v3/tools/bash.rb +174 -0
  42. data/lib/swarm_sdk/v3/tools/clock.rb +32 -0
  43. data/lib/swarm_sdk/v3/tools/edit.rb +111 -0
  44. data/lib/swarm_sdk/v3/tools/glob.rb +96 -0
  45. data/lib/swarm_sdk/v3/tools/grep.rb +200 -0
  46. data/lib/swarm_sdk/v3/tools/message_teammate.rb +15 -0
  47. data/lib/swarm_sdk/v3/tools/message_user.rb +15 -0
  48. data/lib/swarm_sdk/v3/tools/read.rb +181 -0
  49. data/lib/swarm_sdk/v3/tools/read_tracker.rb +40 -0
  50. data/lib/swarm_sdk/v3/tools/registry.rb +208 -0
  51. data/lib/swarm_sdk/v3/tools/sub_task.rb +183 -0
  52. data/lib/swarm_sdk/v3/tools/think.rb +88 -0
  53. data/lib/swarm_sdk/v3/tools/write.rb +87 -0
  54. data/lib/swarm_sdk/v3.rb +145 -0
  55. metadata +84 -148
  56. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -175
  57. data/lib/swarm_sdk/agent/builder.rb +0 -680
  58. data/lib/swarm_sdk/agent/chat.rb +0 -1432
  59. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -375
  60. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  61. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  62. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -85
  63. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -290
  64. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  65. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  66. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -134
  67. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  68. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -146
  69. data/lib/swarm_sdk/agent/context.rb +0 -115
  70. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  71. data/lib/swarm_sdk/agent/definition.rb +0 -581
  72. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -226
  73. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -161
  74. data/lib/swarm_sdk/agent/tool_registry.rb +0 -189
  75. data/lib/swarm_sdk/agent_registry.rb +0 -146
  76. data/lib/swarm_sdk/builders/base_builder.rb +0 -553
  77. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  78. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -39
  79. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  80. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  81. data/lib/swarm_sdk/config.rb +0 -367
  82. data/lib/swarm_sdk/configuration/parser.rb +0 -397
  83. data/lib/swarm_sdk/configuration/translator.rb +0 -283
  84. data/lib/swarm_sdk/configuration.rb +0 -165
  85. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  86. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -102
  87. data/lib/swarm_sdk/context_compactor.rb +0 -335
  88. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  89. data/lib/swarm_sdk/context_management/context.rb +0 -328
  90. data/lib/swarm_sdk/custom_tool_registry.rb +0 -226
  91. data/lib/swarm_sdk/defaults.rb +0 -251
  92. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  93. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  94. data/lib/swarm_sdk/hooks/context.rb +0 -197
  95. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  96. data/lib/swarm_sdk/hooks/error.rb +0 -29
  97. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  98. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  99. data/lib/swarm_sdk/hooks/result.rb +0 -150
  100. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -256
  101. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  102. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  103. data/lib/swarm_sdk/log_collector.rb +0 -227
  104. data/lib/swarm_sdk/log_stream.rb +0 -127
  105. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  106. data/lib/swarm_sdk/model_aliases.json +0 -8
  107. data/lib/swarm_sdk/models.json +0 -44002
  108. data/lib/swarm_sdk/models.rb +0 -161
  109. data/lib/swarm_sdk/node_context.rb +0 -245
  110. data/lib/swarm_sdk/observer/builder.rb +0 -81
  111. data/lib/swarm_sdk/observer/config.rb +0 -45
  112. data/lib/swarm_sdk/observer/manager.rb +0 -236
  113. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  114. data/lib/swarm_sdk/permissions/config.rb +0 -239
  115. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  116. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  117. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  118. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  119. data/lib/swarm_sdk/plugin.rb +0 -309
  120. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  121. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  122. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -117
  123. data/lib/swarm_sdk/restore_result.rb +0 -65
  124. data/lib/swarm_sdk/result.rb +0 -212
  125. data/lib/swarm_sdk/snapshot.rb +0 -156
  126. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  127. data/lib/swarm_sdk/state_restorer.rb +0 -476
  128. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  129. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -648
  130. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -195
  131. data/lib/swarm_sdk/swarm/builder.rb +0 -256
  132. data/lib/swarm_sdk/swarm/executor.rb +0 -290
  133. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -151
  134. data/lib/swarm_sdk/swarm/lazy_delegate_chat.rb +0 -372
  135. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -360
  136. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -270
  137. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  138. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -392
  139. data/lib/swarm_sdk/swarm.rb +0 -843
  140. data/lib/swarm_sdk/swarm_loader.rb +0 -145
  141. data/lib/swarm_sdk/swarm_registry.rb +0 -136
  142. data/lib/swarm_sdk/tools/base.rb +0 -63
  143. data/lib/swarm_sdk/tools/bash.rb +0 -280
  144. data/lib/swarm_sdk/tools/clock.rb +0 -46
  145. data/lib/swarm_sdk/tools/delegate.rb +0 -389
  146. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
  147. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
  148. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
  149. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
  150. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
  151. data/lib/swarm_sdk/tools/edit.rb +0 -145
  152. data/lib/swarm_sdk/tools/glob.rb +0 -166
  153. data/lib/swarm_sdk/tools/grep.rb +0 -235
  154. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
  155. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -167
  156. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  157. data/lib/swarm_sdk/tools/mcp_tool_stub.rb +0 -198
  158. data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
  159. data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
  160. data/lib/swarm_sdk/tools/read.rb +0 -261
  161. data/lib/swarm_sdk/tools/registry.rb +0 -205
  162. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
  163. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
  164. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
  165. data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
  166. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -273
  167. data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
  168. data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
  169. data/lib/swarm_sdk/tools/think.rb +0 -100
  170. data/lib/swarm_sdk/tools/todo_write.rb +0 -237
  171. data/lib/swarm_sdk/tools/web_fetch.rb +0 -264
  172. data/lib/swarm_sdk/tools/write.rb +0 -112
  173. data/lib/swarm_sdk/transcript_builder.rb +0 -278
  174. data/lib/swarm_sdk/utils.rb +0 -68
  175. data/lib/swarm_sdk/validation_result.rb +0 -33
  176. data/lib/swarm_sdk/version.rb +0 -5
  177. data/lib/swarm_sdk/workflow/agent_config.rb +0 -95
  178. data/lib/swarm_sdk/workflow/builder.rb +0 -227
  179. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  180. data/lib/swarm_sdk/workflow/node_builder.rb +0 -593
  181. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -250
  182. data/lib/swarm_sdk/workflow.rb +0 -589
  183. data/lib/swarm_sdk.rb +0 -718
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmSDK
4
+ module V3
5
+ module Memory
6
+ # A self-contained memory unit (<=250 words)
7
+ #
8
+ # Cards are the atomic unit of the memory system. Each card captures
9
+ # a single idea, fact, decision, or concept with enough context to
10
+ # be understood in isolation.
11
+ #
12
+ # @example Create a fact card
13
+ # card = Card.new(
14
+ # text: "The API uses JWT tokens for authentication with RS256 signing.",
15
+ # type: :fact,
16
+ # entities: ["API", "JWT", "RS256"],
17
+ # source_turn_ids: ["turn_001"],
18
+ # )
19
+ class Card
20
+ TYPES = [:fact, :concept, :decision, :constraint, :preference, :incident].freeze
21
+
22
+ # @return [String] Unique card identifier (card_<hex>)
23
+ attr_reader :id
24
+
25
+ # @return [String] Card content (<=250 words, self-contained)
26
+ attr_accessor :text
27
+
28
+ # @return [Symbol] Card type (fact, concept, decision, constraint, preference, incident)
29
+ attr_reader :type
30
+
31
+ # @return [Array<String>] Named entities mentioned in the card
32
+ attr_accessor :entities
33
+
34
+ # @return [Array<String>] Turn IDs that contributed to this card
35
+ attr_accessor :source_turn_ids
36
+
37
+ # @return [Array<Float>, nil] Embedding vector (384 dimensions for MiniLM)
38
+ attr_accessor :embedding
39
+
40
+ # @return [Float] Importance score (0.0-1.0)
41
+ attr_accessor :importance
42
+
43
+ # @return [Float] Confidence score (0.0-1.0, default 1.0)
44
+ attr_accessor :confidence
45
+
46
+ # @return [Integer] Number of times this card was retrieved
47
+ attr_accessor :access_count
48
+
49
+ # @return [Time, nil] Last time this card was accessed
50
+ attr_accessor :last_accessed
51
+
52
+ # @return [Float] Dwell time weight (how long card was in context)
53
+ attr_accessor :dwell
54
+
55
+ # @return [Integer] Compression level (0=raw, 1=summary, 2=bullets, 3=schema, 4=embedding-only)
56
+ attr_accessor :compression_level
57
+
58
+ # @return [String, nil] ID of canonical card if this was merged
59
+ attr_accessor :canonical_id
60
+
61
+ # @return [Time] Creation timestamp
62
+ attr_reader :created_at
63
+
64
+ # @return [Time] Last update timestamp
65
+ attr_accessor :updated_at
66
+
67
+ # Create a new memory card
68
+ #
69
+ # @param text [String] Card content
70
+ # @param type [Symbol] Card type
71
+ # @param entities [Array<String>] Named entities
72
+ # @param source_turn_ids [Array<String>] Contributing turn IDs
73
+ # @param id [String, nil] Card ID (auto-generated if nil)
74
+ # @param embedding [Array<Float>, nil] Embedding vector
75
+ # @param importance [Float] Importance score
76
+ # @param confidence [Float] Confidence score (0.0-1.0)
77
+ # @param access_count [Integer] Access count
78
+ # @param last_accessed [Time, nil] Last access time
79
+ # @param dwell [Float] Dwell time weight
80
+ # @param compression_level [Integer] Compression level (0-4)
81
+ # @param canonical_id [String, nil] Canonical card ID
82
+ # @param created_at [Time, nil] Creation time
83
+ # @param updated_at [Time, nil] Update time
84
+ #
85
+ # @raise [ArgumentError] If type is invalid
86
+ def initialize(
87
+ text:,
88
+ type: :fact,
89
+ entities: [],
90
+ source_turn_ids: [],
91
+ id: nil,
92
+ embedding: nil,
93
+ importance: 0.5,
94
+ confidence: 1.0,
95
+ access_count: 0,
96
+ last_accessed: nil,
97
+ dwell: 0.0,
98
+ compression_level: 0,
99
+ canonical_id: nil,
100
+ created_at: nil,
101
+ updated_at: nil
102
+ )
103
+ @id = id || "card_#{SecureRandom.hex(6)}"
104
+ @text = text
105
+ @type = type.to_sym
106
+ @entities = Array(entities)
107
+ @source_turn_ids = Array(source_turn_ids)
108
+ @embedding = embedding
109
+ @importance = importance.to_f
110
+ @confidence = confidence.to_f
111
+ @access_count = access_count.to_i
112
+ @last_accessed = last_accessed
113
+ @dwell = dwell.to_f
114
+ @compression_level = compression_level.to_i
115
+ @canonical_id = canonical_id
116
+ @created_at = created_at || Time.now
117
+ @updated_at = updated_at || Time.now
118
+
119
+ validate!
120
+ end
121
+
122
+ # Record an access to this card (included in working context)
123
+ #
124
+ # Increments access count, updates last accessed time, and
125
+ # increases dwell weight to track how often this card has
126
+ # been included in the working context.
127
+ #
128
+ # @param dwell_increment [Float] Amount to add to dwell (default: 1.0)
129
+ # @return [void]
130
+ def record_access!(dwell_increment: 1.0)
131
+ @access_count += 1
132
+ @last_accessed = Time.now
133
+ @dwell += dwell_increment
134
+ @updated_at = Time.now
135
+ end
136
+
137
+ # Whether this card has been merged into a canonical card
138
+ #
139
+ # @return [Boolean]
140
+ def merged?
141
+ !@canonical_id.nil?
142
+ end
143
+
144
+ # Serialize to a hash for JSON storage
145
+ #
146
+ # @return [Hash] Serializable representation
147
+ def to_h
148
+ {
149
+ id: @id,
150
+ text: @text,
151
+ type: @type.to_s,
152
+ entities: @entities,
153
+ source_turn_ids: @source_turn_ids,
154
+ embedding: @embedding,
155
+ importance: @importance,
156
+ confidence: @confidence,
157
+ access_count: @access_count,
158
+ last_accessed: @last_accessed&.iso8601,
159
+ dwell: @dwell,
160
+ compression_level: @compression_level,
161
+ canonical_id: @canonical_id,
162
+ created_at: @created_at.iso8601,
163
+ updated_at: @updated_at.iso8601,
164
+ }
165
+ end
166
+
167
+ class << self
168
+ # Deserialize from a hash
169
+ #
170
+ # @param hash [Hash] Serialized card data
171
+ # @return [Card]
172
+ def from_h(hash)
173
+ hash = hash.transform_keys(&:to_sym)
174
+ new(
175
+ id: hash[:id],
176
+ text: hash[:text],
177
+ type: hash[:type]&.to_sym || :fact,
178
+ entities: hash[:entities] || [],
179
+ source_turn_ids: hash[:source_turn_ids] || [],
180
+ embedding: hash[:embedding],
181
+ importance: hash[:importance] || 0.5,
182
+ confidence: hash[:confidence] || 1.0,
183
+ access_count: hash[:access_count] || 0,
184
+ last_accessed: hash[:last_accessed] ? Time.parse(hash[:last_accessed]) : nil,
185
+ dwell: hash[:dwell] || 0.0,
186
+ compression_level: hash[:compression_level] || 0,
187
+ canonical_id: hash[:canonical_id],
188
+ created_at: hash[:created_at] ? Time.parse(hash[:created_at]) : nil,
189
+ updated_at: hash[:updated_at] ? Time.parse(hash[:updated_at]) : nil,
190
+ )
191
+ end
192
+ end
193
+
194
+ private
195
+
196
+ # @raise [ArgumentError] If type is invalid
197
+ def validate!
198
+ raise ArgumentError, "Invalid card type: #{@type}. Must be one of: #{TYPES.join(", ")}" unless TYPES.include?(@type)
199
+ raise ArgumentError, "Card text is required" if @text.nil? || @text.strip.empty?
200
+ raise ArgumentError, "Confidence must be 0.0-1.0" unless (0.0..1.0).cover?(@confidence)
201
+ raise ArgumentError, "Compression level must be 0-4" unless (0..4).cover?(@compression_level)
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmSDK
4
+ module V3
5
+ module Memory
6
+ # A topic cluster grouping related memory cards
7
+ #
8
+ # Clusters provide a higher-level organization of memory cards
9
+ # by topic. Each cluster maintains a rolling summary and tracks
10
+ # its member cards.
11
+ #
12
+ # @example Create a cluster
13
+ # cluster = Cluster.new(
14
+ # title: "Authentication System",
15
+ # rolling_summary: "The project uses JWT-based auth with RS256 signing...",
16
+ # card_ids: ["card_a1b2c3", "card_d4e5f6"],
17
+ # )
18
+ class Cluster
19
+ # @return [String] Unique cluster identifier
20
+ attr_reader :id
21
+
22
+ # @return [String] Cluster title/topic
23
+ attr_accessor :title
24
+
25
+ # @return [Array<Float>, nil] Cluster centroid embedding
26
+ attr_accessor :embedding
27
+
28
+ # @return [String] Rolling summary of cluster contents
29
+ attr_accessor :rolling_summary
30
+
31
+ # @return [Array<String>] Decision log entries
32
+ attr_accessor :decision_log
33
+
34
+ # @return [Array<String>] Key entities in this cluster
35
+ attr_accessor :key_entities
36
+
37
+ # @return [Array<String>] Member card IDs
38
+ attr_accessor :card_ids
39
+
40
+ # @return [Time] Creation timestamp
41
+ attr_reader :created_at
42
+
43
+ # @return [Time] Last update timestamp
44
+ attr_accessor :updated_at
45
+
46
+ # Create a new cluster
47
+ #
48
+ # @param title [String] Cluster title
49
+ # @param id [String, nil] Cluster ID (auto-generated if nil)
50
+ # @param embedding [Array<Float>, nil] Centroid embedding
51
+ # @param rolling_summary [String] Summary of contents
52
+ # @param decision_log [Array<String>] Decision entries
53
+ # @param key_entities [Array<String>] Key entities
54
+ # @param card_ids [Array<String>] Member card IDs
55
+ # @param created_at [Time, nil] Creation time
56
+ # @param updated_at [Time, nil] Update time
57
+ def initialize(
58
+ title:,
59
+ id: nil,
60
+ embedding: nil,
61
+ rolling_summary: "",
62
+ decision_log: [],
63
+ key_entities: [],
64
+ card_ids: [],
65
+ created_at: nil,
66
+ updated_at: nil
67
+ )
68
+ @id = id || "cluster_#{SecureRandom.hex(6)}"
69
+ @title = title
70
+ @embedding = embedding
71
+ @rolling_summary = rolling_summary
72
+ @decision_log = Array(decision_log)
73
+ @key_entities = Array(key_entities)
74
+ @card_ids = Array(card_ids)
75
+ @created_at = created_at || Time.now
76
+ @updated_at = updated_at || Time.now
77
+ end
78
+
79
+ # Add a card to this cluster
80
+ #
81
+ # @param card_id [String] Card ID to add
82
+ # @return [void]
83
+ def add_card(card_id)
84
+ return if @card_ids.include?(card_id)
85
+
86
+ @card_ids << card_id
87
+ @updated_at = Time.now
88
+ end
89
+
90
+ # Remove a card from this cluster
91
+ #
92
+ # @param card_id [String] Card ID to remove
93
+ # @return [void]
94
+ def remove_card(card_id)
95
+ @card_ids.delete(card_id)
96
+ @updated_at = Time.now
97
+ end
98
+
99
+ # Number of cards in this cluster
100
+ #
101
+ # @return [Integer]
102
+ def size
103
+ @card_ids.size
104
+ end
105
+
106
+ # Serialize to a hash
107
+ #
108
+ # @return [Hash]
109
+ def to_h
110
+ {
111
+ id: @id,
112
+ title: @title,
113
+ embedding: @embedding,
114
+ rolling_summary: @rolling_summary,
115
+ decision_log: @decision_log,
116
+ key_entities: @key_entities,
117
+ card_ids: @card_ids,
118
+ created_at: @created_at.iso8601,
119
+ updated_at: @updated_at.iso8601,
120
+ }
121
+ end
122
+
123
+ class << self
124
+ # Deserialize from a hash
125
+ #
126
+ # @param hash [Hash] Serialized cluster data
127
+ # @return [Cluster]
128
+ def from_h(hash)
129
+ hash = hash.transform_keys(&:to_sym)
130
+ new(
131
+ id: hash[:id],
132
+ title: hash[:title],
133
+ embedding: hash[:embedding],
134
+ rolling_summary: hash[:rolling_summary] || "",
135
+ decision_log: hash[:decision_log] || [],
136
+ key_entities: hash[:key_entities] || [],
137
+ card_ids: hash[:card_ids] || [],
138
+ created_at: hash[:created_at] ? Time.parse(hash[:created_at]) : nil,
139
+ updated_at: hash[:updated_at] ? Time.parse(hash[:updated_at]) : nil,
140
+ )
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end