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 +4 -4
- data/lib/ask/agent/cli.rb +118 -1
- data/lib/ask/agent/version.rb +1 -1
- data/lib/ask/skills/agent.build_agents/SKILL.md +261 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 46ea77e0ac0feca360164604367729e05f15e22212f479c59ad1954da5e5e924
|
|
4
|
+
data.tar.gz: 5a1e0198d2e4d9faf7831bd1c7f8520a82a01c7fff46a9a94512a60a6d285409
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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]
|
data/lib/ask/agent/version.rb
CHANGED
|
@@ -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.
|
|
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
|