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 +4 -4
- data/CHANGELOG.md +16 -0
- data/lib/ask/agent/configuration.rb +2 -1
- data/lib/ask/agent/session.rb +101 -0
- data/lib/ask/agent/sub_agent.rb +1 -1
- data/lib/ask/agent/version.rb +1 -1
- data/lib/ask/agent.rb +18 -73
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e97d8067438c6de7e631a5837fc93355b9db6a2b94fd143d0e4421b59c9ec177
|
|
4
|
+
data.tar.gz: 4853729a9c4fa158bca58146b8b9e8305b6264cb1a12fb2b5750d8a2bcc79d03
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
data/lib/ask/agent/session.rb
CHANGED
|
@@ -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
|
data/lib/ask/agent/sub_agent.rb
CHANGED
|
@@ -161,7 +161,7 @@ module Ask
|
|
|
161
161
|
@provider = config[:provider]
|
|
162
162
|
|
|
163
163
|
# Resolve tools from definition
|
|
164
|
-
resolved_tools = Ask::Agent.
|
|
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
|
data/lib/ask/agent/version.rb
CHANGED
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
|
-
|
|
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 =
|
|
342
|
-
|
|
343
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
25
|
+
version: 0.12.0
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: ask-state-providers
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|