Kumi is a declarative rules-and-calculation DSL for Ruby that staticaly analyzes and compiles your business logic.
Ruby
74
499 commits
updated Jun 20, 2026
Kumi is a declarative DSL for calculation logic — tax rules, pricing, scoring, financial projections — that compiles to plain Ruby and JavaScript.
You declare the shape of your input data and the values you want computed. The compiler determines evaluation order, checks types, detects impossible conditions, and emits dependency-free code for each target.
schema do
input do
array :items do
hash :item do
integer :quantity
decimal :unit_price
end
end
end
value :line_totals, input.items.item.quantity * input.items.item.unit_price
value :subtotal, fn(:sum, line_totals)
end
No loops, no iteration plumbing: line_totals is computed per item because that's where the data lives, and fn(:sum, ...) collapses it back to a scalar. This works through arbitrarily nested arrays.
Tax engines, pricing models, financial projections, compliance rules, insurance underwriting, shipping rate calculators — anywhere calculation logic must be correct, auditable, and consistent across platforms.
Status: experimental. Public API may change. Typing and some static checks are still evolving.
Feedback: have a use case or hit a rough edge? Open an issue or reach out (andremuta+kumi@gmail.com).
gem install kumi
Requires Ruby 3.1+. Runtime dependencies: mutex_m and zeitwerk (bundled via Rubygems).
require 'kumi'
module Double
extend Kumi::Schema
schema do
input { integer :x }
value :doubled, input.x * 2
end
end
# Execute in Ruby
result = Double.from(x: 5)
result[:doubled] # => 10
# or just call the method directly
Double._doubled(x: 5) # => 10
# Export to JavaScript (same logic)
Double.write_source("output.mjs", platform: :javascript)
# ./output.mjs
# export function _doubled(input) {
# let t1 = input["x"];
# let t3 = t1 * 2;
# return t3;
# }
You can also override the compilation strategy without touching code by setting
KUMI_COMPILATION_MODE to jit or aot (e.g. export KUMI_COMPILATION_MODE=aot).
Schemas can import other schemas — and the compiler inlines everything at compile time. The generated code has no runtime dependency on the imported module; imported logic participates in broadcasting and loop fusion like any locally written expression.
module TaxPolicy
extend Kumi::Schema
schema do
input { decimal :amount }
value :tax, input.amount * 0.15
end
end
module Order
extend Kumi::Schema
schema do
import :tax, from: TaxPolicy
input do
array :items do
hash :item do
decimal :price
end
end
end
# TaxPolicy only knows a scalar :amount — passing a vectorized
# argument applies it per item automatically.
value :item_taxes, fn(:tax, amount: input.items.item.price)
value :total_tax, fn(:sum, item_taxes)
end
end
Order.from(items: [{ price: 100 }, { price: 200 }, { price: 300 }])[:total_tax]
# => 90.0
The generated JavaScript for total_tax shows what "inline everything" means — the imported tax rule and the sum reduction are fused into one loop, with no call to TaxPolicy, no intermediate array, and no runtime to ship:
export function _total_tax(input) {
let t13 = input["items"];
let acc18 = 0.0;
for (let items_i15 = 0; items_i15 < t13.length; items_i15++) {
let items_el14 = t13[items_i15];
let t16 = items_el14["price"];
let t17 = 0.15;
let t12 = t16 * t17;
acc18 += t12;
}
let t6 = acc18;
return t6;
}
See Schema Imports for renamed inputs, multiple imports, and imports over nested arrays.
Compiled schemas are straight-line code with fused loops — there is no interpreter, rule engine, or library call in the artifact, so the runtime cost is whatever the host language charges for a for loop.
Two playground examples make this tangible:
The playground's example picker groups these by theme (language tour, business logic, scale & speed, simulations) — the Language Intro is the best starting point.
to_string, string conversions, pow) and how the kernels keep them identicalMIT License. See LICENSE.
499 commits
Ruby
89.4%
JavaScript
10.1%
Kumi is a declarative rules-and-calculation DSL for Ruby that staticaly analyzes and compiles your business logic.
Ruby
74
499 commits
updated Jun 20, 2026
Kumi is a declarative DSL for calculation logic — tax rules, pricing, scoring, financial projections — that compiles to plain Ruby and JavaScript.
You declare the shape of your input data and the values you want computed. The compiler determines evaluation order, checks types, detects impossible conditions, and emits dependency-free code for each target.
schema do
input do
array :items do
hash :item do
integer :quantity
decimal :unit_price
end
end
end
value :line_totals, input.items.item.quantity * input.items.item.unit_price
value :subtotal, fn(:sum, line_totals)
end
No loops, no iteration plumbing: line_totals is computed per item because that's where the data lives, and fn(:sum, ...) collapses it back to a scalar. This works through arbitrarily nested arrays.
Tax engines, pricing models, financial projections, compliance rules, insurance underwriting, shipping rate calculators — anywhere calculation logic must be correct, auditable, and consistent across platforms.
Status: experimental. Public API may change. Typing and some static checks are still evolving.
Feedback: have a use case or hit a rough edge? Open an issue or reach out (andremuta+kumi@gmail.com).
gem install kumi
Requires Ruby 3.1+. Runtime dependencies: mutex_m and zeitwerk (bundled via Rubygems).
require 'kumi'
module Double
extend Kumi::Schema
schema do
input { integer :x }
value :doubled, input.x * 2
end
end
# Execute in Ruby
result = Double.from(x: 5)
result[:doubled] # => 10
# or just call the method directly
Double._doubled(x: 5) # => 10
# Export to JavaScript (same logic)
Double.write_source("output.mjs", platform: :javascript)
# ./output.mjs
# export function _doubled(input) {
# let t1 = input["x"];
# let t3 = t1 * 2;
# return t3;
# }
You can also override the compilation strategy without touching code by setting
KUMI_COMPILATION_MODE to jit or aot (e.g. export KUMI_COMPILATION_MODE=aot).
Schemas can import other schemas — and the compiler inlines everything at compile time. The generated code has no runtime dependency on the imported module; imported logic participates in broadcasting and loop fusion like any locally written expression.
module TaxPolicy
extend Kumi::Schema
schema do
input { decimal :amount }
value :tax, input.amount * 0.15
end
end
module Order
extend Kumi::Schema
schema do
import :tax, from: TaxPolicy
input do
array :items do
hash :item do
decimal :price
end
end
end
# TaxPolicy only knows a scalar :amount — passing a vectorized
# argument applies it per item automatically.
value :item_taxes, fn(:tax, amount: input.items.item.price)
value :total_tax, fn(:sum, item_taxes)
end
end
Order.from(items: [{ price: 100 }, { price: 200 }, { price: 300 }])[:total_tax]
# => 90.0
The generated JavaScript for total_tax shows what "inline everything" means — the imported tax rule and the sum reduction are fused into one loop, with no call to TaxPolicy, no intermediate array, and no runtime to ship:
export function _total_tax(input) {
let t13 = input["items"];
let acc18 = 0.0;
for (let items_i15 = 0; items_i15 < t13.length; items_i15++) {
let items_el14 = t13[items_i15];
let t16 = items_el14["price"];
let t17 = 0.15;
let t12 = t16 * t17;
acc18 += t12;
}
let t6 = acc18;
return t6;
}
See Schema Imports for renamed inputs, multiple imports, and imports over nested arrays.
Compiled schemas are straight-line code with fused loops — there is no interpreter, rule engine, or library call in the artifact, so the runtime cost is whatever the host language charges for a for loop.
Two playground examples make this tangible:
The playground's example picker groups these by theme (language tour, business logic, scale & speed, simulations) — the Language Intro is the best starting point.
to_string, string conversions, pow) and how the kernels keep them identicalMIT License. See LICENSE.
499 commits
Ruby
89.4%
JavaScript
10.1%