agentic examples

The Deprecation Shepherd

The Deprecation Shepherd: removing an API is easy; removing it WITHOUT breaking anyone is a data problem wearing an etiquette costume. The failure mode is universal - a warning gets printed for two years, nobody reads warnings, the removal ships, pagers sing. The cure is to treat deprecation as data collection: the shim counts every call WITH its call site, the removal is gated on observed evidence (zero uses in the window, not zero complaints), and while usage exists the …

Observability & Ops Round 20 Rafael França exit 0

source on github

bundle exec ruby examples/deprecation_shepherd.rb

a real captured run

THE DEPRECATION SHEPHERD (remove when the telemetry says nobody's standing there)

  release 2.1 - shim in place, nobody migrated yet:
    removal REFUSED - 8 call(s) observed from 3 site(s):
      site_checkout_flow: 5 call(s) this window
      site_nightly_reconcile: 2 call(s) this window
      site_admin_refunds: 1 call(s) this window

  release 2.2 - checkout and reconcile migrated:
    removal REFUSED - 1 call(s) observed from 1 site(s):
      site_admin_refunds: 1 call(s) this window

  release 2.3 - the refunds admin finally migrates:
    removal APPROVED - zero uses observed. the door can close.

  the shepherd's three rules, all data: the shim DELEGATES (users
  keep working - a deprecation that breaks people is just a
  removal with extra steps) while counting every call with its
  SITE, because 'someone still uses this' is useless and
  'admin_refunds calls it once nightly' is a pull request you can
  write; the gate consumes an observation WINDOW, not a release
  count - time passing is not evidence, traffic passing is; and
  the approval is falsifiable: zero observed uses in a
  representative window, or no removal. two releases of warnings
  convinced nobody in the history of software. one report naming
  the last holdout has ended every deprecation I've ever shipped.

source

# frozen_string_literal: true

# The Deprecation Shepherd: removing an API is easy; removing it
# WITHOUT breaking anyone is a data problem wearing an etiquette
# costume. The failure mode is universal - a warning gets printed
# for two years, nobody reads warnings, the removal ships, pagers
# sing. The cure is to treat deprecation as data collection: the
# shim counts every call WITH its call site, the removal is gated
# on observed evidence (zero uses in the window, not zero
# complaints), and while usage exists the gate names the holdouts
# instead of shaming the void. You don't remove an API when the
# changelog says you may; you remove it when the telemetry says
# nobody's standing there.
#
#   bundle exec ruby examples/deprecation_shepherd.rb
#
# Runs offline; exits 1 unless the gate refuses while evidence
# exists and approves only at observed zero.

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

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

# The library: one deprecated door, one replacement
USAGE = Hash.new(0)
module Billing
  def self.legacy_total(items) # deprecated since 2.1
    site = caller_locations(1, 1).first.label
    USAGE[site] += 1
    total(items) # the shim delegates; behavior identical, data collected
  end

  def self.total(items) = items.sum { |i| i[class="y">:cents] }
end

# The app: three call sites, migrated one phase at a time
def app_workload(migrated:)
  orchestrator = Agentic:class="y">:PlanOrchestrator.new(concurrency_limit: 3)
  jobs = {
    class="s">"checkout_flow" => 5, # calls per run
    class="s">"nightly_reconcile" => 2,
    class="s">"admin_refunds" => 1
  }
  jobs.each do |site, calls|
    task = Agentic:class="y">:Task.new(description: site, agent_spec: {class="s">"name" => site, class="s">"instructions" => class="s">"bill"})
    orchestrator.add_task(task, agent: ->(_t) {
      calls.times do
        items = [{cents: 1200}, {cents: 300}]
        migrated.include?(site) ? Billing.total(items) : send(class="y">:call_from, site, items)
      end
      class="y">:ran
    })
  end
  orchestrator.execute_plan
end

# Distinct call sites for honest attribution (the label is the method name)
def call_from(site, items) = method(class="s">"site_#{site}").call(items)

def site_checkout_flow(items) = Billing.legacy_total(items)

def site_nightly_reconcile(items) = Billing.legacy_total(items)

def site_admin_refunds(items) = Billing.legacy_total(items)

# The gate: evidence in, verdict out
def removal_gate
  USAGE.clear # open a fresh observation window
  yield       # let a representative workload run through it
  if USAGE.empty?
    {verdict: class="y">:approved, holdouts: {}}
  else
    {verdict: class="y">:refused, holdouts: USAGE.dup}
  end
end

puts class="s">"THE DEPRECATION SHEPHERD (remove when the telemetry says nobody's standing there)"
puts

phases = [
  {label: class="s">"release 2.1 - shim in place, nobody migrated yet", migrated: []},
  {label: class="s">"release 2.2 - checkout and reconcile migrated", migrated: [class="s">"checkout_flow", class="s">"nightly_reconcile"]},
  {label: class="s">"release 2.3 - the refunds admin finally migrates", migrated: [class="s">"checkout_flow", class="s">"nightly_reconcile", class="s">"admin_refunds"]}
]

verdicts = phases.map do |phase|
  gate = removal_gate { app_workload(migrated: phase[class="y">:migrated]) }
  puts class="s">"  #{phase[class="y">:label]}:"
  if gate[class="y">:verdict] == class="y">:refused
    puts class="s">"    removal REFUSED - #{gate[class="y">:holdouts].values.sum} call(s) observed from #{gate[class="y">:holdouts].size} site(s):"
    gate[class="y">:holdouts].each { |site, count| puts class="s">"      #{site}: #{count} call(s) this window" }
  else
    puts class="s">"    removal APPROVED - zero uses observed. the door can close."
  end
  puts
  gate
end

failures = []
failures << class="s">"gate approved with 3 live sites" unless verdicts[0][class="y">:verdict] == class="y">:refused && verdicts[0][class="y">:holdouts].size == 3
failures << class="s">"gate lost track of the holdout" unless verdicts[1][class="y">:verdict] == class="y">:refused && verdicts[1][class="y">:holdouts].keys == [class="s">"site_admin_refunds"]
failures << class="s">"gate never approved" unless verdicts[2][class="y">:verdict] == class="y">:approved
failures << class="s">"call counts wrong" unless verdicts[0][class="y">:holdouts].values.sum == 8

puts class="s">"  the shepherd's three rules, all data: the shim DELEGATES (users"
puts class="s">"  keep working - a deprecation that breaks people is just a"
puts class="s">"  removal with extra steps) while counting every call with its"
puts class="s">"  SITE, because 'someone still uses this' is useless and"
puts class="s">"  'admin_refunds calls it once nightly' is a pull request you can"
puts class="s">"  write; the gate consumes an observation WINDOW, not a release"
puts class="s">"  count - time passing is not evidence, traffic passing is; and"
puts class="s">"  the approval is falsifiable: zero observed uses in a"
puts class="s">"  representative window, or no removal. two releases of warnings"
puts class="s">"  convinced nobody in the history of software. one report naming"
puts class="s">"  the last holdout has ended every deprecation I've ever shipped."
exit(failures.empty? ? 0 : 1)