ask-agent 0.27.1 → 0.29.0
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 +46 -0
- data/lib/ask/agent/events.rb +3 -0
- data/lib/ask/agent/loop.rb +44 -2
- data/lib/ask/agent/{extensions → policies}/approval_policy.rb +2 -2
- data/lib/ask/agent/{extensions → policies}/audit_log/active_record_writer.rb +1 -1
- data/lib/ask/agent/{extensions → policies}/audit_log.rb +4 -4
- data/lib/ask/agent/{extensions → policies}/permissions.rb +1 -1
- data/lib/ask/agent/{extensions → policies}/rate_limiter.rb +1 -1
- data/lib/ask/agent/session.rb +10 -5
- data/lib/ask/agent/tool_call_repair.rb +130 -0
- data/lib/ask/agent/version.rb +1 -1
- data/lib/ask/agent.rb +18 -7
- metadata +7 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 11cf486fa2d955d92cc6f8268f30e5a418b6f0567cdfb6fe6eac015a3352c74b
|
|
4
|
+
data.tar.gz: 5b4990efc0b66de943f6e5c3b7940d31cca6295c54c40fafef485c6004433601
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3adf87ccd9b0cbb606133cb91b8baf9e24a328f96e4ed8f77266258883b13663762cbd16a3e1e34475ec0b93ab763518c4b661fa675fba37985144c01435a736
|
|
7
|
+
data.tar.gz: b6093801427cf4c8bc56fd9b6b0bf6f9fd6bc5252ce336731149413984b5f83862e4cfeae2e9705b2517bb73e3063091950293f74cdb32109624965e52ccc760
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,49 @@
|
|
|
1
|
+
## [0.29.0] — 2026-08-06
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- **Tool-call repair** — malformed tool calls get one internal LLM round-trip
|
|
6
|
+
to fix them before execution. When a model emits a call with unparseable
|
|
7
|
+
arguments or an unknown tool name, the loop asks the model to re-emit it
|
|
8
|
+
corrected and executes the corrected version instead of burning a turn on
|
|
9
|
+
the error. Enable with `Session.new(tool_call_repair: true)` (built-in
|
|
10
|
+
repair prompt) or pass a callable for full control:
|
|
11
|
+
`Session.new(tool_call_repair: ->(chat, calls, tools) { ... })`.
|
|
12
|
+
- Corrections are remapped to the original call ids, so tool results stay
|
|
13
|
+
consistent with the conversation history; the internal repair exchange
|
|
14
|
+
is stripped from history.
|
|
15
|
+
- Calls the model cannot correct are dropped (the model saw them in the
|
|
16
|
+
repair prompt); repair is best-effort — a failing round-trip drops the
|
|
17
|
+
malformed calls instead of failing the turn.
|
|
18
|
+
- `Events::ToolCallRepaired` fires with name, id, original and corrected
|
|
19
|
+
arguments.
|
|
20
|
+
|
|
21
|
+
## [0.28.0] — 2026-08-06
|
|
22
|
+
|
|
23
|
+
### Changed (breaking)
|
|
24
|
+
|
|
25
|
+
- **`Ask::Agent::Extensions` is now `Ask::Agent::Policies`.** The
|
|
26
|
+
tool-lifecycle policy classes — `ApprovalPolicy`, `Permissions`,
|
|
27
|
+
`RateLimiter`, `AuditLog` — moved from `lib/ask/agent/extensions/` to
|
|
28
|
+
`lib/ask/agent/policies/` and are namespaced under `Ask::Agent::Policies`.
|
|
29
|
+
The folder is now named after its seam (like `middleware`,
|
|
30
|
+
`stream_transforms`, `persistence`) instead of "extra stuff".
|
|
31
|
+
`Ask::Agent.load_extensions` → `Ask::Agent.load_policies`.
|
|
32
|
+
Update references: `Ask::Agent::Extensions::X` → `Ask::Agent::Policies::X`.
|
|
33
|
+
|
|
34
|
+
**What this means for the taxonomy:** policies are opt-in, replaceable
|
|
35
|
+
implementations of the tool-lifecycle hook seam — the agent loop runs
|
|
36
|
+
without them, and users can swap in their own implementations. Core
|
|
37
|
+
mechanisms are unchanged and stay on `Session`: the approval queue, the
|
|
38
|
+
`:pending` result status, and the `approval: true` option are core;
|
|
39
|
+
`Policies::ApprovalPolicy` is the reference classification policy wired on
|
|
40
|
+
top of them.
|
|
41
|
+
|
|
42
|
+
### Added
|
|
43
|
+
|
|
44
|
+
- **`Ask::Agent.load_policies`** — replaces `load_extensions` (same
|
|
45
|
+
behavior: eagerly requires every policy in the policies directory).
|
|
46
|
+
|
|
1
47
|
## [0.27.1] — 2026-08-06
|
|
2
48
|
|
|
3
49
|
### Fixed
|
data/lib/ask/agent/events.rb
CHANGED
|
@@ -16,6 +16,9 @@ module Ask
|
|
|
16
16
|
MessageEnd = Data.define(:tool_calls)
|
|
17
17
|
|
|
18
18
|
ToolExecutionStart = Data.define(:name, :arguments, :id)
|
|
19
|
+
# Emitted when a malformed tool call (unparseable arguments or unknown
|
|
20
|
+
# tool) was repaired by the model before execution.
|
|
21
|
+
ToolCallRepaired = Data.define(:name, :id, :original_arguments, :corrected_arguments)
|
|
19
22
|
# Emitted when a tool returns Ask::Result.pending (async work started).
|
|
20
23
|
ToolPending = Data.define(:name, :id)
|
|
21
24
|
# Emitted when a pending (async) tool's background work completes.
|
data/lib/ask/agent/loop.rb
CHANGED
|
@@ -17,7 +17,7 @@ module Ask
|
|
|
17
17
|
@max_consecutive_tool_turns = max_consecutive_tool_turns
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
def run_turn(chat:, message:, tools:, tool_executor:, compactor:, hooks:, event_emitter:, session_id: nil, persist: nil)
|
|
20
|
+
def run_turn(chat:, message:, tools:, tool_executor:, compactor:, hooks:, event_emitter:, session_id: nil, persist: nil, tool_call_repair: nil)
|
|
21
21
|
raise MaxTurnsExceeded if @turn_count >= @max_turns
|
|
22
22
|
|
|
23
23
|
event_emitter.emit(Events::TurnStart.new)
|
|
@@ -86,6 +86,10 @@ module Ask
|
|
|
86
86
|
user_tool_calls = response.tool_calls.reject { |id, _| provider_results.key?(id) }
|
|
87
87
|
|
|
88
88
|
if user_tool_calls.any?
|
|
89
|
+
# Repair malformed calls (unparseable arguments, unknown tool
|
|
90
|
+
# names) with one internal LLM round-trip before executing.
|
|
91
|
+
user_tool_calls = repair_malformed_calls(user_tool_calls, tools, chat, event_emitter, tool_call_repair)
|
|
92
|
+
|
|
89
93
|
# Execute user tool calls locally
|
|
90
94
|
# Respect the session's parallel_tools setting: parallel tools run
|
|
91
95
|
# in threads (with the caller's thread-local context inherited);
|
|
@@ -162,7 +166,8 @@ module Ask
|
|
|
162
166
|
hooks: hooks,
|
|
163
167
|
event_emitter: event_emitter,
|
|
164
168
|
session_id: session_id,
|
|
165
|
-
persist: persist
|
|
169
|
+
persist: persist,
|
|
170
|
+
tool_call_repair: tool_call_repair
|
|
166
171
|
)
|
|
167
172
|
end
|
|
168
173
|
|
|
@@ -174,6 +179,43 @@ module Ask
|
|
|
174
179
|
|
|
175
180
|
private
|
|
176
181
|
|
|
182
|
+
# Repair malformed tool calls before execution. Calls with corrected
|
|
183
|
+
# versions execute in place of the originals (same ids); calls the
|
|
184
|
+
# model could not correct are dropped — the model saw them in the
|
|
185
|
+
# repair prompt. Best-effort: a failing repair round-trip drops the
|
|
186
|
+
# malformed calls instead of failing the turn.
|
|
187
|
+
def repair_malformed_calls(user_tool_calls, tools, chat, event_emitter, tool_call_repair)
|
|
188
|
+
return user_tool_calls if tool_call_repair.nil? || user_tool_calls.empty?
|
|
189
|
+
|
|
190
|
+
repairer = if tool_call_repair == true
|
|
191
|
+
@tool_call_repair ||= ToolCallRepair.new
|
|
192
|
+
elsif tool_call_repair.respond_to?(:call)
|
|
193
|
+
ToolCallRepair.new(tool_call_repair)
|
|
194
|
+
end
|
|
195
|
+
return user_tool_calls unless repairer
|
|
196
|
+
|
|
197
|
+
malformed, _valid = user_tool_calls.partition do |_id, tc|
|
|
198
|
+
ToolCallRepair.repair_info(tc, tools)
|
|
199
|
+
end
|
|
200
|
+
return user_tool_calls if malformed.empty?
|
|
201
|
+
|
|
202
|
+
corrections = repairer.call(chat: chat, calls: malformed.to_h, tools: tools)
|
|
203
|
+
|
|
204
|
+
corrections.each do |id, corrected|
|
|
205
|
+
event_emitter.emit(Events::ToolCallRepaired.new(
|
|
206
|
+
name: corrected.name,
|
|
207
|
+
id: id,
|
|
208
|
+
original_arguments: user_tool_calls[id].arguments,
|
|
209
|
+
corrected_arguments: corrected.arguments
|
|
210
|
+
))
|
|
211
|
+
user_tool_calls[id] = corrected
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
malformed_ids = malformed.map(&:first)
|
|
215
|
+
user_tool_calls.reject! { |id, _| malformed_ids.include?(id) && !corrections.key?(id) }
|
|
216
|
+
user_tool_calls
|
|
217
|
+
end
|
|
218
|
+
|
|
177
219
|
# Whether the session asked the loop to stop (barge-in). Emitters that
|
|
178
220
|
# don't support aborting (plain stubs) are treated as never aborted.
|
|
179
221
|
def aborted?(event_emitter)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Ask
|
|
4
4
|
module Agent
|
|
5
|
-
module
|
|
5
|
+
module Policies
|
|
6
6
|
# Approval policy hook: classifies tool calls as approval-required and
|
|
7
7
|
# routes them into an {Ask::Agent::ApprovalQueue}.
|
|
8
8
|
#
|
|
@@ -15,7 +15,7 @@ module Ask
|
|
|
15
15
|
#
|
|
16
16
|
# @example
|
|
17
17
|
# queue = Ask::Agent::ApprovalQueue.new
|
|
18
|
-
# policy = Ask::Agent::
|
|
18
|
+
# policy = Ask::Agent::Policies::ApprovalPolicy.new(queue: queue)
|
|
19
19
|
# session = Ask::Agent::Session.new(
|
|
20
20
|
# model: "gpt-4o",
|
|
21
21
|
# tools: [SendEmail],
|
|
@@ -5,7 +5,7 @@ require "time"
|
|
|
5
5
|
|
|
6
6
|
module Ask
|
|
7
7
|
module Agent
|
|
8
|
-
module
|
|
8
|
+
module Policies
|
|
9
9
|
# Event-driven audit log for agent sessions.
|
|
10
10
|
#
|
|
11
11
|
# Subscribes to all session events and writes them to a configurable
|
|
@@ -93,16 +93,16 @@ module Ask
|
|
|
93
93
|
|
|
94
94
|
case adapter
|
|
95
95
|
when :active_record
|
|
96
|
-
require "ask/agent/
|
|
96
|
+
require "ask/agent/policies/audit_log/active_record_writer"
|
|
97
97
|
AuditLog::ActiveRecordWriter.new
|
|
98
98
|
when Hash
|
|
99
99
|
resolve(adapter[:adapter] || adapter[:writer])
|
|
100
100
|
when Symbol, String
|
|
101
101
|
# Try to load adapter by convention:
|
|
102
|
-
# :active_record → ask/agent/
|
|
102
|
+
# :active_record → ask/agent/policies/audit_log/active_record_writer
|
|
103
103
|
name = adapter.to_s
|
|
104
104
|
begin
|
|
105
|
-
require "ask/agent/
|
|
105
|
+
require "ask/agent/policies/audit_log/#{name}_writer"
|
|
106
106
|
klass_name = name.split("_").map(&:capitalize).join
|
|
107
107
|
klass = AuditLog.const_get(klass_name)
|
|
108
108
|
klass.new
|
data/lib/ask/agent/session.rb
CHANGED
|
@@ -22,7 +22,8 @@ module Ask
|
|
|
22
22
|
id: nil, system_prompt: nil, parallel_tools: true,
|
|
23
23
|
reflector: nil, telemetry: true, meta_agent: nil,
|
|
24
24
|
agent_dir: nil, evaluator: nil, audit_log: nil,
|
|
25
|
-
skills_disclosure: true, approval: nil,
|
|
25
|
+
skills_disclosure: true, approval: nil,
|
|
26
|
+
tool_call_repair: nil, **chat_options)
|
|
26
27
|
@id = id || SecureRandom.uuid
|
|
27
28
|
@agent_dir = agent_dir
|
|
28
29
|
@max_turns = max_turns
|
|
@@ -54,6 +55,7 @@ module Ask
|
|
|
54
55
|
@hooks = Hooks.new(hooks)
|
|
55
56
|
@audit_log = build_audit_log(audit_log)
|
|
56
57
|
@approval_queue = build_approval(approval)
|
|
58
|
+
@tool_call_repair = tool_call_repair
|
|
57
59
|
|
|
58
60
|
@system_context = build_system_context(system_prompt)
|
|
59
61
|
apply_system_context
|
|
@@ -129,6 +131,7 @@ module Ask
|
|
|
129
131
|
hooks: @hooks,
|
|
130
132
|
event_emitter: self,
|
|
131
133
|
session_id: @id,
|
|
134
|
+
tool_call_repair: @tool_call_repair,
|
|
132
135
|
persist: @state ? method(:persist!) : nil
|
|
133
136
|
)
|
|
134
137
|
|
|
@@ -202,7 +205,8 @@ module Ask
|
|
|
202
205
|
compactor: @compactor,
|
|
203
206
|
hooks: @hooks,
|
|
204
207
|
event_emitter: self,
|
|
205
|
-
session_id: @id
|
|
208
|
+
session_id: @id,
|
|
209
|
+
tool_call_repair: @tool_call_repair
|
|
206
210
|
)
|
|
207
211
|
|
|
208
212
|
@total_input_tokens += @loop.last_input_tokens.to_i
|
|
@@ -241,7 +245,8 @@ module Ask
|
|
|
241
245
|
compactor: @compactor,
|
|
242
246
|
hooks: @hooks,
|
|
243
247
|
event_emitter: self,
|
|
244
|
-
session_id: @id
|
|
248
|
+
session_id: @id,
|
|
249
|
+
tool_call_repair: @tool_call_repair
|
|
245
250
|
)
|
|
246
251
|
|
|
247
252
|
@total_input_tokens += @loop.last_input_tokens.to_i
|
|
@@ -421,7 +426,7 @@ module Ask
|
|
|
421
426
|
def build_audit_log(config)
|
|
422
427
|
config ||= Ask::Agent.configuration.audit_log
|
|
423
428
|
return nil unless config
|
|
424
|
-
Ask::Agent::
|
|
429
|
+
Ask::Agent::Policies::AuditLog.new(self, adapter: config)
|
|
425
430
|
end
|
|
426
431
|
|
|
427
432
|
# Build the approval queue + policy when approval is enabled.
|
|
@@ -451,7 +456,7 @@ module Ask
|
|
|
451
456
|
)
|
|
452
457
|
end
|
|
453
458
|
|
|
454
|
-
policy = Ask::Agent::
|
|
459
|
+
policy = Ask::Agent::Policies::ApprovalPolicy.new(
|
|
455
460
|
queue: queue,
|
|
456
461
|
require_approval: policy_opts[:require_approval],
|
|
457
462
|
tools: @tools
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Ask
|
|
6
|
+
module Agent
|
|
7
|
+
# Repairs malformed tool calls before they execute.
|
|
8
|
+
#
|
|
9
|
+
# When a model emits a tool call with unparseable arguments or an unknown
|
|
10
|
+
# tool name, execution currently fails and the model burns a turn seeing
|
|
11
|
+
# the error. Repair intercepts those calls before execution, asks the
|
|
12
|
+
# model to re-emit them corrected (one internal LLM round-trip), and
|
|
13
|
+
# executes the corrected versions — remapped to the original call ids so
|
|
14
|
+
# tool results stay consistent with the conversation history.
|
|
15
|
+
#
|
|
16
|
+
# Enable with `Session.new(tool_call_repair: true)` for the built-in
|
|
17
|
+
# repair prompt, or pass a callable for full control:
|
|
18
|
+
#
|
|
19
|
+
# Session.new(tool_call_repair: ->(chat, calls, tools) {
|
|
20
|
+
# { calls.keys.first => ToolCallInfo.new(
|
|
21
|
+
# id: calls.keys.first, name: "bash", arguments: "{}"
|
|
22
|
+
# ) }
|
|
23
|
+
# })
|
|
24
|
+
#
|
|
25
|
+
# The callable receives the chat, the malformed calls hash (id =>
|
|
26
|
+
# ToolCallInfo), and the available tools; it returns a hash of
|
|
27
|
+
# corrections keyed by the ORIGINAL call ids. Calls without a correction
|
|
28
|
+
# are dropped — the model saw them in the prompt and declined to fix
|
|
29
|
+
# them.
|
|
30
|
+
class ToolCallRepair
|
|
31
|
+
# @param tool_call [ToolCallInfo]
|
|
32
|
+
# @param tools [Array<Object>]
|
|
33
|
+
# @return [String, nil] why the call is repairable, or nil when it is
|
|
34
|
+
# well-formed and executable
|
|
35
|
+
def self.repair_info(tool_call, tools)
|
|
36
|
+
unless tools.any? { |t| t.respond_to?(:name) && t.name == tool_call.name }
|
|
37
|
+
return "unknown tool '#{tool_call.name}'"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
args = tool_call.arguments
|
|
41
|
+
return nil if args.is_a?(Hash)
|
|
42
|
+
|
|
43
|
+
parsed = JSON.parse(args.to_s)
|
|
44
|
+
return nil if parsed.is_a?(Hash)
|
|
45
|
+
|
|
46
|
+
"arguments must be a JSON object, got #{parsed.class}"
|
|
47
|
+
rescue JSON::ParserError => e
|
|
48
|
+
"arguments are not valid JSON: #{e.message}"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @param callable [Proc, nil] custom repair function; nil uses the
|
|
52
|
+
# built-in repair prompt
|
|
53
|
+
def initialize(callable = nil)
|
|
54
|
+
@callable = callable
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Ask the model to correct the malformed calls.
|
|
58
|
+
#
|
|
59
|
+
# @param chat [Ask::Agent::Chat] the session chat (history is restored
|
|
60
|
+
# after the internal round-trip)
|
|
61
|
+
# @param calls [Hash{String => ToolCallInfo}] malformed calls by id
|
|
62
|
+
# @param tools [Array<Object>]
|
|
63
|
+
# @return [Hash{String => ToolCallInfo}] corrections keyed by the
|
|
64
|
+
# original call ids
|
|
65
|
+
def call(chat:, calls:, tools:)
|
|
66
|
+
if @callable
|
|
67
|
+
normalize(@callable.call(chat, calls, tools))
|
|
68
|
+
else
|
|
69
|
+
built_in(chat, calls, tools)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def built_in(chat, calls, tools)
|
|
76
|
+
size = chat.messages.size
|
|
77
|
+
response = chat.ask(repair_prompt(calls, tools))
|
|
78
|
+
# Remove the internal repair exchange from the conversation so the
|
|
79
|
+
# history stays clean and the model never sees it.
|
|
80
|
+
chat.messages.slice!(size..)
|
|
81
|
+
|
|
82
|
+
corrections = {}
|
|
83
|
+
response.tool_calls.values.each_with_index do |tc, index|
|
|
84
|
+
original_id = calls.keys[index]
|
|
85
|
+
break unless original_id
|
|
86
|
+
|
|
87
|
+
corrections[original_id] = ToolCallInfo.new(
|
|
88
|
+
id: original_id, name: tc.name, arguments: tc.arguments
|
|
89
|
+
)
|
|
90
|
+
end
|
|
91
|
+
corrections
|
|
92
|
+
rescue StandardError
|
|
93
|
+
# Repair is best-effort: on any failure, drop the malformed calls
|
|
94
|
+
# rather than failing the turn. Restore history either way.
|
|
95
|
+
chat.messages.slice!(size..) rescue nil
|
|
96
|
+
{}
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Normalize a custom callable's result to the corrections contract
|
|
100
|
+
# (original id => ToolCallInfo), dropping anything malformed.
|
|
101
|
+
def normalize(result)
|
|
102
|
+
return {} unless result.respond_to?(:each)
|
|
103
|
+
|
|
104
|
+
result.each_with_object({}) do |(id, tc), acc|
|
|
105
|
+
next unless tc.respond_to?(:name)
|
|
106
|
+
|
|
107
|
+
acc[id] = ToolCallInfo.new(id: id, name: tc.name, arguments: tc.arguments)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def repair_prompt(calls, tools)
|
|
112
|
+
lines = calls.map.with_index do |(id, tc), i|
|
|
113
|
+
reason = self.class.repair_info(tc, tools) || "invalid"
|
|
114
|
+
"#{i + 1}. call id \"#{id}\", tool \"#{tc.name}\", " \
|
|
115
|
+
"arguments: #{tc.arguments.inspect} — #{reason}"
|
|
116
|
+
end
|
|
117
|
+
tool_list = tools.map { |t| t.respond_to?(:name) ? t.name : t.to_s }.join(", ")
|
|
118
|
+
|
|
119
|
+
<<~PROMPT.strip
|
|
120
|
+
Some tool calls from your last message were invalid and could not be executed:
|
|
121
|
+
#{lines.join("\n")}
|
|
122
|
+
|
|
123
|
+
Available tools: #{tool_list.empty? ? "(none)" : tool_list}
|
|
124
|
+
|
|
125
|
+
Reply by calling the tools again with corrected arguments, in the same order as listed above. Call ONLY the tools listed above that you can correct; if a tool truly does not exist or cannot be corrected, do not call it.
|
|
126
|
+
PROMPT
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
data/lib/ask/agent/version.rb
CHANGED
data/lib/ask/agent.rb
CHANGED
|
@@ -21,13 +21,24 @@ module Ask
|
|
|
21
21
|
|
|
22
22
|
class UnknownAgent < Error; end
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
# Policies are opt-in, replaceable implementations of the tool-lifecycle
|
|
25
|
+
# hook seam (before_tool / after_tool). The agent loop runs without them
|
|
26
|
+
# and their semantics are unchanged by their absence; users compose and
|
|
27
|
+
# swap them freely (see Ask::Agent::Hooks for the seam).
|
|
28
|
+
#
|
|
29
|
+
# A policy is NOT core machinery. Core mechanisms live on Session — the
|
|
30
|
+
# approval queue, the :pending result status, and the `approval: true`
|
|
31
|
+
# option are core; Policies::ApprovalPolicy is the reference
|
|
32
|
+
# classification policy wired on top of them.
|
|
33
|
+
module Policies
|
|
34
|
+
autoload :Permissions, "ask/agent/policies/permissions"
|
|
35
|
+
autoload :RateLimiter, "ask/agent/policies/rate_limiter"
|
|
36
|
+
autoload :AuditLog, "ask/agent/policies/audit_log"
|
|
37
|
+
autoload :ApprovalPolicy, "ask/agent/policies/approval_policy"
|
|
29
38
|
end
|
|
30
39
|
|
|
40
|
+
autoload :ToolCallRepair, "ask/agent/tool_call_repair"
|
|
41
|
+
|
|
31
42
|
module Middleware
|
|
32
43
|
autoload :Base, "ask/agent/middleware/base"
|
|
33
44
|
autoload :Pipeline, "ask/agent/middleware/pipeline"
|
|
@@ -236,8 +247,8 @@ module Ask
|
|
|
236
247
|
yield configuration
|
|
237
248
|
end
|
|
238
249
|
|
|
239
|
-
def self.
|
|
240
|
-
Dir[File.expand_path("agent/
|
|
250
|
+
def self.load_policies
|
|
251
|
+
Dir[File.expand_path("agent/policies/*.rb", __dir__)].each { |f| require f }
|
|
241
252
|
rescue Errno::ENOENT
|
|
242
253
|
end
|
|
243
254
|
end
|
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.
|
|
4
|
+
version: 0.29.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kaka Ruto
|
|
@@ -174,11 +174,6 @@ files:
|
|
|
174
174
|
- lib/ask/agent/definition.rb
|
|
175
175
|
- lib/ask/agent/evaluator.rb
|
|
176
176
|
- lib/ask/agent/events.rb
|
|
177
|
-
- lib/ask/agent/extensions/approval_policy.rb
|
|
178
|
-
- lib/ask/agent/extensions/audit_log.rb
|
|
179
|
-
- lib/ask/agent/extensions/audit_log/active_record_writer.rb
|
|
180
|
-
- lib/ask/agent/extensions/permissions.rb
|
|
181
|
-
- lib/ask/agent/extensions/rate_limiter.rb
|
|
182
177
|
- lib/ask/agent/hooks.rb
|
|
183
178
|
- lib/ask/agent/loop.rb
|
|
184
179
|
- lib/ask/agent/meta_agent.rb
|
|
@@ -190,6 +185,11 @@ files:
|
|
|
190
185
|
- lib/ask/agent/middleware/retry_on_failure.rb
|
|
191
186
|
- lib/ask/agent/persistence/base.rb
|
|
192
187
|
- lib/ask/agent/persistence/in_memory.rb
|
|
188
|
+
- lib/ask/agent/policies/approval_policy.rb
|
|
189
|
+
- lib/ask/agent/policies/audit_log.rb
|
|
190
|
+
- lib/ask/agent/policies/audit_log/active_record_writer.rb
|
|
191
|
+
- lib/ask/agent/policies/permissions.rb
|
|
192
|
+
- lib/ask/agent/policies/rate_limiter.rb
|
|
193
193
|
- lib/ask/agent/reflector.rb
|
|
194
194
|
- lib/ask/agent/scheduler.rb
|
|
195
195
|
- lib/ask/agent/session.rb
|
|
@@ -204,6 +204,7 @@ files:
|
|
|
204
204
|
- lib/ask/agent/telemetry.rb
|
|
205
205
|
- lib/ask/agent/test.rb
|
|
206
206
|
- lib/ask/agent/tool_abort_controller.rb
|
|
207
|
+
- lib/ask/agent/tool_call_repair.rb
|
|
207
208
|
- lib/ask/agent/tool_executor.rb
|
|
208
209
|
- lib/ask/agent/version.rb
|
|
209
210
|
homepage: https://github.com/ask-rb/ask-agent
|