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.
The model
Section titled “The model”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); Retract("ApproveSmallInvoice");}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:
| Name | Direction | What it is |
|---|---|---|
out | write | Key/value output collector. out.Set(k, v), out.Append(k, s). This is the result. |
audit | write | Append-only trail. audit.Log(ruleID, msg), audit.LogWithData(ruleID, msg, data). |
findings | write | Severity-graded validation list. Not constructible from a rule — see below. |
in | read | Memory 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.
Spatial documents
Section titled “Spatial documents”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:
| Capability | kis CLI | rules.svc |
|---|---|---|
| Rule-set load and execution | yes | yes |
Utility namespaces (strings, num, time, math, map, array, util) | yes | yes |
| Document model, extraction, OCR ingest | yes | yes |
log namespace | no | no |
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.Get.
The rule text is identical either way — bbox.Get("page") returns the document, and every field,
checkbox, table and matching call on it behaves the same. 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.
Execution lifecycle
Section titled “Execution lifecycle”Setup happens once; steps 4 onward repeat per execution.
- Construct the engine on the
grulebackend — the only backend — with the document namespace and the utility namespaces registered. - Initialise namespaces. Each namespace initialises once; the facts it contributes are bound into every execution from then on.
- 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.
- Bind facts into a fresh data context: start-up facts, then your facts, then the per-execution
document bindings, then
out/findings/audit, thenin. Later bindings overwrite earlier ones. - Clone a fresh knowledge base for the rule set, so concurrent executions never share state.
- Cycle. Check the context, evaluate every non-retracted
when, stop if none are runnable, else fire the single highest-salience rule and repeat. - 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.
What the engine owns, and what you author
Section titled “What the engine owns, and what you author”| The engine owns | You author |
|---|---|
| Parsing and compiling GRL into a rule set; reporting the line and column of every parse error | the .grl rule text |
| Conflict resolution — one rule per cycle, highest salience first | a distinct salience per rule |
Termination — Retract(), Complete(), the cycle cap, context cancellation | a Retract() in every then that does not falsify its own when |
| Fact binding by reflection, and the reserved names above | the fact map, and accessor syntax that matches the fact’s Go type |
Provenance — every out.Set records the firing rule; one audit entry per firing | extra audit.Log calls where you want them |
| Per-execution isolation and a fresh collector set per call | nothing |
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, checkboxes | the 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.
Continue with
Section titled “Continue with”| Concern | Page |
|---|---|
| One rule set, executed end to end | Quickstart |
| Rule sets, salience, retraction, chaining | Writing rules |
| GRL syntax, operators, fact access | The rule language |
Everything callable from a then block | Functions |
| Working rule shapes to copy | Patterns |
| Boxes, words, blocks, lines, reading order | The document model |
| Fields, checkboxes, anchors, regions | Extraction |
| Table and master-detail detection | Tables |
| hOCR ingest, converters, text cleanup | OCR input |
| Fuzzy matching and confidence scoring | Matching |
| Reaching a document from a rule | Documents in rules |
| Running rules and converting documents locally | The CLI |
POST /rule, tenancy, rule delivery | Running rules.svc |
| Bootstrap keys, flags, TLS | Configuration |
| Empty responses, silent boots, stale rules | Troubleshooting |
| Error codes and messages | Errors |