Open-World vs Closed-World Reasoning
When the inference engine cannot prove something, what does that mean? The answer depends on whether you’re using closed-world or open-world reasoning — and choosing the right one is critical for getting correct results.
The core distinction
Closed-world assumption (default)
“If I can’t prove it, it’s false.”
Under closed-world reasoning, the knowledge base is assumed to be complete. If a fact isn’t present, it’s treated as definitively not true. This is how SQL databases and most programming languages work — if a row doesn’t exist, the query returns no results.
// Closed-world (default)const result = await client.inference.backwardChain({ goal: TermInput.byName('has_allergy', { patient: FeatureInput.string('Alice'), allergen: FeatureInput.variable('?Allergen'), }),});// If no allergy facts exist for Alice → 0 solutions// Interpretation: "Alice has no allergies"Open-world assumption
“If I can’t prove it, it’s unknown.”
Under open-world reasoning, the knowledge base is assumed to be incomplete. Missing information doesn’t mean “false” — it means “we don’t know yet.” The engine tracks what information is missing and reports it alongside whatever it can prove.
// Open-worldconst result = await client.inference.backwardChain({ goal: TermInput.byName('has_allergy', { patient: FeatureInput.string('Alice'), allergen: FeatureInput.variable('?Allergen'), }), open_world: true,});// If no allergy facts exist for Alice → may still return solutions// with evidence_ratio < 1.0 and residuated_sorts listing what's missing// Interpretation: "We can't confirm or deny allergies — data is incomplete"When to use which
| Scenario | Use | Reason |
|---|---|---|
| Database-like queries | Closed | Your data is complete — what’s stored is what exists |
| Medical diagnosis | Open | Absence of a symptom doesn’t mean the symptom doesn’t exist — it might not have been checked |
| Compliance checking | Closed | If a required document isn’t present, the check fails |
| Scientific research | Open | Incomplete data is the norm — you want to know what’s proven vs. unknown |
| Inventory lookup | Closed | If an item isn’t in the system, it’s not in stock |
| Incident investigation | Open | Root cause analysis shouldn’t dismiss possibilities just because some data is missing |
How open-world reasoning works in practice
Evidence ratio
Open-world solutions include an evidence ratio — the fraction of a rule’s antecedents that were actually matched. This tells you how much evidence supports the conclusion.
const result = await client.inference.backwardChain({ goal: TermInput.byName('diagnosis', { condition: FeatureInput.variable('?Condition'), }), open_world: true,});
for (const solution of result.solutions) { console.log(`Condition: ${solution.substitution.bindings[0]?.bound_to_display}`); console.log(`Evidence ratio: ${solution.evidence_ratio}`); // 1.0 = all antecedents matched (fully proven) // 0.75 = 3 of 4 antecedents matched (partially proven) // 0.5 = half the evidence is missing}Residuated sorts
When the engine encounters a sort it can’t resolve (no matching facts), it residuates — suspends that part of the proof and reports which sorts couldn’t be resolved.
for (const solution of result.solutions) { if (solution.residuated_sorts?.length) { console.log('Missing information about:', solution.residuated_sorts); // e.g., ["blood_test", "imaging_result"] // These sorts had no matching facts — the proof is incomplete }}This is directly connected to the concept of residuation in Psi-terms — the system’s ability to say “I cannot determine this yet” rather than failing outright.
Connection to residuation
Residuation is the mechanism that makes open-world reasoning possible. When the engine encounters missing information:
- In closed-world: it treats the missing info as “false” and the proof fails at that branch
- In open-world: it residuates — suspends that branch, records what’s missing, and continues with whatever can be proved
This means open-world solutions are partial proofs. They tell you:
- What was proved (the bindings and certainty)
- What couldn’t be proved (the residuated sorts)
- How strong the evidence is (the evidence ratio)
Practical example
Consider a rule: “A patient is at risk if they have high blood pressure AND a family history of heart disease AND are over 50.”
// Rule: at_risk :- high_bp AND family_history AND age > 50await client.inference.addRule({ term: TermInput.byName('at_risk', { patient: FeatureInput.variable('?Patient'), }), antecedents: [ TermInput.byName('blood_pressure', { patient: FeatureInput.variable('?Patient'), level: FeatureInput.string('high'), }), TermInput.byName('family_history', { patient: FeatureInput.variable('?Patient'), condition: FeatureInput.string('heart_disease'), }), TermInput.byName('patient_info', { patient: FeatureInput.variable('?Patient'), age: FeatureInput.constrainedVar('?Age', guard('gt', 50)), }), ],});
// We only know Alice has high blood pressure and is 65await client.inference.addFact({ term: TermInput.byName('blood_pressure', { patient: FeatureInput.string('Alice'), level: FeatureInput.string('high'), }),});await client.inference.addFact({ term: TermInput.byName('patient_info', { patient: FeatureInput.string('Alice'), age: FeatureInput.integer(65), }),});// Note: no family_history fact for AliceClosed-world result: 0 solutions. Alice is not at risk (because family history isn’t recorded, which is treated as “no family history”).
Open-world result: 1 solution with evidence_ratio: 0.67 and residuated_sorts: ["family_history"]. Alice might be at risk — 2 of 3 conditions are met, but family history data is missing.
The open-world result is much more useful for a clinical application — it flags the incomplete data rather than silently concluding “no risk.”