Class: Silas::Schedule
- Inherits:
-
Object
- Object
- Silas::Schedule
- Defined in:
- lib/silas/schedule.rb,
lib/silas/schedule/compiler.rb
Overview
A schedule is a TRIGGER: it fires on a cron cadence and starts a normal, fully-durable turn. Two forms (eve's shapes):
.md with cron frontmatter — "task mode": the body becomes the turn input,
output discarded (fire-and-forget).
.rb subclassing Silas::Schedule::Handler — programmatic control (fan-out,
continue an existing session, conditional start).
Identity is the filesystem path under app/agent/schedules (subdirs included): schedules/billing/sweep.rb -> "billing/sweep" -> Agent::Schedules::Billing::Sweep. Named agents own their schedules the same way they own tools and skills: app/agents/analyst/schedules/monday_kpis.md -> "agents/analyst/monday_kpis", and its ticks start THAT agent — a staff member's cron never wakes the root agent. Schedules are NOT model-visible capabilities, so they never enter the definitions digest — adding or removing one cannot diverge an in-flight turn.
Defined Under Namespace
Modules: Compiler Classes: Handler
Constant Summary collapse
- KINDS =
%i[task handler].freeze
Instance Attribute Summary collapse
-
#agent_name ⇒ Object
readonly
Returns the value of attribute agent_name.
-
#cron ⇒ Object
readonly
Returns the value of attribute cron.
-
#kind ⇒ Object
readonly
Returns the value of attribute kind.
-
#name ⇒ Object
readonly
Returns the value of attribute name.
-
#payload ⇒ Object
readonly
Returns the value of attribute payload.
-
#queue ⇒ Object
readonly
Returns the value of attribute queue.
Class Method Summary collapse
- .from_handler(name, klass, agent_name: nil) ⇒ Object
- .from_markdown(name, path, agent_name: nil) ⇒ Object
- .parse(path, root:) ⇒ Object
-
.parse_named(path, agents_dir:) ⇒ Object
app/agents/
/schedules/ — the "agents/" name prefix keeps names (and therefore recurring keys) collision-free against a root schedule that happens to share the path shape.
Instance Method Summary collapse
-
#initialize(name:, cron:, queue:, kind:, payload:, agent_name: nil) ⇒ Schedule
constructor
A new instance of Schedule.
- #recurring_entry ⇒ Object
- #recurring_key ⇒ Object
-
#trigger! ⇒ Object
The only behavioural difference between the two forms.
Constructor Details
#initialize(name:, cron:, queue:, kind:, payload:, agent_name: nil) ⇒ Schedule
Returns a new instance of Schedule.
24 25 26 27 28 29 30 31 |
# File 'lib/silas/schedule.rb', line 24 def initialize(name:, cron:, queue:, kind:, payload:, agent_name: nil) @name = name @cron = cron @queue = queue @kind = kind @payload = payload @agent_name = agent_name end |
Instance Attribute Details
#agent_name ⇒ Object (readonly)
Returns the value of attribute agent_name.
22 23 24 |
# File 'lib/silas/schedule.rb', line 22 def agent_name @agent_name end |
#cron ⇒ Object (readonly)
Returns the value of attribute cron.
22 23 24 |
# File 'lib/silas/schedule.rb', line 22 def cron @cron end |
#kind ⇒ Object (readonly)
Returns the value of attribute kind.
22 23 24 |
# File 'lib/silas/schedule.rb', line 22 def kind @kind end |
#name ⇒ Object (readonly)
Returns the value of attribute name.
22 23 24 |
# File 'lib/silas/schedule.rb', line 22 def name @name end |
#payload ⇒ Object (readonly)
Returns the value of attribute payload.
22 23 24 |
# File 'lib/silas/schedule.rb', line 22 def payload @payload end |
#queue ⇒ Object (readonly)
Returns the value of attribute queue.
22 23 24 |
# File 'lib/silas/schedule.rb', line 22 def queue @queue end |
Class Method Details
.from_handler(name, klass, agent_name: nil) ⇒ Object
83 84 85 86 87 88 89 90 91 92 |
# File 'lib/silas/schedule.rb', line 83 def self.from_handler(name, klass, agent_name: nil) unless klass.ancestors.include?(Schedule::Handler) raise Error, "#{klass} (schedule #{name}) must inherit Silas::Schedule::Handler" end cron = klass.cron raise Error, "schedule #{name}: handler must declare `cron` or `every`" if cron.nil? new(name: name, cron: cron, queue: (klass.queue || Silas.config.queue_name).to_s, kind: :handler, payload: klass, agent_name: agent_name) end |
.from_markdown(name, path, agent_name: nil) ⇒ Object
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/silas/schedule.rb', line 65 def self.from_markdown(name, path, agent_name: nil) content = File.read(path) # Tolerate leading whitespace/blank lines (e.g. an ERB comment in a # generator template renders to an empty first line). if content.sub(/\A\s+/, "") =~ /\A---\s*\n(.*?)\n---\s*\n(.*)\z/m frontmatter = YAML.safe_load(Regexp.last_match(1)) || {} body = Regexp.last_match(2) else frontmatter = {} body = content end cron = frontmatter["cron"] || frontmatter["schedule"] raise Error, "schedule #{name}: missing `cron:` frontmatter" if cron.nil? new(name: name, cron: cron.to_s, queue: (frontmatter["queue"] || Silas.config.queue_name).to_s, kind: :task, payload: body.strip, agent_name: agent_name) end |
.parse(path, root:) ⇒ Object
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/silas/schedule.rb', line 33 def self.parse(path, root:) agents_dir = root.join("app/agents") if path.to_s.start_with?("#{agents_dir}#{File::SEPARATOR}") parse_named(path, agents_dir: agents_dir) else rel = path.relative_path_from(root.join("app/agent/schedules")) name = rel.sub_ext("").to_s case path.extname when ".md" then from_markdown(name, path) when ".rb" const = "Agent::Schedules::" + name.split("/").map(&:camelize).join("::") from_handler(name, const.constantize) end end end |
.parse_named(path, agents_dir:) ⇒ Object
app/agents/
52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/silas/schedule.rb', line 52 def self.parse_named(path, agents_dir:) rel = path.relative_path_from(agents_dir) # analyst/schedules/monday_kpis.md agent = rel.each_filename.first sched = rel.relative_path_from(Pathname(agent).join("schedules")).sub_ext("").to_s name = "agents/#{agent}/#{sched}" case path.extname when ".md" then from_markdown(name, path, agent_name: agent) when ".rb" const = "Agents::#{agent.camelize}::Schedules::" + sched.split("/").map(&:camelize).join("::") from_handler(name, const.constantize, agent_name: agent) end end |
Instance Method Details
#recurring_entry ⇒ Object
109 110 111 |
# File 'lib/silas/schedule.rb', line 109 def recurring_entry { "class" => "Silas::ScheduleJob", "args" => [ name ], "schedule" => cron, "queue" => queue } end |
#recurring_key ⇒ Object
107 |
# File 'lib/silas/schedule.rb', line 107 def recurring_key = "silas_schedule_#{name.tr('/', '_')}" |
#trigger! ⇒ Object
The only behavioural difference between the two forms. A named agent's task starts THAT agent (its tools, instructions, digest — the loop swaps the scope in for every turn of the session it creates).
97 98 99 100 101 102 103 104 105 |
# File 'lib/silas/schedule.rb', line 97 def trigger! case kind when :task owner = agent_name ? Silas.agent(agent_name) : Silas.agent owner.start(input: payload, metadata: { "trigger" => "schedule", "schedule" => name }) when :handler payload.new(schedule: self).call end end |