Skip to content
Talk to our solutions team

Writing output

out is the result. What it holds when the run ends is exactly what the caller receives, so everything a rule wants to say ends up here.

rule Approve salience 10 {
when true
then out.Set("approved", true);
out.Set("customer.address.city", "Chennai");
}
facts
{}
out
{ "approved": true, "customer": { "address": { "city": "Chennai" } } }

A key is a path. Dots nest, and intermediate levels are created on write, so the second line produces a nested object rather than a key with dots in it.

SituationBehaviour
A literal dot inside a keyEscape it with a backslash: "release\\.notes" writes the key release.notes
A trailing dotLiteral, no escaping needed
Removing a keyout.Delete(path)

A later rule can read what an earlier one wrote, with the same typed-reader shape as in: a default, and no failure mode.

CallReturns
out.Has(path)Whether anything was written there
out.IsEmpty(path)Written but empty
out.Str(path, default)String
out.Int(path, default) / out.Double(path, default)Numbers. A float default needs a decimal point: 0.0
out.Bool(path, default)Boolean
out.Date(path)A date
out.Map(path)A nested object
out.Value(path)The typed value; out.Value(path).Rows() reads back a list of records

out.GetStr, GetInt, GetFloat, GetBool and GetMap still work, with the zero value as the default, but are deprecated and load with a warning: write out.Str, out.Int, out.Double, out.Bool and out.Map. out.Get(path) returns the raw value; comparing it for a key nobody wrote aborts the execution, so compare through a typed reader instead.

rule Standard "every claim starts at standard" salience 100 {
when
in.Double("amount", 0.0) > 0.0
then
out.Set("tier", "standard");
out.Set("amount", in.Double("amount", 0.0));
}
rule Escalate "high value goes to a manager" salience 90 {
when
out.Str("tier", "") == "standard" && out.Double("amount", 0.0) > 10000.0
then
out.Set("tier", "manager");
}
facts
{ "amount": 12500.0 }
out
{ "tier": "manager", "amount": 12500 }

A rule’s when that reads out sees what earlier rules wrote, so Escalate fires on the cycle after Standard. See Rule patterns.

These rules decide the shape of what lands in the output, and the difference is visible to whoever consumes the result.

You writeOutput holds
A mapAn object. Not a one-row table
A list of recordsA table: a list of objects
An empty list of recordsAn empty list. A real result, kept
Nothing: a missing in.Rows, in.Map, or a document read that missedNothing. The key is absent, not null

The first row matters more than it looks. A map is the dominant shape in real rule sets, and coercing one to a single-row table would turn every object in the output into an array, a silent change to the shape every consumer already parses.

The last row is the useful one to write against: a miss produces an absent key rather than a JSON null, so a caller can tell “this rule did not find it” from “this rule found nothing”, which null cannot express.

rule Shapes salience 10 {
when true
then out.Set("borrower", map.Of("name", in.Str("name", ""), "state", in.Str("state", "")));
out.Set("lines", in.Rows("lines"));
out.Set("credits", in.Rows("credits"));
out.Set("fees", in.Rows("fees"));
}
facts
{ "name": "Jane Doe", "state": "MA", "lines": [ { "sku": "A-1" } ], "credits": [] }
out
{ "borrower": { "name": "Jane Doe", "state": "MA" }, "lines": [ { "sku": "A-1" } ], "credits": [] }

A value can carry metadata beside it. Most usefully how confident the producer was.

CallPurpose
out.Attr(path, name, value)Attach one attribute to a value already written
out.AttrNum(path, name, default)Read a numeric attribute
out.AttrStr(path, name, default)Read a string attribute
out.HasAttr(path, name)Whether the attribute exists
out.Attributes(path)Every attribute on that value

Attributes ride beside the value; they are not part of the output object. This is what lets a rule gate on extraction quality rather than on the extracted text:

rule ExtractAmount salience 90 {
when true
then out.Set("CDLoanAmount", "412000.00");
out.Attr("CDLoanAmount", "confidence", 0.62);
}
rule LowConfidenceAmount "flag a loan amount we are unsure about" salience 80 {
when
out.AttrNum("CDLoanAmount", "confidence", 1.0) < 0.7
then
findings.Warn("LowConfidenceAmount", "extraction", "loan amount extracted with low confidence");
out.Set("needs_review", true);
}
facts
{}
out
{ "CDLoanAmount": "412000.00", "needs_review": true }

The default of 1.0 is deliberate: a value with no confidence attribute is treated as certain, so the rule only fires on a value that actually reported doubt.

Every write records the rule that made it. Nothing is required of you.

CallGives
out.History()Every change, in order, each attributed to its rule
out.HistoryFor(key)The changes to one key
out.All()The whole result map
out.AllAttributes()Every attribute on every value

That history is what kis rules --history prints and what rules.svc returns under history when a call sets options.history. It is how you answer “which rule set this” without adding logging.

out.JSON() returns a value and an error, which the engine cannot call from a rule. It loads, then aborts the execution when reached with multiple value returns are not supported. Read the output from the run result instead.