agentic examples

The Stack VM Plan

The Stack VM Plan: I have spent twenty years compiling Ruby onto other people's virtual machines, so for the strange round I built the inverse: a virtual machine made OUT OF the framework. An arithmetic expression compiles to stack bytecode (push/add/sub/ mul/div - a pocket YARV), and then each INSTRUCTION becomes a task: the plan is the instruction stream, the dependency chain is the program counter, and the stack threads through previous_output as an immutable value. …

Scheduling & Concurrency Round 19 Charles Nutter exit 0

source on github

bundle exec ruby examples/stack_vm_plan.rb

a real captured run

THE STACK VM PLAN (a virtual machine whose instructions are jobs)

  (2 + 3) * (10 - 4)  =>  30   (Ruby says 30)
      push 2    -> [2]
      push 3    -> [2, 3]
      add       -> [5]
      push 10   -> [5, 10]
      push 4    -> [5, 10, 4]
      sub       -> [5, 6]
      mul       -> [30]
      peephole: 7 instructions -> 1 (push 30), same answer: true

  72 / (2 + 6) - 4  =>  5   (Ruby says 5)
      peephole: 7 instructions -> 1 (push 5), same answer: true

  1 + 2 * 3 - 4 / 2  =>  5   (Ruby says 5)
      peephole: 9 instructions -> 1 (push 5), same answer: true

  what the bit decompiles to: 'the plan is the program' stops
  being a metaphor when the tasks ARE instructions - the chain is
  the program counter, previous_output is the operand stack
  (frozen: this machine has no registers to corrupt), and the
  whole thing cross-checks against the only reference
  implementation that matters, Ruby herself. and the peephole
  pass is the JRuby lesson in one line: the fastest instruction
  is the one you delete before the executor ever sees it - every
  program above folded to a SINGLE push, because arithmetic on
  constants is the compiler's job, not the runtime's. twenty
  years of VM work, and the moral still fits in a peephole.

source

# frozen_string_literal: true

# The Stack VM Plan: I have spent twenty years compiling Ruby onto
# other people's virtual machines, so for the strange round I built
# the inverse: a virtual machine made OUT OF the framework. An
# arithmetic expression compiles to stack bytecode (push/add/sub/
# mul/div - a pocket YARV), and then each INSTRUCTION becomes a
# task: the plan is the instruction stream, the dependency chain is
# the program counter, and the stack threads through previous_output
# as an immutable value. Absurd? Completely. But it decompiles the
# word "executor" back to its roots - and it comes with a peephole
# optimizer, because no instruction stream of mine ships unoptimized.
#
#   bundle exec ruby examples/stack_vm_plan.rb
#
# Runs offline; exits 1 unless the plan-VM agrees with Ruby itself
# on every program, before AND after optimization.

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

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

# --- the compiler: recursive descent, postfix out (a pocket YARV) -------------------
def compile(src)
  tokens = src.scan(%r{\d+|[-+*/()]})
  code = []
  expr = term = factor = nil
  factor = -> {
    if tokens.first == class="s">"("
      tokens.shift
      expr.call
      tokens.shift # class="s">")"
    else
      code << [class="y">:push, tokens.shift.to_i]
    end
  }
  term = -> {
    factor.call
    while [class="s">"*", class="s">"/"].include?(tokens.first)
      op = tokens.shift
      factor.call
      code << [(op == class="s">"*") ? class="y">:mul : class="y">:div]
    end
  }
  expr = -> {
    term.call
    while [class="s">"+", class="s">"-"].include?(tokens.first)
      op = tokens.shift
      term.call
      code << [(op == class="s">"+") ? class="y">:add : class="y">:sub]
    end
  }
  expr.call
  code
end

# --- the peephole optimizer: constant folding until fixpoint ------------------------
def optimize(code)
  folded = code.dup
  loop do
    index = (0..folded.size - 3).find { |i|
      folded[i][0] == class="y">:push && folded[i + 1][0] == class="y">:push && [class="y">:add, class="y">:sub, class="y">:mul, class="y">:div].include?(folded[i + 2][0])
    }
    break unless index
    a, b, op = folded[index][1], folded[index + 1][1], folded[index + 2][0]
    value = {add: a + b, sub: a - b, mul: a * b, div: a / b}.fetch(op)
    folded[index, 3] = [[class="y">:push, value]]
  end
  folded
end

# --- the machine: one task per instruction, stack via previous_output ---------------
def run_on_plan_vm(code)
  orchestrator = Agentic:class="y">:PlanOrchestrator.new(concurrency_limit: 1) # a program counter, not a pool
  previous = nil
  trace = []
  code.each_with_index do |(op, arg), pc|
    task = Agentic:class="y">:Task.new(description: class="s">"pc=#{pc} #{op} #{arg}".strip, agent_spec: {class="s">"name" => op.to_s, class="s">"instructions" => class="s">"w"})
    orchestrator.add_task(task, previous ? [previous] : [], agent: ->(t) {
      stack = (t.previous_output || []).dup
      case op
      when class="y">:push then stack.push(arg)
      when class="y">:add then b = stack.pop
                     stack.push(stack.pop + b)
      when class="y">:sub then b = stack.pop
                     stack.push(stack.pop - b)
      when class="y">:mul then b = stack.pop
                     stack.push(stack.pop * b)
      when class="y">:div then b = stack.pop
                     stack.push(stack.pop / b)
      end
      trace << class="s">"#{"#{op} #{arg}class="s">".strip.ljust(9)} -> [#{stack.join(", class="s">")}]"
      stack.freeze
    })
    previous = task
  end
  result = orchestrator.execute_plan
  [result.task_result(previous.id).output.first, trace]
end

PROGRAMS = [class="s">"(2 + 3) * (10 - 4)", class="s">"72 / (2 + 6) - 4", class="s">"1 + 2 * 3 - 4 / 2"].freeze

puts class="s">"THE STACK VM PLAN (a virtual machine whose instructions are jobs)"
puts

failures = []
PROGRAMS.each do |src|
  code = compile(src)
  lean = optimize(code)
  value, trace = run_on_plan_vm(code)
  lean_value, = run_on_plan_vm(lean)
  truth = eval(src) # rubocop:disable Security/Eval -- the reference implementation is Ruby itself, on a literal from this file

  puts class="s">"  #{src}  =>  #{value}   (Ruby says #{truth})"
  trace.each { |line| puts class="s">"      #{line}" } if src == PROGRAMS.first
  puts class="s">"      peephole: #{code.size} instructions -> #{lean.size} (#{lean.map { |op, arg| "#{op} #{arg}class="s">".strip }.join("; class="s">")}), same answer: #{lean_value == value}"
  puts
  failures << class="s">"#{src}: plan-VM says #{value}, Ruby says #{truth}" unless value == truth
  failures << class="s">"#{src}: optimizer changed the answer" unless lean_value == truth
  failures << class="s">"#{src}: optimizer didn't optimize" unless lean.size < code.size
end

puts class="s">"  what the bit decompiles to: 'the plan is the program' stops"
puts class="s">"  being a metaphor when the tasks ARE instructions - the chain is"
puts class="s">"  the program counter, previous_output is the operand stack"
puts class="s">"  (frozen: this machine has no registers to corrupt), and the"
puts class="s">"  whole thing cross-checks against the only reference"
puts class="s">"  implementation that matters, Ruby herself. and the peephole"
puts class="s">"  pass is the JRuby lesson in one line: the fastest instruction"
puts class="s">"  is the one you delete before the executor ever sees it - every"
puts class="s">"  program above folded to a SINGLE push, because arithmetic on"
puts class="s">"  constants is the compiler's job, not the runtime's. twenty"
puts class="s">"  years of VM work, and the moral still fits in a peephole."
exit(failures.empty? ? 0 : 1)