agentic examples

The API Surface Census

The API Surface Census: your public API is not what you documented - it's every public method a user CAN call, because that's what semver binds you to. This census counts the whole surface, then cross-references 100 example programs to split it into the API people actually use and the accidental API nobody asked for but everyone can break themselves against.

Data & Pipelines Round 12 Yehuda Katz (wycats) exit 0

source on github

bundle exec ruby examples/api_surface.rb

a real captured run

API SURFACE CENSUS (11 core classes vs 177 example programs)

  class                      surface   exercised   accidental (public, unused by any example)
  PlanOrchestrator           19        10          find_eligible_tasks, overall_status, retry?
  Task                       26        16          agent_spec, retry_count=, output_schema
  ExecutionJournal           6         5           fsync_every
  RateLimit                  8         7           in_flight
  CapabilitySpecification    12        10          requirements_description, from_h
  CapabilityValidator        2         2
  CapabilityProvider         3         2           implementation
  TaskFailure                11        6           context, possibly_transient?, timestamp
  TaskResult                 7         5           task_id, failed?
  PlanExecutionResult        16        7           in_progress?, plan_id, completed_tasks_count
  RelationRules              4         2           present?, validate_declaration!

  total public surface: 114 methods; 72 (63%) exercised by the corpus.

  reading the census like a steward: the exercised set is your REAL
  API - 100 programs voted with their call sites, and every one of
  those methods now carries a semver promise whether the docs say
  so or not. the accidental set (42 methods) is surface you're
  paying interest on without collecting rent: each is a thing a
  user could couple to tomorrow, constraining refactors forever.
  the move isn't deletion - it's DECLARATION: mark them @api
  private (or make them private) while nobody depends on them,
  because the day after somebody does, they're yours for a major
  version. public-by-default is a loan; the census is the bill.

source

# frozen_string_literal: true

# The API Surface Census: your public API is not what you documented -
# it's every public method a user CAN call, because that's what semver
# binds you to. This census counts the whole surface, then
# cross-references 100 example programs to split it into the API
# people actually use and the accidental API nobody asked for but
# everyone can break themselves against.
#
#   bundle exec ruby examples/api_surface.rb
#
# Runs offline; the examples directory is the usage corpus.

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

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

# Load everything so the census sees the whole surface
Zeitwerk:class="y">:Registry.loaders.each(&class="y">:eager_load) if defined?(Zeitwerk:class="y">:Registry)

CORE = [
  Agentic:class="y">:PlanOrchestrator, Agentic:class="y">:Task, Agentic:class="y">:ExecutionJournal,
  Agentic:class="y">:RateLimit, Agentic:class="y">:CapabilitySpecification, Agentic:class="y">:CapabilityValidator,
  Agentic:class="y">:CapabilityProvider, Agentic:class="y">:TaskFailure, Agentic:class="y">:TaskResult,
  Agentic:class="y">:PlanExecutionResult, Agentic:class="y">:RelationRules
].freeze

corpus = Dir[File.join(__dir__, class="s">"*.rb")].reject { |f| f.end_with?(class="s">"api_surface.rb") }
  .map { |f| File.read(f, encoding: class="s">"UTF-8") }.join(class="s">"\n")

puts class="s">"API SURFACE CENSUS (#{CORE.size} core classes vs #{Dir[File.join(__dir__, "*.rbclass="s">")].size - 1} example programs)"
puts
puts format(class="s">"  %-26s %-9s %-11s %s", class="s">"class", class="s">"surface", class="s">"exercised", class="s">"accidental (public, unused by any example)")

total_surface = 0
total_exercised = 0
accidental_all = []
CORE.each do |klass|
  # Owner-checked: only methods this class itself defines count as ITS
  # surface - inherited Object/Psych noise is someone else's ledger
  methods = (klass.public_instance_methods(false) +
    klass.singleton_class.public_instance_methods(false).select { |m|
      klass.singleton_class.instance_method(m).owner == klass.singleton_class
    }).uniq.reject { |m| m.to_s.start_with?(class="s">"_") }
  used, unused = methods.partition { |m| corpus.match?(/\.#{Regexp.escape(m.to_s.chomp(class="s">"?").chomp(class="s">"!"))}\b/) || corpus.include?(class="s">".#{m}") }
  total_surface += methods.size
  total_exercised += used.size
  accidental_all.concat(unused.map { |m| class="s">"#{klass.name.split("::class="s">").last}##{m}" })
  puts format(class="s">"  %-26s %-9d %-11d %s",
    klass.name.split(class="s">"::").last, methods.size, used.size, unused.take(3).join(class="s">", "))
end

puts
puts format(class="s">"  total public surface: %d methods; %d (%.0f%%) exercised by the corpus.",
  total_surface, total_exercised, total_exercised * 100.0 / total_surface)
puts
puts class="s">"  reading the census like a steward: the exercised set is your REAL"
puts class="s">"  API - 100 programs voted with their call sites, and every one of"
puts class="s">"  those methods now carries a semver promise whether the docs say"
puts class="s">"  so or not. the accidental set (#{accidental_all.size} methods) is surface you're"
puts class="s">"  paying interest on without collecting rent: each is a thing a"
puts class="s">"  user could couple to tomorrow, constraining refactors forever."
puts class="s">"  the move isn't deletion - it's DECLARATION: mark them @api"
puts class="s">"  private (or make them private) while nobody depends on them,"
puts class="s">"  because the day after somebody does, they're yours for a major"
puts class="s">"  version. public-by-default is a loan; the census is the bill."