agentic examples

EventProf for Plans

EventProf for Plans: TestProf taught test suites to answer "where does the time GO?" by group, not by file. Same question for plans: tag every task by its kind (llm:, db:, render:), collect durations from the lifecycle hooks, and report task-seconds by tag - plus the number nobody computes: how much of that time ran in parallel, and how much of the wall clock one tag owns hostage.

Testing & Verification Round 12 Vladimir Dementyev exit 0

source on github

bundle exec ruby examples/event_prof.rb

a real captured run

EVENT PROF (task-seconds by tag; wall clock 349ms, 3 lanes)

  tag      seconds    share    tasks    worst offender
  llm         651ms    77.5%   3        llm:draft (250ms)  ##########################
  db          107ms    12.7%   3        db:fetch_orders (42ms)  ####
  render       82ms     9.8%   3        render:pdf (50ms)  ###

  task-seconds: 840ms across 349ms of wall = 2.4x effective parallelism

  the TestProf move is reading the SHARE column before touching any
  code: llm owns 77% of all task-seconds, so a 20% win there is
  worth more than deleting the entire render stage - optimizing
  db: or render: is polishing doorknobs on a burning building.
  and the parallelism line is the second lesson: 2.4x on 3 lanes
  means the stage barriers are eating part of the overlap -
  llm tasks can't start until ALL db tasks finish. profile by
  group, fix the biggest group, re-profile. boring, effective,
  and the hooks made it fifteen lines.

source

# frozen_string_literal: true

# EventProf for Plans: TestProf taught test suites to answer "where
# does the time GO?" by group, not by file. Same question for plans:
# tag every task by its kind (llm:, db:, render:), collect durations
# from the lifecycle hooks, and report task-seconds by tag - plus the
# number nobody computes: how much of that time ran in parallel, and
# how much of the wall clock one tag owns hostage.
#
#   bundle exec ruby examples/event_prof.rb
#
# Runs offline; durations are scripted, accounting is real.

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

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

WORK = {
  class="s">"db:fetch_users" => 0.03, class="s">"db:fetch_orders" => 0.04, class="s">"db:fetch_stock" => 0.03,
  class="s">"llm:summarize" => 0.22, class="s">"llm:classify" => 0.18, class="s">"llm:draft" => 0.25,
  class="s">"render:header" => 0.01, class="s">"render:body" => 0.02, class="s">"render:pdf" => 0.05
}.freeze

samples = []
hooks = {
  after_task_success: ->(task_id:, task:, result:, duration:) {
    samples << [task.description, duration]
  }
}

orchestrator = Agentic:class="y">:PlanOrchestrator.new(concurrency_limit: 3, lifecycle_hooks: hooks)
tasks = WORK.to_h { |name, cost| [name, Agentic:class="y">:Task.new(description: name, agent_spec: {class="s">"name" => name, class="s">"instructions" => class="s">"w"})] }

# db feeds llm feeds render - three stages, three lanes
db, llm, render = %w[db llm render].map { |prefix| tasks.select { |n, _| n.start_with?(prefix) }.values }
db.each { |t| orchestrator.add_task(t, agent: ->(task) { sleep(WORK[task.description]) }) }
llm.each { |t| orchestrator.add_task(t, db, agent: ->(task) { sleep(WORK[task.description]) }) }
render.each { |t| orchestrator.add_task(t, llm, agent: ->(task) { sleep(WORK[task.description]) }) }

wall_start = Process.clock_gettime(Process:class="y">:CLOCK_MONOTONIC)
orchestrator.execute_plan
wall = Process.clock_gettime(Process:class="y">:CLOCK_MONOTONIC) - wall_start

# --- the profile ----------------------------------------------------------------
by_tag = samples.group_by { |name, _| name[/\A\w+/] }
  .transform_values { |rows| {seconds: rows.sum { |_, d| d }, count: rows.size, worst: rows.max_by { |_, d| d }} }
task_seconds = samples.sum { |_, d| d }

puts class="s">"EVENT PROF (task-seconds by tag; wall clock #{(wall * 1000).round}ms, 3 lanes)"
puts
puts format(class="s">"  %-8s %-10s %-8s %-8s %s", class="s">"tag", class="s">"seconds", class="s">"share", class="s">"tasks", class="s">"worst offender")
by_tag.sort_by { |_, v| -v[class="y">:seconds] }.each do |tag, stats|
  share = stats[class="y">:seconds] / task_seconds * 100
  puts format(class="s">"  %-8s %6.0fms   %5.1f%%   %-8d %s (%.0fms)  %s",
    tag, stats[class="y">:seconds] * 1000, share, stats[class="y">:count],
    stats[class="y">:worst][0], stats[class="y">:worst][1] * 1000, class="s">"#" * (share / 3).round)
end

parallelism = task_seconds / wall
puts
puts format(class="s">"  task-seconds: %.0fms across %.0fms of wall = %.1fx effective parallelism", task_seconds * 1000, wall * 1000, parallelism)
puts
llm_share = by_tag[class="s">"llm"][class="y">:seconds] / task_seconds * 100
puts class="s">"  the TestProf move is reading the SHARE column before touching any"
puts format(class="s">"  code: llm owns %.0f%% of all task-seconds, so a 20%% win there is", llm_share)
puts class="s">"  worth more than deleting the entire render stage - optimizing"
puts class="s">"  db: or render: is polishing doorknobs on a burning building."
puts format(class="s">"  and the parallelism line is the second lesson: %.1fx on 3 lanes", parallelism)
puts class="s">"  means the stage barriers are eating part of the overlap -"
puts class="s">"  llm tasks can't start until ALL db tasks finish. profile by"
puts class="s">"  group, fix the biggest group, re-profile. boring, effective,"
puts class="s">"  and the hooks made it fifteen lines."