reasoning_bank

ReasoningBank: Procedural recipes for ontology exploration with RLM

Overview

The ReasoningBank provides ontology-specific procedural recipes that guide the LLM in domain-specific ontology exploration tasks.

Architecture: Memory vs Recipes

IMPORTANT ARCHITECTURAL DECISION (2026-01-19 Refactor):

This module was refactored to align with the ReasoningBank paper’s memory-based learning model:

  • General strategies (universal patterns): → procedural_memory.bootstrap_general_strategies() (LEARNED)
  • Ontology-specific patterns (PROV, SIO, etc.): → reasoning_bank.ONTOLOGY_RECIPES (AUTHORED)

Rationale: - General strategies are LEARNED from experience and stored as MemoryItems (BM25-retrieved) - Ontology-specific patterns are AUTHORED by domain experts and stored as Recipes (always injected) - This separation enables: learning new strategies over time while maintaining domain-specific guidance

Four-Layer Context Injection

  1. Layer 0: Sense Card - Compact ontology metadata (always injected)
  2. Layer 1: Retrieved Memories - General strategies from procedural_memory (BM25-retrieved)
  3. Layer 2: Ontology Recipes - Domain-specific patterns (this module)
  4. Layer 3: Base Context - GraphMeta summary

Design Principles

  • Explicit procedures: Step-by-step tool usage patterns
  • Grounded in tools: Only reference available functions
  • Expected iterations: Set clear convergence expectations
  • Domain-specific: Recipes capture ontology-specific conventions (e.g., PROV Activity-Entity patterns)

Imports

Recipe Schema


Recipe


def Recipe(
    id:str, title:str, when_to_use:str, procedure:str, expected_iterations:int, layer:int, task_types:list,
    ontology:Optional=None
)->None:

A procedural recipe for ontology exploration.

Recipes provide explicit step-by-step guidance on HOW to use ontology exploration tools to accomplish specific tasks.

Ontology-Specific Recipes (Layer 2)

After 2026-01-19 Refactor: This section now contains ONLY ontology-specific recipes.

What moved: Universal patterns (describe entity, find subclasses, etc.) moved to procedural_memory.bootstrap_general_strategies().

What stays: Ontology-specific patterns like: - PROV: Activity-Entity relationship patterns - SIO: Measurement and process patterns
- Domain-specific conventions and idioms

Current status: Placeholder (ONTOLOGY_RECIPES = []) - to be populated with domain-specific recipes as needed.

# Test Recipe creation
test_recipe = CORE_RECIPES[1]  # recipe-1-describe-entity
print(f"Recipe: {test_recipe.title}")
print(f"Layer: {test_recipe.layer}")
print(f"Expected iterations: {test_recipe.expected_iterations}")
print(f"Task types: {test_recipe.task_types}")
print(f"\nFormatted:")
print(test_recipe.format_for_injection())

Recipe Retrieval Functions


format_recipes_for_injection


def format_recipes_for_injection(
    recipes:list
)->str:

Format recipes as markdown for context injection.

Args: recipes: List of Recipe objects

Returns: Formatted markdown string


retrieve_task_recipes


def retrieve_task_recipes(
    task_type:str, k:int=2
)->list:

Retrieve task-type specific recipes.

Args: task_type: Task type from classify_task_type() k: Number of recipes to retrieve

Returns: List of relevant Recipe objects


get_core_recipes


def get_core_recipes(
    limit:int=2
)->list:

Get always-injected core recipes.

Args: limit: Maximum number of core recipes to return

Returns: List of core Recipe objects


classify_task_type


def classify_task_type(
    query:str
)->str:

Classify query into task type for recipe selection.

Args: query: User query string

Returns: Task type: ‘entity_discovery’, ‘entity_description’, ‘hierarchy’, ‘property_discovery’, ‘pattern_search’, ‘relationship_discovery’

# Test recipe retrieval
print("Test 1: Classify task types")
queries = [
    "What is Activity?",
    "Find all subclasses of Activity",
    "What properties have Entity as domain?",
    "How are Activity and Entity related?"
]

for q in queries:
    task_type = classify_task_type(q)
    print(f"  '{q}' → {task_type}")

print("\nTest 2: Get core recipes")
core = get_core_recipes(limit=2)
print(f"  Retrieved {len(core)} core recipes:")
for r in core:
    print(f"    - {r.title}")

print("\nTest 3: Retrieve task-specific recipes")
task_recipes = retrieve_task_recipes('hierarchy', k=2)
print(f"  Retrieved {len(task_recipes)} recipes for 'hierarchy':")
for r in task_recipes:
    print(f"    - {r.title}")

print("\nTest 4: Format recipes")
formatted = format_recipes_for_injection(core[:1])
print(f"  Formatted length: {len(formatted)} chars")
print(f"\nFormatted output (first 300 chars):")
print(formatted[:300] + "...")

Context Injection

Four-layer context injection strategy: 1. Layer 0: Sense card (from structured sense) 2. Layer 1: Core recipes (always injected) 3. Layer 2: Task-type recipes (query-dependent) 4. Layer 3: Ontology-specific knowledge (future)


inject_context


def inject_context(
    query:str, base_context:str, sense:dict=None, memory_store:NoneType=None, ontology:str=None, max_memories:int=3,
    max_task_recipes:int=2
)->str:

Build complete context with sense + memories + recipes.

Injection order: 1. Sense card (Layer 0) - if provided 2. Retrieved memories (Layer 1) - general strategies from memory_store 3. Task-type recipes (Layer 2) - ontology-specific patterns 4. Base context - original graph summary

Args: query: User query base_context: Base context string (e.g., GraphMeta.summary()) sense: Structured sense document (from build_sense_structured) memory_store: MemoryStore with general strategies ontology: Optional ontology name for ontology-specific recipes max_memories: Maximum memories to retrieve max_task_recipes: Maximum task-type recipes to inject

Returns: Enhanced context string

Enhanced RLM Runner


rlm_run_enhanced


def rlm_run_enhanced(
    query:str, context:str, ns:dict=None, sense:dict=None, memory_store:NoneType=None, ontology:str=None,
    kwargs:VAR_KEYWORD
)->tuple:

RLM run with sense and memory injection.

This is a drop-in replacement for rlm_run() that automatically enhances context with: - Layer 0: Structured sense card - Layer 1: Retrieved procedural memories (general strategies) - Layer 2: Ontology-specific recipes

Args: query: User query context: Base context (e.g., GraphMeta.summary()) ns: Namespace dict sense: Structured sense document (from build_sense_structured) memory_store: MemoryStore with general strategies ontology: Optional ontology name **kwargs: Additional arguments passed to rlm_run()

Returns: (answer, iterations, final_ns) - same as rlm_run()

Tests

# Test inject_context with memory_store
from rlm.procedural_memory import MemoryStore, bootstrap_general_strategies

print("Test: inject_context() with memory_store")
print("=" * 70)

# Create memory store with general strategies
memory_store = MemoryStore()
strategies = bootstrap_general_strategies()
for s in strategies:
    memory_store.add(s)

print(f"Memory store: {len(memory_store.memories)} strategies")

# Simulate a sense document
test_sense = {
    'sense_card': {
        'ontology_id': 'test',
        'domain_scope': 'Test ontology',
        'triple_count': 100,
        'class_count': 10,
        'property_count': 5,
        'key_classes': [],
        'key_properties': [],
        'label_predicates': ['rdfs:label'],
        'description_predicates': ['rdfs:comment'],
        'available_indexes': {},
        'quick_hints': ['Test hint'],
        'uri_pattern': 'http://test.org/'
    },
    'sense_brief': {}
}

query = "What is Activity?"
base_context = "Test base context"

enhanced = inject_context(
    query=query,
    base_context=base_context,
    sense=test_sense,
    memory_store=memory_store,
    max_memories=2
)

print(f"\nQuery: '{query}'")
print(f"\nEnhanced context length: {len(enhanced)} chars")
print(f"\nContext structure:")
if '# Ontology:' in enhanced:
    print("  ✓ Layer 0: Sense card")
if 'Relevant Prior Experience' in enhanced or 'Describe Entity' in enhanced:
    print("  ✓ Layer 1: Retrieved memories (general strategies)")
if 'Base Context' in enhanced:
    print("  ✓ Base context included")

print(f"\nFirst 400 chars of enhanced context:")
print("-" * 70)
print(enhanced[:400] + "...")
Test: inject_context() with memory_store
======================================================================
Memory store: 7 strategies

Query: 'What is Activity?'

Enhanced context length: 1003 chars

Context structure:
  ✓ Layer 0: Sense card
  ✓ Layer 1: Retrieved memories (general strategies)
  ✓ Base context included

First 400 chars of enhanced context:
----------------------------------------------------------------------
# Ontology: test

**Domain**: Test ontology

**Stats**: 100 triples, 10 classes, 5 properties

**Key Classes**:

**Key Properties**:

**Labels via**: rdfs:label

**Quick Hints**:
- Test hint

## Relevant Prior Experience

Before taking action, briefly assess which of these strategies apply to your current task and which do not.

### 1. Describe Entity by Label
Universal pattern for finding and des...

Validation

Validate memory-recipe separation and ensure correct architecture.


validate_memory_recipe_separation


def validate_memory_recipe_separation(
    memory_store
)->dict:

Ensure general strategies aren’t duplicated in ONTOLOGY_RECIPES.

Validates: - No title overlap between universal memories and ontology recipes - All ONTOLOGY_RECIPES have ontology field set (domain-specific)

Args: memory_store: MemoryStore (typically with bootstrap strategies)

Returns: Dictionary with validation results

# Test memory-recipe separation validation
from rlm.procedural_memory import MemoryStore, bootstrap_general_strategies

print("Test: validate_memory_recipe_separation()")
print("=" * 60)

memory_store = MemoryStore()
for s in bootstrap_general_strategies():
    memory_store.add(s)

result = validate_memory_recipe_separation(memory_store)
print(f"Valid: {result['valid']}")
print(f"Overlap count: {result['overlap_count']}")
print(f"Ontology recipes count: {result['ontology_recipes_count']}")
print(f"All recipes have ontology field: {result['all_recipes_have_ontology']}")

if result['valid']:
    print("\n✓ Memory-recipe separation validated successfully")
else:
    print(f"\n✗ Validation failed: {result['overlapping_titles']}")
Test: validate_memory_recipe_separation()
============================================================
Valid: True
Overlap count: 0
Ontology recipes count: 0
All recipes have ontology field: True

✓ Memory-recipe separation validated successfully