agentic examples

The Fireworks Show

The Fireworks Show: choreography IS concurrency. A show is three staggered volleys and then a finale - five shells that must burst TOGETHER, which is only physically possible if someone can light five fuses at once. We run the same score twice: once with a single igniter (the intern), once with a full crew (the fiber scheduler), and the burst timeline itself is the argument. You cannot sequence your way to a finale.

Scheduling & Concurrency Round 18 Samuel Williams exit 0

source on github

bundle exec ruby examples/fireworks_show.rb

a real captured run

THE FIREWORKS SHOW (you cannot sequence your way to a finale)

  one igniter, lighting fuses in a row (685ms):
    willow         *
    peony              *
    comet                 *
    chrysanth                 *
    crossette                     *
    strobe                            *
    palm                                 *
    ring                                     *
    fish                                        *
    finale-1                                         #
    finale-2                                              #
    finale-3                                                  #
    finale-4                                                      #
    finale-5                                                          #

  full crew, same score (216ms):
    willow                   *
    comet                    *
    peony                      *
    crossette                             *
    chrysanth                               *
    strobe                                  *
    palm                                              *
    fish                                              *
    ring                                                 *
    finale-1                                                          #
    finale-2                                                          #
    finale-3                                                          #
    finale-4                                                          #
    finale-5                                                          #

  finale spread: intern 204ms (a sad trickle of #-marks)
                 crew   1ms (one vertical WALL of sky)
  show length:   intern 685ms vs crew 216ms
                 (650ms of total fuse burned either way - parallel isn't
                 faster fire, it's fire arranged in TIME)

  the score is a plan: volleys are dependency layers (volley 2
  waits on volley 1 - that's rhythm), and the finale is a fan-in
  that must OVERLAP, which no amount of sequential diligence can
  produce. the scheduler isn't an optimization here; it's the
  performance itself. async is usually sold as throughput -
  requests per second, workers kept busy. the finale is the purer
  case: five things that must happen AT ONCE, and the only tool
  that can say 'at once' is the one holding all five fuses.

source

# frozen_string_literal: true

# The Fireworks Show: choreography IS concurrency. A show is three
# staggered volleys and then a finale - five shells that must burst
# TOGETHER, which is only physically possible if someone can light
# five fuses at once. We run the same score twice: once with a
# single igniter (the intern), once with a full crew (the fiber
# scheduler), and the burst timeline itself is the argument. You
# cannot sequence your way to a finale.
#
#   bundle exec ruby examples/fireworks_show.rb
#
# Runs offline; exits 1 unless the crewed finale is simultaneous
# AND the intern's provably wasn't.

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

Agentic.logger.level = class="y">:fatal

def mono = Process.clock_gettime(Process:class="y">:CLOCK_MONOTONIC)

VOLLEYS = [
  [{name: class="s">"willow", fuse: 0.04}, {name: class="s">"peony", fuse: 0.05}, {name: class="s">"comet", fuse: 0.04}],
  [{name: class="s">"chrysanth", fuse: 0.05}, {name: class="s">"crossette", fuse: 0.04}, {name: class="s">"strobe", fuse: 0.05}],
  [{name: class="s">"palm", fuse: 0.04}, {name: class="s">"ring", fuse: 0.05}, {name: class="s">"fish", fuse: 0.04}]
].freeze
FINALE = 5.times.map { |i| {name: class="s">"finale-#{i + 1}", fuse: 0.05} }.freeze

def run_show(crew_size)
  orchestrator = Agentic:class="y">:PlanOrchestrator.new(concurrency_limit: crew_size)
  t0 = mono
  bursts = {}
  previous_volley = []
  VOLLEYS.each_with_index do |volley, v|
    previous_volley = volley.map do |shell|
      task = Agentic:class="y">:Task.new(description: shell[class="y">:name], agent_spec: {class="s">"name" => shell[class="y">:name], class="s">"instructions" => class="s">"fly"})
      orchestrator.add_task(task, previous_volley, agent: ->(_t) {
        sleep(shell[class="y">:fuse])
        bursts[shell[class="y">:name]] = {volley: v, at: mono - t0}
        class="y">:boom
      })
      task
    end
  end
  FINALE.each do |shell|
    task = Agentic:class="y">:Task.new(description: shell[class="y">:name], agent_spec: {class="s">"name" => shell[class="y">:name], class="s">"instructions" => class="s">"fly"})
    orchestrator.add_task(task, previous_volley, agent: ->(_t) {
      sleep(shell[class="y">:fuse])
      bursts[shell[class="y">:name]] = {volley: class="y">:finale, at: mono - t0}
      class="y">:BOOM
    })
  end
  orchestrator.execute_plan
  [bursts, mono - t0]
end

def timeline(bursts, duration, label)
  cols = 56
  puts class="s">"  #{label} (#{(duration * 1000).round}ms):"
  bursts.sort_by { |_, b| b[class="y">:at] }.each do |name, b|
    col = (b[class="y">:at] / duration * (cols - 1)).round
    mark = (b[class="y">:volley] == class="y">:finale) ? class="s">"#" : class="s">"*"
    puts class="s">"    #{name.ljust(10)} #{" class="s">" * col}#{mark}"
  end
end

def finale_spread(bursts)
  times = bursts.select { |_, b| b[class="y">:volley] == class="y">:finale }.map { |_, b| b[class="y">:at] }
  times.max - times.min
end

puts class="s">"THE FIREWORKS SHOW (you cannot sequence your way to a finale)"
puts

intern_bursts, intern_time = run_show(1)
crew_bursts, crew_time = run_show(8)

timeline(intern_bursts, intern_time, class="s">"one igniter, lighting fuses in a row")
puts
timeline(crew_bursts, crew_time, class="s">"full crew, same score")
puts

intern_spread = finale_spread(intern_bursts)
crew_spread = finale_spread(crew_bursts)
total_fuse = (VOLLEYS.flatten + FINALE).sum { |s| s[class="y">:fuse] }

puts class="s">"  finale spread: intern #{(intern_spread * 1000).round}ms (a sad trickle of #-marks)"
puts class="s">"                 crew   #{(crew_spread * 1000).round}ms (one vertical WALL of sky)"
puts class="s">"  show length:   intern #{(intern_time * 1000).round}ms vs crew #{(crew_time * 1000).round}ms"
puts class="s">"                 (#{(total_fuse * 1000).round}ms of total fuse burned either way - parallel isn't"
puts class="s">"                 faster fire, it's fire arranged in TIME)"
puts
puts class="s">"  the score is a plan: volleys are dependency layers (volley 2"
puts class="s">"  waits on volley 1 - that's rhythm), and the finale is a fan-in"
puts class="s">"  that must OVERLAP, which no amount of sequential diligence can"
puts class="s">"  produce. the scheduler isn't an optimization here; it's the"
puts class="s">"  performance itself. async is usually sold as throughput -"
puts class="s">"  requests per second, workers kept busy. the finale is the purer"
puts class="s">"  case: five things that must happen AT ONCE, and the only tool"
puts class="s">"  that can say 'at once' is the one holding all five fuses."
crewed_wall = crew_spread < 0.02
intern_trickle = intern_spread > crew_spread * 3
exit((crewed_wall && intern_trickle) ? 0 : 1)