A hosted rules engine

Reckoning: Rules that explain themselves.

Pricing, billing, eligibility, commissions, tariffs, game rules: anything written down as clauses and tables. Point your coding agent at the source, let it write a blueprint proven against worked cases, then call it from your app and get exact answers with every step explained.

bundle add reckoning-client
of cases pass before a version publishes
100%
HTTP call from your app
1
exit codes an agent loops on
3

How it works

From rulebook to answer

01Point an agent at it.

Hand your coding agent the source: a price list, a policy, a tariff, a rulebook. It reads reckoning guide, writes the blueprint and the worked cases, and loops until every case passes.

agent@laptop: ~/rules/bill

$ reckoning guide

$ reckoning check bill.yml

{"problems": []}exit 0

$ reckoning test bill.yml cases.yml

{"passed": 5, "failed": 2}exit 3

failed: a peak read on 16 July is charged at the new 50c rate

expected charge 1.00, got 0.90

agent The rate changes on 2026-07-15. Adding on: date to the rate lookup.

$ reckoning test bill.yml cases.yml

{"passed": 7, "failed": 0}exit 0

cases

02Publish it.

reckoning push checks the blueprint and proves its cases on the service. reckoning publish makes a numbered version. Blueprints import each other by name, so big rule sets are built from small, proven parts.

agent@laptop: ~/rules/bill

$ reckoning push bill bill.yml cases.yml

{"problems": [], "passed": 7, "failed": 0, ...}exit 0

$ reckoning publish bill

{"version": 3}exit 0

versions of bill

v1 v2 v3
# a bigger blueprint, built from proven parts
imports: [holidays@3, network_tariffs@7, bill@3]

03Call it from your app.

One HTTP call, or the Ruby SDK. Decimals stay exact, and every answer comes back with the steps that produced it.

your app
answer = Reckoning::Client.run("bill@3", facts)
answer.total # => 2.3
curl -X POST https://reckoning.example.com/api/definitions/bill/run \
  -H "Authorization: Bearer $KEY" -d '{"facts": {...}, "trace": true}'
{"outputs": {"total": "2.3", "usage_charge": "1.2", "supply_charge": "1.1"},
 "steps": [...]}

Traces

Every number explains itself.

Ask for a trace and the answer comes back with the steps that reached it: which table row matched, which rate applied on which date, and the arithmetic in between. It is the same working you wrote in the case, so a reviewer can check it line by line.

cases.yml
- name: one day on the flat rate is the kWh used at 30c plus the $1.10 daily supply
  # (1.5 + 2.5) kWh x 0.30 = 1.20; 1 day x 1.10 = 1.10; 1.20 + 1.10 = 2.30
  facts: {plan: flat_rate, period_from: 2026-07-01, period_to: 2026-07-01, reads: [...]}
  expect: {usage_charge: 1.20, supply_charge: 1.10, total: 2.30}

bill@3 · total

usage_charge + supply_charge - feed_in_credit

2.30

  • usage_charge 1.20

    4 kWh x 0.30, rate looked up for flat_rate on 2026-07-01

    • read 1: 1.5 kWh x 0.300.45
    • read 2: 2.5 kWh x 0.300.75
  • supply_charge 1.10

    1 day x 1.10 daily supply

  • feed_in_credit 0.00

    no solar exported this day

matches the hand-worked case: 1.20 + 1.10 = 2.30

Domains

Any rules, one engine.

Tables, formulas, rule sets, lookups by date and band, and runs over lists cover more than you would think. Every card is an excerpt in the real blueprint format.

Billing

An electricity bill

  • time-of-use windows
  • a price change on a date
  • solar feed-in
tables:
  windows:
    - {day_type: weekday, until: 7, period: off_peak}
    - {day_type: weekday, until: 15, period: shoulder}
    - {day_type: weekday, until: 21, period: peak}
  usage_rates:
    - {plan: time_of_use, from: 2026-01-01, period: peak, rate: 0.45}
    - {plan: time_of_use, from: 2026-07-15, period: peak, rate: 0.50}
calculations:
  read_charge:
    steps:
      - name: period
        lookup: {table: windows, where: {day_type: day_type}, value: period, band: {by: hour, below: until}}
      - name: rate
        lookup: {table: usage_rates, where: {plan: plan, period: period}, value: rate, on: date}
      - {name: charge, formula: kwh * rate}

Compensation

A sales commission plan

  • marginal tiers
  • a cap
  • SPIFFs
  • clawbacks
  • a recoverable draw
tables:
  tiers:
    - {plan: first_half, lower: 0, upper: 0.5, rate: 0.05}
    - {plan: first_half, lower: 0.5, upper: 1, rate: 0.10}
    - {plan: first_half, lower: 1, upper: 1.5, rate: 0.15}
calculations:
  quarter:
    steps:
      - name: slices
        run: {calculation: tier_slice, over: plan_tiers}
      - name: tiered
        sum: {output: amount, of: slices}
      - {name: commission, formula: 'MIN(tiered, terms.cap_multiple * target_incentive)'}
      - {name: earned, formula: commission + spiffs}
      - {name: payout, formula: 'ROUND(MAX(draw, recoverable), 2)'}

Games

Call of Cthulhu skill checks and chases

  • bonus and penalty dice
  • hard and extreme successes
  • pushed rolls
calculations:
  skill_check:
    steps:
      - {name: net_bonus, formula: bonus_dice - penalty_dice}
      - {name: roll, formula: 'IF(net_bonus > 0, MIN(candidates), MAX(candidates))'}
      - name: success_level
        rules:
          pick: first
          rules:
            - {name: critical, when: roll = 1}
            - {name: fumble, when: roll = 100 OR (skill < 50 AND roll >= 96)}
            - {name: extreme, when: roll <= ROUNDDOWN(skill / 5)}
            - {name: hard, when: roll <= ROUNDDOWN(skill / 2)}
            - {name: regular, when: roll <= skill}
            - {name: failure, when: true}

Commerce

Pricing and eligibility

  • price lists by date
  • who qualifies, and why not
  • exact totals
calculations:
  quote:
    steps:
      - name: eligibility
        rules:
          pick: first
          rules:
            - {name: too_young, when: age < 18}
            - {name: outside_area, when: NOT(area_served)}
            - {name: eligible, when: true}
      - name: unit_price
        lookup: {table: prices, where: {tier: tier}, value: price, on: date}
      - {name: total, formula: 'ROUND(unit_price * quantity * (1 - discount), 2)'}

Yours

If it is written down, it can be a blueprint.

Tariffs, rosters, royalties, scoring, grants, fees. Hand the document to your agent and let the cases decide when it is done.

Start a blueprint

Ruby SDK

Feels at home in Rails.

Configure a client, then let a model declare what it reckons. The block turns your records into facts, and the answer reads like any other attribute.

app/models/rep.rb
Reckoning::Client.configure do |config|
  config.url = ENV["RECKONING_API_URL"]
  config.api_key = ENV["RECKONING_API_KEY"]
end

class Rep < ApplicationRecord
  include Reckoning::Client::Model

  reckons :statement, with: "sales_commission@3", calculation: :year do |year|
    {deals: deals.closed_in(year).map(&:to_fact), quarters: quarter_targets.in(year).map(&:to_fact)}
  end
end

rep.statement(2026).total_paid # => 55830

Statement, 2026

rep.statement(2026) · sales_commission@3

proven
Quarter Credited Attained Paid
Q1 the draw, recovered later 48,000 80% 5,000
Q2 103,000 103% 8,250
Q3 126,000 105% 11,580
Q4 commission capped at 30,000 270,000 180% 31,000
Paid for the year 55,830

Agents

Ready for your coding agent.

The CLI speaks JSON and answers with exit codes an agent can loop on. It never has to guess whether it is done: the cases say so.

0
ok, carry on
2
problems in the blueprint, with where and why
3
a case failed, with expected and actual
  1. you
    Here is our tariff sheet. Turn it into a bill blueprint, work the examples on page 4 into cases, and publish it when they all pass.
  2. agent

    $ reckoning guide

    Reading how blueprints, cases and lookups by date fit together.

  3. agent

    $ reckoning check bill.yml

    {"problems": [...]}exit 2

    The rate lookup names a table I have not written yet. Adding usage_rates.

  4. agent

    $ reckoning check bill.yml

    {"problems": []}exit 0

  5. agent

    $ reckoning test bill.yml cases.yml

    {"passed": 5, "failed": 2}exit 3

    Two peak reads after 15 July are short. The price change needs on: date.

  6. agent

    $ reckoning test bill.yml cases.yml

    {"passed": 7, "failed": 0}exit 0

  7. agent

    $ reckoning push bill bill.yml cases.yml && reckoning publish bill

    {"version": 3}exit 0

    Published bill@3. Every worked example on page 4 is a passing case.

the whole loop
reckoning guide                      # how to build a blueprint
reckoning check bill.yml             # {"problems": []}            exit 0
reckoning test bill.yml cases.yml    # {"passed": 7, "failed": 0}  exit 0
reckoning push bill bill.yml cases.yml
reckoning publish bill               # {"version": 3}
reckoning run bill --facts week.json --trace

Rules that explain themselves.

Point an agent at the rulebook. Publish what it proves. Call it from your app.

$ bundle add reckoning-client