1. Introduction to SESL
SESL is a Simple Expert System Language that allows you to express decision logic as clear, structured rules. Instead of hiding business logic deep inside application code, SESL encourages you to describe decisions in a human readable format that can be evaluated by a dedicated engine.
A SESL model is a single text document that contains rules and example data. The engine reads this model, combines it with input facts, and produces a result together with rich explanation data. The explanation data makes it clear which rules fired, which rules were skipped, and why each conclusion was reached.
1.1 Main purpose of SESL
The main purpose of SESL is to separate decision logic from application code while keeping that decision logic transparent, traceable, and easy to change. SESL is especially suited for situations where:
- Non-technical users need to understand or review the rules that drive decisions.
- Decisions must be explained to regulators, auditors, customers, or internal reviewers.
- Decision logic changes frequently and should be edited without large code changes.
1.2 Typical use cases
SESL is typically used in the following scenarios:
- Eligibility checks such as loan or insurance eligibility.
- Risk assessment and scoring.
- Discount and pricing rules.
- Compliance checks and policy enforcement.
- Operational decision automation such as routing and prioritisation.
1.3 Benefits compared with alternative approaches
Compared with hard coded decision logic, SESL offers several benefits:
- Readability. Rules are organised in a structured text format that can be read and discussed by both technical and non-technical stakeholders.
- Explainability. The engine records which rules fired, why they fired, and which fields they changed.
- Maintainability. Rules can be added, removed, and reordered without changing the engine itself.
- Testability. Each model can include multiple fact scenarios that act as simple test cases.
SESL is intentionally small and focused. It does not attempt to be a general programming language. Instead, it focuses on clear, declarative rule evaluation.
2. What SESL is used for
SESL is designed for decision problems that can be expressed as a set of rules operating on structured data. The rules read facts, apply conditions, and write conclusions to result fields.
2.1 Types of problems
- Binary decisions such as approve or decline, pass or fail, flag or clear.
- Multi-level classifications such as risk bands and rating categories.
- Numeric calculations that depend on conditions, such as surcharges or discounts.
- Aggregation of signals into a single score or recommendation.
2.2 How SESL fits into a larger system
In a typical application, SESL sits alongside existing services:
- Your application gathers input data and builds a facts structure.
- Your application loads a SESL model from a model file or configuration store.
- The SESL engine evaluates the rules against the facts.
- The engine returns updated facts, including result fields and explanation data.
- Your application uses those results to drive user experience or downstream actions.
3. Installation instructions
3.1 Prerequisites
- A recent Python interpreter (for example version three point ten or later).
- Permission to install Python packages on your machine.
- For local development, a text editor to write model files.
3.2 Installing the SESL engine as a Python package
If SESL is available as a Python package, you can install it using the Python package manager.
Replace sesl-engine with the actual package name used by your distribution.
pip install sesl-engine
3.3 Installing SESL from source
- Clone or download the SESL source repository.
- Ensure that the Python interpreter is available on your path.
- Install dependencies such as the YAML parser library.
git clone https://example.com/sesl.git
cd sesl
pip install -r requirements.txt
3.4 Verifying the installation
You can verify that the engine module is importable from Python:
python -c "import sesl; print('SESL is available')"
If this prints the confirmation message without errors, the engine is installed correctly.
3.5 Upgrading and uninstalling
To upgrade SESL to the latest version:
pip install --upgrade sesl-engine
To remove SESL:
pip uninstall sesl-engine
4. Quick start guide
This quick start shows a complete path from a very small SESL model to a running evaluation and a readable result. The goal is not to cover all features, but to give you a concrete sense of how SESL feels in practice.
4.1 Create a minimal model file
Create a new text file called loan-example.sesl.yaml. Paste the following
content into the file. The format is a structured text mapping with a model name, optional
constants, rules, and fact scenarios.
model: "Loan eligibility example"
meta:
author: "Example author"
created: "2025-01-01"
source: "Internal demo"
const:
minimum_income: 25000
high_income: 60000
rules:
- rule: "Set high income flag"
if: "applicant.income >= const.minimum_income"
then:
result.high_income: true
reason: "Applicant meets the minimum income threshold"
- rule: "Recommend approval for very high income"
priority: 10
if: "applicant.income >= const.high_income"
then:
result.decision: "approve"
result.decision_reason: "Very high income"
reason: "Applicant has very high income"
facts:
- name: "Applicant A"
applicant:
income: 65000
result: {}
This model contains two rules and one fact scenario:
- The first rule sets a flag when the income meets the minimum.
- The second rule decides to approve the loan for very high income.
4.2 Running the model through Python
The provided engine exposes two main functions that you will use frequently:
load_model_from_yaml(text)to load rules and scenarios from a model file.forward_chain(rules, facts, monitor, ...)to run the inference engine.
A simple runner script could look like this:
import pathlib
from sesl_engine import load_model_from_yaml, forward_chain, Monitor
# Load model text
model_path = pathlib.Path("loan-example.sesl.yaml")
text = model_path.read_text(encoding="utf-8")
# Parse into rules and named scenarios
rules, scenarios = load_model_from_yaml(text)
# Take the first scenario
scenario_name, facts = scenarios[0]
# Optional monitor for explainability
monitor = Monitor(theme="colour")
# Run the engine
forward_chain(rules, facts, monitor=monitor)
# Print the result section
print("Scenario:", scenario_name)
print("Result:", facts.get("result"))
4.3 Understanding the output
After running the script, you will see a printed result mapping similar to the
following:
Scenario: Applicant A
Result: {
'high_income': True,
'decision': 'approve',
'decision_reason': 'Very high income',
'monitor': [...],
'monitor_blocks': [...],
'monitor_theme': 'colour'
}
Key points:
high_incomeis set by the first rule.decisionanddecision_reasonare set by the second rule.-
monitorandmonitor_blockscontain a detailed explanation of which rules were evaluated and which ones fired.
5. Structure of a SESL model
A SESL model file is a structured text document that typically contains the following top level sections:
- model: name or identifier of the model.
- meta: optional metadata such as author, creation date, and usage notes.
- const: constants that can be referenced by rules.
- rules: a list of rule definitions.
- facts: example input scenarios for testing and demonstration.
5.1 Example model with annotations
# Model name
model: "Customer loyalty scoring"
# Optional descriptive metadata
meta:
author: "Loyalty team"
created: "2025-02-10"
source: "Marketing rules"
considerations: "Not for credit risk decisions"
# Constants shared by all rules
const:
gold_threshold: 1000
silver_threshold: 500
# List of rule objects
rules:
- rule: "Set gold tier"
priority: 20
if: "customer.points >= const.gold_threshold"
then:
result.tier: "gold"
reason: "Customer points are greater than or equal to gold threshold"
- rule: "Set silver tier"
priority: 10
if: "customer.points >= const.silver_threshold"
then:
result.tier: "silver"
reason: "Customer points are greater than or equal to silver threshold"
# Example fact scenarios
facts:
- name: "Customer with 1200 points"
customer:
points: 1200
result: {}
- name: "Customer with 700 points"
customer:
points: 700
result: {}
This example shows how all parts work together:
- Rules reference constants through the
const.prefix. - Each fact scenario contains an initial
resultmapping. - Higher priority rules run before lower priority rules when necessary.
6. Rules structure
A rule in SESL connects conditions with actions. If the conditions are satisfied, the actions write values into the facts structure.
6.1 Anatomy of a rule
The engine expects each rule entry to be a mapping with fields similar to:
- rule: "Human readable rule name"
priority: 10
if: "customer.age >= 18"
let:
age_band: "customer.age >= 65"
then:
result.is_adult: true
reason: "Customer has reached legal adulthood"
stop: false
Important fields:
- rule: required name of the rule.
- priority: optional integer, higher values indicate higher priority.
- if: condition or nested condition structure that must be satisfied.
- let: optional mapping of helper variables computed before evaluating actions.
- then: mapping of target paths to expressions to write when the rule fires.
- reason: human readable explanation of why the rule exists.
- stop: optional boolean. When true, the engine stops evaluating after this rule fires.
6.2 Rule evaluation order
SESL evaluates rules in a deterministic order that depends on priority and data dependencies:
- Rules are grouped by priority. Higher priority groups are evaluated before lower ones.
- Within each priority group, the engine performs a dependency based ordering. For example, if one rule writes to a section that another rule reads, the writing rule is evaluated first.
- Within a single priority and when there are no dependencies, rules keep the original order from the model file.
The engine runs several iterations until there are no more changes, or until the maximum number of iterations is reached. This forward chaining behaviour allows rules to build on values produced by other rules.
7. Rule statements and full syntax
SESL supports several kinds of statements inside rules. This section describes each of them, together with syntax and examples.
7.1 Condition expressions
The simplest form of a condition is a single expression string in the if field.
The expression has the form:
<left operand> <operator> <right operand>
Supported comparison operators include:
==equal to!=not equal to>greater than>=greater than or equal to<less than<=less than or equal toinmembership in a containernot innot a member of a container
Example:
if: "customer.country == 'United Kingdom'"
7.2 Logical condition blocks
For more complex conditions, the if field can be a structured mapping using
logical keys all, any, and not.
if:
all:
- "customer.age >= 18"
- any:
- "customer.country == 'United Kingdom'"
- "customer.country == 'Ireland'"
Meaning:
- All listed conditions in the top level must be true.
- Within the
anyblock, at least one condition must be true.
7.3 Let statements
The let mapping allows you to define intermediate values. Each key is the name of
a variable and each value is an expression. These expressions support arithmetic and logical
operations on existing facts and on other previously computed variables.
let:
income_band: "applicant.income >= 80000"
total_exposure: "existing_loans + requested_amount"
During evaluation, the engine computes each let entry and stores it into a special
internal structure. You can then reference income_band and
total_exposure inside conditions or actions.
7.4 Actions in the then block
The then block is a mapping from target paths to expressions. When a rule fires,
each target path is written with the evaluated value of its expression.
then:
result.decision: "approve"
result.score: "base_score + bonus"
Important details:
-
Paths are dot separated sequences such as
result.decisionorcustomer.flags.high_risk. -
In strict path mode, intermediate parts such as
resultmust exist unless automatic creation is enabled. -
Expressions on the right-hand side can reference facts, constants, and helper variables from
the
letblock.
7.5 Summary table of rule statement fields
| Field | Purpose | Key elements | Notes |
|---|---|---|---|
rule |
Human readable name of the rule. | Text value. | Required. Used in monitor output and explanations. |
priority |
Controls evaluation order and conflict resolution. | Integer, higher means earlier evaluation. | Optional. Defaults to zero when omitted. |
if |
Specifies conditions for rule firing. | May be a single expression string or a structured logical mapping. | May be omitted. A rule without conditions always matches. |
let |
Defines helper values for use in conditions and actions. | Mapping from variable names to expressions. | Variables cannot reference themselves directly. |
then |
Describes which paths to write when the rule fires. | Mapping from target paths to expressions. | Required for rules that are expected to change facts. |
reason |
Explanation text used in monitoring and justification. | Text value. | Strongly recommended for explainability. |
stop |
Indicates whether engine evaluation should stop after this rule fires. | Boolean value. | Use sparingly when early stopping is clearly required. |
8. Facts structure
Facts represent the input data that rules operate on. They are structured as nested mappings of keys to values. The SESL engine treats the top level of the facts mapping as the main namespace for rule paths.
8.1 Facts inside a model file
Within a model file, the facts section is a list of scenarios. Each scenario has
its own copy of the facts mapping.
facts:
- name: "Young customer"
customer:
age: 22
country: "United Kingdom"
result: {}
- name: "Senior customer"
customer:
age: 70
country: "United Kingdom"
result: {}
Common patterns:
-
Use nested keys to group related information, such as all customer data under
customer. -
Include an initial
resultmapping to hold outputs from rules. The engine will create one if it is not present, but adding it explicitly is clearer.
8.2 Engine metadata in facts
When the engine runs, it stores its own metadata under a reserved top level key named
_sesl. This metadata includes:
- A snapshot of the original facts.
- Support information for truth maintenance.
- Monitoring information and execution metrics.
- Configuration information derived from the engine configuration object.
Rules should not write to fields under _sesl. That namespace is reserved for
engine data.
8.3 How rules consume facts
Rules access facts using dot separated paths, for example:
customer.agecustomer.countryresult.score
The engine resolves these paths against the current facts mapping. In strict path mode, a missing intermediate segment leads to a clear error. This protects you from subtle mistakes such as misspelt keys.
9. How the SESL engine works
The SESL engine is a forward chaining inference engine implemented in Python. It loads rules and facts, validates them, and then fires rules until no more changes occur or a limit is reached.
9.1 High level architecture
-
Model loading. The
load_model_from_yamlfunction parses the structured text model and returns compiled rules together with fact scenarios. -
Preflight validation. The
preflight_validate_modelfunction performs inexpensive checks such as duplicate rule names and obvious path issues. - Dependency ordering. Rules are sorted by priority and by the dependencies between read and written sections.
-
Forward chaining. The
forward_chainfunction repeatedly evaluates rule conditions and applies actions. -
Explainability and metrics. The
Monitorclass records events, actions, and metrics for later inspection.
9.2 Lifecycle of a single run
- The engine takes a list of compiled rules and a facts mapping. It also optionally receives an engine configuration and a monitor instance.
- It makes a snapshot of the starting facts for later explanations and truth maintenance.
-
It attaches configuration and rule metadata under the
_seslkey of the facts. - It enters a loop that runs for at most a fixed number of iterations (for example twenty).
-
In each iteration, the engine evaluates each rule in the sorted order:
- It prepares a
_letmapping and computes each helper expression. - It evaluates the rule conditions using the current facts and helper values.
- If the conditions are satisfied, it applies actions by writing to target paths in the facts mapping.
- It records monitoring events, including rule evaluation and firing.
- It prepares a
- At the end of each iteration, it compares a fingerprint of the user visible facts. If the fingerprint has not changed since the previous iteration, the engine stops because the state is stable.
9.3 Walkthrough example
Consider the following very small model:
model: "Simple flag demo"
rules:
- rule: "Flag high amount"
if: "transaction.amount > 1000"
then:
result.flagged: true
result.flag_reason: "High transaction amount"
reason: "Highlight unusually large transactions"
facts:
- name: "Transaction demo"
transaction:
amount: 1500
result: {}
Processing steps for this example:
- The engine loads one rule and one scenario.
- It starts the first iteration and evaluates the rule.
-
The condition
"transaction.amount > 1000"is true, because the amount is one thousand five hundred. - The engine writes two values into the
resultmapping. - It records that the rule fired and why.
- At the end of the iteration, the fingerprint of the facts differs from the starting fingerprint, so the engine checks another iteration.
- In the next iteration, evaluating the same rule does not change any value.
- The fingerprint now matches the previous one, so the engine stops.
The final result might look like this:
result: {
"flagged": true,
"flag_reason": "High transaction amount",
"monitor": [...],
"monitor_blocks": [...],
"monitor_theme": "colour"
}
10. Output of run commands
This section describes the main parts of the engine output when you call the
forward_chain function. The engine mutates the facts mapping in place and attaches
several helpful structures.
10.1 Result section
The most important part for application logic is the result mapping. This is where
your rules usually write decisions and scores.
result: {
"decision": "approve",
"decision_reason": "Very high income",
"score": 78
}
Each field in the result mapping is entirely defined by your model. Common patterns include:
decision: approve, decline, refer, and similar outcomes.decision_reason: free text explanation of the decision.score: numeric score or rating.flags: sub-mapping with Boolean flags describing conditions.
10.2 Monitor fields
When you pass a monitor instance to the engine, the engine attaches monitoring information
under both the _sesl key and the result mapping for convenience.
result: {
"decision": "approve",
"monitor": [
{"step": 1, "message": "--- Iteration 1 ---"},
{"step": 2, "message": "Evaluating Set high income flag", "details": {...}},
{"step": 3, "message": "Rule Set high income flag sets result.high_income = True"},
{"step": 4, "message": "Rule Set high income flag FIRED", "details": {...}}
],
"monitor_blocks": [
"π’ Rule Set high income flag\n ββ Evaluation:\n β matched: True\n β ...",
"π’ Rule Recommend approval for very high income\n ..."
],
"monitor_theme": "colour"
}
Field meanings:
- monitor: a list of raw monitor events, in chronological order. Each event has a numeric step and a message, and may have details.
- monitor_blocks: human readable multi-line blocks summarising each rule, its evaluation, and actions.
- monitor_theme: a hint string used by user interfaces to pick a presentation style.
10.3 Metrics and configuration
The engine attaches metrics and configuration details under the _sesl key on the
facts mapping. For example:
_sesl:
metrics:
iterations: 2
rules_evaluated: 4
rules_fired: 2
elapsed_ms: 3.712
convergence_fingerprint: "<hash string>"
_config:
strict_operands: true
strict_paths: true
auto_create_paths: false
let_missing: "error"
dependency_sort: true
strict_actions: false
error_style: "fancy"
conflict_policy: "warn"
logger: "NullLogger"
These fields help with tuning and diagnostics:
- iterations: number of forward chaining cycles completed.
- rules_evaluated: how many rule evaluations were performed.
- rules_fired: how many times rules actually changed facts.
- elapsed_ms: total execution time in milliseconds.
- convergence_fingerprint: hash of user visible facts used to detect stable state.
10.4 Truth maintenance support
SESL collects support information for paths that rules write. This support information is stored
under _sesl.support and _sesl.support_detail and can be used to
explain why a fact has a particular value.
_sesl:
support:
"result.decision": ["Approve on high income"]
support_detail:
"result.decision":
"Approve on high income":
reason: "Applicant has very high income"
priority: 10
The helper function explain_fact(facts, "result.decision") returns a structured
justification chain for a given path.
11. Examples: SESL in business contexts
This section presents five business examples that show SESL in realistic situations. Each example consists of a scenario description, a model fragment, sample input data, and a short explanation of the engine output.
11.1 Insurance risk assessment
Scenario. An insurance company wants to flag high risk motor policies.
model: "Motor risk flags"
rules:
- rule: "Flag young driver"
if: "policy.driver_age < 25"
then:
result.risk_flags.young_driver: true
reason: "Driver is younger than twenty five years"
- rule: "Flag powerful car"
if: "policy.engine_power_kw > 150"
then:
result.risk_flags.powerful_car: true
reason: "Vehicle has high engine power"
- rule: "Set overall risk band"
let:
has_any_flag: "result.risk_flags.young_driver or result.risk_flags.powerful_car"
if: "has_any_flag"
then:
result.risk_band: "high"
reason: "At least one high risk flag is present"
facts:
- name: "Example policy"
policy:
driver_age: 22
engine_power_kw: 160
result: {}
Execution.
rules, scenarios = load_model_from_yaml(open("motor.sesl.yaml").read())
name, facts = scenarios[0]
monitor = Monitor()
forward_chain(rules, facts, monitor=monitor)
print(facts["result"])
Interpretation.
- The first rule sets
result.risk_flags.young_driverto true. - The second rule sets
result.risk_flags.powerful_carto true. -
The third rule sees that at least one flag is set and assigns the overall
result.risk_bandto"high". - The explanation data shows clearly which flags led to the high risk band.
11.2 Banking credit scoring
Scenario. A bank wants to compute a simple credit score.
model: "Credit score demo"
const:
income_threshold: 30000
low_debt_ratio: 0.3
rules:
- rule: "Base score"
then:
result.score: 50
reason: "Starting score"
- rule: "Increase score for high income"
if: "applicant.income >= const.income_threshold"
then:
result.score: "result.score + 20"
reason: "Stable income"
- rule: "Decrease score for high debt ratio"
let:
debt_ratio: "applicant.total_debt / max(applicant.income, 1)"
if: "debt_ratio > 0.5"
then:
result.score: "result.score - 30"
reason: "High debt compared with income"
facts:
- name: "Applicant example"
applicant:
income: 40000
total_debt: 15000
result:
score: 0
Execution and outcome.
- The base score rule sets the score to fifty.
- The high income rule adds twenty, bringing the score to seventy.
- The debt ratio is fifteen thousand divided by forty thousand, which is zero point three seven five.
- The debt ratio rule does not fire because the ratio is not greater than zero point five.
- The final score is seventy.
11.3 Retail pricing and discounts
Scenario. A retailer wants to apply volume discounts.
model: "Volume discount"
rules:
- rule: "Ten percent discount for ten or more items"
if: "basket.quantity >= 10"
then:
result.discount_rate: 0.10
reason: "Volume discount for ten or more items"
- rule: "Fifteen percent discount for twenty or more items"
priority: 20
if: "basket.quantity >= 20"
then:
result.discount_rate: 0.15
reason: "Higher volume discount for twenty or more items"
- rule: "Compute total price"
let:
discounted_price_per_unit: "basket.unit_price * (1 - result.discount_rate)"
then:
result.total_price: "basket.quantity * discounted_price_per_unit"
reason: "Apply discount rate to quantity and unit price"
facts:
- name: "Basket example"
basket:
quantity: 22
unit_price: 5.00
result:
discount_rate: 0.0
Interpretation.
-
Both discount rules match, but the higher priority rule that sets fifteen percent discount
takes ownership of
result.discount_rate. - The compute total price rule uses the final discount rate and writes the total price. With a quantity of twenty two and a unit price of five, the discounted price per unit is four point two five, giving a total price of ninety three point five.
11.4 Supply chain routing
Scenario. A logistics team wants to pick a warehouse to ship from.
model: "Warehouse selection"
rules:
- rule: "Prefer local warehouse"
if:
all:
- "order.destination_country == 'United Kingdom'"
- "stock.local_available >= order.quantity"
then:
result.source_warehouse: "local"
reason: "Destination is local and local stock is sufficient"
- rule: "Fallback to regional warehouse"
if: "result.source_warehouse is None and stock.regional_available >= order.quantity"
then:
result.source_warehouse: "regional"
reason: "Local warehouse cannot fulfil the order"
facts:
- name: "Order example"
order:
destination_country: "United Kingdom"
quantity: 50
stock:
local_available: 40
regional_available: 100
result:
source_warehouse: null
Outcome.
- The local warehouse rule does not fire because local stock is only forty.
-
The regional warehouse rule fires and sets
result.source_warehouseto"regional". - Explainability data records that the fallback rule provided the value, together with its reason text.
11.5 Compliance checking
Scenario. A compliance team wants to check whether a transaction breaches simple policy rules.
model: "Policy compliance"
const:
maximum_single_transaction: 10000
restricted_country_list: ["Country X", "Country Y"]
rules:
- rule: "Flag large transaction"
if: "transaction.amount > const.maximum_single_transaction"
then:
result.policy_flags.large_transaction: true
reason: "Amount is greater than policy limit"
- rule: "Flag restricted destination country"
if: "transaction.country in const.restricted_country_list"
then:
result.policy_flags.restricted_country: true
reason: "Destination country is on the restricted list"
- rule: "Set compliance outcome"
let:
any_flag: "result.policy_flags.large_transaction or result.policy_flags.restricted_country"
if: "any_flag"
then:
result.compliance_outcome: "review_required"
reason: "At least one policy flag is present"
facts:
- name: "Payment example"
transaction:
amount: 12000
country: "Country X"
result:
policy_flags: {}
Outcome.
- The large transaction rule fires because the amount is greater than ten thousand.
- The restricted country rule also fires because the destination country is in the list.
-
The final outcome is
result.compliance_outcomeset to"review_required". - The monitor blocks show exactly which conditions and values led to this outcome, which helps when reviewing cases.
12. Best practices and common pitfalls
12.1 Organising models, rules, and facts
- Use one SESL model per coherent decision area, such as one model for eligibility and another for pricing.
- Group related rules near each other and use descriptive rule names that start with a verb, such as βSet tierβ or βFlag high riskβ.
- Keep example fact scenarios small and focused. Each scenario should illustrate one main path through the rules.
12.2 Naming conventions
-
Use lower case and underscores for keys, such as
total_debtrather thanTotalDebt. -
Use consistent namespaces such as
applicant,customer,transaction, andresult. -
Use the
resultnamespace only for outputs from rules, not for inputs. This keeps it clear which fields are derived.
12.3 Performance considerations
-
Avoid unnecessary complexity in conditions. If a condition becomes very long, consider
computing parts of it in the
letblock. - Use priorities to avoid repeatedly overwriting the same path from many rules.
- Monitor iteration counts and rule fire counts. Unexpectedly high values may indicate cycles or rules that constantly flip values back and forth.
12.4 Common pitfalls
-
Missing parent paths. When strict path mode is enabled and automatic path
creation is disabled, attempting to write to
result.scorewithout having aresultmapping leads to a helpful error. Seedresult: {}in facts. -
Unquoted string literals. In strict operand mode, text values must be quoted.
Writing
then: result.status: approvedwithout quotes will cause a resolution error. - Self-referential let expressions. A helper variable cannot refer to itself. This is caught at compile time to prevent confusing behaviour.
- Conflicting writes. Multiple rules that write to the same path at the same priority may conflict. The engine can warn or raise an error depending on configuration.
13. Conclusion and next steps
SESL provides a focused, transparent way to express decision logic as rules over structured data. You have seen how models are structured, how rules and facts interact, how the engine evaluates conditions and actions, and how to interpret the output and explanation data.
Possible next steps include:
- Creating your own small model for a decision in your organisation.
- Adding more example scenarios to test edge cases and ensure that rules behave as expected.
- Integrating SESL into an application by building a wrapper that prepares facts and consumes the result and explanation structures.
- Exploring the monitor and truth maintenance data to build user interfaces that explain decisions.
With careful model design and the engine features described in this guide, SESL can act as a reliable and understandable decision layer in many different domains.