# 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())reasoning_bank
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
- Layer 0: Sense Card - Compact ontology metadata (always injected)
- Layer 1: Retrieved Memories - General strategies from procedural_memory (BM25-retrieved)
- Layer 2: Ontology Recipes - Domain-specific patterns (this module)
- 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.
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