agentic examples

The Collaboration Tracer

The Collaboration Tracer: lifecycle hooks record every message the orchestrator sends and every reply that comes back, then the run is drawn as a sequence diagram. Object-oriented programs are conversations; this makes the conversation visible.

Observability & Ops Round 3 Sandi Metz exit 0

source on github

bundle exec ruby examples/collaboration_tracer.rb

a real captured run

COLLABORATION TRACE (8 messages)

  Orchestrator     Researcher        Writer          Editor
        |               |               |               |
        |-------------->|               |               |
        | perform(resea |               |               |
        |<--------------|               |               |
        | done: "3 fact |               |               |
        |------------------------------>|               |
        | perform(writer)               |               |
        |------------------------------>|               |
        | here's "3 facts about fibe... |               |
        |<------------------------------|               |
        | done: "draft built on: 3 ..." |               |
        |---------------------------------------------->|
        | perform(editor)               |               |
        |---------------------------------------------->|
        | here's "draft built on: 3 ..."|               |
        |<----------------------------------------------|
        | done: "tightened: draft b..." |               |
        |               |               |               |

read it like a conversation: every arrow is a message, every
reply flows back before the next collaborator is addressed.

source

# frozen_string_literal: true

# The Collaboration Tracer: lifecycle hooks record every message the
# orchestrator sends and every reply that comes back, then the run is
# drawn as a sequence diagram. Object-oriented programs are
# conversations; this makes the conversation visible.
#
#   bundle exec ruby examples/collaboration_tracer.rb
#
# Runs offline: a three-agent editorial pipeline, traced.

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

PIPELINE = {
  class="s">"Researcher" => {work: ->(_prev) { class="s">"3 facts about fibers" }},
  class="s">"Writer" => {work: ->(prev) { class="s">"draft built on: #{prev}" }},
  class="s">"Editor" => {work: ->(prev) { class="s">"tightened: #{prev.split(":class="s">").first}" }}
}.freeze

trace = []
hooks = {
  before_task_execution: ->(task_id:, task:) {
    trace << {from: class="s">"Orchestrator", to: task.description, label: class="s">"perform(#{task.description.downcase})"}
    unless task.dependency_outputs.empty?
      task.dependency_outputs.each_value do |output|
        trace << {from: class="s">"Orchestrator", to: task.description, label: class="s">"here's \"#{output.to_s[0, 18]}...\class="s">""}
      end
    end
  },
  after_task_success: ->(task_id:, task:, result:, duration:) {
    trace << {from: task.description, to: class="s">"Orchestrator", label: class="s">"done: \"#{result.output.to_s[0, 18]}...\class="s">""}
  }
}

orchestrator = Agentic:class="y">:PlanOrchestrator.new(lifecycle_hooks: hooks)
previous = nil
PIPELINE.each do |role, spec|
  task = Agentic:class="y">:Task.new(
    description: role,
    agent_spec: {class="s">"name" => role, class="s">"instructions" => class="s">"collaborate"},
    payload: spec[class="y">:work]
  )
  orchestrator.add_task(task, previous ? [previous] : [], agent: ->(t) {
    t.payload.call(t.dependency_outputs.values.first)
  })
  previous = task
end

orchestrator.execute_plan

# --- render the conversation as a sequence diagram --------------------------
actors = [class="s">"Orchestrator"] + PIPELINE.keys
width = 16
positions = actors.each_with_index.to_h { |actor, i| [actor, i * width + width / 2] }
line_width = actors.size * width

puts class="s">"COLLABORATION TRACE (#{trace.size} messages)"
puts
puts actors.map { |a| a.center(width) }.join
puts positions.values.each_with_object(class="s">" " * line_width) { |pos, line|
       line[pos] = class="s">"|"
     }

trace.each do |message|
  from_pos = positions[message[class="y">:from]]
  to_pos = positions[message[class="y">:to]]
  left, right = [from_pos, to_pos].minmax

  # the arrow line, with lifelines drawn through
  line = class="s">" " * line_width
  positions.each_value { |pos| line[pos] = class="s">"|" }
  (left + 1...right).each { |i| line[i] = class="s">"-" }
  line[(from_pos < to_pos) ? right - 1 : left + 1] = (from_pos < to_pos) ? class="s">">" : class="s">"<"
  puts line

  # the label line
  label = class="s">" " * line_width
  positions.each_value { |pos| label[pos] = class="s">"|" }
  text = message[class="y">:label][0, right - left - 3]
  label[left + 2, text.length] = text
  puts label
end

puts positions.values.each_with_object(class="s">" " * line_width) { |pos, line|
       line[pos] = class="s">"|"
     }
puts
puts class="s">"read it like a conversation: every arrow is a message, every"
puts class="s">"reply flows back before the next collaborator is addressed."