ask-agent 0.5.0 → 0.6.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 +34 -0
- data/lib/ask/agent/configuration.rb +6 -0
- data/lib/ask/agent/scheduler.rb +172 -0
- data/lib/ask/agent/version.rb +1 -1
- data/lib/ask/agent.rb +1 -0
- metadata +16 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0f2eeb2cf5c59ddd76d509d628a809ebe903d9881f5dca8617f511331baaecf7
|
|
4
|
+
data.tar.gz: 25d07e7d421013bef9ba175011461cbdab6d8370d81e54e8fc742429bbe5473d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 36eab4ddb395657b6bac444f4347cdf783d130e4370c300da944c9636e87ac6ee310b3af98029e2aaffa318d7632a6ae5a89c7168e4a7ae8b247f4b640ba40da
|
|
7
|
+
data.tar.gz: f2926d63179b15cc08706c4e5b29a4b5f5a8cf32015ad4d62e266eaff6049951d7afe238eb7b2340038dcee0c14f602ad37b9651b801b2c9a775997055df3851
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,37 @@
|
|
|
1
|
+
## [0.6.0] — 2026-07-21
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- **Agent Scheduler** — `Ask::Agent::Scheduler` runs recurring agent tasks on cron schedules or human-readable intervals. Configure tasks alongside middleware and transforms, then start the background loop.
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
Ask::Agent.configure do |c|
|
|
9
|
+
c.scheduler.every "5 minutes", name: "health-check" do
|
|
10
|
+
Ask::Agent::Session.new(model: "gpt-4o").run("Check server health")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
c.scheduler.cron "0 9 * * 1-5", name: "morning-report" do
|
|
14
|
+
Ask::Agent::Session.new(model: "gpt-4o").run("Generate daily report")
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
Ask::Agent::Scheduler.start # background thread loop
|
|
19
|
+
Ask::Agent::Scheduler.stop # graceful shutdown
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Manage the scheduler at runtime:
|
|
23
|
+
- `Ask::Agent::Scheduler.running?` — check if the loop is active
|
|
24
|
+
- `Ask::Agent::Scheduler.jobs` — list all scheduled jobs (returns `Rufus::Scheduler::Job` objects with `.name`, `.next_time`, etc.)
|
|
25
|
+
- `Ask::Agent::Scheduler.job_by_name("health-check")` — find a specific job
|
|
26
|
+
- Tasks without blocks are valid — they register but execute nothing
|
|
27
|
+
|
|
28
|
+
Powered by `rufus-scheduler` (added as a runtime dependency). The scheduler is optional — users who don't configure any tasks are unaffected.
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- **`Ask::Agent::Configuration`** now exposes `#scheduler` returning a `SchedulerConfig` DSL proxy. No breaking changes for existing users.
|
|
33
|
+
- **Gemspec** — added `rufus-scheduler ~> 3.9` as a runtime dependency.
|
|
34
|
+
|
|
1
35
|
## [0.5.0] — 2026-07-21
|
|
2
36
|
|
|
3
37
|
### Added
|
|
@@ -22,6 +22,12 @@ module Ask
|
|
|
22
22
|
|
|
23
23
|
@middleware = Middleware::Pipeline.new
|
|
24
24
|
@stream_transforms = StreamTransforms::Pipeline.new
|
|
25
|
+
@scheduler_config = SchedulerConfig.new(self)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @return [SchedulerConfig] DSL for defining recurring agent tasks
|
|
29
|
+
def scheduler
|
|
30
|
+
@scheduler_config
|
|
25
31
|
end
|
|
26
32
|
end
|
|
27
33
|
end
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ask
|
|
4
|
+
module Agent
|
|
5
|
+
# Schedules recurring agent runs.
|
|
6
|
+
#
|
|
7
|
+
# Configure tasks through {Ask::Agent::Configuration#scheduler}, then
|
|
8
|
+
# start the scheduler loop. Tasks run in background threads managed by
|
|
9
|
+
# +rufus-scheduler+.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# Ask::Agent.configure do |c|
|
|
13
|
+
# c.scheduler.every "5 minutes", name: "health-check" do
|
|
14
|
+
# Ask::Agent::Session.new(model: "gpt-4o").run("Check server health")
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# c.scheduler.cron "0 9 * * 1-5", name: "morning-report" do
|
|
18
|
+
# Ask::Agent::Session.new(model: "gpt-4o").run("Generate daily report")
|
|
19
|
+
# end
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# Ask::Agent::Scheduler.start # background thread loop
|
|
23
|
+
# Ask::Agent::Scheduler.running? # => true
|
|
24
|
+
# Ask::Agent::Scheduler.jobs # => list of rufus jobs
|
|
25
|
+
# Ask::Agent::Scheduler.stop # graceful shutdown
|
|
26
|
+
class Scheduler
|
|
27
|
+
@instance = nil
|
|
28
|
+
@mutex = Mutex.new
|
|
29
|
+
|
|
30
|
+
class << self
|
|
31
|
+
# @return [Scheduler] the singleton instance
|
|
32
|
+
def instance
|
|
33
|
+
@instance ||= new
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Start the scheduler background thread.
|
|
37
|
+
# All configured tasks begin running on their schedules.
|
|
38
|
+
# @return [Scheduler] the singleton instance
|
|
39
|
+
def start
|
|
40
|
+
@mutex.synchronize do
|
|
41
|
+
return instance if instance.up?
|
|
42
|
+
|
|
43
|
+
instance.start
|
|
44
|
+
end
|
|
45
|
+
instance
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Gracefully stop the scheduler and wait for running tasks.
|
|
49
|
+
def stop
|
|
50
|
+
@mutex.synchronize do
|
|
51
|
+
instance.stop
|
|
52
|
+
@instance = nil
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @return [Boolean] whether the scheduler loop is active
|
|
57
|
+
def running?
|
|
58
|
+
instance.up?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @return [Array<Rufus::Scheduler::Job>] currently scheduled jobs
|
|
62
|
+
def jobs
|
|
63
|
+
instance.jobs
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Find a scheduled job by its name.
|
|
67
|
+
# @param name [String] the job name
|
|
68
|
+
# @return [Rufus::Scheduler::Job, nil]
|
|
69
|
+
def job_by_name(name)
|
|
70
|
+
jobs.find { |j| j.name == name }
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def initialize
|
|
75
|
+
@rufus = nil
|
|
76
|
+
@own_mutex = Mutex.new
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Start the rufus-scheduler loop and register all configured tasks.
|
|
80
|
+
# @return [self]
|
|
81
|
+
def start
|
|
82
|
+
@own_mutex.synchronize do
|
|
83
|
+
return self if @rufus&.up?
|
|
84
|
+
|
|
85
|
+
require "rufus-scheduler"
|
|
86
|
+
|
|
87
|
+
@rufus = Rufus::Scheduler.new
|
|
88
|
+
|
|
89
|
+
config = Ask::Agent.configuration
|
|
90
|
+
|
|
91
|
+
# Register tasks that were configured statically
|
|
92
|
+
config.scheduler.each_task do |task|
|
|
93
|
+
schedule_task(task)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
self
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Stop the scheduler and wait for running jobs to finish.
|
|
100
|
+
def stop
|
|
101
|
+
@own_mutex.synchronize do
|
|
102
|
+
@rufus&.shutdown(:wait)
|
|
103
|
+
@rufus = nil
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# @return [Boolean] true if the rufus scheduler is active
|
|
108
|
+
def up?
|
|
109
|
+
@rufus&.up? || false
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# @return [Array<Rufus::Scheduler::Job>] currently scheduled jobs
|
|
113
|
+
def jobs
|
|
114
|
+
@rufus&.jobs || []
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
private
|
|
118
|
+
|
|
119
|
+
def schedule_task(task)
|
|
120
|
+
block = task[:block]
|
|
121
|
+
name = task[:name]
|
|
122
|
+
|
|
123
|
+
case task[:type]
|
|
124
|
+
when :every
|
|
125
|
+
@rufus.every task[:interval], name: name do
|
|
126
|
+
block&.call
|
|
127
|
+
end
|
|
128
|
+
when :cron
|
|
129
|
+
@rufus.cron task[:cron], name: name do
|
|
130
|
+
block&.call
|
|
131
|
+
end
|
|
132
|
+
else
|
|
133
|
+
raise ArgumentError, "Unknown schedule type: #{task[:type].inspect}"
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# DSL proxy used inside {Ask::Agent::Configuration#scheduler}.
|
|
139
|
+
# Collects task definitions that the {Scheduler} registers on start.
|
|
140
|
+
class SchedulerConfig
|
|
141
|
+
def initialize(config)
|
|
142
|
+
@config = config
|
|
143
|
+
@tasks = []
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Schedule a block to run on a recurring interval.
|
|
147
|
+
#
|
|
148
|
+
# @param interval [String] human-readable interval (e.g. "5 minutes", "1 hour")
|
|
149
|
+
# @param name [String, nil] optional job name for identification
|
|
150
|
+
# @yield block to execute on each tick
|
|
151
|
+
def every(interval, name: nil, &block)
|
|
152
|
+
@tasks << { type: :every, interval: interval, name: name, block: block }
|
|
153
|
+
self
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Schedule a block to run on a cron schedule.
|
|
157
|
+
#
|
|
158
|
+
# @param cron_expression [String] standard cron syntax (e.g. "0 9 * * 1-5")
|
|
159
|
+
# @param name [String, nil] optional job name for identification
|
|
160
|
+
# @yield block to execute at each scheduled time
|
|
161
|
+
def cron(cron_expression, name: nil, &block)
|
|
162
|
+
@tasks << { type: :cron, cron: cron_expression, name: name, block: block }
|
|
163
|
+
self
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Yield each configured task definition.
|
|
167
|
+
def each_task(&block)
|
|
168
|
+
@tasks.each(&block)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
data/lib/ask/agent/version.rb
CHANGED
data/lib/ask/agent.rb
CHANGED
|
@@ -70,6 +70,7 @@ require_relative "agent/compactor"
|
|
|
70
70
|
require_relative "agent/hooks"
|
|
71
71
|
require_relative "agent/configuration"
|
|
72
72
|
require_relative "agent/meta_agent"
|
|
73
|
+
require_relative "agent/scheduler"
|
|
73
74
|
require_relative "agent/persistence/base"
|
|
74
75
|
require_relative "agent/persistence/in_memory"
|
|
75
76
|
require_relative "agent/skills/load_skill_tool"
|
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.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kaka Ruto
|
|
@@ -79,6 +79,20 @@ dependencies:
|
|
|
79
79
|
- - ">="
|
|
80
80
|
- !ruby/object:Gem::Version
|
|
81
81
|
version: '0.1'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: rufus-scheduler
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '3.9'
|
|
89
|
+
type: :runtime
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '3.9'
|
|
82
96
|
- !ruby/object:Gem::Dependency
|
|
83
97
|
name: minitest
|
|
84
98
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -152,6 +166,7 @@ files:
|
|
|
152
166
|
- lib/ask/agent/persistence/base.rb
|
|
153
167
|
- lib/ask/agent/persistence/in_memory.rb
|
|
154
168
|
- lib/ask/agent/reflector.rb
|
|
169
|
+
- lib/ask/agent/scheduler.rb
|
|
155
170
|
- lib/ask/agent/session.rb
|
|
156
171
|
- lib/ask/agent/skills/load_skill_tool.rb
|
|
157
172
|
- lib/ask/agent/stream_transforms/base.rb
|