agentic examples

The Graph Critic

The Graph Critic: reviews a plan's dependency structure BEFORE it runs, the way you'd review a class diagram. God tasks, deep chains, and orphans are design smells in a graph exactly as they are in objects - and they're cheaper to fix before execution than after.

Plans & Graphs Round 4 Sandi Metz exit 0

source on github

bundle exec ruby examples/graph_critic.rb

a real captured run

GRAPH CRITIC: 10 tasks reviewed before execution

  [god task] join
     gathers 5 dependencies - does it join, or does it do everything? consider staged joins so each has one reason to wait

  [deep chain] publish
     sits 5 levels down - every level is latency and a failure domain; could any middle link merge with a neighbor?

  [orphan] lonely
     no dependencies, no dependents - is it in the wrong plan, or is the connection that justifies it missing?

prescription: fix ONE - start with the god task. five ingests
joining at once usually means 'join' hides a pipeline: stage the
joins (a+b, c+d+e, then both) and each join gets one reason to
change. rerun the critic; the chain may resolve itself.

source

# frozen_string_literal: true

# The Graph Critic: reviews a plan's dependency structure BEFORE it
# runs, the way you'd review a class diagram. God tasks, deep chains,
# and orphans are design smells in a graph exactly as they are in
# objects - and they're cheaper to fix before execution than after.
#
#   bundle exec ruby examples/graph_critic.rb
#
# Runs offline; no task is executed. The review IS the program.

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

def task_named(name)
  Agentic:class="y">:Task.new(
    description: name,
    agent_spec: {class="s">"name" => name, class="s">"instructions" => class="s">"work"}
  )
end

# A plan with three deliberate smells
orchestrator = Agentic:class="y">:PlanOrchestrator.new
tasks = {}
%w[ingest_a ingest_b ingest_c ingest_d ingest_e clean join report publish lonely].each do |name|
  tasks[name] = task_named(name)
end

orchestrator.add_task(tasks[class="s">"ingest_a"])
orchestrator.add_task(tasks[class="s">"ingest_b"])
orchestrator.add_task(tasks[class="s">"ingest_c"])
orchestrator.add_task(tasks[class="s">"ingest_d"])
orchestrator.add_task(tasks[class="s">"ingest_e"])
# the god task: everything funnels through join
orchestrator.add_task(tasks[class="s">"join"], %w[ingest_a ingest_b ingest_c ingest_d ingest_e].map { |n| tasks[n] })
# a chain hanging off it
orchestrator.add_task(tasks[class="s">"clean"], [tasks[class="s">"join"]])
orchestrator.add_task(tasks[class="s">"report"], [tasks[class="s">"clean"]])
orchestrator.add_task(tasks[class="s">"publish"], [tasks[class="s">"report"]])
# and a task nobody references
orchestrator.add_task(tasks[class="s">"lonely"])

# --- the critique ----------------------------------------------------------
# This example's original feature request, granted: a read-only view of
# the plan's topology. No more crowbar.
graph = orchestrator.graph
dependencies = graph[class="y">:dependencies]
names = graph[class="y">:tasks].transform_values(&class="y">:description)

dependents = Hash.new { |h, k| h[k] = [] }
dependencies.each { |task_id, deps| deps.each { |dep| dependents[dep] << task_id } }

# Depth now ships precomputed in the snapshot
depth_of = ->(task_id) { graph[class="y">:stats][class="y">:depth][task_id] }

findings = []

dependencies.each do |task_id, deps|
  if deps.size >= 4
    findings << {smell: class="s">"god task", task: names[task_id],
                 note: class="s">"gathers #{deps.size} dependencies - does it join, or does it do everything? " \
                   class="s">"consider staged joins so each has one reason to wait"}
  end
end

deepest = dependencies.keys.max_by { |task_id| depth_of.call(task_id) }
if depth_of.call(deepest) >= 4
  findings << {smell: class="s">"deep chain", task: names[deepest],
               note: class="s">"sits #{depth_of.call(deepest)} levels down - every level is latency and a failure " \
                 class="s">"domain; could any middle link merge with a neighbor?"}
end

dependencies.each do |task_id, deps|
  if deps.empty? && dependents[task_id].empty? && dependencies.size > 1
    findings << {smell: class="s">"orphan", task: names[task_id],
                 note: class="s">"no dependencies, no dependents - is it in the wrong plan, or is the " \
                   class="s">"connection that justifies it missing?"}
  end
end

puts class="s">"GRAPH CRITIC: #{dependencies.size} tasks reviewed before execution"
puts
findings.each do |finding|
  puts class="s">"  [#{finding[class="y">:smell]}] #{finding[class="y">:task]}"
  puts class="s">"     #{finding[class="y">:note]}"
  puts
end

puts class="s">"prescription: fix ONE - start with the god task. five ingests"
puts class="s">"joining at once usually means 'join' hides a pipeline: stage the"
puts class="s">"joins (a+b, c+d+e, then both) and each join gets one reason to"
puts class="s">"change. rerun the critic; the chain may resolve itself."