Durable Refund Agent

Copy Markdown View Source
package_root = Path.expand("../..", __DIR__)

Mix.install(
  [
    {:kino, "~> 0.14"},
    {:jidoka, path: package_root}
  ],
  consolidate_protocols: false,
  lockfile: Path.join(package_root, "mix.lock")
)

Code.require_file(Path.expand("../loader.exs", __DIR__))
JidokaExamples.Loader.load!(__DIR__)

alias JidokaExamples.DurableRefund.Agent, as: DurableRefundAgent
alias JidokaExamples.DurableRefund.Scenario

Jidoka.Kino.setup_notebook(model: "test:durable-refund-script", check_provider?: false)

Run The Demo

This Livebook runs one deterministic refund agent through asynchronous streaming, cancellation, execution limits, crash recovery, and a safe session fork. It also runs two read-only operations together and proves that model observation order stays stable. Additional sections record redacted local trace data, aggregate usage, and run the same agent under process hosting. It does not use a provider key or network access.

Inspect The Agent

{:ok, _inspection} = Jidoka.Kino.debug_agent(DurableRefundAgent)
{:ok, _diagram} = Jidoka.Kino.agent_diagram(DurableRefundAgent)

Stream One Async Request

{:ok, stream_report} = Scenario.async_streaming(observer: self())

unless stream_report.answer == stream_report.text and
         length(stream_report.terminal_events) == 1 do
  raise "the async stream did not produce one correlated result"
end

%{
  answer: stream_report.answer,
  thinking: stream_report.thinking,
  event_names: Enum.map(stream_report.events, & &1.event)
}

Record Usage And A Redacted Local Trace

{:ok, observability_report} = Scenario.observability()

%{
  usage: observability_report.usage,
  trace_entries: length(observability_report.trace),
  last_trace_entry: List.last(observability_report.trace)
}

Run Under Process Hosting

{:ok, host_report} = Scenario.process_host()

%{
  id: host_report.id,
  answer: host_report.result.content,
  terminal_status: host_report.terminal.status,
  stopped?: Jidoka.whereis(host_report.id) == nil
}

Run Parallel Operations In Stable Order

Both policy checks start before either can finish. The scenario releases B2002 first. Jidoka still gives the model the observations in its requested order: A1001, then B2002.

{:ok, parallel_report} = Scenario.parallel_operations(observer: self())

unless parallel_report.completion_order == ["B2002", "A1001"] and
         parallel_report.observation_order == ["A1001", "B2002"] do
  raise "parallel operations did not preserve model order"
end

%{
  completion_order: parallel_report.completion_order,
  observation_order: parallel_report.observation_order,
  answer: parallel_report.answer
}

Cancel Active Work

{:ok, cancellation_report} = Scenario.typed_cancellation(observer: self())

unless cancellation_report.cancellation.reason == :cancelled and
         cancellation_report.capability_alive? == false and
         length(cancellation_report.terminal_events) == 1 do
  raise "typed cancellation did not clean up the active capability"
end

%{
  cancellation: cancellation_report.cancellation,
  terminal_events: cancellation_report.terminal_events
}

Enforce Execution Limits

{:ok, budget_report} = Scenario.bounded_execution(observer: self())

%{
  max_output_tokens: budget_report.max_tokens,
  operation_calls: budget_report.operation_calls,
  turn_error: budget_report.turn_result,
  timeout_error: budget_report.timeout_result
}

Recover After A Worker Crash

The first worker stops after the unsafe refund result is in the durable snapshot. The second worker takes the expired lease and finishes from the stored result.

{:ok, recovery_report} = Scenario.durable_recovery(observer: self())

unless recovery_report.operation_calls == 1 and
         recovery_report.session.status == :finished do
  raise "crash recovery repeated or lost the refund"
end

%{
  answer: recovery_report.answer,
  operation_calls: recovery_report.operation_calls,
  session_revision: recovery_report.session.revision,
  stored_effects: map_size(recovery_report.durable_snapshot.turn_state.journal.results)
}

Fork One Safe Snapshot

{:ok, fork_report} = Scenario.safe_fork()

unless fork_report.source_answer != fork_report.branch_answer and
         fork_report.branch.lineage.parent_session_id == fork_report.source.session_id and
         fork_report.source_replay.status == :finished do
  raise "the safe fork did not create an independent lineage-aware branch"
end

%{
  source: %{id: fork_report.source.session_id, answer: fork_report.source_answer},
  replay: %{
    status: fork_report.source_replay.status,
    event_count: length(fork_report.source_replay.timeline)
  },
  branch: %{
    id: fork_report.branch.session_id,
    answer: fork_report.branch_answer,
    lineage: Jidoka.project(fork_report.branch.lineage)
  }
}