Skip to content

Tutorial 6: Extending the Ontology

This tutorial teaches you how to extend Mistaber's ontology by adding new foods, new rules, and even new halachic domains.

Learning Objectives

  • Add new foods to the knowledge base
  • Define food types and relationships
  • Create new halachic rules
  • Add a new commentator world
  • Test your extensions

Prerequisites

Estimated Time

25 minutes

Steps

Step 1: Understanding the Ontology Structure

The ontology is organized as:

mistaber/ontology/
├── schema/           # Type definitions and constraints
│   ├── sorts.lp      # Basic sorts (food, vessel, world, etc.)
│   ├── disjointness.lp
│   └── constraints.lp
├── base/             # Foundational concepts
│   ├── substance.lp  # Physical properties
│   ├── status.lp     # Halachic statuses
│   └── madrega.lp    # Normative levels
├── worlds/           # Authority perspectives
│   ├── base.lp
│   ├── mechaber.lp
│   └── rema.lp
├── corpus/           # Encoded halacha by topic
│   └── yd_87/        # Yoreh Deah 87 (Basar Bechalav)
│       └── base.lp
└── interpretations/  # Commentator modifications
    ├── shach.lp
    └── taz.lp

Step 2: Add a New Food Item

To add a new food, create facts using the existing predicates.

Create a file my_foods.lp:

% my_foods.lp
% Adding new food items to the ontology

% Declare foods
food(quinoa).
food(tempeh).
food(tofu).
food(almond_milk).

% Classify by type
food_type(quinoa, grain).      % Assuming grain type exists
food_type(tempeh, parve).      % Parve (neutral)
food_type(tofu, parve).
food_type(almond_milk, parve). % Plant-based, not real chalav

% Note: almond_milk is NOT classified as chalav because it's plant-based
% This reflects the halacha that plant-based "milks" are parve

Step 3: Add a New Food Category

If you need a new category, add it to the schema:

% In schema/sorts.lp or a custom file

% New food category
food_category(kitniyot).  % Legumes (relevant for Pesach)

% Add to hierarchy
subcategory(kitniyot, maakhal).

% Specific items
food_type(rice, kitniyot).
food_type(corn, kitniyot).
food_type(beans, kitniyot).

Step 4: Create Rules for New Foods

Now create rules that apply to your new foods:

% kitniyot_rules.lp
% Rules about kitniyot for Pesach

% === Ashkenazi Position ===
% Kitniyot are forbidden on Pesach for Ashkenazim

rule(r_kitniyot_pesach_ashk).
makor(r_kitniyot_pesach_ashk, rema("oc:453:1")).
madrega(r_kitniyot_pesach_ashk, minhag).
scope(r_kitniyot_pesach_ashk, rema).

asserts(rema, issur(achiila, F, minhag)) :-
    food(F),
    food_type(F, kitniyot),
    context(pesach).

% === Sefardi Position ===
% Kitniyot are permitted for Sefardim

rule(r_kitniyot_pesach_seph).
makor(r_kitniyot_pesach_seph, sa("oc:453")).
scope(r_kitniyot_pesach_seph, mechaber).

asserts(mechaber, heter(achiila, F)) :-
    food(F),
    food_type(F, kitniyot),
    context(pesach).

Step 5: Add a New World (Commentator)

Let's add the Pri Megadim as a new authority:

% worlds/pri_megadim.lp
% Pri Megadim - R' Yosef Teomim (1727-1792)
% Commentary on Shulchan Aruch (Mishbetzot Zahav on Taz, Eshel Avraham on Magen Avraham)

% === World Declaration ===
world(pri_megadim).

% Pri Megadim comments on both Taz and Shach
% For our purposes, inherit from rema (Ashkenazi baseline)
accessible(pri_megadim, rema).

% === Include Corpus ===
#include "../corpus/yd_87/base.lp".

% === Pri Megadim's Specific Rulings ===

% Example: PM on fish + dairy
% The PM generally agrees with the lenient Ashkenazi position
rule(r_pm_dag_chalav).
makor(r_pm_dag_chalav, pri_megadim("yd:87")).
scope(r_pm_dag_chalav, pri_megadim).

asserts(pri_megadim, heter(achiila, M)) :-
    is_dag_chalav_mixture(M).

% === Safek Policy ===
safek_policy(pri_megadim, d_oraita, l_chumra).
safek_policy(pri_megadim, d_rabanan, l_kula).

% === Minhag Region ===
minhag_region(pri_megadim, ashkenaz).

% === Show Directives ===
#show world/1.
#show accessible/2.
#show asserts/2.

Step 6: Add New Interpretations

Create an interpretation layer for a commentator:

% interpretations/pri_chadash.lp
% Pri Chadash - R' Chizkiya da Silva (1659-1698)
% Known for lenient interpretations in some areas

% Pri Chadash's interpretation of bitul rules
% PC allows bitul even for davar sheyesh lo matirin in some cases

expands_scope(pri_chadash, r_bitul_shishim, allows_davar_sheyesh_lo_matirin).
makor(interpretation(pri_chadash, r_bitul_shishim), pri_chadash("yd:102")).

% This means: when querying with Pri Chadash's interpretation,
% the davar sheyesh lo matirin exception to bitul may not apply

Step 7: Test Your Extensions

Create a test file:

# test_my_extensions.py
"""Tests for my ontology extensions."""

import pytest
from pathlib import Path

try:
    import clingo
    CLINGO_AVAILABLE = True
except ImportError:
    CLINGO_AVAILABLE = False

PROJECT_ROOT = Path(__file__).parent.parent
ONTOLOGY_DIR = PROJECT_ROOT / "core" / "ontology"


def run_with_extensions(extra_files: list[str], scenario: str) -> list[str]:
    """Run clingo with base ontology plus custom extensions."""
    ctl = clingo.Control(["--warn=none"])

    # Load base ontology
    for subdir in ["schema", "base", "worlds"]:
        dir_path = ONTOLOGY_DIR / subdir
        if dir_path.exists():
            for lp_file in dir_path.glob("*.lp"):
                ctl.load(str(lp_file))

    # Load extensions
    for ext_file in extra_files:
        ctl.load(ext_file)

    # Add scenario
    ctl.add("test", [], scenario)

    # Ground and solve
    ctl.ground([("base", []), ("test", [])])

    atoms = []
    with ctl.solve(yield_=True) as handle:
        for model in handle:
            atoms = [str(a) for a in model.symbols(shown=True)]
            break

    return atoms


@pytest.mark.skipif(not CLINGO_AVAILABLE, reason="clingo not installed")
def test_new_food_classification():
    """Test that new foods are properly classified."""
    scenario = """
    food(quinoa).
    food_type(quinoa, parve).
    """

    atoms = run_with_extensions([], scenario)

    # Verify the food is recognized
    assert "food(quinoa)" in atoms


@pytest.mark.skipif(not CLINGO_AVAILABLE, reason="clingo not installed")
def test_new_world_exists():
    """Test that our new Pri Megadim world is recognized."""
    scenario = """
    world(pri_megadim).
    accessible(pri_megadim, rema).
    """

    atoms = run_with_extensions([], scenario)

    assert "world(pri_megadim)" in atoms
    assert "accessible(pri_megadim,rema)" in atoms

Step 8: Integrate with Existing Ontology

To make your extensions permanent, decide where they belong:

Extension Type Location
New food categories schema/sorts.lp
New food items base/substance.lp or new file
New halachic rules corpus/[topic]/
New world worlds/
New interpretations interpretations/

Step 9: Add a Complete New Siman

Let's outline adding Yoreh Deah 89 (waiting between meat and dairy):

% corpus/yd_89/base.lp
% Yoreh Deah Siman 89: Waiting Between Meat and Dairy

% === Waiting Time Rules ===

% Base requirement: must wait after eating meat before dairy
rule(r_yd89_waiting_base).
makor(r_yd89_waiting_base, sa("yd:89:1")).
madrega(r_yd89_waiting_base, d_rabanan).

requires_waiting(meat_to_dairy).

% Time periods (in hours)
waiting_period_type(six_hours).
waiting_period_type(three_hours).
waiting_period_type(one_hour).

% === World-Specific Waiting Times ===

% Mechaber: 6 hours
% "tzarich l'hamtin shesh shaot" (one must wait six hours)
rule(r_yd89_mechaber_six).
makor(r_yd89_mechaber_six, sa("yd:89:1")).
scope(r_yd89_mechaber_six, mechaber).

asserts(mechaber, waiting_time(meat_to_dairy, six_hours)).

% Rema: 6 hours for standard Ashkenazi practice
% But notes that some have custom of 3 hours (German) or 1 hour (Dutch)
rule(r_yd89_rema_six).
makor(r_yd89_rema_six, rema("yd:89:1")).
scope(r_yd89_rema_six, rema).

asserts(rema, waiting_time(meat_to_dairy, six_hours)).

Step 10: Document Your Extensions

Create documentation for your additions:

# YD 89: Waiting Between Meat and Dairy

## Overview
This module implements the laws of waiting between eating meat and dairy.

## Sources
- SA YD 89:1 - Base requirement
- Rema YD 89:1 - Ashkenazi variations

## Predicates Added
- `requires_waiting/1` - Marks a transition requiring wait
- `waiting_time/2` - (direction, period) pairs
- `waiting_period_type/1` - Valid waiting periods

## Worlds Affected
- mechaber: 6 hours
- rema: 6 hours (with noted variations)

Best Practices for Extensions

1. Follow Naming Conventions

% Rules: r_[topic]_[detail]
rule(r_yd89_waiting_base).

% Sources: authority("reference")
makor(r_rule, sa("yd:89:1")).
makor(r_rule, rema("yd:89:1")).

2. Always Include Sources

Every normative rule needs makor citations:

rule(r_my_rule).
makor(r_my_rule, source("reference")).  % Required!
madrega(r_my_rule, d_rabanan).
scope(r_my_rule, world_name).

3. Respect the Hierarchy

New worlds should properly inherit:

world(new_world).
accessible(new_world, parent_world).  % Inherit from appropriate parent

4. Test Incrementally

Test each addition before moving on:

# Test new foods
assert "food(my_food)" in atoms

# Test new rules fire
assert "issur(...)" in atoms or "heter(...)" in atoms

# Test world inheritance
assert "accessible(new_world, parent)" in atoms

Exercises

Exercise 1: Add a New Food

Add "soy_milk" as a parve food and verify it's not treated as dairy.

Solution
food(soy_milk).
food_type(soy_milk, parve).  % NOT chalav

% Test: mixture with soy_milk and meat should not trigger basar bechalav
mixture(meat_soy).
contains(meat_soy, beef).
contains(meat_soy, soy_milk).
food_type(beef, beheima).

% Result: no is_beheima_chalav_mixture(meat_soy) because soy_milk is not chalav

Exercise 2: Add Kitniyot Rules

Implement the full kitniyot rules with Ashkenazi/Sefardi distinction.

Solution
% New category
food_category(kitniyot).
food_type(rice, kitniyot).
food_type(beans, kitniyot).

% Ashkenazi: forbidden on Pesach
asserts(rema, issur(achiila, F, minhag)) :-
    food(F),
    food_type(F, kitniyot),
    context(pesach).

% Sefardi: permitted
asserts(mechaber, heter(achiila, F)) :-
    food(F),
    food_type(F, kitniyot),
    context(pesach).

Exercise 3: Add a New World

Create a world for the Kaf HaChaim (Sefardi posek).

Solution
world(kaf_hachaim).
accessible(kaf_hachaim, mechaber).  % Sefardi tradition

minhag_region(kaf_hachaim, sefardi_edot_hamizrach).

safek_policy(kaf_hachaim, d_oraita, l_chumra).
safek_policy(kaf_hachaim, d_rabanan, l_kula).

% Add specific rulings...

What You've Learned

  • How to add new foods with proper classification
  • How to create new food categories
  • How to write rules that apply to new foods
  • How to add new commentator worlds
  • How to create interpretation layers
  • How to test your extensions

Next Steps

You've completed the tutorial series! Consider:


Production Encoding Workflow

Ready to contribute to the Mistaber ontology? The Encoding Workflow Guide provides a systematic, AI-assisted pipeline with human review checkpoints using the mistaber-skills Claude Code plugin.

Quick Reference

% Add food
food(item_name).
food_type(item_name, category).

% Add category
food_category(new_category).
subcategory(new_category, parent_category).

% Add world
world(new_world).
accessible(new_world, parent_world).

% Add rule
rule(r_rule_id).
makor(r_rule_id, source("ref")).
madrega(r_rule_id, level).
scope(r_rule_id, world).

asserts(world, conclusion) :- conditions.