agentic examples

The Bakery Rush

The Bakery Rush: two ovens, thirteen orders, one queue - and the morning is decided before the first tray goes in, by ENQUEUE ORDER. A bakery is a queue wearing an apron: the plan is the queue, the concurrency limit is the ovens, and the discipline (who bakes first) is policy you choose, not fate. We run the same morning twice - first-come-first-served with the wedding cake up front, then shortest-bake-first - and measure customer sadness. Queue theory has opinions about …

Testing & Verification Round 18 Rosa Gutiérrez exit 0

source on github

bundle exec ruby examples/bakery_rush.rb

a real captured run

THE BAKERY RUSH (a bakery is a queue wearing an apron)

  monday, first-come-first-served (two cakes hog BOTH ovens at 6am):
    mean wait 210ms; customers lost to the cafe next door: 8
      walked out: croissant #1 (waited 184ms, patience 150ms)
      walked out: croissant #2 (waited 205ms, patience 150ms)
      walked out: croissant #3 (waited 205ms, patience 150ms)
      walked out: croissant #4 (waited 225ms, patience 150ms)
      walked out: croissant #5 (waited 226ms, patience 150ms)
      walked out: croissant #6 (waited 246ms, patience 150ms)
      walked out: eclair #1 (waited 310ms, patience 300ms)
      walked out: eclair #2 (waited 327ms, patience 300ms)
    cakes: done at 183ms and 205ms - both on time

  tuesday, shortest-bake-first (same ovens, same orders, new discipline):
    mean wait 79ms; customers lost to the cafe next door: 0
    cakes: done at 326ms and 386ms - both on time

  nothing about the bakery changed overnight - not the ovens, not
  the orders, not the bake times. only the DISCIPLINE: monday
  baked in arrival order and the wedding cake sat in an oven for
  200ms while croissant customers (patience: 150ms) studied the
  cafe across the street. tuesday baked shortest-first, because
  shortest-job-first provably minimizes mean wait - and the cake
  was never actually urgent: ordered first, due at NOON. arrival
  order and deadline order are different orders. every queue
  system rediscovers this; bakers knew it already. the queue is
  the product - choose its discipline like you chose the recipes.

source

# frozen_string_literal: true

# The Bakery Rush: two ovens, thirteen orders, one queue - and the
# morning is decided before the first tray goes in, by ENQUEUE
# ORDER. A bakery is a queue wearing an apron: the plan is the
# queue, the concurrency limit is the ovens, and the discipline
# (who bakes first) is policy you choose, not fate. We run the same
# morning twice - first-come-first-served with the wedding cake up
# front, then shortest-bake-first - and measure customer sadness.
# Queue theory has opinions about croissants. They are correct.
#
#   bundle exec ruby examples/bakery_rush.rb
#
# Runs offline; exits 1 unless the discipline change rescues every
# croissant customer without making the cake late.

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)

# The 6am board: everyone "arrived" at open; patience varies wildly
ORDERS = [
  {item: class="s">"wedding cake", bake: 0.20, patience: 1.00},  # due at noon; the baker's pride
  {item: class="s">"birthday cake", bake: 0.18, patience: 1.00}, # due at three; has sprinkles
  *6.times.map { |i| {item: class="s">"croissant ##{i + 1}", bake: 0.02, patience: 0.15} },
  *3.times.map { |i| {item: class="s">"baguette ##{i + 1}", bake: 0.04, patience: 0.30} },
  *2.times.map { |i| {item: class="s">"eclair ##{i + 1}", bake: 0.03, patience: 0.30} }
].freeze

def open_the_doors(orders_in_queue_order)
  orchestrator = Agentic:class="y">:PlanOrchestrator.new(concurrency_limit: 2) # two ovens, no negotiating
  opened = mono
  tickets = {}
  orders_in_queue_order.each do |order|
    task = Agentic:class="y">:Task.new(description: order[class="y">:item], agent_spec: {class="s">"name" => order[class="y">:item], class="s">"instructions" => class="s">"bake"})
    orchestrator.add_task(task, agent: ->(_t) {
      waited = mono - opened
      sleep(order[class="y">:bake]) # the oven does not care about your feelings
      tickets[order[class="y">:item]] = {waited: waited, done: mono - opened, patience: order[class="y">:patience]}
      class="y">:golden_brown
    })
  end
  orchestrator.execute_plan
  tickets
end

def morning_report(label, tickets)
  lost = tickets.select { |_, t| t[class="y">:waited] > t[class="y">:patience] }
  cakes = tickets.select { |item, _| item.include?(class="s">"cake") }.values
  mean_wait = tickets.values.sum { |t| t[class="y">:waited] } / tickets.size
  puts class="s">"  #{label}:"
  puts class="s">"    mean wait #{(mean_wait * 1000).round}ms; customers lost to the cafe next door: #{lost.size}"
  lost.each { |item, t| puts class="s">"      walked out: #{item} (waited #{(t[class="y">:waited] * 1000).round}ms, patience #{(t[class="y">:patience] * 1000).round}ms)" }
  cakes_ok = cakes.all? { |c| c[class="y">:done] <= c[class="y">:patience] }
  puts class="s">"    cakes: done at #{cakes.map { |c| "#{(c[class="y">:done] * 1000).round}msclass="s">" }.join(" and class="s">")} #{cakes_ok ? "- both on timeclass="s">" : "- LATE, catastropheclass="s">"}"
  [mean_wait, lost.size, cakes_ok]
end

puts class="s">"THE BAKERY RUSH (a bakery is a queue wearing an apron)"
puts

fifo = open_the_doors(ORDERS) # cake first: it was ordered first, seems fair
fifo_mean, fifo_lost, fifo_cake_ok = morning_report(class="s">"monday, first-come-first-served (two cakes hog BOTH ovens at 6am)", fifo)
puts

sjf = open_the_doors(ORDERS.sort_by { |o| o[class="y">:bake] }) # shortest bake first; cake waits, but its deadline is NOON
sjf_mean, sjf_lost, sjf_cake_ok = morning_report(class="s">"tuesday, shortest-bake-first (same ovens, same orders, new discipline)", sjf)
puts

puts class="s">"  nothing about the bakery changed overnight - not the ovens, not"
puts class="s">"  the orders, not the bake times. only the DISCIPLINE: monday"
puts class="s">"  baked in arrival order and the wedding cake sat in an oven for"
puts class="s">"  200ms while croissant customers (patience: 150ms) studied the"
puts class="s">"  cafe across the street. tuesday baked shortest-first, because"
puts class="s">"  shortest-job-first provably minimizes mean wait - and the cake"
puts class="s">"  was never actually urgent: ordered first, due at NOON. arrival"
puts class="s">"  order and deadline order are different orders. every queue"
puts class="s">"  system rediscovers this; bakers knew it already. the queue is"
puts class="s">"  the product - choose its discipline like you chose the recipes."
exit((sjf_lost.zero? && fifo_lost.positive? && sjf_cake_ok && fifo_cake_ok && sjf_mean < fifo_mean) ? 0 : 1)