SmartMemory
Guides

Ontology Management

SmartMemory includes an ontology management system that helps structure and organize knowledge for better extraction, validation, and retrieval. This guide covers how to define, manage, and leverage ontologies in your agentic memory system.

Overview

An ontology in SmartMemory defines:

  • Entity types: The kinds of entities the system recognizes (e.g. algorithm, person, organization), including their properties and inheritance hierarchy
  • Relationship types: How entity types may relate to each other, with allowed source/target types
  • Rules: Validation, enrichment, and inference rules attached to the ontology
  • Constraints: Required properties and per-property constraints (type, cardinality, soft/hard enforcement)

The runtime surface for building and managing ontologies is the OntologyManager class plus the Ontology model and its type-definition dataclasses. The correct imports are:

from smartmemory.ontology.manager import OntologyManager
from smartmemory.ontology.models import (
    Ontology,
    EntityTypeDefinition,
    RelationshipTypeDefinition,
    OntologyRule,
    PropertyConstraint,
)

OntologyManager API

OntologyManager is the entry point for ontology lifecycle operations. Its public methods are:

MethodPurpose
create_ontology(name, domain="", description="")Create and persist a new empty Ontology
load_ontology(ontology_id)Load an ontology by ID (returns Ontology or None)
set_active_ontology(ontology)Set the ontology used for extraction guidance
get_active_ontology()Return the currently active ontology (or None)
infer_ontology_from_extractions(extraction_history, ontology_name="inferred")Build an ontology from historical extraction patterns
migrate_freeform_to_ontology(extraction_history, base_ontology=None)Migrate freeform extractions into an ontology-guided one
list_ontologies()List all stored ontologies with basic metadata
delete_ontology(ontology_id)Delete an ontology by ID

The manager is backed by pluggable storage (OntologyStorage). By default it uses FileSystemOntologyStorage, which persists each ontology as a JSON file:

from smartmemory.ontology.manager import OntologyManager
from smartmemory.stores.ontology import FileSystemOntologyStorage

# Defaults to FileSystemOntologyStorage("ontologies")
manager = OntologyManager()

# Or supply an explicit storage directory
manager = OntologyManager(storage=FileSystemOntologyStorage("my_ontologies"))

Defining an Ontology

Create an ontology and add entity types

Entities are described with EntityTypeDefinition. Properties are a name -> type map, and required_properties/parent_types/aliases are sets.

from datetime import datetime, UTC
from smartmemory.ontology.manager import OntologyManager
from smartmemory.ontology.models import EntityTypeDefinition

manager = OntologyManager()

# Create a new ontology (persisted immediately)
ontology = manager.create_ontology(
    "technology",
    domain="technology",
    description="Technology and ML concepts",
)

# Define an entity type
deep_learning = EntityTypeDefinition(
    name="deep_learning",
    description="Deep learning techniques",
    properties={"complexity": "number"},      # property_name -> property_type
    required_properties=set(),
    parent_types=set(),                        # inheritance hierarchy (type names)
    aliases=set(),
    examples=["ConvolutionalNeuralNetwork", "Transformer"],
    created_by="human",
    created_at=datetime.now(UTC),
)
ontology.add_entity_type(deep_learning)

# A child type that inherits from deep_learning
cnn = EntityTypeDefinition(
    name="convolutional_neural_network",
    description="CNN architectures",
    properties={"complexity": "number"},
    required_properties=set(),
    parent_types={"deep_learning"},
    aliases={"cnn"},
    examples=["ResNet", "VGG"],
    created_by="human",
    created_at=datetime.now(UTC),
)
ontology.add_entity_type(cnn)

Define relationship types

Relationships are described with RelationshipTypeDefinition. source_types and target_types constrain which entity types may participate.

from smartmemory.ontology.models import RelationshipTypeDefinition

uses = RelationshipTypeDefinition(
    name="uses",
    description="One technique uses another",
    source_types={"deep_learning"},
    target_types={"deep_learning"},
    properties={},
)
ontology.add_relationship_type(uses)

similar_to = RelationshipTypeDefinition(
    name="similar_to",
    description="Two techniques are similar",
    source_types={"deep_learning"},
    target_types={"deep_learning"},
    properties={},
    bidirectional=True,
)
ontology.add_relationship_type(similar_to)

Property constraints

Per-property constraints are expressed with PropertyConstraint and attached to an entity type's property_constraints map. kind controls enforcement: soft warns, hard rejects.

from smartmemory.ontology.models import PropertyConstraint

deep_learning.property_constraints = {
    "complexity": PropertyConstraint(
        required=False,
        type="number",
        cardinality="one",
        kind="soft",
    ),
    "application_domain": PropertyConstraint(
        required=False,
        type="enum",
        cardinality="many",
        kind="soft",
        enum_values=["nlp", "computer_vision", "robotics", "speech"],
    ),
}

Add rules

Validation, enrichment, and inference rules use OntologyRule:

import uuid
from datetime import datetime, UTC
from smartmemory.ontology.models import OntologyRule

rule = OntologyRule(
    id=str(uuid.uuid4()),
    name="require_complexity",
    description="Encourage a complexity score on deep_learning entities",
    rule_type="validation",
    conditions={"entity_type": "deep_learning"},
    actions={"suggest_property": "complexity"},
)
ontology.add_rule(rule)

Persist changes

create_ontology() saves on creation, but mutations to the in-memory Ontology object are not auto-persisted. Save through the manager's storage after editing:

manager.storage.save_ontology(ontology)

Validating Against an Ontology

The Ontology model can validate entities and relationships directly. Both methods return a list of error strings (empty when valid).

# Validate an entity instance against its type definition
errors = ontology.validate_entity("deep_learning", {"complexity": 0.8})
# [] when valid; ["Unknown entity type: ..."] / missing-required-property messages otherwise

# Validate a relationship's source/target types
rel_errors = ontology.validate_relationship(
    "uses",
    source_type="deep_learning",
    target_type="deep_learning",
)

Lookups are case-insensitive:

entity_def = ontology.get_entity_type("Deep_Learning")
rel_def = ontology.get_relationship_type("USES")

Inferring an Ontology From Extractions

Rather than authoring an ontology by hand, you can infer one from prior extraction results. infer_ontology_from_extractions analyzes a list of extraction records (each with entities and relations) and builds entity/relationship type definitions for patterns seen at least twice.

extraction_history = [
    {
        "entities": [
            {"type": "algorithm", "name": "CNN", "properties": {"domain": "vision"}},
        ],
        "relations": [
            {"relation_type": "uses", "source": "CNN", "target": "convolution"},
        ],
    },
    # ... more extraction records
]

inferred = manager.infer_ontology_from_extractions(
    extraction_history,
    ontology_name="inferred_tech",
)

print(list(inferred.entity_types.keys()))
print(list(inferred.relationship_types.keys()))

The inferred ontology is persisted automatically. Confidence scores scale with how often each type was observed, and required properties are derived from properties present in more than 80% of instances of a type.

Note — OntoGPT engine: When ontology.inference.ontogpt.enabled is set in config, inference is delegated to the OntoGPTInferenceEngine. If that path fails it logs a warning and falls back to the built-in frequency-based inference shown above.

Migrating Freeform Extractions

migrate_freeform_to_ontology turns a history of freeform (LLM-driven) extractions into an ontology-guided one. With no base ontology it infers a fresh one; with a base ontology it extends it and records a migration rule.

# Build a brand-new ontology from freeform extraction history
migrated = manager.migrate_freeform_to_ontology(extraction_history)

# Or extend an existing ontology
migrated = manager.migrate_freeform_to_ontology(
    extraction_history,
    base_ontology=ontology,
)

Listing and Deleting Ontologies

# List stored ontologies (id, name, version, domain, entity_count, ...)
for meta in manager.list_ontologies():
    print(meta["id"], meta["name"], meta["entity_count"])

# Delete by ID
manager.delete_ontology(ontology.id)

Ontology-Guided Ingestion

SmartMemory's ingestion pipeline includes an ontology_constrain stage that validates and constrains extracted entities/relations against the active ontology. It is enabled by default and controlled by the enable_ontology constructor flag:

from smartmemory.smart_memory import SmartMemory

# Ontology constraint stage on (default)
memory = SmartMemory(enable_ontology=True)

# Disable the stage entirely
memory = SmartMemory(enable_ontology=False)

item_id = memory.ingest(
    "Convolutional Neural Networks are a deep learning architecture "
    "used for computer vision tasks."
)

The ontology_constrain stage reads the unified :OntologyType / :OntologyRelation graph labels and routes all reads/writes through the internal OntologyService. This is the productized path that ontology definitions feed into — there is no separate ontology_config= constructor argument, and entity extraction is governed by the pipeline stage rather than by per-call search() flags.

REST API: The service exposes ontology management over HTTP under /memory/ontology/* — including GET /memory/ontology/status, GET/POST /memory/ontology/registries, registry snapshots/changelog/rollback, templates, patterns, import/export, and POST /memory/ontology/inference/run. See the service API reference for the full route list.

Capabilities Not Currently Exposed

Some ontology features that might be expected are not part of the current public API. They are noted here so you don't depend on methods that don't exist:

  • Per-call ontology search expansion — there is no search(use_ontology=...) / expand_concepts argument. Retrieval does not take ontology flags; ontology influence happens at ingestion time via the ontology_constrain stage.
  • Concept-keyed memory listing / statistics — methods like get_memories_by_concept(...) and get_concept_statistics() do not exist. Use standard memory.search(...) plus graph queries to retrieve entities and their relations.
  • Per-memory extracted-concept readout — there is no get_extracted_concepts(memory_id) accessor. Extracted entities are written to the knowledge graph during ingestion; query the graph to inspect them.

These are intentional gaps in the current surface, not shipped methods — treat any code that calls them as incorrect.

On this page