ask-agent 0.23.0 → 0.24.1

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: cf7561a6eebc134d4e84ba829e637638a783eb7dd425c1b1c24d2d879e5ff47e
4
- data.tar.gz: 73cdf03eec1b27f3e50cbfa28399bdf17994ea1b5925be36ce463ebf48c6669d
3
+ metadata.gz: e5268e3474f109d7471749692a535a89b266e5a9d10c20499a565087500eaee8
4
+ data.tar.gz: 24fc0068d51f1eedc2e0d6f427234db468db375e915a4383533086993dab8b5d
5
5
  SHA512:
6
- metadata.gz: 37b261bba11ac89e4051b2f631305ed6741ae8ba6a18f0973a2fa7a04416d3dd84d2ca9e4517d01f4776a21fbf143125a51bcceb74b67ce361d9c4ea35e15279
7
- data.tar.gz: bc3051e56b9b68a003da46e1ee4c4ec10265239bfc2482c6cc335caf510f099256e1487526ead9277687004a0bb4c68926efb9f51db338b103f69188f21ed60a
6
+ metadata.gz: 8dddca135834b42108d217fc09a121a4fc4fd4ad44da326849b9654f30ba393bc115b44a3bc756433a7cbb8bd1b729d02dc246feb540e6f0ff39dc2aff1e56ad
7
+ data.tar.gz: 5cde0691f9aa8bd6723c20344211c50cc18e15ffdf392d97710896d4752b8343c583faab5de7e4156e261049676dbe06e2136162a8d10d1b48e9ae3e174f7786
data/CHANGELOG.md CHANGED
@@ -1,3 +1,21 @@
1
+ ## [0.24.0] — 2026-07-30
2
+
3
+ ### Changed
4
+
5
+ - **Agent class convention is now `<Name>::Agent`, not `<Name>Agent`.**
6
+ `agents/health_check/agent.rb` defines `module HealthCheck; class Agent <
7
+ Ask::Agent::Definition` — matching the ecosystem's `Xxx::Workflow` /
8
+ `Xxx::Create` naming. Directory-based discovery is unchanged.
9
+
10
+ ### Fixed
11
+
12
+ - **Discovery re-points definitions when the class constant is re-opened.**
13
+ Requiring an `agent.rb` from a second location (same agent name, e.g. test
14
+ fixtures) re-opens the existing `<Name>::Agent` constant instead of
15
+ redefining it, so `inherited` never fires and the definition kept its old
16
+ directory. Discovery now falls back to matching by conventional class name
17
+ and re-points `_config[:dir]` at the current directory.
18
+
1
19
  ## [0.23.0] — 2026-07-30
2
20
 
3
21
  ### Added
data/README.md CHANGED
@@ -213,6 +213,42 @@ c.middleware.use :model_fallback,
213
213
  eligible_errors: [Ask::RateLimitError, Ask::ServerError]
214
214
  ```
215
215
 
216
+ ## Agents
217
+
218
+ Declarative agents follow a file convention. Each agent lives in a
219
+ directory under `agents/` (or `app/agents/` in Rails); the directory
220
+ name is the agent name, the file `agent.rb` defines the agent as a
221
+ `<Name>::Agent < Ask::Agent::Definition` subclass, and a sibling
222
+ `instructions.md` is auto-loaded as the system prompt.
223
+
224
+ ```
225
+ agents/
226
+ └── health_check/ # agent name: "health_check"
227
+ ├── agent.rb # module HealthCheck; class Agent < Ask::Agent::Definition
228
+ ├── instructions.md # auto-loaded system prompt
229
+ └── tools/ # per-agent tools (referenced with `tools :name`)
230
+ ```
231
+
232
+ ```ruby
233
+ # agents/health_check/agent.rb
234
+ module HealthCheck
235
+ class Agent < Ask::Agent::Definition
236
+ model "gpt-4o"
237
+ tools :bash, :read, :grep
238
+ end
239
+ end
240
+ ```
241
+
242
+ Run it by name:
243
+
244
+ ```ruby
245
+ agent = Ask::Agent.new("health_check")
246
+ response = agent.run("Check server health")
247
+ ```
248
+
249
+ Shared tools for all agents go in `agents/shared/tools/`. Per-agent
250
+ skills go in `agents/<name>/skills/`, shared skills in `agents/shared/skills/`.
251
+
216
252
  ## Configuration
217
253
 
218
254
  ```ruby
@@ -9,10 +9,12 @@ module Ask
9
9
  # Instructions load automatically from a sibling +instructions.md+.
10
10
  #
11
11
  # @example +agents/health_check/agent.rb+
12
- # class HealthCheckAgent < Ask::Agent::Definition
13
- # model "gpt-4o"
14
- # tools :bash, :read, :grep
15
- # schedule "every 5 minutes"
12
+ # module HealthCheck
13
+ # class Agent < Ask::Agent::Definition
14
+ # model "gpt-4o"
15
+ # tools :bash, :read, :grep
16
+ # schedule "every 5 minutes"
17
+ # end
16
18
  # end
17
19
  #
18
20
  # # agents/health_check/instructions.md is auto-loaded
@@ -25,10 +27,18 @@ module Ask
25
27
 
26
28
  class << self
27
29
  # All subclasses that have been loaded, in definition order.
28
- attr_reader :subclasses
30
+ # Reads from Definition itself so intermediate base classes
31
+ # (e.g. ApplicationAgent) report the same registry.
32
+ def subclasses
33
+ Definition.instance_variable_get(:@subclasses) || []
34
+ end
29
35
 
30
36
  def inherited(subclass)
31
- @subclasses << subclass
37
+ # Track on Definition itself, not self — inherited fires with the
38
+ # immediate parent as receiver, so an intermediate base class
39
+ # (e.g. an ApplicationAgent in a Rails app) would otherwise have a
40
+ # nil @subclasses ivar.
41
+ Definition.subclasses << subclass
32
42
  subclass.instance_variable_set(:@_config, { tools: [] })
33
43
 
34
44
  # Auto-detect the directory from the file where this class is defined
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ask
4
4
  module Agent
5
- VERSION = "0.23.0"
5
+ VERSION = "0.24.1"
6
6
  end
7
7
  end
data/lib/ask/agent.rb CHANGED
@@ -117,10 +117,13 @@ module Ask
117
117
 
118
118
  require file
119
119
 
120
- # Find the Definition subclass whose directory matches
121
- match = Definition.subclasses.find { |klass|
122
- klass._config[:dir] == dir
123
- }
120
+ # Find the Definition subclass whose directory matches.
121
+ # Falls back to matching by conventional class name
122
+ # (<Name>::Agent) for agents re-opened from another location
123
+ # (e.g. same-named fixtures across test runs) — Ruby reopens
124
+ # an existing constant instead of redefining it, so the
125
+ # inherited hook doesn't fire a second time.
126
+ match = find_definition_for(dir, name)
124
127
 
125
128
  if match
126
129
  @registry[name] = [match, dir]
@@ -129,6 +132,22 @@ module Ask
129
132
  end
130
133
  end
131
134
 
135
+ # Find the Definition subclass for an agent directory.
136
+ #
137
+ # First tries an exact directory match (fresh definitions). When the
138
+ # class constant already exists — loaded from a different directory
139
+ # with the same agent name — re-points it at this directory so the
140
+ # returned definition is always current.
141
+ def find_definition_for(dir, name)
142
+ match = Definition.subclasses.find { |klass| klass._config[:dir] == dir }
143
+ return match if match
144
+
145
+ convention_name = name.split("_").map(&:capitalize).join + "::Agent"
146
+ match = Definition.subclasses.find { |klass| klass.name == convention_name }
147
+ match&._config&.[]=(:dir, dir)
148
+ match
149
+ end
150
+
132
151
  def discover_shared_tools(base)
133
152
  shared_tools_dir = File.join(base, "shared", "tools")
134
153
  return unless File.directory?(shared_tools_dir)
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.23.0
4
+ version: 0.24.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kaka Ruto