Console

Primitive

Noul

A yes-or-no question. The answer is a single number, noul: the probability that the answer is yes. Threshold it wherever the cost of being wrong tells you to.

Signature

type: "noul"

Returns

noul = P(yes), 0 to 1

Use it for

A yes-or-no call.

Was the insured driver at fault?

→ 0.91

Is this ID document genuine?

→ 0.13

Does the ticket ask for a refund?

→ 0.96

When to use noul#

Use noul whenever the question has two answers and you act on one of them: is it fraud, is it urgent, does it break the rules, did the step succeed. You get one number back, and your code decides where yes starts. If there are more than two answers, use choice. If the answer is a matter of degree (how bad, how urgent), use score.

Question object#

type
"noul"required
Selects this primitive.
instructions
stringrequired
The yes-or-no question. Up to 4,000 characters.
criteria
object
Optional. Exactly two keys, true and false, describing what each answer means (up to 2,000 characters each). Without it, Wity reads the question as a plain yes or no.

Example#

A mail gateway checks each incoming message before it reaches an inbox. Most mail is plainly fine. What matters is catching the messages that pretend to come from a service the reader trusts and ask for a password. That is one yes-or-no question per message.

  • state is an object with the message text and the sender address, so Wity can weigh both. The sender is often the giveaway.
  • instructions is the question, phrased so that yes is the case you act on.
  • criteria says what yes and no mean. "Phishing" on its own is fuzzy: spam and pushy marketing are unwanted too, but they are not phishing. The true description narrows yes to attempts to steal credentials, money or access.
{
"state": {
"message": "Your account will be suspended in 24 hours. Verify your password at secure-login-update.co",
"sender": "support@paypa1-security.com"
},
"questions": {
"phishing": {
"type": "noul",
"instructions": "Is this message a phishing attempt?",
"criteria": {
"true": "It tries to trick the reader into giving away credentials, money or access",
"false": "It is a genuine message from the organisation it claims to be from"
}
}
},
"reasoning": "auto"
}

The answer is 0.98: almost certainly phishing. Several signs point the same way. The sender's domain swaps a letter for a digit (paypa1), the link goes to a domain the company does not own, and the message threatens a deadline to rush the reader.

yes98%
no2.0%
"phishing": { "type": "noul", "noul": 0.98 }

Reading the answer#

noul is the probability of yes; the probability of no is 1 − noul. There is no separate label and no confidence field. How far the number sits from 0.5 already tells you how sure Wity is.

  • Close to 1: a clear yes. Close to 0: a clear no.
  • Around 0.5: the state does not settle it. That is a useful answer, and usually a case for a person or for more information.
  • With reasoning: "auto", if Wity thought before answering, the answer also has direct_noul: what it would have said without thinking. Use noul. See Reasoning & auto mode.

Where you put the threshold depends on what a mistake costs. Quarantining a real email is annoying; delivering a phishing email can be expensive. So the gateway acts on its own only when it is very sure, and sends the grey zone to a person:

if p >= 0.95:
quarantine() # sure enough to act
elif p >= 0.50:
flag_for_review() # lean yes, check first
else:
deliver()

Why a probability and not yes/no

A bare yes hides how close the call was. With noul the same question can drive an automatic action at 0.95 and a review queue at 0.6, without asking twice.

A checklist in one call#

Some decisions are too big for one yes-or-no question. A person checking them would work through a list of smaller questions, each easy to answer on its own. Nouls work the same way: ask each check as its own question in one request, then combine the answers in your code, where the rules are yours and easy to audit.

Here, a finance team's accounts-payable inbox receives this email from a regular supplier:

The email

Hi, please note our bank details have changed for all future payments, new IBAN is in the attached letter. Our previous bank is closing its business accounts. Invoice INV-3302 is due this Friday, so please make sure it goes to the new account. Thanks, Maria, Accounts, Nordic Steel

A bank change might be real, or it might be payment-redirection fraud, a common and costly scam that finance teams face, and one that looks like routine admin. Rather than asking only "is this fraud?", the team checks the specific warning signs:

  • changes_payment_details: is this a request to pay a different account at all? If not, none of the rest matters.
  • sender_matches_vendor: does the email come from the supplier's real domain? The state includes the domain on file so Wity can compare. The criteria say lookalike domains count as no.
  • time_pressure: fraud usually comes with a deadline, so there is no time to check.
  • references_open_invoice: a real invoice number makes the email more convincing, and suggests the sender has seen real correspondence. The state lists the open invoices.
  • fraud: the overall judgement, kept alongside the individual checks.

Four of the questions leave out criteria: they are plain yes-or-no questions and need no extra definition.

{
"state": {
"email": "Hi, please note our bank details have changed for all future payments, new IBAN is in the attached letter. Our previous bank is closing its business accounts. Invoice INV-3302 is due this Friday, so please make sure it goes to the new account. Thanks, Maria, Accounts, Nordic Steel",
"from": "maria.accounts@nordicsteel-ab.com",
"vendor_on_file": { "name": "Nordic Steel AB", "domain": "nordicsteel.se" },
"open_invoices": ["INV-3302", "INV-3317"]
},
"questions": {
"changes_payment_details": {
"type": "noul",
"instructions": "Does the email ask us to pay to different bank details than before?"
},
"sender_matches_vendor": {
"type": "noul",
"instructions": "Is the sender's domain the vendor's domain on file?",
"criteria": {
"true": "Exactly the domain on file",
"false": "A different domain, including lookalikes with extra words, hyphens or swapped letters"
}
},
"time_pressure": {
"type": "noul",
"instructions": "Does the email push us to act quickly?"
},
"references_open_invoice": {
"type": "noul",
"instructions": "Does the email mention one of our open invoices by number?"
},
"fraud": {
"type": "noul",
"instructions": "Is this email likely a payment-redirection fraud attempt?"
}
},
"reasoning": "auto"
}

Each check comes back as its own probability:

{
"answers": {
"changes_payment_details": { "type": "noul", "noul": 0.97 },
"sender_matches_vendor": { "type": "noul", "noul": 0.04 },
"time_pressure": { "type": "noul", "noul": 0.64 },
"references_open_invoice": { "type": "noul", "noul": 0.95 },
"fraud": { "type": "noul", "noul": 0.81 }
}
}

What the answers say

  • changes_payment_details at 0.97: this is clearly a bank-change request.
  • sender_matches_vendor at 0.04: nordicsteel-ab.com is not nordicsteel.se. This single check is the strongest warning sign in the email.
  • time_pressure at 0.64: "due this Friday" is some pressure, but a mild one. The number reflects that it is borderline.
  • references_open_invoice at 0.95: INV-3302 is a real open invoice. That makes the email convincing, not safe.
  • fraud at 0.81: Wity's overall judgement leans strongly towards fraud, but on its own it would not explain why. The individual checks do.

Acting on it

The team's rules put the most specific evidence first. A bank change from a domain that is not on file is always held, whatever the overall fraud score says, and the supplier is called on the number the team already has, never one from the email. The overall fraud answer only decides the cases the hard rule does not cover.

a = {k: v["noul"] for k, v in r.json()["answers"].items()}
# Hard rule: a bank change from an unverified domain never goes through on its own.
if a["changes_payment_details"] >= 0.5 and a["sender_matches_vendor"] < 0.5:
hold_payments(vendor="Nordic Steel AB")
call_vendor_on_file_number()
alert_security(reason="bank change from lookalike domain")
elif a["fraud"] >= 0.5:
send_to_review()
elif a["changes_payment_details"] >= 0.5:
start_bank_change_check() # genuine-looking, still verified by phone
else:
file_as_normal()

For this email, the hard rule fires. Payments to Nordic Steel are held, someone calls Maria on the number on file, and security gets an alert with the reason attached. Because each check is its own number, the audit trail shows exactly why the payment was held.

Writing good noul questions#

  • Ask one thing. "Is it urgent and from a VIP?" is two questions: ask both and combine them in code, as in the checklist above.
  • Phrase the question so that yes is the case you act on. It keeps thresholds easy to read.
  • Give the state what the question needs. "Is the sender the real vendor?" only works if the vendor's real domain is in the state.
  • If the true answer is a chance rather than a fact ("will this shipment be late?"), say so and put the numbers in the state: see Forecast questions.

Pinning down borderline cases

Without criteria, Wity reads the question the way most people would. That is fine until your business draws the line somewhere specific. Take a support team that sends refund requests to finance:

"refund": {
"type": "noul",
"instructions": "Is the customer asking for a refund?"
}

A customer who writes "a refund or a replacement, whichever is faster" is asking for both. Does that count? Is a discount on the next order a refund? The plain question leaves it to Wity's best guess.

"refund": {
"type": "noul",
"instructions": "Is the customer asking for a refund?",
"criteria": {
"true": "Asks for money back, in full or in part, even as one of several options they would accept. E.g. 'refund or replacement, whichever is faster'.",
"false": "Asks only for a replacement, a repair, a discount on a future order or information. E.g. 'can you send a new one?'"
}
}

With criteria, the team's rule is spelled out. Any request that includes money back, even as one option, is a yes, and discounts are a no. Each side has an example of a typical message. Now the number answers your definition, not a general one.

Yes or no about an image#

Add an image to the request and every noul in it looks at the image too. Is the seal on this parcel intact? Is there a signature on this form? Did the page finish loading? Use the state to say what the image shows and what matters. See Images.