Skip to content
Talk to our solutions team

Rules

The Rules block runs decision logic that lives outside your application code: you author rule sets in GRL, hand the engine a set of facts, and read back whatever the rules wrote. The deployable service is rules.svc.

A rule set is a named collection of rules. A rule is a condition and an action block:

rule ApproveSmallInvoice "auto-approve small invoices" salience 10 {
when
invoice["total"] < 5000
then
out.Set("approved", true);
}

Execution is forward chaining, not a script. Every cycle the engine re-evaluates the when of every rule that has not been retracted, then fires exactly one rule — the runnable rule with the highest salience. It repeats until nothing is runnable, a rule calls Complete(), the caller’s context is cancelled, or the cycle cap is exceeded. The cap is fixed at 5000 cycles on both surfaces; neither the CLI nor rules.svc exposes a setting for it. Tripping it fails the execution and discards everything the run produced.

Facts go in as a map. Four names are bound by the engine after your facts and are not yours to use:

NameDirectionWhat it is
outwriteKey/value output collector. out.Set(k, v), out.Append(k, s). This is the result.
auditwriteAppend-only trail. audit.Log(ruleID, msg), audit.LogWithData(ruleID, msg, data).
findingswriteSeverity-graded validation list. findings.Add(...), findings.Error/Warn/Info/Critical(...). All strings.
inreadMemory slots and value transforms: in.MemSet, in.Transform, in.NewMapWithValues.

A fact you supply under any of those names is silently replaced. Facts you supply under a helper namespace (util, num, strings, time, math, map, array) silently replace the helper.

The block also carries a spatial document model — OCR ingest, word merging, line and block reconstruction, reading order, tables, fields, checkboxes, fuzzy matching and confidence scoring. Documents are addressed from rules through the bbox namespace, so a rule can ask what is written below this label rather than parsing text.

Both surfaces reach the same capabilities:

Capabilitykis CLIrules.svc
Rule-set load and executionyesyes
Utility namespaces (strings, num, time, math, map, array, util)yesyes
Document model, extraction, OCR ingestyesyes
log namespaceyesyes

A document reaches a rule the same way on both: you name the document alongside the facts, and the engine binds the analysed document into that execution under bbox. On the CLI that is kis rules --bbox <name>=<path>; over HTTP it is a reserved bbox object on the POST /rule body, carrying the same document-name to word-box map.

{
"name": "invoice-checks",
"total": 1200,
"bbox": {
"page": {
"pages": [
{
"number": 1,
"width": 612,
"height": 792,
"words": [
{"text": "Borrower", "box": {"x0": 72, "y0": 100, "x1": 150, "y1": 115}, "confidence": 0.98}
]
}
]
}
}
}

The value under each document name is the .bbox word-box JSON described in OCR input, and the key (page here) is the name a rule passes to bbox.Doc(name) — or omits entirely, since bbox.Doc() takes the only loaded document.

The rule text is identical either way. Rules read documents through bbox.Doc(), the curated facade; every scope, verb and coercion on it behaves the same regardless of how the document arrived. Matching there is fuzzy and case-insensitive by default — a property of the API, not something a rule configures. See Documents in rules for the bindings and Running rules.svc for the rest of the request.

The document and rule commands (kis rules, kis ocr, kis bbox, kis hocr2bbox) are proxy commands: the unified CLI passes every argument to a separately installed plugin binary. When it is not installed you get Error: 'rules' is not installed. and nothing runs. See the CLI.

Setup happens once; steps 4 onward repeat per execution.

  1. Construct the engine on the grule backend — the only backend — with the document namespace and the utility namespaces registered.
  2. Initialise namespaces. Each namespace initialises once; the facts it contributes are bound into every execution from then on.
  3. Load rules. GRL text is parsed and compiled into a rule set at load time. Syntax errors, unsupported constructs and duplicate rule names all surface here, never at execution.
  4. Bind facts into a fresh data context: start-up facts, then your facts, then the per-execution document bindings, then out/findings/audit, then in. Later bindings overwrite earlier ones.
  5. Clone a fresh knowledge base for the rule set, so concurrent executions never share state.
  6. Cycle. Check the context, evaluate every non-retracted when, stop if none are runnable, else fire the single highest-salience rule and repeat.
  7. Return the result — output, audit trail, findings, firing count and wall-clock duration.

Any evaluation error, action error, cycle-limit breach or context cancellation returns an error and no partial result: no output, no audit, no findings.

Loading is not concurrency-safe; execution is. Rule sets load before they are served, and executions then run concurrently without sharing state.

The engine ownsYou author
Parsing and compiling GRL into a rule set; reporting the line and column of every parse errorthe .grl rule text
Conflict resolution — one rule per cycle, highest salience firsta distinct salience per rule
Termination — automatic retract, Complete(), the cycle cap, context cancellationnothing — a rule is retracted after it fires
Binding your facts into the rule context, and the reserved names abovethe facts you send, and accessor syntax that matches how they arrive
Provenance — every out.Set records the firing rule; one audit entry per firingextra audit.Log calls where you want them
Per-execution isolation and a fresh collector set per callnothing
Value normalisation primitives (trim, upper, lower, normalize_spaces, trim_currency, regex extraction)the in.Transform chain
Document analysis — word merging, line formation, reading order, tables, fields, checkboxesthe queries your rules make against it

Two things the engine does not do: it does not validate facts against a schema, and it does not isolate a failing rule from the rest of the set.

ConcernPage
One rule set, executed end to endQuickstart
Rule sets, salience, retraction, chainingWriting rules
GRL syntax, operators, fact accessThe rule language
Everything callable from a then blockFunctions
Working rule shapes to copyPatterns
Boxes, words, blocks, lines, reading orderThe document model
Fields, checkboxes, anchors, regionsExtraction
Table and master-detail detectionTables
hOCR ingest, converters, text cleanupOCR input
Fuzzy matching and confidence scoringMatching
Reaching a document from a ruleDocuments in rules
Running rules and converting documents locallyThe CLI
POST /rule, tenancy, rule deliveryRunning rules.svc
Bootstrap keys, flags, TLSConfiguration
Empty responses, silent boots, stale rulesTroubleshooting
Error codes and messagesErrors

The rule language is GRL, from the open-source Grule rule engine. We run a heavily modified and wrapped build of it. The language itself is left largely intact — a rule you have written against upstream GRL will read as familiar — while the enterprise work sits in the layers around it: the namespaces a rule may call, the spatial document surface, tenancy and rule delivery, per-execution isolation, provenance and the audit trail, and the error and termination behaviour described above. Where this documentation and the upstream README disagree, this documentation describes what actually runs.