The calculation framework your AI agent can actually reason about.
Define business logic as typed, self-describing data — Balkis handles execution, validation,
dependency resolution, audit trails, what-if scenarios, and documentation.
▶ Try Balkis in your browser — the real engine, live, nothing mocked
Every payroll system, pricing engine, tax calculator, and financial model rots the same way: thousands of formulas scattered across handlers, components, and utility files — implicit ordering, no validation at boundaries, and no way to answer "why did this number come out this way?"
And now there's a second reader of your codebase: AI agents. An agent can't safely modify a discount formula buried in a 400-line route handler. It can safely work with a calculation that declares its inputs, outputs, dependencies, and version as data.
import { z } from "zod";
import { defineCalculation, runCalculation } from "@balkis/core";
const grossSalary = defineCalculation({
id: "payroll.gross-salary",
version: "1.0.0",
summary: "Annual gross salary: base salary plus bonus.",
input: z.object({ baseSalary: z.number().nonnegative(), bonus: z.number().default(0) }),
output: z.object({ gross: z.number() }),
calculate: ({ input }) => ({ gross: input.baseSalary + input.bonus }),
});
const incomeTax = defineCalculation({
id: "payroll.income-tax",
version: "1.0.0",
summary: "Flat 20% income tax on gross salary.",
input: z.object({}),
output: z.object({ tax: z.number() }),
dependencies: [grossSalary], // ← object reference: typed, refactor-safe,
calculate: ({ deps }) => ({ // and cycles are structurally impossible
tax: deps["payroll.gross-salary"].gross * 0.2,
}),
});
const result = await runCalculation(incomeTax, { baseSalary: 90_000, bonus: 10_000 });
result.value.value.tax; // 20000 — fully typed
result.value.trace; // complete audit trail: every step, input, output, duration
result.value.executionId; // reproducible: same inputs + options ⇒ same run, bit for bitNo base classes. No decorators. No hidden execution. Calculations are frozen values; the framework derives everything else.
| 🧮 Calculations as data | Declare inputs, outputs, dependencies, versions. The engine derives execution order, validation, docs, and metadata. |
| 🔒 Validated at every boundary | Zod schemas guard every input and output. A calculation can never observe or emit an unvalidated value. |
| 🎯 Deterministic by contract | One frozen timestamp per run, errors as typed values, reproducible reports. checkDeterminism catches Math.random() leaks in CI. |
| 🧾 Audit trail built in | Every run yields the full trace — inputs, outputs, fired rules, durations — and @balkis/audit persists successes and failures. |
| ⚖️ Rules as JSON | { fact: "tier", op: "eq", value: "vip" } condition trees with priorities and explicit strategies. Storable, diffable, generatable. |
| 🔮 What-if scenarios | Named input overlays, baseline comparison with per-field deltas, one-factor sensitivity analysis. |
| ⚡ Parallel when it pays | mode: "parallel" overlaps independent async branches — measured at up to 56.8×. Sync graphs stay sequential, and we tell you why. |
| 💰 Exact decimal math | @balkis/decimal: bigint fixed-point with banker's rounding. 0.1 + 0.2 = 0.3, invoices to the exact cent, decimals travel as JSON-safe strings. |
| 🧠 Incremental recalculation | Pass an ExecutionCache and re-run: only nodes whose inputs or upstream outputs changed recompute — the rest are served from cache, marked in the trace. |
| 🤖 AI-native metadata | registry.describe() emits the whole catalog — JSON Schemas, dependency graph, rule ASTs — so agents modify logic without reading implementations. |
| 🔌 One command to serve agents | balkis mcp ./module.js — every calculation becomes an MCP tool your agent calls with validated inputs and an audit trail. balkis serve does the same as HTTP + OpenAPI. |
| 👥 Shadow deployments for formulas | Run a candidate catalog against real inputs alongside the current one; get field-level divergence before anything ships. Semantic catalog diffs flag breaking schema changes. |
| 🗣️ Explains itself | explainReport(report) renders any run as a deterministic prose narrative — which rules fired, which didn't, what each step computed. balkis run … --explain. |
| 📊 Escape from Excel | importWorkbook(xlsx) turns spreadsheet formulas into calculations with inferred dependencies — plus an honest coverage report for what it couldn't translate. |
Point any coding agent at your registry and it gets a machine-readable map of your entire business logic:
registry.describe();
// {
// framework: "balkis",
// calculations: [{ id, version, summary, tags, dependencies,
// inputSchema, outputSchema }, ...], // JSON Schema, not prose
// graph: { nodes: [...], edges: [{ from, to }, ...] }
// }- Discover every calculation, its shape, and what depends on it — without executing anything
- Modify a formula knowing the output schema will reject regressions at the boundary
- Explain any historical number from the audit trace: which rules fired, which inputs flowed where
- Generate new calculations that are validated at definition time, not in production
Rules are pure JSON ASTs. Scenarios are pure JSON overlays. Errors carry stable machine-readable codes. This isn't an AI feature bolted on — it's what "no hidden behavior" buys you.
graph LR
DC["defineCalculation()"] --> REG["CalculationRegistry"]
RULES["@balkis/rules<br/>JSON condition trees"] -->|compile to calculations| REG
FIN["@balkis/formulas-finance<br/>NPV · IRR · amortization"] --> REG
REG --> ENG["Engine<br/>sequential | parallel"]
SCEN["@balkis/scenarios<br/>what-if overlays"] --> ENG
ENG --> REP["ExecutionReport<br/>value + audit trace"]
REP --> AUD["@balkis/audit"]
REP --> VIZ["@balkis/visualization"]
REG --> CLI["balkis CLI<br/>inspect · graph · docs · run"]
pnpm add @balkis/core zod # engine + schemas (the only runtime deps)
pnpm add @balkis/rules # optional: declarative rule engine
pnpm add @balkis/scenarios # optional: what-if analysisRules read like configuration because they are configuration:
const discount = ruleCalculation({
id: "pricing.discount",
version: "1.0.0",
input: z.object({ customerTier: z.string() }),
output: z.object({ discountPct: z.number() }),
dependencies: [orderTotal],
group: defineRuleGroup({
id: "pricing.discount-rules",
summary: "Selects the discount percentage.",
rules: [
defineRule({
id: "vip", summary: "VIP tier gets 20%.", priority: 10,
when: { fact: "customerTier", op: "eq", value: "vip" },
output: { discountPct: 20 },
}),
defineRule({
id: "large-order", summary: "Orders of 1000+ get 10%.", priority: 5,
when: { fact: "pricing.order-total.total", op: "gte", value: 1000 },
output: { discountPct: 10 },
}),
],
fallback: { discountPct: 0 },
}),
});And what-if analysis is one call:
const comparison = await runScenarios(engine, profit, baseInputs, [bestCase, worstCase]);
// diffs: [{ scenarioId: "best-case",
// changes: [{ path: "profit", baseline: 20000, value: 30500,
// delta: 10500, deltaPct: 52.5 }] }]Run the full demo: pnpm install && pnpm build && pnpm --filter @balkis/examples demo — payroll, rules, scenarios, and a rendered dependency graph in examples/.
| Package | What it does | Tests |
|---|---|---|
@balkis/core |
defineCalculation, registry, dependency graph, deterministic engine (sequential + parallel), audit trace, ref() late binding, incremental recalculation, explainReport |
48 |
@balkis/rules |
JSON condition ASTs, 14 operators + custom, priorities, first-match / all-matches, compiles to calculations | 33 |
@balkis/scenarios |
Input overlays with extends, baseline diffs, sensitivity analysis, seeded Monte Carlo |
25 |
@balkis/decimal |
Exact bigint fixed-point decimals, five rounding modes (banker's default), Zod schema helpers | 14 |
@balkis/mcp |
Model Context Protocol server — calculations as agent tools over stdio, zero deps | 7 |
@balkis/versioning |
Semantic catalog diffs with breaking-change detection, shadow runs over input corpora | 8 |
@balkis/xlsx |
Excel import — formulas become calculations with inferred dependencies + honest coverage report; zero deps | 11 |
@balkis/formulas-finance |
FV/PV, NPV, IRR, loan + amortization, depreciation, ROI — golden-tested against textbook tables | 16 |
@balkis/testing |
Snapshot-stable reports, golden-value cases, determinism checks | 9 |
@balkis/cli |
balkis inspect / graph / docs / run / serve / mcp — catalog tools, HTTP API with OpenAPI, MCP server |
12 |
@balkis/audit |
AuditedEngine + pluggable sinks (in-memory, JSONL); failures audited too |
6 |
@balkis/visualization |
Standalone SVG/HTML dependency graphs with execution overlays | 4 |
From BENCHMARKS.md (Node 24, Apple Silicon):
- Engine overhead: ~0.6 µs per node — a 1,000-node graph executes and traces in ~0.6 ms
- Parallel mode on async fan-ins: 3.9× / 15.3× / 56.8× at width 4 / 16 / 64
- Parallel mode on sync work: ~0.8× — so it's not the default, and the docs say so
Every performance claim in this repo has a benchmark behind it. That's a design rule, not a slogan.
Fifteen recorded decisions with trade-offs in ARCHITECTURE.md — why dependencies are object references (D1), why errors are values (D4), why rule conditions are JSON (D7), why parallel execution preserves every determinism guarantee (D14), and more. If you disagree with one, the reasoning is there to argue with.
- Core engine · rules · scenarios · finance formulas · testing · CLI · audit · visualization · benchmarks
- Exact-decimal arithmetic for currency-grade math (
@balkis/decimal) - Incremental recalculation / cross-run memoization (
ExecutionCache) - Monte Carlo scenario sampling (seeded, deterministic)
- Docs site — mohammadmarwanbalkis-sketch.github.io/balkis
- Live in-browser playground — the real engine, nothing mocked
- npm publish (
@balkis/*) - Worker-thread execution for CPU-bound graphs (benchmarked before claimed)
pnpm install && pnpm test gets you a green 134-test suite in seconds. See CONTRIBUTING.md — the short version: strict TypeScript, no hidden behavior, every phase ships with tests and docs, and performance claims require benchmarks.
MIT © Mohammad Balkis
If Balkis saved your business logic from becoming spaghetti — or saved your AI agent from guessing — a ⭐ helps others find it.