ask-agent 0.40.18 → 0.40.19

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: e97d8067438c6de7e631a5837fc93355b9db6a2b94fd143d0e4421b59c9ec177
4
- data.tar.gz: 4853729a9c4fa158bca58146b8b9e8305b6264cb1a12fb2b5750d8a2bcc79d03
3
+ metadata.gz: 46ea77e0ac0feca360164604367729e05f15e22212f479c59ad1954da5e5e924
4
+ data.tar.gz: 5a1e0198d2e4d9faf7831bd1c7f8520a82a01c7fff46a9a94512a60a6d285409
5
5
  SHA512:
6
- metadata.gz: 75def0a97595224d12c77fdbfadb875608c11afc0247b10e37058de1d1004f37addc1eb1ccbe64dd1c28a0a0f712ae024efe42d5372341a4876b6e8b32f5b0eb
7
- data.tar.gz: 526100d9bc12da6404419c2ab21b64b73f735e0b5c773ced77816eb231c23f0816cce4a9b344108a542fc3b50f8b84781bb81244dc6e991c89435e26e0ef4920
6
+ metadata.gz: d195e1e86df0f281de603cdd607d0b2d88bbbc3d07f4c46b5dbb81235fd102d7f4a19781e625b8ddd8c304babb47eaf60fa894c6513b5e944f493b0e4928c74f
7
+ data.tar.gz: c4289b1c74e4ee05de9ffe2c0c266e46599e49c546b0c22c23ea76258a23e291dd5804e3d5cdd0d514375540fc22d7732a920de1e6d303f7dfde0fcbc04ca883
data/lib/ask/agent/cli.rb CHANGED
@@ -1,11 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "fileutils"
4
+
3
5
  module Ask
4
6
  module Agent
5
7
  module CLI
6
8
  module_function
7
9
 
8
10
  def run(argv)
11
+ auto_sync_skills
9
12
  case argv.first
10
13
  when "run"
11
14
  cmd_run(argv[1..])
@@ -162,13 +165,21 @@ module Ask
162
165
  cmd_skills_show(args[1])
163
166
  when "search"
164
167
  cmd_skills_search(args[1])
168
+ when "install"
169
+ cmd_skills_install(args[1..])
170
+ when "uninstall"
171
+ cmd_skills_uninstall(args[1..])
165
172
  else
166
- puts "Usage: askr skills <list|show|search>"
173
+ puts "Usage: askr skills <list|show|search|install|uninstall>"
167
174
  puts ""
168
175
  puts "Commands:"
169
176
  puts " list List all discovered skills"
170
177
  puts " show <name> Show skill details and sibling files"
171
178
  puts " search <query> Search skills by name, description, or tags"
179
+ puts " install [--global] Install ask-agent skill to ~/.agents/skills/ (default)"
180
+ puts " install --local Install to .agents/skills/ in current directory"
181
+ puts " uninstall [--global] Remove installed skill"
182
+ puts " uninstall --local Remove locally installed skill"
172
183
  end
173
184
  end
174
185
 
@@ -259,6 +270,112 @@ module Ask
259
270
  end
260
271
  end
261
272
 
273
+ SKILL_NAME = "agent.build_agents"
274
+ SKILL_SOURCE_REL = "ask/skills/agent.build_agents/SKILL.md"
275
+
276
+ # Keep installed copies in sync after `gem update ask-agent`.
277
+ # Only touches copies created by `askr skills install` (identified
278
+ # by the marker file). Failures are swallowed.
279
+ def auto_sync_skills
280
+ skill_candidates.each do |dest|
281
+ next unless File.file?(dest)
282
+ next unless skill_managed?(dest)
283
+ skill_sync_copy(dest)
284
+ end
285
+ rescue StandardError
286
+ nil
287
+ end
288
+
289
+ def cmd_skills_install(args)
290
+ opts = parse_skill_flags(args)
291
+ dest = skill_target_dir(opts)
292
+ source = skill_bundled_path
293
+ unless File.file?(source)
294
+ puts "Bundled skill not found at #{source} — reinstall ask-agent"
295
+ exit 1
296
+ end
297
+
298
+ FileUtils.mkdir_p(File.dirname(dest))
299
+ FileUtils.cp(source, dest)
300
+ skill_stamp(dest)
301
+ puts "Installed #{SKILL_NAME} skill -> #{dest}"
302
+ 0
303
+ end
304
+
305
+ def cmd_skills_uninstall(args)
306
+ opts = parse_skill_flags(args)
307
+ dest = skill_target_dir(opts)
308
+ if File.file?(dest)
309
+ FileUtils.rm(dest)
310
+ FileUtils.rm_f(skill_managed_marker(dest))
311
+ puts "Removed #{dest}"
312
+ else
313
+ puts "Not installed at #{dest} (nothing to do)"
314
+ end
315
+ 0
316
+ end
317
+
318
+ def skill_target_dir(opts)
319
+ if opts[:dir]
320
+ File.expand_path(File.join(opts[:dir], SKILL_NAME, "SKILL.md"))
321
+ elsif opts[:local]
322
+ File.join(Dir.pwd, ".agents", "skills", SKILL_NAME, "SKILL.md")
323
+ else
324
+ File.expand_path("~/.agents/skills/#{SKILL_NAME}/SKILL.md")
325
+ end
326
+ end
327
+
328
+ def skill_bundled_path
329
+ File.expand_path("../../#{SKILL_SOURCE_REL}", __dir__)
330
+ end
331
+
332
+ def skill_managed_marker(dest)
333
+ File.join(File.dirname(dest), ".ask-agent-managed")
334
+ end
335
+
336
+ def skill_managed?(dest)
337
+ File.file?(skill_managed_marker(dest))
338
+ end
339
+
340
+ def skill_stamp(dest)
341
+ File.write(skill_managed_marker(dest), "managed by askr skills install; safe to auto-update\n")
342
+ rescue StandardError
343
+ nil
344
+ end
345
+
346
+ def skill_candidates
347
+ [
348
+ File.expand_path("~/.agents/skills/#{SKILL_NAME}/SKILL.md"),
349
+ File.join(Dir.pwd, ".agents", "skills", SKILL_NAME, "SKILL.md")
350
+ ]
351
+ end
352
+
353
+ def skill_sync_copy(dest)
354
+ source = skill_bundled_path
355
+ FileUtils.cp(source, dest) if File.exist?(source) && File.read(source) != File.read(dest)
356
+ skill_stamp(dest)
357
+ rescue StandardError
358
+ nil
359
+ end
360
+
361
+ def parse_skill_flags(args)
362
+ opts = {}
363
+ args.each do |arg|
364
+ case arg
365
+ when "--local" then opts[:local] = true
366
+ when "--global" then nil # default
367
+ when "--dir"
368
+ # next arg is the path; handled by shifting in the caller
369
+ when /\A--dir=(.+)/ then opts[:dir] = $1
370
+ when "--help", "-h"
371
+ puts "Usage: askr skills install [--global] [--local] [--dir <path>]"
372
+ puts " askr skills uninstall [--global] [--local] [--dir <path>]"
373
+ exit 0
374
+ end
375
+ end
376
+ opts
377
+ end
378
+
262
379
  def cmd_help
263
380
  puts <<~HELP
264
381
  Usage: askr <command> [options]
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ask
4
4
  module Agent
5
- VERSION = "0.40.18"
5
+ VERSION = "0.40.19"
6
6
  end
7
7
  end
@@ -0,0 +1,261 @@
1
+ ---
2
+ name: agent.build_agents
3
+ description: Build AI agents with ask-rb — define agents, register tools, configure sessions, and wire up the agent loop. Use when creating new agents, adding tools to existing agents, debugging agent behavior, or setting up agent definitions.
4
+ tags: agents, tools, sessions, ask-rb, development
5
+ ---
6
+
7
+ # Building Agents with ask-rb
8
+
9
+ Step-by-step methodology for creating AI agents using the ask-rb ecosystem.
10
+
11
+ ## Agent Definition (Convention-Based)
12
+
13
+ Every agent lives in a directory under `agents/` or `app/agents/`. The directory name is the agent name.
14
+
15
+ ### Directory Structure
16
+
17
+ ```
18
+ agents/
19
+ ├── health_check/
20
+ │ ├── agent.rb → Definition subclass (required)
21
+ │ ├── instructions.md → System prompt (auto-loaded)
22
+ │ ├── tools/ → Per-agent tools (optional)
23
+ │ │ └── disk_check.rb
24
+ │ └── skills/ → Per-agent skills (optional)
25
+ │ └── nginx_debug/SKILL.md
26
+ ├── shared/
27
+ │ ├── tools/ → Shared across all agents
28
+ │ │ └── notify.rb
29
+ │ └── skills/ → Shared skills
30
+ └── daily_report/
31
+ ├── agent.rb
32
+ └── instructions.md
33
+ ```
34
+
35
+ ### Definition DSL
36
+
37
+ ```ruby
38
+ # agents/health_check/agent.rb
39
+ module HealthCheck
40
+ class Agent < Ask::Agent::Definition
41
+ model "gpt-4o" # required: which LLM to use
42
+ provider :anthropic # optional: override provider
43
+ tools :bash, :read, :grep # tool symbols or classes
44
+ max_turns 30 # optional: conversation limit
45
+ parallel_tools true # optional: parallel execution (default: true)
46
+ skills_disclosure true # optional: load_skill tool (default: true)
47
+ schedule "every 5 minutes" # optional: cron/interval
48
+ option :temperature, 0.7 # optional: arbitrary Session option
49
+ end
50
+ end
51
+ ```
52
+
53
+ ### Tool Symbol Resolution
54
+
55
+ Symbols (`:bash`, `:read`) are resolved in order:
56
+ 1. Per-agent tools: `agents/<name>/tools/<name>.rb`
57
+ 2. Shared tools: `agents/shared/tools/<name>.rb`
58
+ 3. Global registry: `Ask::Tools[name]` (built-in tools from ask-tools)
59
+
60
+ ## Session Creation (Unified API)
61
+
62
+ `Session.build_from_definition` is the single source of truth. Three entry points converge on it:
63
+
64
+ ### From a Definition (Recommended)
65
+
66
+ ```ruby
67
+ # Via Agent.new — convenience shorthand
68
+ agent = Ask::Agent.new("health_check")
69
+ agent.run("Check server health")
70
+
71
+ # Explicit — when you have the class and directory
72
+ session = Ask::Agent::Session.build_from_definition(
73
+ HealthCheck::Agent, "agents/health_check"
74
+ )
75
+ ```
76
+
77
+ ### Direct (No Definition)
78
+
79
+ ```ruby
80
+ session = Ask::Agent::Session.new(
81
+ model: "gpt-4o",
82
+ tools: [Ask::Tools::Bash, Ask::Tools::Read],
83
+ system_prompt: "You are a helpful assistant."
84
+ )
85
+ session.run("Hello")
86
+ ```
87
+
88
+ ### One-Shot Chat
89
+
90
+ ```ruby
91
+ Ask.chat("Check health")
92
+ Ask.chat("Check health", model: "claude-sonnet-4")
93
+ Ask.chat("Check health", name: "health_check") # from definition
94
+ ```
95
+
96
+ ### Overriding Definition Config
97
+
98
+ Any explicit option overrides the definition's value:
99
+
100
+ ```ruby
101
+ agent = Ask::Agent.new("health_check", model: "claude-sonnet-4")
102
+ agent = Ask::Agent.new("health_check", system_prompt: "Custom prompt")
103
+ ```
104
+
105
+ ## Writing Tools
106
+
107
+ ### Tool Class
108
+
109
+ ```ruby
110
+ # agents/health_check/tools/disk_check.rb
111
+ class DiskCheck < Ask::Tool
112
+ description "Check disk usage on a path"
113
+ param :path, type: :string, desc: "Path to check", required: true
114
+ param :threshold, type: :integer, desc: "Warning threshold in %", default: 80
115
+
116
+ def execute(path:, threshold: 80)
117
+ usage = `df -h #{path} | tail -1 | awk '{print $5}'`.strip.to_i
118
+ if usage > threshold
119
+ Ask::Result.error(data: "Disk usage #{usage}% exceeds threshold #{threshold}%")
120
+ else
121
+ Ask::Result.ok(data: "Disk usage: #{usage}%")
122
+ end
123
+ end
124
+ end
125
+ ```
126
+
127
+ ### Tool Registration
128
+
129
+ ```ruby
130
+ # In agent.rb — symbols resolve via convention
131
+ tools :bash, :read, :disk_check
132
+
133
+ # Or pass classes directly (no file convention needed)
134
+ tools Ask::Tools::Bash, DiskCheck
135
+ ```
136
+
137
+ ### Async Tools (Background Execution)
138
+
139
+ ```ruby
140
+ class LongTask < Ask::Tool
141
+ description "Run a long-running task in the background"
142
+
143
+ def execute(task:)
144
+ # Return pending — the loop continues while this runs
145
+ Ask::Result.pending(
146
+ tool_call_id: current_tool_call_id,
147
+ message: "Task started in background"
148
+ )
149
+ end
150
+ end
151
+ ```
152
+
153
+ Complete with `session.complete_pending_tool(tool_call_id:, result:)` from a background thread.
154
+
155
+ ## Session Options
156
+
157
+ | Option | Default | Purpose |
158
+ |--------|---------|---------|
159
+ | `model:` | (required) | LLM model identifier |
160
+ | `tools:` | `[]` | Tool classes or instances |
161
+ | `system_prompt:` | `nil` | System instructions |
162
+ | `max_turns:` | `25` | Conversation turn limit |
163
+ | `max_tool_retries:` | `3` | Retries per failed tool call |
164
+ | `parallel_tools:` | `true` | Execute tools concurrently |
165
+ | `skills_disclosure:` | `true` | Include load_skill tool |
166
+ | `state:` | `nil` | Persistence adapter |
167
+ | `checkpoints:` | `false` | Enable fork/rollback |
168
+ | `todos:` | `false` | Enable task list tool |
169
+ | `plan_mode:` | `false` | Read-only research phase |
170
+ | `memory:` | `nil` | Durable memory adapter |
171
+ | `memory_learning:` | `false` | Auto-extract facts |
172
+ | `evaluator:` | `nil` | Independent evaluation |
173
+ | `reflector:` | `nil` | Self-reflection |
174
+ | `approval:` | `nil` | Human-in-the-loop |
175
+ | `compactor:` | `nil` | Context window management |
176
+ | `hooks:` | `{}` | Before/after tool callbacks |
177
+ | `telemetry:` | `true` | Error tracking |
178
+
179
+ ## Event System
180
+
181
+ ```ruby
182
+ session.on_event do |event|
183
+ case event
184
+ when Ask::Agent::Events::TextDelta
185
+ print event.content
186
+ when Ask::Agent::Events::ToolExecutionStart
187
+ puts "Running #{event.name}..."
188
+ when Ask::Agent::Events::SessionEnd
189
+ puts "Done: #{event.tool_calls_made} tools, $#{event.cost}"
190
+ end
191
+ end
192
+ ```
193
+
194
+ ## Common Patterns
195
+
196
+ ### Multi-Agent Coordination
197
+
198
+ ```ruby
199
+ search = Ask::Agent::SubAgent.new("web_search")
200
+ review = Ask::Agent::SubAgent.new("code_review")
201
+
202
+ coordinator = Ask::Agent::Session.new(
203
+ model: "gpt-4o",
204
+ tools: [search, review, Ask::Tools::Bash]
205
+ )
206
+ coordinator.run("Find the latest Rails release and check our Gemfile")
207
+ ```
208
+
209
+ ### Persistent Sessions
210
+
211
+ ```ruby
212
+ store = Ask::State::Providers::SQLite.new
213
+ session = Ask::Agent::Session.new(
214
+ model: "gpt-4o",
215
+ tools: tools,
216
+ state: store,
217
+ checkpoints: true
218
+ )
219
+
220
+ session.run("Investigate the error")
221
+ restored = Ask::Agent::Session.load(session.id, adapter: store)
222
+ restored.run("What else should I check?")
223
+ ```
224
+
225
+ ### Scheduled Agents
226
+
227
+ ```ruby
228
+ # In agent.rb:
229
+ class HealthCheck < Ask::Agent::Definition
230
+ model "gpt-4o"
231
+ tools :bash, :read
232
+ schedule "every 5 minutes"
233
+ end
234
+
235
+ # Or globally:
236
+ Ask::Agent.configure do |c|
237
+ c.scheduler.every "5 minutes", name: "health-check" do
238
+ Ask::Agent.new("health_check").run("Check server health")
239
+ end
240
+ end
241
+ Ask::Agent::Scheduler.start
242
+ ```
243
+
244
+ ## CLI
245
+
246
+ ```bash
247
+ askr list # List all discovered agents
248
+ askr run health_check # Run an agent
249
+ askr run health_check "..." # Run with a prompt
250
+ askr new deploy_bot # Scaffold a new agent
251
+ askr skills list # List all discovered skills
252
+ askr skills install # Install ask-agent skill to ~/.agents/skills/
253
+ askr skills uninstall # Remove installed skill
254
+ ```
255
+
256
+ ## Troubleshooting
257
+
258
+ - **Agent not found**: Check `agents/<name>/agent.rb` exists and the class subclasses `Ask::Agent::Definition`
259
+ - **Tool not resolving**: Verify the tool file is in `agents/<name>/tools/` or registered in `Ask::Tools`
260
+ - **No system prompt**: Ensure `instructions.md` exists next to `agent.rb`
261
+ - **Skills not loading**: Check `skills_disclosure: true` (default) and the skill file follows `SKILL.md` convention
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.18
4
+ version: 0.40.19
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kaka Ruto
@@ -219,6 +219,7 @@ files:
219
219
  - lib/ask/agent/tool_executor.rb
220
220
  - lib/ask/agent/tool_output_store.rb
221
221
  - lib/ask/agent/version.rb
222
+ - lib/ask/skills/agent.build_agents/SKILL.md
222
223
  homepage: https://github.com/ask-rb/ask-agent
223
224
  licenses:
224
225
  - MIT