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.

out.Set("approved", true);
out.Set("customer.address.city", "Chennai");

A key is a path. Dots nest, and intermediate levels are created on write, so the second line produces {"customer": {"address": {"city": "Chennai"}}} rather than a key with dots in it.

SituationBehaviour
A literal dot in a keyEscape it: "Work Phone Ext\\."
A trailing dotLiteral, no escaping needed
Appending to a stringout.Append(key, text)
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
out.Bool(path, default)Boolean
out.Date(path)A date
out.Map(path)A nested object
out.Value(path)The typed value
out.GetStr / GetInt / GetFloat / GetBool / GetMapThe same, with the zero value as the default
rule Escalate "high value goes to a manager" salience 90 {
when
out.GetStr("tier") == "standard" && out.Double("amount", 0.0) > 10000.0
then
out.Set("tier", "manager");
}

Reading a collector inside a when is memoised, and out.Set invalidates the memo itself — so a rule that reacts to another rule’s write does not need Changed.

Three 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
An empty listA table with no rows — a real result, kept
A nil list or mapNothing. 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.

out.Set("borrower", in.GetMemAsMap()); // object
out.Set("lines", bbox.Doc().Table().Records()); // table

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

This is what lets a rule gate on extraction quality rather than on the extracted text:

rule LowConfidenceAmount "flag a loan amount we are unsure about" salience 80 {
when
out.AttrNum("CDLoanAmount", "confidence", 1.0) < 0.7
then
findings.Warn("check_amount", "extraction",
"loan amount extracted with low confidence");
}

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 --history prints on the CLI and what the audit trail is built from — it is how you answer “which rule set this” without adding logging.

out.JSON() returns a value and an error, which the grammar rejects. Read the output from the run result instead.