Idea

An idea behind the tools

Ask any function why

When a result looks wrong, you want to know where it came from. Which inputs moved it? Which inputs could never reach it at all? What was each intermediate value on this run? Usually you find out with a debugger or print statements, changing one input at a time.

Why works on any definition in a Bosatsu program: a calculator output, a helper, a service handler. Give it inputs and Yichus runs it, varies each input to show what moves, and states which inputs can reach the result at all, a fact that holds for every input. For a pure calculation it also shows each named step's value.

Run a function on chosen inputs →Press Why on a live calculator →

Credit: tracing a result back to what produced it comes from spreadsheet trace-precedents, program slicing (Weiser, 1981) and why-provenance in databases (Buneman, Khanna and Tan, 2001); varying one input at a time is one-at-a-time sensitivity analysis. Yichus applies them to any named definition in a compiled program and keeps what is proven apart from what one run showed.

Three answers, each labelled by how it is known

The work. Each named intermediate value in a pure definition, with its value on this run. Each value comes from the real evaluator running the definition's own compiled code up to that step.

What each input moves. Each input is varied (a whole number goes 10% up or down, rounded, then doubled, halved, and zero), and the answer lists which result fields changed and by how much. These are observations of this run.

What is proven. A static dataflow pass over the compiled program sorts each parameter: it may reach the result, it only steers which path runs, or it reaches neither the result nor any effect. These hold for every input.

An example: an invoice with a planted bug

A small invoice program from the project's tests: expenses are taxed but never added to the total. Output of yichus why --brief, trimmed:

invoice(hours = 12, rate_cents = 9500, expenses_cents = 20000, tax_pct = 13, loyal = True)
  labour   = 114000
  discount = 11400
  taxable  = 134000
  tax      = 17420
  total    = 120020

What each input moves (this run's variations, not proof):
  hours 12 -> 13 (+1): labour +9500, discount +950, tax +1235, total +9785
  expenses_cents 20000 -> 22000 (+2000): expenses +2000, tax +260, total +260

Adding 2000 in expenses moves the total by only the 260 of tax on it. No step value is wrong; the move is what gives the bug away.

Where to ask

yichus why --binding Pkg/Name::binding --inputs '{…}' prints JSON, a short page with --brief, or sentences with --narrative. --record saves the run with its sources; --replay re-runs it and reports any difference. Agents call the same engine as the api_why MCP tool, which the browser workbench also runs. To follow dependencies without running anything, yichus explore --trace and --trace-flow walk a binding's upstream edges. The calculator's Why panel is the same idea built into a generated page; how that view is generated.

What a why answer does not establish

“May reach the result” means a path exists in the program, not that this input changes this result. Only “reaches neither” excludes dependence.

More limits
  • A result that never changed across the variations is not proof of independence, and the answer says so. A may-reach input that never moved stays unresolved.
  • An input that only steers which path runs can still change the result by choosing a different branch.
  • Steps are shown for pure definitions only. A handler runs against a fresh in-memory database seeded with the rows you supply, not a deployed one.
  • Enum inputs are varied as a whole, not field by field.
  • An answer shows how the definition computed its result. It does not say the formula is the one intended: the invoice bug shows up as a small move, and a reader still has to notice that it is wrong.

Where this fits