agentic examples

The Hill Chart

The Hill Chart: Basecamp's answer to "how's it going?" - work climbs the hill while it's still uncertain (queued, waiting on dependencies) and rolls down once it's just execution. Three live snapshots of a running plan, drawn from lifecycle hooks. No status meeting convened.

Scheduling & Concurrency Round 8 DHH exit 0

source on github

bundle exec ruby examples/hill_chart.rb

a real captured run

THE HILL CHART (uphill = still uncertain, downhill = just execution)

  early:
                        ___________A
                   ____/           \____
            BC____/DEF                  \____
         ____/                               \____
    ____/                                         \____

  mid-flight:
                        ___________CD
                   ____/           \____
            EF____/                     \____    AB
         ____/                               \____
    ____/                                         \____

  at the end:
                        ___________
                   ____/           \____
              ____/                     \____    ABCDEF
         ____/                               \____
    ____/                                         \____

    legend: A=audit copy, B=build hero, C=cut video, D=draft email, E=embed video, F=final review

the crest is the honest divider: left of it, tasks are waiting on
dependencies or a slot (uncertainty you can't schedule away);
right of it, it's just execution. the chart never asks anyone
'percent complete?' - the states are facts from hooks.

source

# frozen_string_literal: true

# The Hill Chart: Basecamp's answer to "how's it going?" - work climbs
# the hill while it's still uncertain (queued, waiting on dependencies)
# and rolls down once it's just execution. Three live snapshots of a
# running plan, drawn from lifecycle hooks. No status meeting convened.
#
#   bundle exec ruby examples/hill_chart.rb
#
# Runs offline; watch the letters roll downhill.

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

WORK = {
  class="s">"A: audit copy" => {sleep: 0.05, deps: []},
  class="s">"B: build hero" => {sleep: 0.09, deps: []},
  class="s">"C: cut video" => {sleep: 0.12, deps: []},
  class="s">"D: draft email" => {sleep: 0.06, deps: [class="s">"A: audit copy"]},
  class="s">"E: embed video" => {sleep: 0.05, deps: [class="s">"B: build hero", class="s">"C: cut video"]},
  class="s">"F: final review" => {sleep: 0.04, deps: [class="s">"D: draft email", class="s">"E: embed video"]}
}.freeze

# Position on the hill, 0.0 (left base) to 1.0 (right base)
POSITIONS = {pending: 0.15, queued: 0.35, running: 0.55, done: 0.9}.freeze

states = WORK.keys.to_h { |name| [name, class="y">:pending] }
snapshots = []

take_snapshot = -> { snapshots << states.dup }

hooks = {
  before_task_execution: ->(task_id:, task:) { states[task.description] = class="y">:queued },
  task_slot_acquired: ->(task_id:, task:, waited:) {
    states[task.description] = class="y">:running
    take_snapshot.call
  },
  after_task_success: ->(task_id:, task:, result:, duration:) {
    states[task.description] = class="y">:done
  }
}

orchestrator = Agentic:class="y">:PlanOrchestrator.new(concurrency_limit: 2, lifecycle_hooks: hooks)
tasks = {}
WORK.each do |name, spec|
  tasks[name] = Agentic:class="y">:Task.new(description: name,
    agent_spec: {class="s">"name" => name, class="s">"instructions" => class="s">"work"}, payload: spec[class="y">:sleep])
  orchestrator.add_task(tasks[name], spec[class="y">:deps].map { |d| tasks.fetch(d) },
    agent: ->(t) { sleep(t.payload) || class="y">:ok })
end
orchestrator.execute_plan
take_snapshot.call # the finished hill

# --- draw the hill -------------------------------------------------------------
HILL = [
  class="s">"                    ___________                    ",
  class="s">"               ____/           \\____               ",
  class="s">"          ____/                     \\____          ",
  class="s">"     ____/                               \\____     ",
  class="s">"____/                                         \\____"
].freeze

def draw_hill(states)
  width = HILL.first.length
  rows = HILL.map(&class="y">:dup)

  # Height of the hill surface at each column, from the art itself
  surface = (0...width).map { |col| rows.index { |row| row[col] != class="s">" " } || rows.size - 1 }

  states.each do |name, state|
    col = (POSITIONS.fetch(state) * (width - 1)).round
    row = [surface[col] - 1, 0].max
    letter = name[0]
    col += 1 while rows[row][col] != class="s">" " && col < width - 1
    rows[row][col] = letter
  end
  rows.each { |row| puts class="s">"    #{row}" }
end

puts class="s">"THE HILL CHART (uphill = still uncertain, downhill = just execution)"
[0, snapshots.size / 2, snapshots.size - 1].uniq.each_with_index do |index, i|
  snap = snapshots[index]
  puts
  puts class="s">"  #{["early:class="s">", "mid-flight:class="s">", "at the end:class="s">"][i]}"
  draw_hill(snap)
end

puts
puts class="s">"    legend: #{WORK.keys.map { |n| n.split(":class="s">").first + "=class="s">" + n.split(": class="s">").last }.join(", class="s">")}"
puts
puts class="s">"the crest is the honest divider: left of it, tasks are waiting on"
puts class="s">"dependencies or a slot (uncertainty you can't schedule away);"
puts class="s">"right of it, it's just execution. the chart never asks anyone"
puts class="s">"'percent complete?' - the states are facts from hooks."