agentic examples

PlanScript

PlanScript: a DSL where BAREWORDS build the graph. Inside the block, `fetch` isn't a variable or a method you defined - it's method_missing, catching the name and declaring a step; `rank after: fetch` catches two. That's the strange half. The principled half is that the DSL is a real compiler, and real compilers should be BIDIRECTIONAL: the graph decompiles back to canonical PlanScript source, the source re-parses to the same graph, and emit(parse(emit(g))) == emit(g) - a …

Data & Pipelines Round 19 Yehuda Katz exit 0

source on github

bundle exec ruby examples/plan_script.rb

a real captured run

PLANSCRIPT (barewords in, graph out, source back - a compiler that round-trips)

  the graph, decompiled to canonical PlanScript:
    plan do
      fetch_feed
      dedupe after: :fetch_feed
      rank after: :dedupe
      summarize after: :rank
      render after: [:rank, :summarize]
    end

  parse(emit(graph)) == graph:      true
  emit(parse(emit(g))) == emit(g):  true (the decompiler reached its fixpoint)

  compiled and executed the REPARSED graph: completed, 5 steps,
  dependency order respected: true

  two tricks, one principle. the trick you see: method_missing
  turning barewords into declarations, so the script reads like a
  napkin sketch (`rank after: dedupe`) with no def, no symbols, no
  quotes - respond_to_missing? and a Struct, that's the whole
  parser. the principle underneath: a DSL is a compiler, and
  compilers you can trust are BIDIRECTIONAL - the live graph
  decompiles to a canonical source that re-parses to the same
  graph, byte-stable at one iteration. that's what makes the
  emitted source safe to commit, diff, and regenerate: it isn't
  documentation ABOUT the plan, it IS the plan, in its Sunday
  clothes. one-way DSLs age into folklore; round-trip DSLs age
  into file formats.

source

# frozen_string_literal: true

# PlanScript: a DSL where BAREWORDS build the graph. Inside the
# block, `fetch` isn't a variable or a method you defined - it's
# method_missing, catching the name and declaring a step; `rank
# after: fetch` catches two. That's the strange half. The principled
# half is that the DSL is a real compiler, and real compilers should
# be BIDIRECTIONAL: the graph decompiles back to canonical
# PlanScript source, the source re-parses to the same graph, and
# emit(parse(emit(g))) == emit(g) - a fixpoint. A config format you
# can regenerate from the live object is documentation that cannot
# lie. One you can't is a one-way door with a nice font.
#
#   bundle exec ruby examples/plan_script.rb
#
# Runs offline; exits 1 unless the round-trip reaches its fixpoint
# and the compiled plan actually executes.

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

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

class PlanScript
  StepRef = Struct.new(class="y">:name)
  attr_reader class="y">:steps # {name => [dep names]}

  def initialize
    @steps = {}
  end

  # Barewords arrive here. First sighting declares; `after:` wires.
  def method_missing(name, after: nil)
    @steps[name] ||= []
    @steps[name] |= Array(after).map { |d| d.is_a?(StepRef) ? d.name : d }
    Array(after).each { |d| @steps[d.is_a?(StepRef) ? d.name : d] ||= [] }
    StepRef.new(name)
  end

  def respond_to_missing?(*) = true

  def self.parse(source = nil, &block)
    script = new
    source ? script.instance_eval(source, class="s">"plan_script") : script.instance_eval(&block)
    script
  end

  # The decompiler: canonical source from the graph. Sorted deps,
  # declaration order preserved - a NORMAL FORM, so emit is stable.
  def emit
    lines = @steps.map do |name, deps|
      if deps.empty?
        name.to_s
      else
        class="s">"#{name} after: #{(deps.size == 1) ? deps.first.inspect : deps.sort.inspect}"
      end
    end
    class="s">"plan do\n" + lines.map { |l| class="s">"  #{l}" }.join(class="s">"\n") + class="s">"\nend"
  end

  def compile
    orchestrator = Agentic:class="y">:PlanOrchestrator.new(concurrency_limit: 4)
    tasks = @steps.keys.to_h { |name| [name, Agentic:class="y">:Task.new(description: name.to_s, agent_spec: {class="s">"name" => name.to_s, class="s">"instructions" => class="s">"w"})] }
    ran = []
    @steps.each do |name, deps|
      orchestrator.add_task(tasks[name], deps.map { |d| tasks[d] }, agent: ->(_t) {
        ran << name
        class="s">"#{name}: done"
      })
    end
    [orchestrator, ran]
  end

  # `plan do ... end` inside emitted source
  def plan(&block) = instance_eval(&block)
end

puts class="s">"PLANSCRIPT (barewords in, graph out, source back - a compiler that round-trips)"
puts

# --- the script: no symbols, no strings, no defs. just names. -----------------------
original = PlanScript.parse do
  fetch_feed
  dedupe after: fetch_feed
  rank after: dedupe
  summarize after: rank
  render after: [rank, summarize]
end

source = original.emit
puts class="s">"  the graph, decompiled to canonical PlanScript:"
source.lines.each { |l| puts class="s">"    #{l.rstrip}" }
puts

# --- the fixpoint: parse(emit(g)) == g, and emit stabilizes --------------------------
reparsed = PlanScript.parse(source)
same_graph = reparsed.steps == original.steps
fixpoint = reparsed.emit == source
puts class="s">"  parse(emit(graph)) == graph:      #{same_graph}"
puts class="s">"  emit(parse(emit(g))) == emit(g):  #{fixpoint} (the decompiler reached its fixpoint)"
puts

# --- and it's not just pretty: the reparsed graph RUNS -------------------------------
orchestrator, ran = reparsed.compile
result = orchestrator.execute_plan
order_ok = ran.index(class="y">:fetch_feed) < ran.index(class="y">:dedupe) && ran.index(class="y">:rank) < ran.index(class="y">:render) && ran.index(class="y">:summarize) < ran.index(class="y">:render)
puts class="s">"  compiled and executed the REPARSED graph: #{result.status}, #{ran.size} steps,"
puts class="s">"  dependency order respected: #{order_ok}"
puts

failures = []
failures << class="s">"round-trip lost the graph" unless same_graph
failures << class="s">"emit not a normal form" unless fixpoint
failures << class="s">"reparsed plan broken" unless result.status == class="y">:completed && order_ok

puts class="s">"  two tricks, one principle. the trick you see: method_missing"
puts class="s">"  turning barewords into declarations, so the script reads like a"
puts class="s">"  napkin sketch (`rank after: dedupe`) with no def, no symbols, no"
puts class="s">"  quotes - respond_to_missing? and a Struct, that's the whole"
puts class="s">"  parser. the principle underneath: a DSL is a compiler, and"
puts class="s">"  compilers you can trust are BIDIRECTIONAL - the live graph"
puts class="s">"  decompiles to a canonical source that re-parses to the same"
puts class="s">"  graph, byte-stable at one iteration. that's what makes the"
puts class="s">"  emitted source safe to commit, diff, and regenerate: it isn't"
puts class="s">"  documentation ABOUT the plan, it IS the plan, in its Sunday"
puts class="s">"  clothes. one-way DSLs age into folklore; round-trip DSLs age"
puts class="s">"  into file formats."
exit(failures.empty? ? 0 : 1)