agentic examples

The Mirror Plan

The Mirror Plan: every task ships its own inverse, so every plan has a REFLECTION - same graph with the arrows flipped, undo agents in place of do agents - and running plan-then-mirror returns the world to its exact initial state, byte for byte. Which sounds like a party trick until a plan fails halfway with real side effects already committed: then the mirror of the completed prefix is the compensation saga you'd otherwise write by hand at 3am, and the strangest thing about …

Reliability & Recovery Round 19 Luca Guidi exit 0

source on github

bundle exec ruby examples/mirror_plan.rb

a real captured run

THE MIRROR PLAN (every plan carries its own reflection)

  act 1 - the plan runs forward: lamp reserved, card charged, listing live
    world changed: true (inventory 2, ledger 1 entries)
    ...then its mirror runs (same steps, arrows flipped, undo for do):
    world restored byte-for-byte: true

  act 2 - the same plan dies at step 3 (payment provider said no... late):
    completed before the crash: reserve stock, charge card
    stock is reserved and money is TAKEN - the classic 3am state.
    the mirror of the COMPLETED PREFIX runs (a saga, auto-derived):
    world restored byte-for-byte: true

  act 3 - mirror(mirror(plan)) == plan: true (reflection is an involution;
    the mandala next door would like a word)

  the honest fine print: this works because every step declared an
  inverse that TRULY inverts - pop for push, += for -=. some real
  actions have no inverse (you cannot unsend an email; you can only
  send an apology), and the discipline the mirror imposes is exactly
  that: it forces you to ANSWER, per step, 'what is the undo?' -
  and steps with no answer get quarantined behind the ones that
  have one. compensation isn't a framework feature you buy; it's a
  question you agree to keep answering. the mirror just makes the
  question mandatory, which is the most architecture anything in
  this file does.

source

# frozen_string_literal: true

# The Mirror Plan: every task ships its own inverse, so every plan
# has a REFLECTION - same graph with the arrows flipped, undo agents
# in place of do agents - and running plan-then-mirror returns the
# world to its exact initial state, byte for byte. Which sounds like
# a party trick until a plan fails halfway with real side effects
# already committed: then the mirror of the completed prefix is the
# compensation saga you'd otherwise write by hand at 3am, and the
# strangest thing about it is that it was sitting in the graph all
# along, wearing the plan's clothes backwards.
#
#   bundle exec ruby examples/mirror_plan.rb
#
# Runs offline; exits 1 unless both mirrors restore the world.

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

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

# Each step declares its action AND its inverse, over a shared world
STEPS = [
  {name: class="s">"reserve stock",
   do: ->(w) { w[class="y">:inventory][class="s">"lamp"] -= 1 },
   undo: ->(w) { w[class="y">:inventory][class="s">"lamp"] += 1 }},
  {name: class="s">"charge card",
   do: ->(w) { w[class="y">:ledger] << {charge: 4900} },
   undo: ->(w) { w[class="y">:ledger].pop }},
  {name: class="s">"publish listing",
   do: ->(w) { w[class="y">:published] << class="s">"lamp" },
   undo: ->(w) { w[class="y">:published].delete(class="s">"lamp") }},
  {name: class="s">"notify warehouse",
   do: ->(w) { w[class="y">:outbox] << class="s">"pick lamp" },
   undo: ->(w) { w[class="y">:outbox].pop }}
].freeze

def run(steps, world, direction:, fail_at: nil)
  orchestrator = Agentic:class="y">:PlanOrchestrator.new(concurrency_limit: 1, retry_policy: {max_retries: 0, retryable_errors: []})
  ordered = (direction == class="y">:mirror) ? steps.reverse : steps
  completed = []
  previous = nil
  ordered.each do |step|
    action = (direction == class="y">:mirror) ? step[class="y">:undo] : step[class="y">:do]
    task = Agentic:class="y">:Task.new(description: class="s">"#{direction}: #{step[class="y">:name]}", agent_spec: {class="s">"name" => step[class="y">:name], class="s">"instructions" => class="s">"w"})
    orchestrator.add_task(task, previous ? [previous] : [], agent: ->(_t) {
      raise Agentic:class="y">:Errors:class="y">:LlmAuthenticationError, class="s">"payment provider said no" if step[class="y">:name] == fail_at
      action.call(world)
      completed << step
      class="y">:done
    })
    previous = task
  end
  orchestrator.execute_plan
  completed
end

def snapshot(world) = Marshal.dump(world)

fresh_world = -> { {inventory: {class="s">"lamp" => 3}, ledger: [], published: [], outbox: []} }

puts class="s">"THE MIRROR PLAN (every plan carries its own reflection)"
puts

# --- act 1: forward, then the full mirror - the world round-trips -------------------
world = fresh_world.call
genesis = snapshot(world)
run(STEPS, world, direction: class="y">:forward)
changed = snapshot(world) != genesis
puts class="s">"  act 1 - the plan runs forward: lamp reserved, card charged, listing live"
puts class="s">"    world changed: #{changed} (inventory #{world[class="y">:inventory]["lampclass="s">"]}, ledger #{world[class="y">:ledger].size} entries)"
run(STEPS, world, direction: class="y">:mirror)
restored_full = snapshot(world) == genesis
puts class="s">"    ...then its mirror runs (same steps, arrows flipped, undo for do):"
puts class="s">"    world restored byte-for-byte: #{restored_full}"
puts

# --- act 2: the plan fails halfway - mirror only what completed ---------------------
world = fresh_world.call
genesis = snapshot(world)
completed = run(STEPS, world, direction: class="y">:forward, fail_at: class="s">"publish listing")
puts class="s">"  act 2 - the same plan dies at step 3 (payment provider said no... late):"
puts class="s">"    completed before the crash: #{completed.map { |s| s[class="y">:name] }.join(", class="s">")}"
puts class="s">"    stock is reserved and money is TAKEN - the classic 3am state."
run(completed, world, direction: class="y">:mirror)
restored_partial = snapshot(world) == genesis
puts class="s">"    the mirror of the COMPLETED PREFIX runs (a saga, auto-derived):"
puts class="s">"    world restored byte-for-byte: #{restored_partial}"
puts

# --- act 3: the mirror of the mirror is the plan ------------------------------------
involution = STEPS.reverse.reverse == STEPS
puts class="s">"  act 3 - mirror(mirror(plan)) == plan: #{involution} (reflection is an involution;"
puts class="s">"    the mandala next door would like a word)"
puts

failures = []
failures << class="s">"forward plan changed nothing" unless changed
failures << class="s">"full mirror failed to restore" unless restored_full
failures << class="s">"compensation failed to restore" unless restored_partial
failures << class="s">"reflection is not an involution" unless involution

puts class="s">"  the honest fine print: this works because every step declared an"
puts class="s">"  inverse that TRULY inverts - pop for push, += for -=. some real"
puts class="s">"  actions have no inverse (you cannot unsend an email; you can only"
puts class="s">"  send an apology), and the discipline the mirror imposes is exactly"
puts class="s">"  that: it forces you to ANSWER, per step, 'what is the undo?' -"
puts class="s">"  and steps with no answer get quarantined behind the ones that"
puts class="s">"  have one. compensation isn't a framework feature you buy; it's a"
puts class="s">"  question you agree to keep answering. the mirror just makes the"
puts class="s">"  question mandatory, which is the most architecture anything in"
puts class="s">"  this file does."
exit(failures.empty? ? 0 : 1)