agentic examples

The Capability Autoloader

The Capability Autoloader: Zeitwerk's contract, ported. Drop a file at packs/text/summarize.rb defining Text::Summarize, and the capability "text.summarize" exists - no registration ceremony, no init file that lists everything twice. The convention IS the registry: file path <-> constant name <-> capability name, one bijection, three views. Lazy in development (load on first use), eager in production (load everything, and VERIFY the bijection - a file that defines the wrong …

Testing & Verification Round 17 Xavier Noria exit 0

source on github

bundle exec ruby examples/capability_autoloader.rb

a real captured run

THE CAPABILITY AUTOLOADER (the convention is the registry)

  pack on disk: 3 files; capabilities loaded: 0 (laziness is a feature)
  first use of text.summarize -> loaded 1/3 files, said: "Zeitwerk maps files to constants."

  eager_load! (production parity): 2 loaded, 1 contract violation:
    BOOT ERROR: expected math/percentile.rb to define Math::Percentile, but it doesn't

  reload! then re-use -> "EDIT THE FILE!" (the edit, live, no restart)

  one bijection, three views: the file path, the constant, the
  capability name. lazy loading makes development start instantly;
  eager loading makes production fail at BOOT when a file lies about
  its constant (math/percentile.rb defining Math2 - caught, named,
  fixable); reload makes the edit-run loop restart-free. none of it
  required the framework's cooperation - though a registry miss-hook
  (const_missing for capabilities) would make the loader invisible,
  which is what a loader should be.

source

# frozen_string_literal: true

# The Capability Autoloader: Zeitwerk's contract, ported. Drop a
# file at packs/text/summarize.rb defining Text::Summarize, and the
# capability "text.summarize" exists - no registration ceremony, no
# init file that lists everything twice. The convention IS the
# registry: file path <-> constant name <-> capability name, one
# bijection, three views. Lazy in development (load on first use),
# eager in production (load everything, and VERIFY the bijection -
# a file that defines the wrong constant is a bug you want at boot,
# not at 3am), reloadable in between.
#
#   bundle exec ruby examples/capability_autoloader.rb
#
# Runs offline; a capability pack is written to a tmpdir, then
# lazy-loaded, eager-verified, and hot-reloaded.

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

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

class CapabilityAutoloader
  attr_reader class="y">:loaded

  def initialize(root)
    @root = root
    @loaded = []
    @generation = 0
  end

  # "text.summarize" -> packs/text/summarize.rb -> Text::Summarize
  def ensure!(name)
    return if @loaded.include?(name)
    path = File.join(@root, *name.split(class="s">"."))
    raise class="s">"no file at #{path}.rb for capability #{name.inspect}" unless File.exist?(class="s">"#{path}.rb")
    load class="s">"#{path}.rb"
    constant = constant_for(name)
    register(name, constant)
    @loaded << name
  end

  # Production parity: load every file, and verify each one defines
  # the constant its path promises - the whole Zeitwerk contract
  def eager_load!
    errors = []
    Dir[File.join(@root, class="s">"**", class="s">"*.rb")].sort.each do |file|
      name = file.delete_prefix(class="s">"#{@root}/").delete_suffix(class="s">".rb").tr(class="s">"/", class="s">".")
      begin
        ensure!(name)
      rescue NameError
        errors << class="s">"expected #{file.delete_prefix("#{@root}/class="s">")} to define #{camelize(name)}, but it doesn't"
      end
    end
    errors
  end

  # Development's third gear: forget everything, next use reloads
  # from disk - edits included, no process restart
  def reload!
    @loaded.each do |name|
      parts = camelize(name).split(class="s">"::")
      parent = parts[0..-2].inject(Object) { |m, c| m.const_get(c) }
      parent.send(class="y">:remove_const, parts.last.to_sym)
    end
    @loaded.clear
    # The registry resolves unversioned lookups to the LATEST version,
    # so each reload generation registers as 1.0.N and simply outranks
    # its predecessor - old providers age out instead of being mutated
    @generation += 1
  end

  private

  def camelize(name) = name.split(class="s">".").map { |part| part.split(class="s">"_").map(&class="y">:capitalize).join }.join(class="s">"::")

  def constant_for(name) = Object.const_get(camelize(name))

  def register(name, constant)
    spec = Agentic:class="y">:CapabilitySpecification.new(
      name: name, version: class="s">"1.0.#{@generation}",
      description: constant.const_defined?(class="y">:DESCRIPTION) ? constant:class="y">:DESCRIPTION : name,
      inputs: constant.const_defined?(class="y">:INPUTS) ? constant:class="y">:INPUTS : {},
      outputs: constant.const_defined?(class="y">:OUTPUTS) ? constant:class="y">:OUTPUTS : {}
    )
    Agentic.register_capability(spec, Agentic:class="y">:CapabilityProvider.new(capability: spec, implementation: ->(inputs) { constant.call(inputs) }))
  end
end

# --- the pack, written by convention (in real life: your repo) ----------------------
PACK = {
  class="s">"text/summarize.rb" => <<~RUBY,
    module Text
      module Summarize
        DESCRIPTION = class="s">"First sentence wins"
        def self.call(inputs) = {summary: inputs[class="y">:text].split(class="s">". ").first + class="s">"."}
      end
    end
  RUBY
  class="s">"text/word_count.rb" => <<~RUBY,
    module Text
      module WordCount
        def self.call(inputs) = {count: inputs[class="y">:text].split.size}
      end
    end
  RUBY
  class="s">"math/percentile.rb" => <<~RUBY
    module Math2 # <- 5pm strikes again: wrong constant for math/percentile.rb
      module Percentile
        def self.call(inputs) = {value: inputs[class="y">:samples].sort[(inputs[class="y">:samples].size * inputs[class="y">:p]).floor]}
      end
    end
  RUBY
}.freeze

failures = []
Dir.mktmpdir(class="s">"capability_pack") do |root|
  PACK.each { |rel, src|
    FileUtils.mkdir_p(File.dirname(File.join(root, rel)))
    File.write(File.join(root, rel), src)
  }
  loader = CapabilityAutoloader.new(root)

  puts class="s">"THE CAPABILITY AUTOLOADER (the convention is the registry)"
  puts
  puts class="s">"  pack on disk: #{PACK.size} files; capabilities loaded: #{loader.loaded.size} (laziness is a feature)"

  # Lazy: first use loads exactly one file
  loader.ensure!(class="s">"text.summarize")
  agent = Agentic:class="y">:Agent.build { |a| a.name = class="s">"Reader" }
  agent.add_capability(class="s">"text.summarize")
  summary = agent.execute_capability(class="s">"text.summarize", {text: class="s">"Zeitwerk maps files to constants. The rest is commentary. Ask fxn."})
  puts class="s">"  first use of text.summarize -> loaded #{loader.loaded.size}/#{PACK.size} files, said: #{summary[class="y">:summary].inspect}"
  failures << class="s">"lazy load loaded too much" unless loader.loaded.size == 1

  # Eager: production wants everything loaded AND the bijection verified
  errors = loader.eager_load!
  puts
  puts class="s">"  eager_load! (production parity): #{loader.loaded.size} loaded, #{errors.size} contract violation:"
  errors.each { |e| puts class="s">"    BOOT ERROR: #{e}" }
  failures << class="s">"eager load missed the misnamed constant" unless errors.any? { |e| e.include?(class="s">"Math:class="y">:Percentile") }

  # Reload: edit on disk, forget, next use sees the edit - no restart
  File.write(File.join(root, class="s">"text/summarize.rb"), PACK[class="s">"text/summarize.rb"].sub(class="s">"First sentence wins", class="s">"Now shouty").sub(class="s">"first + \".\class="s">"}", class="s">"first.upcase + \"!\class="s">"}"))
  loader.reload!
  loader.ensure!(class="s">"text.summarize")
  # Cached references are every reloader's one true enemy: the agent
  # snapshots its provider at add_capability time, so refresh the add.
  # (Zeitwerk fights the same war against `MyClass = SomeConstant`.)
  agent.add_capability(class="s">"text.summarize")
  shouty = agent.execute_capability(class="s">"text.summarize", {text: class="s">"Edit the file. See it live."})
  puts
  puts class="s">"  reload! then re-use -> #{shouty[class="y">:summary].inspect} (the edit, live, no restart)"
  failures << class="s">"reload didn't pick up the edit" unless shouty[class="y">:summary] == class="s">"EDIT THE FILE!"
end

puts
puts class="s">"  one bijection, three views: the file path, the constant, the"
puts class="s">"  capability name. lazy loading makes development start instantly;"
puts class="s">"  eager loading makes production fail at BOOT when a file lies about"
puts class="s">"  its constant (math/percentile.rb defining Math2 - caught, named,"
puts class="s">"  fixable); reload makes the edit-run loop restart-free. none of it"
puts class="s">"  required the framework's cooperation - though a registry miss-hook"
puts class="s">"  (const_missing for capabilities) would make the loader invisible,"
puts class="s">"  which is what a loader should be."
exit(failures.empty? ? 0 : 1)