ask-agent 0.40.16 → 0.40.18

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: c21109424c4b8f3216504c2a4aa1174e29b7fd9542436969a09d5281f9fdeb83
4
- data.tar.gz: 024ea7d9470db47178237c36f5b2a36eb71ffedad9754a482e0f6aa6667f39e9
3
+ metadata.gz: e97d8067438c6de7e631a5837fc93355b9db6a2b94fd143d0e4421b59c9ec177
4
+ data.tar.gz: 4853729a9c4fa158bca58146b8b9e8305b6264cb1a12fb2b5750d8a2bcc79d03
5
5
  SHA512:
6
- metadata.gz: 8ffb7ad67edcf4cca883837f1bf69dd1d9cecfc7b55a036c1a0b926d114e2cf1ee240fe369e671c2fc5b84183412fc7627648add81b014fe24a33a5377e67b60
7
- data.tar.gz: 93379d0652c02ee137d46c97e1ea4b31098b57b2f912617bf0de66ba6044216f2011e18c665d959939ad8da7823293baadc74fab2e04adc7672313f722e1bffb
6
+ metadata.gz: 75def0a97595224d12c77fdbfadb875608c11afc0247b10e37058de1d1004f37addc1eb1ccbe64dd1c28a0a0f712ae024efe42d5372341a4876b6e8b32f5b0eb
7
+ data.tar.gz: 526100d9bc12da6404419c2ab21b64b73f735e0b5c773ced77816eb231c23f0816cce4a9b344108a542fc3b50f8b84781bb81244dc6e991c89435e26e0ef4920
data/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ ## [0.40.17] — 2026-09-18
2
+
3
+ ### Added
4
+
5
+ - **`decision_provider` — a session can take a decision provider, and its
6
+ tool calls get judged.** Set it globally
7
+ (`Ask::Agent.configure { |c| c.decision_provider = :typesafe }`) or per
8
+ session. `Ask::Decisions::AgentAdapter` then wires a gate in front of every
9
+ tool call and an output judge behind it; ask-agent itself never requires
10
+ ask-decisions, and with the setting unset nothing changes at all.
11
+
12
+ ### Changed
13
+
14
+ - Requires `ask-core >= 0.12.0` for the decision vocabulary.
15
+
16
+
1
17
  ## [0.40.10] — 2026-09-05
2
18
 
3
19
  ### Fixed
@@ -6,7 +6,8 @@ module Ask
6
6
  attr_accessor :default_model, :default_provider, :default_max_turns,
7
7
  :compactor_enabled, :compactor_threshold, :parallel_tool_execution,
8
8
  :max_tool_retries, :prompt_caching, :default_evaluator_model,
9
- :audit_log, :compactor_reserve_tokens, :compactor_keep_recent_tokens
9
+ :audit_log, :compactor_reserve_tokens, :compactor_keep_recent_tokens,
10
+ :decision_provider
10
11
 
11
12
  # @return [Middleware::Pipeline] the middleware pipeline for provider calls
12
13
  attr_reader :middleware
@@ -20,6 +20,60 @@ module Ask
20
20
  attr_reader :meta_agent_results
21
21
  # @return [Ask::Skills::Registry, nil] auto-discovered skills registry
22
22
  attr_reader :skills_registry
23
+ # @return [Ask::Decisions::AgentAdapter, nil] decision adapter (when decision_provider is set)
24
+ attr_reader :decision_adapter
25
+
26
+ # Build a Session from a discovered Definition class and directory.
27
+ #
28
+ # This is the shared implementation used by both Ask::Agent.new(name)
29
+ # and Session.new(name:). It resolves the definition's config, tools,
30
+ # and instructions, then applies any caller-supplied overrides.
31
+ #
32
+ # @param klass [Class] the Definition subclass
33
+ # @param dir [String] the agent directory path
34
+ # @param opts [Hash] runtime overrides (model, provider, tools, system_prompt, etc.)
35
+ # @return [Session]
36
+ def self.build_from_definition(klass, dir, opts = {})
37
+ config = klass._config
38
+ session_opts = { model: config[:model] || Ask::Agent.configuration.default_model }
39
+
40
+ # Pass optional config
41
+ session_opts[:provider] = config[:provider] if config[:provider]
42
+ session_opts[:max_turns] = config[:max_turns] if config[:max_turns]
43
+ session_opts[:parallel_tools] = config[:parallel_tools] if config.key?(:parallel_tools)
44
+ session_opts[:skills_disclosure] = config[:skills_disclosure] if config.key?(:skills_disclosure)
45
+
46
+ # Pass arbitrary session options
47
+ if config[:options]
48
+ session_opts.merge!(config[:options])
49
+ end
50
+
51
+ # Pass agent directory for per-agent skills discovery
52
+ session_opts[:agent_dir] = dir
53
+
54
+ # Resolve tools
55
+ tools = resolve_definition_tools(config[:tools], dir)
56
+ session_opts[:tools] = tools if tools.any?
57
+
58
+ # Load instructions
59
+ prompt = klass.instructions_content
60
+ session_opts[:system_prompt] = prompt if prompt
61
+
62
+ # Apply schedule if defined
63
+ schedule = config[:schedule]
64
+ if schedule
65
+ task_block = ->(_sess = nil) {
66
+ agent = build_from_definition(klass, dir)
67
+ agent.run("")
68
+ }
69
+ Ask::Agent.configuration.scheduler.every(schedule, name: File.basename(dir), &task_block)
70
+ end
71
+
72
+ # Caller-supplied runtime options win over the definition's config.
73
+ session_opts.merge!(opts) unless opts.empty?
74
+
75
+ new(**session_opts)
76
+ end
23
77
 
24
78
  def initialize(model:, tools: [], max_turns: 25, max_tool_retries: 3,
25
79
  compactor: nil, hooks: {}, state: nil, persistence: nil,
@@ -151,6 +205,25 @@ module Ask
151
205
  )
152
206
  end
153
207
 
208
+ # Decision provider integration: wire Gate, OutputJudge, and
209
+ # other decision components when a decision_provider is configured.
210
+ # This is opt-in — when not set, everything works as before.
211
+ decision_provider_name = chat_options.delete(:decision_provider) || Ask::Agent.configuration.decision_provider
212
+ decision_config = chat_options.delete(:decision_config) || {}
213
+ @decision_adapter = nil
214
+ if decision_provider_name && defined?(Ask::Decisions)
215
+ require "ask/decisions/agent_adapter" unless defined?(Ask::Decisions::AgentAdapter)
216
+ @decision_adapter = Ask::Decisions::AgentAdapter.new(decision_provider_name, decision_config)
217
+
218
+ # Wire Gate (before_tool) and OutputJudge (after_tool) into hooks.
219
+ existing_before = Array(@hooks.instance_variable_get(:@before_tool))
220
+ existing_after = Array(@hooks.instance_variable_get(:@after_tool))
221
+ @hooks = Hooks.new(
222
+ before_tool: @decision_adapter.before_tool_hooks + existing_before,
223
+ after_tool: existing_after + @decision_adapter.after_tool_hooks
224
+ )
225
+ end
226
+
154
227
  @system_context = build_system_context(system_prompt)
155
228
  apply_system_context
156
229
 
@@ -1127,6 +1200,34 @@ end
1127
1200
 
1128
1201
  # Recursively convert string keys to symbol keys in hashes.
1129
1202
  # Needed when loading session data that was serialized through JSON.
1203
+ # Resolve tool specs (symbols, strings, or classes) from a Definition
1204
+ # into instantiated tool objects. Symbols are looked up in the agent's
1205
+ # per-agent tools/ directory first, then shared tools, then the global
1206
+ # Ask::Tools registry.
1207
+ def self.resolve_definition_tools(tool_specs, dir)
1208
+ tools = []
1209
+ tool_specs.each do |spec|
1210
+ case spec
1211
+ when Symbol, String
1212
+ name = spec.to_s
1213
+ # Try per-agent tools directory
1214
+ agent_tool_path = File.join(dir, "tools", "#{name}.rb")
1215
+ if File.exist?(agent_tool_path)
1216
+ require agent_tool_path
1217
+ end
1218
+
1219
+ resolved = Ask::Agent.resolve_tool_symbol(name)
1220
+ if resolved
1221
+ tool_class = resolved.is_a?(Class) ? resolved : Ask::Tools[name]
1222
+ tools << tool_class if tool_class
1223
+ end
1224
+ when Class
1225
+ tools << spec
1226
+ end
1227
+ end
1228
+ tools
1229
+ end
1230
+
1130
1231
  def self.deep_symbolize_keys(obj)
1131
1232
  case obj
1132
1233
  when Hash
@@ -161,7 +161,7 @@ module Ask
161
161
  @provider = config[:provider]
162
162
 
163
163
  # Resolve tools from definition
164
- resolved_tools = Ask::Agent.__send__(:resolve_definition_tools, config[:tools] || [], dir)
164
+ resolved_tools = Ask::Agent::Session.resolve_definition_tools(config[:tools] || [], dir)
165
165
  @tools = resolved_tools.map { |t| t.is_a?(Class) ? t.new : t }
166
166
 
167
167
  # Load instructions from definition
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ask
4
4
  module Agent
5
- VERSION = "0.40.16"
5
+ VERSION = "0.40.18"
6
6
  end
7
7
  end
data/lib/ask/agent.rb CHANGED
@@ -94,7 +94,7 @@ module Ask
94
94
  raise UnknownAgent, "Unknown agent: #{name.inspect}. Searched agents/ and app/agents/." unless entry
95
95
 
96
96
  klass, dir = entry
97
- build_session_from_definition(klass, dir, opts)
97
+ Session.build_from_definition(klass, dir, opts)
98
98
  end
99
99
 
100
100
  # Force re-discovery of agent definitions.
@@ -188,73 +188,6 @@ module Ask
188
188
  @shared_tools[tool_name] = tool_name
189
189
  end
190
190
  end
191
-
192
- def build_session_from_definition(klass, dir, opts = {})
193
- config = klass._config
194
- session_opts = { model: config[:model] || Ask::Agent.configuration.default_model }
195
-
196
- # Pass optional config
197
- session_opts[:provider] = config[:provider] if config[:provider]
198
- session_opts[:max_turns] = config[:max_turns] if config[:max_turns]
199
- session_opts[:parallel_tools] = config[:parallel_tools] if config.key?(:parallel_tools)
200
- session_opts[:skills_disclosure] = config[:skills_disclosure] if config.key?(:skills_disclosure)
201
-
202
- # Pass arbitrary session options
203
- if config[:options]
204
- session_opts.merge!(config[:options])
205
- end
206
-
207
- # Pass agent directory for per-agent skills discovery
208
- session_opts[:agent_dir] = dir
209
-
210
- # Resolve tools
211
- tools = resolve_definition_tools(config[:tools], dir)
212
- session_opts[:tools] = tools if tools.any?
213
-
214
- # Load instructions
215
- prompt = klass.instructions_content
216
- session_opts[:system_prompt] = prompt if prompt
217
-
218
- # Apply schedule if defined
219
- schedule = config[:schedule]
220
- if schedule
221
- task_block = ->(sess = nil) {
222
- agent = build_session_from_definition(klass, dir)
223
- agent.run("")
224
- }
225
- Ask::Agent.configuration.scheduler.every(schedule, name: File.basename(dir), &task_block)
226
- end
227
-
228
- # Caller-supplied runtime options (model/provider/api_key/api_base/...)
229
- # win over the definition's config.
230
- session_opts.merge!(opts) unless opts.empty?
231
-
232
- Session.new(**session_opts)
233
- end
234
-
235
- def resolve_definition_tools(tool_specs, dir)
236
- tools = []
237
- tool_specs.each do |spec|
238
- case spec
239
- when Symbol, String
240
- name = spec.to_s
241
- # Try per-agent tools directory
242
- agent_tool_path = File.join(dir, "tools", "#{name}.rb")
243
- if File.exist?(agent_tool_path)
244
- require agent_tool_path
245
- end
246
-
247
- resolved = resolve_tool_symbol(name)
248
- if resolved
249
- tool_class = resolved.is_a?(Class) ? resolved : Ask::Tools[name]
250
- tools << tool_class if tool_class
251
- end
252
- when Class
253
- tools << spec
254
- end
255
- end
256
- tools
257
- end
258
191
  end
259
192
 
260
193
  # Register the global config
@@ -336,12 +269,24 @@ end
336
269
  # Ask.chat("Tell me about X", model: "gpt-4o")
337
270
  # Ask.chat("Stream this") { |chunk| puts chunk.content }
338
271
  #
272
+ # With a named definition:
273
+ #
274
+ # Ask.chat("Check health", name: "health_check")
275
+ # Ask.chat("Check health", name: "health_check", model: "claude-sonnet-4")
276
+ #
339
277
  module Ask
340
- def self.chat(message, model: nil, system_prompt: nil, &block)
341
- session = Agent::Session.new(
342
- model: model || Agent.configuration.default_model,
343
- system_prompt: system_prompt
344
- )
278
+ def self.chat(message, name: nil, model: nil, system_prompt: nil, &block)
279
+ session = if name
280
+ overrides = {}
281
+ overrides[:model] = model if model
282
+ overrides[:system_prompt] = system_prompt if system_prompt
283
+ Agent.new(name, **overrides)
284
+ else
285
+ Agent::Session.new(
286
+ model: model || Agent.configuration.default_model,
287
+ system_prompt: system_prompt
288
+ )
289
+ end
345
290
  if block
346
291
  session.run(message, &block)
347
292
  else
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ask-agent
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.40.16
4
+ version: 0.40.18
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kaka Ruto
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: 0.11.4
18
+ version: 0.12.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
- version: 0.11.4
25
+ version: 0.12.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: ask-state-providers
28
28
  requirement: !ruby/object:Gem::Requirement