ask-agent 0.1.1 → 0.1.2

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: e875e155f72f3bc773ff218b64243ac918354fac930b0181e02ae1a74d0bc4eb
4
- data.tar.gz: 9ec62c98c64b7307f445311cce0f6fd6bf81a9f7fde52daa505cd532d4734520
3
+ metadata.gz: cc1ea1b84a7139944acf9787cfdd3e5a9fad739b759fc4501f59f68bdd3c0b77
4
+ data.tar.gz: 37733f0982e0d991e284d46c9202fe5846fff67a433acae0385a703912c7f898
5
5
  SHA512:
6
- metadata.gz: 14d6490ab16f5e675e05ac3e72f37c113edcfd7e7339c10fe6a73290b88e4cab0ab8535d21860e51d09aac36c7a7066b3dbfe1f19b04d5c873a301094fb610fe
7
- data.tar.gz: 729b178fb7f474e2fe5c84ce692169ba184fefe1b761d7ddc2a1dfaf39c57d184155f9f67e186bd91b13faaab05a5e79aac49107055d0089269ca10e9e100f94
6
+ metadata.gz: 8e4ff593f7856c2fe0da46dfe8091ad38217e29b27aa343e95f6f939ab741b8c249217dbaa397ad609c21b9b4f39e09a781738a556368e69307fa01329fb00d9
7
+ data.tar.gz: fc45d3ae62bc2e325a22dedab88625f312d3e9b07188d9614c5cb2ddfa9683ed31e2bb8473f10344d73ac18f7cef202fcf53cd68272affbab54c208770017dde
@@ -14,6 +14,8 @@ module Ask
14
14
  end
15
15
 
16
16
  attr_reader :meta_agent_results
17
+ # @return [Ask::Skills::Registry, nil] auto-discovered skills registry
18
+ attr_reader :skills_registry
17
19
 
18
20
  def initialize(model:, tools: [], max_turns: 25, max_tool_retries: 3,
19
21
  compactor: nil, hooks: {}, persistence: nil,
@@ -39,6 +41,16 @@ module Ask
39
41
  @tool_executor = ToolExecutor.new(max_retries: max_tool_retries, parallel: parallel_tools)
40
42
  @compactor = compactor ? build_compactor(compactor) : nil
41
43
  @hooks = Hooks.new(hooks)
44
+
45
+ # Auto-discover skills and inject into system prompt
46
+ @skills_registry = Ask::Skills.discover rescue nil
47
+ if @skills_registry && !@skills_registry.names.empty?
48
+ skill_text = @skills_registry.format_for_prompt
49
+ if !skill_text.empty? && @chat.messages.any? { |m| m.role == :system }
50
+ current = @chat.messages.find { |m| m.role == :system }.content.to_s
51
+ @chat.with_instructions(current + skill_text)
52
+ end
53
+ end
42
54
  @persistence = persistence
43
55
 
44
56
  reflector_opts = reflector.is_a?(Hash) ? reflector : {}
@@ -202,6 +214,28 @@ module Ask
202
214
 
203
215
  def abort_requested? = @abort_requested
204
216
 
217
+ # Load a skill by name or file path.
218
+ # Injects the skill's full instructions into the conversation as a system message.
219
+ #
220
+ # @param name [String] skill name (e.g. "rails.db_debug") or path to a .md file
221
+ # @raise [Ask::Skills::Error] if the skill is not found
222
+ def skill(name)
223
+ if @skills_registry && (s = @skills_registry[name])
224
+ @chat.add_message(
225
+ role: :system,
226
+ content: "## Skill: #{s.name}\n\n#{s.description}\n\n---\n\n#{s.instructions}"
227
+ )
228
+ elsif File.exist?(name.to_s)
229
+ content = File.read(name.to_s)
230
+ @chat.add_message(
231
+ role: :system,
232
+ content: "## Skill: #{name}\n\n---\n\n#{content}"
233
+ )
234
+ else
235
+ raise Ask::Skills::Error, "Skill not found: #{name.inspect}"
236
+ end
237
+ end
238
+
205
239
  def reset_messages!
206
240
  @chat.reset_messages!
207
241
  @messages = []
@@ -0,0 +1,237 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+ require "time"
5
+
6
+ module Ask
7
+ module Agent
8
+ class Session
9
+ attr_reader :id, :chat, :tools, :turn_count, :created_at, :messages
10
+ attr_reader :tool_calls_made
11
+
12
+ def reflection_count
13
+ @reflector&.reflection_count || 0
14
+ end
15
+
16
+
17
+ # @return [Ask::Skills::Registry, nil] auto-discovered skills registry
18
+ attr_reader :skills_registry
19
+ attr_reader :meta_agent_results
20
+ # @return [Ask::Skills::Registry, nil] auto-discovered skills registry
21
+ attr_reader :skills_registry
22
+
23
+ def initialize(model:, tools: [], max_turns: 25, max_tool_retries: 3,
24
+ compactor: nil, hooks: {}, persistence: nil,
25
+ id: nil, system_prompt: nil, parallel_tools: true,
26
+ reflector: nil, telemetry: true, meta_agent: nil, **chat_options)
27
+ @id = id || SecureRandom.uuid
28
+ @max_turns = max_turns
29
+ @max_tool_retries = max_tool_retries
30
+ @parallel_tools = parallel_tools
31
+ @event_handlers = { all: [] }
32
+ @running = false
33
+ @deleted = false
34
+ @abort_requested = false
35
+ @turn_count = 0
36
+ @created_at = Time.now
37
+ @_no_tools_instructed = false
38
+
39
+ @telemetry = telemetry.is_a?(Telemetry) ? telemetry : Telemetry.new(enabled: !!telemetry)
40
+
41
+ @chat = build_chat(model, system_prompt, tools, **chat_options)
42
+ @tools = resolve_tools(tools)
43
+ @loop = Loop.new(max_turns: max_turns)
44
+ @tool_executor = ToolExecutor.new(max_retries: max_tool_retries, parallel: parallel_tools)
45
+
46
+ # Auto-discover skills and inject into system prompt
47
+ @skills_registry = Ask::Skills.discover rescue nil
48
+ if @skills_registry && !@skills_registry.names.empty?
49
+ skill_text = @skills_registry.format_for_prompt
50
+ if !skill_text.empty? && @chat.messages.any? { |m| m.role == :system }
51
+ current = @chat.messages.find { |m| m.role == :system }.content.to_s
52
+ @chat.with_instructions(current + skill_text)
53
+ end
54
+ end
55
+ @compactor = compactor ? build_compactor(compactor) : nil
56
+ @hooks = Hooks.new(hooks)
57
+
58
+ # Auto-discover skills and inject into system prompt
59
+ @skills_registry = Ask::Skills.discover rescue nil
60
+ if @skills_registry && !@skills_registry.names.empty?
61
+ skill_text = @skills_registry.format_for_prompt
62
+ if !skill_text.empty? && @chat.messages.any? { |m| m.role == :system }
63
+ current = @chat.messages.find { |m| m.role == :system }.content.to_s
64
+ @chat.with_instructions(current + skill_text)
65
+ end
66
+ end
67
+ @persistence = persistence
68
+
69
+ reflector_opts = reflector.is_a?(Hash) ? reflector : {}
70
+ @reflector = if reflector
71
+ Reflector.new(
72
+ model: @chat,
73
+ max_reflections: reflector_opts[:max_reflections] || 1
74
+ )
75
+ end
76
+
77
+ @meta_agent_config = meta_agent
78
+ @meta_agent_results = nil
79
+
80
+
81
+ # Auto-discover skills and inject into system prompt
82
+ @skills_registry = Ask::Skills.discover rescue nil
83
+ if @skills_registry && !@skills_registry.names.empty?
84
+ skill_text = @skills_registry.format_for_prompt
85
+ if !skill_text.empty? && @chat.messages.any? { |m| m.role == :system }
86
+ current = @chat.messages.find { |m| m.role == :system }.content.to_s
87
+ @chat.with_instructions(current + skill_text)
88
+ end
89
+ end
90
+ @compactor&.chat = @chat
91
+ end
92
+
93
+ def run(message, tools: nil)
94
+ raise "Session deleted" if @deleted
95
+ raise "Session already running" if @running
96
+
97
+ @running = true
98
+ @abort_requested = false
99
+ @turn_count = 0
100
+ @loop.reset!
101
+
102
+ emit(Events::SessionStart.new)
103
+
104
+ active_tools = resolve_tools(tools || [])
105
+ active_tools = @tools if active_tools.empty?
106
+
107
+ if active_tools.empty? && !@_no_tools_instructed
108
+ @chat.add_message(role: :system, content: "You have no tools available. Do not claim you can look up information or use tools of any kind. Just respond based on your existing knowledge.")
109
+ @_no_tools_instructed = true
110
+ end
111
+
112
+ begin
113
+ @tool_executor.telemetry = @telemetry
114
+
115
+ response = @loop.run_turn(
116
+ chat: @chat,
117
+ message: message,
118
+ tools: active_tools,
119
+ tool_executor: @tool_executor,
120
+ compactor: @compactor,
121
+ hooks: @hooks,
122
+ event_emitter: self,
123
+ session_id: @id
124
+ )
125
+ rescue MaxTurnsExceeded => e
126
+ emit(Events::MaxTurnsExceeded.new(max_turns: @max_turns))
127
+ @telemetry.log(:max_turns_exceeded, session_id: @id, max_turns: @max_turns)
128
+ response = last_content
129
+ rescue LoopDetected => e
130
+ emit(Events::LoopDetected.new(tool_name: e.message, repeated_count: 3))
131
+ @telemetry.log(:loop_detected, session_id: @id, tool_name: e.message, repeated_count: 3)
132
+ response = last_content
133
+ rescue Ask::ContextLengthExceeded
134
+ if @compactor && !@compactor.overflow_recovered?
135
+ @compactor.recover_from_overflow
136
+ retry
137
+ end
138
+ response = "I'm sorry, the conversation has grown too long. Please start a new session."
139
+ rescue StandardError => e
140
+ emit(Events::Error.new(error: e.message, recoverable: true))
141
+ raise
142
+ ensure
143
+ @running = false
144
+ persist! if @persistence
145
+ end
146
+
147
+ @tool_calls_made = @tool_executor.total_executions
148
+
149
+ if @reflector && @reflector.reflect?(@tool_calls_made) && !@abort_requested
150
+ eval_result = @reflector.evaluate(response: response, event_emitter: self)
151
+ @telemetry.log(:reflection_end, session_id: @id, decision: eval_result[:decision], feedback: eval_result[:feedback])
152
+
153
+ if eval_result[:decision] == :improve && !@abort_requested
154
+ @chat.add_message(
155
+ role: :system,
156
+ content: "Improve your last response: #{eval_result[:feedback]}"
157
+ )
158
+
159
+ response = @loop.run_turn(
160
+ chat: @chat,
161
+ message: "",
162
+ tools: active_tools,
163
+ tool_executor: @tool_executor,
164
+ compactor: @compactor,
165
+ hooks: @hooks,
166
+ event_emitter: self,
167
+ session_id: @id
168
+ )
169
+ end
170
+ end
171
+
172
+ if @meta_agent_config
173
+ @telemetry.increment_session_count!
174
+ try_auto_meta_agent
175
+ end
176
+
177
+ emit(Events::SessionEnd.new(result: response, turn_count: @turn_count, tool_calls_made: @tool_calls_made))
178
+ @messages = @chat.messages.dup
179
+
180
+ response
181
+ end
182
+
183
+ def on_event(&block)
184
+ @event_handlers[:all] << block
185
+ self
186
+ end
187
+
188
+ def on(type, &block)
189
+ @event_handlers[type] ||= []
190
+ @event_handlers[type] << block
191
+ self
192
+ end
193
+
194
+ def emit(event)
195
+ @event_handlers[:all].each { |h| h.call(event) }
196
+ handlers = @event_handlers[event.class]
197
+ handlers&.each { |h| h.call(event) }
198
+ end
199
+
200
+ def running? = @running
201
+ def deleted? = @deleted
202
+
203
+ def save
204
+ persist! if @persistence
205
+ end
206
+
207
+ def self.load(id, adapter:)
208
+ data = adapter.load(id)
209
+ return nil unless data
210
+
211
+ session = new(
212
+ id: data[:id],
213
+ model: data.dig(:metadata, :model),
214
+ tools: data.dig(:metadata, :tools)&.map(&:constantize) || [],
215
+ persistence: adapter
216
+ )
217
+
218
+ data[:messages].each do |msg|
219
+ session.chat.add_message(
220
+ role: msg[:role].to_sym,
221
+ content: msg[:content],
222
+ tool_call_id: msg[:tool_call_id]
223
+ )
224
+ end
225
+
226
+ session
227
+ end
228
+
229
+ def delete
230
+ @deleted = true
231
+ @persistence&.delete(@id)
232
+ end
233
+
234
+ def abort
235
+ @abort_requested = true
236
+ end
237
+
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ask
4
4
  module Agent
5
- VERSION = "0.1.1"
5
+ VERSION = "0.1.2"
6
6
  end
7
7
  end
data/lib/ask/agent.rb CHANGED
@@ -4,6 +4,7 @@ require "fileutils"
4
4
  require "json"
5
5
  require "securerandom"
6
6
  require "time"
7
+ require "ask/skills"
7
8
 
8
9
  module Ask
9
10
  module Agent
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.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kaka Ruto
@@ -51,6 +51,20 @@ dependencies:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
53
  version: '0.1'
54
+ - !ruby/object:Gem::Dependency
55
+ name: ask-skills
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '0.1'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '0.1'
54
68
  - !ruby/object:Gem::Dependency
55
69
  name: ask-tools-shell
56
70
  requirement: !ruby/object:Gem::Requirement
@@ -133,6 +147,7 @@ files:
133
147
  - lib/ask/agent/persistence/in_memory.rb
134
148
  - lib/ask/agent/reflector.rb
135
149
  - lib/ask/agent/session.rb
150
+ - lib/ask/agent/session_backup.rb
136
151
  - lib/ask/agent/telemetry.rb
137
152
  - lib/ask/agent/tool_abort_controller.rb
138
153
  - lib/ask/agent/tool_executor.rb