agentic examples

The Round Trip

The Round Trip: serialize a plan's graph to JSON, rebuild a fresh orchestrator from the JSON, and prove the rebuilt topology is isomorphic to the original - same shape, same labels, new ids. A projection you can't invert is a projection you can't trust with your plans.

Data & Pipelines Round 6 Xavier Noria exit 0

source on github

bundle exec ruby examples/plan_roundtrip.rb

a real captured run

THE WIRE FORMAT
  {
    "tasks": [
      "gather",
      "check",
      "weave",
      "ship"
    ],
    "edges": [
      {
        "from": "check",
        "to": "weave",
        "label": null
      },
      {
        "from": "gather",
        "to": "weave",
        "label": "threads"
      },
      {
        "from": "weave",
        "to": "ship",
        "label": null
      }
    ]
  }

THE VERDICT
  round trip is faithful: 3 edges, labels intact,
  topological order preserved (gather -> check -> weave -> ship)

task ids are per-process and correctly absent from the wire format -
identity travels as description, structure travels as edges, and
needs: labels survive because graph[:edges] carries them.

source

# frozen_string_literal: true

# The Round Trip: serialize a plan's graph to JSON, rebuild a fresh
# orchestrator from the JSON, and prove the rebuilt topology is
# isomorphic to the original - same shape, same labels, new ids. A
# projection you can't invert is a projection you can't trust with
# your plans.
#
#   bundle exec ruby examples/plan_roundtrip.rb
#
# Runs offline; prints the wire format and the verdict.

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

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

# --- an original plan with every edge flavor ---------------------------------
original = Agentic:class="y">:PlanOrchestrator.new
gather = step(class="s">"gather")
check = step(class="s">"check")
weave = step(class="s">"weave")
ship = step(class="s">"ship")

original.add_task(gather)
original.add_task(check)
original.add_task(weave, [check], needs: {threads: gather})
original.add_task(ship, [weave])

# --- serialize: graph -> wire format (ids replaced by descriptions) ---------
def serialize(graph)
  names = graph[class="y">:tasks].transform_values(&class="y">:description)
  {
    class="s">"tasks" => graph[class="y">:order].map { |id| names[id] },
    class="s">"edges" => graph[class="y">:edges].map { |e|
      {class="s">"from" => names[e[class="y">:from]], class="s">"to" => names[e[class="y">:to]], class="s">"label" => e[class="y">:label]&.to_s}
    }
  }
end

# --- deserialize: wire format -> a fresh orchestrator ------------------------
def deserialize(data)
  orchestrator = Agentic:class="y">:PlanOrchestrator.new
  tasks = data[class="s">"tasks"].to_h { |name| [name, step(name)] }

  data[class="s">"tasks"].each do |name|
    edges_in = data[class="s">"edges"].select { |e| e[class="s">"to"] == name }
    plain = edges_in.reject { |e| e[class="s">"label"] }.map { |e| tasks.fetch(e[class="s">"from"]) }
    named = edges_in.select { |e| e[class="s">"label"] }
      .to_h { |e| [e[class="s">"label"].to_sym, tasks.fetch(e[class="s">"from"])] }

    orchestrator.add_task(tasks[name], plain, needs: named.empty? ? nil : named)
  end
  orchestrator
end

wire = JSON.pretty_generate(serialize(original.graph))
rebuilt = deserialize(JSON.parse(wire))

puts class="s">"THE WIRE FORMAT"
puts wire.gsub(/^/, class="s">"  ")
puts

# --- the isomorphism check: compare shapes, not ids --------------------------
def shape(graph)
  names = graph[class="y">:tasks].transform_values(&class="y">:description)
  {
    order: graph[class="y">:order].map { |id| names[id] },
    edges: graph[class="y">:edges].map { |e| [names[e[class="y">:from]], names[e[class="y">:to]], e[class="y">:label]] }.sort_by(&class="y">:to_s)
  }
end

before = shape(original.graph)
after = shape(rebuilt.graph)

puts class="s">"THE VERDICT"
if before == after
  puts class="s">"  round trip is faithful: #{before[class="y">:edges].size} edges, labels intact,"
  puts class="s">"  topological order preserved (#{after[class="y">:order].join(" -> class="s">")})"
else
  puts class="s">"  DRIFT DETECTED:"
  puts class="s">"  before: #{before.inspect}"
  puts class="s">"  after:  #{after.inspect}"
  exit 1
end
puts
puts class="s">"task ids are per-process and correctly absent from the wire format -"
puts class="s">"identity travels as description, structure travels as edges, and"
puts class="s">"needs: labels survive because graph[class="y">:edges] carries them."