agentic examples

The Plan Gantt

The Plan Gantt: lifecycle hooks timestamp every task, then the run is rendered as an ASCII timeline - where your wall clock actually went. A diamond dependency graph with a tight concurrency limit makes the scheduler's decisions visible to the naked eye.

Scheduling & Concurrency Round 3 Aaron Patterson exit 0

source on github

bundle exec ruby examples/plan_gantt.rb

a real captured run

PLAN GANTT (concurrency 2, 414ms wall)

  fetch:users      |############                              |   0-121ms
  fetch:orders     |####################                      |   0-203ms
  fetch:events     |............########                      |   0-203ms
  join:revenue     |                    ###############       | 203-353ms
  join:activity    |                    ##########            | 203-303ms
  report:weekly    |                                   ###### | 353-413ms

                   |0         1         2         3         4  |
  (one column = 10ms; '.' = queued for a slot, '#' = running)

  serial floor 710ms -> actual 414ms (1.7x from the scheduler)

source

# frozen_string_literal: true

# The Plan Gantt: lifecycle hooks timestamp every task, then the run is
# rendered as an ASCII timeline - where your wall clock actually went.
# A diamond dependency graph with a tight concurrency limit makes the
# scheduler's decisions visible to the naked eye.
#
#   bundle exec ruby examples/plan_gantt.rb [concurrency]
#
# Runs offline; task durations are simulated IO.

require class="s">"bundler/setup"
require class="s">"agentic"

WORK = {
  class="s">"fetch:users" => {sleep: 0.12, deps: []},
  class="s">"fetch:orders" => {sleep: 0.20, deps: []},
  class="s">"fetch:events" => {sleep: 0.08, deps: []},
  class="s">"join:activity" => {sleep: 0.10, deps: [class="s">"fetch:users", class="s">"fetch:events"]},
  class="s">"join:revenue" => {sleep: 0.15, deps: [class="s">"fetch:users", class="s">"fetch:orders"]},
  class="s">"report:weekly" => {sleep: 0.06, deps: [class="s">"join:activity", class="s">"join:revenue"]}
}.freeze

concurrency = (ARGV.first || 2).to_i
timeline = {}
plan_start = nil

hooks = {
  before_task_execution: ->(task_id:, task:) {
    plan_start ||= Process.clock_gettime(Process:class="y">:CLOCK_MONOTONIC)
    (timeline[task.description] ||= {})[class="y">:start] =
      Process.clock_gettime(Process:class="y">:CLOCK_MONOTONIC) - plan_start
  },
  task_slot_acquired: ->(task_id:, task:, waited:) {
    timeline[task.description][class="y">:running] =
      Process.clock_gettime(Process:class="y">:CLOCK_MONOTONIC) - plan_start
  },
  after_task_success: ->(task_id:, task:, result:, duration:) {
    timeline[task.description][class="y">:finish] =
      Process.clock_gettime(Process:class="y">:CLOCK_MONOTONIC) - plan_start
  }
}

orchestrator = Agentic:class="y">:PlanOrchestrator.new(concurrency_limit: concurrency, lifecycle_hooks: hooks)
tasks = {}
WORK.each do |name, spec|
  task = Agentic:class="y">:Task.new(
    description: name,
    agent_spec: {class="s">"name" => name, class="s">"instructions" => class="s">"simulate"},
    payload: spec[class="y">:sleep]
  )
  tasks[name] = task
  orchestrator.add_task(task, spec[class="y">:deps].map { |d| tasks.fetch(d) }, agent: ->(t) {
    sleep(t.payload)
    class="y">:done
  })
end

result = orchestrator.execute_plan

# Render: 1 column = 10ms
total = timeline.values.map { |t| t[class="y">:finish] }.max
columns = (total * 100).ceil
puts class="s">"PLAN GANTT (concurrency #{concurrency}, #{(result.execution_time * 1000).round}ms wall)"
puts
timeline.each do |name, t|
  from = (t[class="y">:start] * 100).round
  slot = ((t[class="y">:running] || t[class="y">:start]) * 100).round
  queued = [slot - from, 0].max
  width = [((t[class="y">:finish] - (t[class="y">:running] || t[class="y">:start])) * 100).round, 1].max
  bar = ((class="s">" " * from) + (class="s">"." * queued) + (class="s">"#" * width)).ljust(columns)
  puts format(class="s">"  %-16s |%s| %3d-%3dms", name, bar, t[class="y">:start] * 1000, t[class="y">:finish] * 1000)
end
puts
puts format(class="s">"  %-16s |%s|", class="s">"", (0..columns).step(10).map { |c| (c / 10).to_s.ljust(10) }.join[0, columns + 1])
puts class="s">"  (one column = 10ms; '.' = queued for a slot, '#' = running)"
puts
serial_floor = WORK.values.sum { |w| w[class="y">:sleep] }
puts format(class="s">"  serial floor %.0fms -> actual %.0fms (%.1fx from the scheduler)",
  serial_floor * 1000, result.execution_time * 1000, serial_floor / result.execution_time)