v2.0

GoRules Version 2 is here - redesigned, now with managed cloud.GoRules Version 2 is here!

Watch the launch videoWatch
All posts

Stefan, Co-founderAug 19, 2026Blog · Engineering

Business logic humans can read and machines can run

Most companies keep two copies of their business logic: a document people read, and code that actually runs. The two drift apart. We built GoRules so you can keep one copy, something the owner reads and every system runs.

Every company past a certain size keeps two copies of its business logic. One copy is a document, something like the credit policy or the pricing sheet, and it sits in Confluence or a PDF. The person who owns that document understands the business. The other copy is code, and engineers wrote it by reading the document.

Companies come to us with what happens next, and we hear the same story in every industry. Take a lending team. The policy says that anyone with an active bankruptcy gets declined. Automatically, no exceptions. The code mostly agrees, except that a contractor added a bypass for one partner years ago. That partner is gone. The bypass still runs. It catches so few applications that nobody notices, until an auditor asks which copy is the real policy.

Nobody can answer that without an engineer, who has to read the service and compare it against the document from memory. Every change to the policy goes through that same engineer. So the analyst stops trusting the document, then stops updating it, and now it deserves the distrust. Trust only moves down from here.

We think a company should keep one copy. The person who owns the logic can read it, and every system that needs an answer can run it. Everything we built comes from refusing to give up either half.

Why not just write the if-statements?

Because the analyst who owns the credit policy can't read a pull request. Code runs everywhere, and that's the easy half. The hard half is review, and when the reviewer can't read the thing under review, review turns into translation. Translation drifts.

Usually it's worse than one translation. The same logic gets built twice, once in the service for online requests and once in SQL or Spark for the nightly batch. Count the document and one policy now exists in three places. Anyone who has run scoring logic online and offline knows how that ends. The two engines disagree on some Tuesday, and nobody can say which one was right.

Didn't Drools already solve this?

It tried, honestly, and it deserves credit. The Rete algorithm made large rule sets fast, and DMN gave the industry a standard way to model decisions. The ambition was right too: business users should own the logic.

Two problems never got solved. The first is portability. Drools lives on the JVM, so if your services run Python or Node, or your nightly batch runs PySpark, the engine becomes another server you call over the network. You're back to keeping copies. The second is language. Authoring happens in a console that still feels like 2010, and nothing in it speaks the owner's tongue: DMN gives you tables and a diagram that wires them together, but never a sentence a policy owner would recognize as their policy. We keep a feature-by-feature comparison if you want the long version.

Why not let an LLM decide?

A decision system owes you three things. The same input gets the same answer. You can see why before you trust it. And someone else can check it later. An LLM fails all three by construction, not by immaturity: the answer can change between runs, the reasoning is a story the model tells afterward, and there's no trace anyone can replay. No future model version fixes that, because the failure isn't a capability gap. It's what probabilistic means.

The model does have a right seat, though. An agent can read the messy email, ask the follow-up question, and then call the policy for the decision itself. The model proposes, the policy decides, and all three guarantees come back.

So what's the solution?

We built GoRules. The engine underneath is called ZEN, we wrote it in Rust, and it's been MIT licensed and open source since 2023. Teams have run decision graphs and tables on it for years. Policies, the document format this blog is about, shipped this summer. So this is recent work.

A policy is a flat list of typed blocks inside a document: data models, dictionaries, decision tables, single expressions, match blocks, assertions, and ordinary prose between them. You never wire block A to block B.

So if nothing's wired up, what runs first?

The engine works that out for you. It reads what every block consumes and what every block produces, builds the dependency graph, and derives the execution order from that.

A spreadsheet recalculates the same way. A build system schedules targets the same way. For a policy document it means you can arrange the pages for whoever reads them, with the summary up front and the rate tables in an appendix, and the engine still runs the thing correctly.

The compiler holds you to two rules while it does this. Only one block may write a given property. And no block may depend on itself, directly or through a chain of other blocks. Break either one and your document doesn't compile. You get a named diagnostic on your screen instead of a surprise in production.

Document order changes. Execution order stays put, derived from what each block reads and writes.

Who writes the English, a model?

Nobody writes it. The compiler emits it. An engineer types customer.tier == "gold" and order.total > 500, and the analyst reads "If the customer's tier is Gold and the order total is greater than 500."

The obvious guess in 2026 is that a language model produced that sentence. It didn't, and none of this depends on one. The engine compiles the expression and describes the result piece by piece: this part is a field and here is its type, this part is a comparison, this part is a value with a display label. A locale table turns each piece into a phrase, and English, Spanish, and Brazilian Portuguese ship today. The projection is deterministic, and it only runs one way, from the compiled expression to the sentence.

Editing runs the other way, and that's the one place we do use a language model. You change the sentence, the model proposes an expression, and the compiler accepts it or rejects it. What renders afterward is the deterministic projection of whatever the compiler accepted. The sentence is never the source of truth. So the analyst's view and the engineer's view can't drift apart, because both of them are renderings of one compiled document.

Two renderings of one compiled artifact. Editing either view lands in the same underlying expression.

Isn't this COBOL again?

No, because the arrow points the other way. COBOL and the natural-language programming that followed it failed for one reason: somebody wrote English and a machine tried to execute it. Ambiguity went in, ambiguity came out.

Here an engineer writes a small typed expression, and the English falls out of the compiled form, readable and editable. If the sentence renders on your screen, that sentence is exactly what runs. (If you're an engineer: it's a disassembler whose listings read as English.)

How fast is it?

About 10 microseconds for the median of the 89 real-world decision models in our benchmark suite. A simple decision table lands around 2.4. Money math runs on decimals, never floats. A full execution trace, which tells you which rules fired and what every property that got read contained, costs roughly an extra tenth on top.

Those numbers matter for what they let you do. The engine is a library rather than a service, so the same document that answers a REST call also runs inside a PySpark worker. You send the rules to every worker once, each worker loads its own engine, and rows get evaluated right where the data sits. There's no per-row API call and no second copy of the policy written in Spark SQL, so your nightly batch can't disagree with your API. Both of them run the identical document. Native bindings ship for Node, Python, Go, Rust, Java, Kotlin, C#, and Swift, and we documented the Spark pattern using the plain Python binding.

Can I see a whole one?

Here's the policy from the opening, small enough to read in one sitting. Two knockout assertions and a risk tier table, plus one application evaluated with the trace open.

The trace is our favorite part of the product. An engineer used to answer "why was this application declined" by attaching a debugger to production. Now anyone answers it by reading.

What stops an analyst from changing a threshold at 4pm?

The same thing that stops an engineer from doing it, which is review. We version policies and put them through review and release like everything else in the product. So the 4pm change lands as a commit somebody approved, not as an incident you find on Monday.

When is this a bad idea?

When your rules change twice a year and engineers are the only people who touch them. Write the if-statements. A dependency you don't need costs you more than repetition you can read.

This starts paying when the logic changes weekly, or when the person who owns it doesn't ship code. It pays double when the same decision has to run in more than one place.

Two sharper edges, so you hear them from us first. We type the rules statically, so type mismatches and undefined variables fail compilation, and the compiler tracks nullability through the types. Exhaustiveness is the soft spot. Write a rule that lists cases but no catch-all, and it still compiles. Feed it an input none of the cases cover and you get null back, an empty answer instead of an error. We have mixed feelings about whether we should make that stricter.

The other edge is contributions. The engine is MIT and it will stay MIT, but we currently accept documentation and tests only, not code. With a compiler-shaped core, a drive-by feature PR costs us more to review than it cost anyone to write, and we'd rather say that out loud than let pull requests rot. We know how that reads to an open source audience. It's still the honest tradeoff.

Where do I get it?

ZEN, the engine, is on GitHub under MIT. Everything else lives at docs.gorules.io: the policy format, the projection layer, the editor, and the integration guides. The benchmark suite publishes with every release, regression alerts included.