ask-decisions 0.2.3 → 0.2.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e94c483511119929cad9bbdf2d0925ca498e4b4f98f1119c05ac3c26a239edfc
4
- data.tar.gz: e09632e3fbaa5f2dca5ffc8692afe586d1818b985db8afaa600f3498e614ab9f
3
+ metadata.gz: e64f40bca45e7b9bc9a11f33fabe1e178c30cfd9e61f4460395b77eb8092cc5a
4
+ data.tar.gz: 4e32792a9c4bdc411731beb94d9a078e98d92f3135aaa4da779090fd6c4f96c9
5
5
  SHA512:
6
- metadata.gz: a55fb6e583a983afd05acde0048d203d2e81c3098ef3cfa9d278214a3e4cd2de47ba2bd0f78fcb572f078da1c246fc4dc2aa22f2d23380cd79dc7d5c5fdbfe70
7
- data.tar.gz: 57e4a24f195eb27e518e88e7340850e27a90499c2a22ac5a3069cca63d787fee3623f350ccf893fb0794f4eb13667656fd82abff369b9ce71c0c62b30f1a0490
6
+ metadata.gz: e6338ccd1b049f19612e7b33d4e7dbedce5192d4789c29ac62bf6fa075107fa7184764b71c3df9e0edc6284e8d9ed0013225d5ee84a6be9858b5d3ca49d2b874
7
+ data.tar.gz: f9c69aca54eaae484d182ab78c7265de776fafff15e5b4813908028dac1857e8df4f85428fc9474282dd54b4418a6c9981e8cb5e76673f028593daff8be3102b
data/CHANGELOG.md CHANGED
@@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
+ ## [0.2.4] - 2026-09-18
8
+
9
+ ### Fixed
10
+
11
+ - **`resolve_provider` memoizes the provider instance.** Every call to
12
+ `resolve_provider` previously created a new `Typesafe` (or other provider)
13
+ object. In a pipeline with 10+ decision calls per cycle, this meant 10+
14
+ redundant allocations with identical configuration. The provider is now
15
+ cached for the lifetime of the process — it holds only read-only state
16
+ (API key, base URL, model, timeout) set once at boot.
17
+
7
18
  ## [0.2.3] - 2026-09-18
8
19
 
9
20
  ### Added
@@ -145,6 +145,8 @@ module Ask
145
145
  end
146
146
 
147
147
  # First message and the most recent +preserve_recent+ messages are pinned.
148
+ # Pinned messages are never removed, but their tool calls are still
149
+ # evaluated by Jev — a recent tool result can still be irrelevant.
148
150
  def compute_pinned(messages)
149
151
  pinned = Set.new([0])
150
152
  start = [messages.size - @preserve_recent, 1].max
@@ -152,6 +154,11 @@ module Ask
152
154
  pinned
153
155
  end
154
156
 
157
+ # Whether a message index is safe to remove (not pinned).
158
+ def removable?(idx, pinned)
159
+ !pinned.include?(idx)
160
+ end
161
+
155
162
  # ── State ────────────────────────────────────────────────────────
156
163
 
157
164
  # Build the state Jev sees: full conversation with tool results replaced
@@ -162,7 +169,6 @@ module Ask
162
169
  pairs.each { |p| result_index[p[:result_msg_idx]] = p }
163
170
 
164
171
  lines = messages.each_with_index.map do |msg, idx|
165
- role = msg[:role] || "unknown"
166
172
  content = msg[:content].to_s
167
173
 
168
174
  if result_index[idx]
@@ -180,7 +186,6 @@ module Ask
180
186
  end
181
187
  end
182
188
 
183
- goal_section = @goal ? "\n\nGoal: #{@goal}" : ""
184
189
  { conversation: lines, goal: @goal }.compact
185
190
  end
186
191
 
@@ -195,6 +200,8 @@ module Ask
195
200
  def build_questions(pairs, pinned)
196
201
  questions = {}
197
202
  pairs.each do |pair|
203
+ # Skip pairs where both the call and result live in pinned messages.
204
+ # Pinned messages are never touched — Jev does not score them.
198
205
  next if pinned.include?(pair[:call_msg_idx]) && pinned.include?(pair[:result_msg_idx])
199
206
 
200
207
  call_id = pair[:call_id]
@@ -312,8 +319,13 @@ module Ask
312
319
 
313
320
  case decision[:action]
314
321
  when :drop
315
- remove_indices.add(pair[:call_msg_idx])
316
- remove_indices.add(pair[:result_msg_idx])
322
+ # Only remove messages that are not pinned.
323
+ if removable?(pair[:call_msg_idx], pinned)
324
+ remove_indices.add(pair[:call_msg_idx])
325
+ end
326
+ if removable?(pair[:result_msg_idx], pinned)
327
+ remove_indices.add(pair[:result_msg_idx])
328
+ end
317
329
  when :truncate
318
330
  truncate_indices[pair[:result_msg_idx]] = pair[:call]
319
331
  end
@@ -342,7 +354,7 @@ module Ask
342
354
  # ── Stats ────────────────────────────────────────────────────────
343
355
 
344
356
  def compute_stats(original, pruned, decisions)
345
- action_counts = decisions.values.tally { |d| d[:action] }
357
+ action_counts = decisions.values.map { |d| d[:action] }.tally
346
358
  original_chars = original.sum { |m| m[:content].to_s.length }
347
359
  pruned_chars = pruned.sum { |m| m[:content].to_s.length }
348
360
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ask
4
4
  module Decisions
5
- VERSION = "0.2.3"
5
+ VERSION = "0.2.4"
6
6
  end
7
7
  end
data/lib/ask-decisions.rb CHANGED
@@ -122,13 +122,19 @@ module Ask
122
122
  end
123
123
 
124
124
  # Resolve a provider by name, raising on unknown.
125
+ # Memoized: the provider is stateless after boot (API key,
126
+ # base URL, model, timeout are all read-only), so one instance
127
+ # per configuration lifetime is safe and avoids redundant
128
+ # allocations across the pipeline.
125
129
  def resolve_provider(name)
126
- klass = Ask::DecisionProvider.resolve(name)
127
-
128
- if configuration.provider_options.any?
129
- klass.new(**configuration.provider_options)
130
- else
131
- klass.new
130
+ @resolved_provider ||= begin
131
+ klass = Ask::DecisionProvider.resolve(name)
132
+
133
+ if configuration.provider_options.any?
134
+ klass.new(**configuration.provider_options)
135
+ else
136
+ klass.new
137
+ end
132
138
  end
133
139
  end
134
140
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ask-decisions
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kaka Ruto