Skip to content

Backend contracts

OdinEngine(backend) accepts a GraphBackend and obtains database operations through its capabilities. KnowledgeBootstrapper receives a training source and model store. Raw database handles raise a migration error. These contracts are new in 0.4.0; see Backend migration for installation and API changes. Neo4j engine support is not included yet.

Engine capabilities

Every engine backend supplies an accessor with iter_out, iter_in, iter_out_edges, nodes, degree, get_node, and community_seed_norm. iter_out_edges yields one record per outgoing edge with required keys _id, u, rel, v and weight, plus optional metadata; see Edge records. The engine validates this surface at construction rather than relying on deferred attribute errors.

Backends may be retrieval-only. They initialize with auto_train=False and use constant confidence. Automatic training and retrain_model() require both a non-null TripleSource and ModelStore; otherwise the engine raises BackendCapabilityError. Unexpected bootstrap failures propagate rather than being converted into constant confidence.

Implementing a backend

All protocols are defined in odin.backends.base (odin/backends/base.py). GraphBackend, TrainingBackend, TripleSource and ModelStore are also re-exported from odin.backends; GlobalAccessBackend, SchemaInspectionBackend and SchemaIntrospector are imported from odin.backends.base. ArangoBackend is the reference implementation; the in-memory example in Adapters is the smallest retrieval-only one.

Protocol Method Required Purpose
GraphBackend accessor(community_id, community_mode) Always Returns a GraphAccessor for retrieval.
TrainingBackend triple_source() For training Returns a TripleSource whose snapshot() yields a TrainingSnapshot.
TrainingBackend model_store() For training Returns a ModelStore with load(key) and save(key, document, expected_revision=...).
GlobalAccessBackend global_accessor() Optional Direct access to global signals such as bridges and affinity.
SchemaInspectionBackend schema_inspector() Optional Returns an object with get_schema_map(refresh=False) for inspect_schema(backend).

A backend must supply:

  • Identity. Node IDs from the accessor and the entity IDs in training triples must be the same strings, with exact, case-sensitive relation labels.
  • A global training snapshot. Training is global; communities only scope retrieval.
  • Atomic storage. save must be a compare-and-swap on an opaque revision token, raising ModelConflictError on a conflict, and namespaced so different databases or graph mappings never share an artifact.
  • The documented errors. Use the errors in the table under Model artifacts and concurrency.

Everything else on this page is Arango's implementation of those requirements (AQL extraction, _id identities, _rev checks, the OdinModels collection) and does not constrain another database. Without TrainingBackend, construct the engine with auto_train=False.

Snapshot identity

TripleSource.snapshot() returns a frozen TrainingSnapshot. Its triples are immutable, lexicographically ordered (source, relation, target) string tuples. Duplicates are retained. SHA-256 covers the UTF-8 JSON serialization of these exact tuples (ensure_ascii=False, compact separators). There is no separate fingerprint query, count-based identity, or second extraction during bootstrap. Two sources returning the same triples get the same fingerprint, irrespective of database or iteration order. Backend training scope belongs to the store namespace, not the training-data digest.

Arango extracts relationship and type triples in one AQL query. Entity IDs are full _id strings, matching retrieval. Relationship labels preserve case and spaces; missing or non-string labels fail extraction. Type triples are (entity._id, "has_type", entity.type), with a string type value. Dangling relationships are excluded, as in the previous extractor. Training uses the globally configured node and edge collections; training is global even when retrieval is scoped to a community.

This intentionally replaces the old bare-key/lowercased training identities. Existing weights must be retrained; there is no compatibility lookup. Rules contain unconditional relation priors plus chains and symmetry supported by observed facts. False premises do not count as support. Exact relation names, ordered joins, and deterministic rule IDs are preserved for arbitrary vocabularies.

Model artifacts and concurrency

ModelStore.load(key) returns StoredModel(document, revision) or None only when absent. save(key, document, expected_revision=...) returns the new opaque revision. A None expected revision means create only. An existing revision means replace only if the artifact has not changed. Bootstrap reads this token before training, including forced retraining. It does not retry a rejected save. Startup may reload a fully matching winner; forced retraining still propagates conflicts.

The Arango namespace is the canonical JSON array of database name, global training scope, node collection, edge collection, relation field, and entity type field. The storage key hashes [namespace, logical_model_key]; both values are checked on load. Retrieval communities share this global artifact.

The envelope contains namespace, model_key, and the complete artifact, alongside Arango _key / _rev metadata. Create uses non-overwriting insert. Update uses document replacement with _rev and check_rev=True, so removed nested fields are actually removed and competing writers cannot silently win.

Artifact version 7.0 requires:

  • model_type, storage_type, trained_at, data_hash, and version;
  • inference_state with the complete config, seed, scorer recipe, runtime version, example count, full loss history, and a checksum-protected scorer-state blob;
  • all rule_weights and ordered rules (ID, text, confidence);
  • schema_snapshot with entity, relation, and fact counts and every relation name;
  • a complete training_report, including both iteration histories and convergence criteria.

The format is finite JSON with string object keys. Complete nested values and additional JSON evidence fields are preserved. No relation or history cap is applied. Unscoped OdinModels/npll_current documents remain untouched and are not reused. Older numeric versions and stale configuration schemas are returned with their revision and retrained by bootstrap. Newer versions raise NewerModelVersionError and are never replaced by an older reader. Version components compare numerically: 7 and 7.0 are equal, while 10.0 is newer than 7.0. Non-numeric versions raise CorruptModelError. Damaged current documents fail validation. There is no legacy serving path.

Version protection only covers changes that bump ARTIFACT_VERSION. Within one version, an older reader treats changed NPLL configuration fields or values, SCORER_RECIPE, or generated rules as stale and replaces the artifact. Any such change must bump ARTIFACT_VERSION; tests/unit/test_artifact_version_guard.py pins these semantics to the current version and fails until the bump is made. Runtime-only settings such as device are excluded.

Errors have distinct meanings:

Outcome Meaning
None Artifact or model collection is absent
CorruptModelError Damaged current artifact, invalid version or invalid envelope
NewerModelVersionError Stored artifact requires a newer reader; upgrade Odin and preserve the document
BackendIOError Extraction, transport, permission, or persistence failure
ModelConflictError Another writer created, replaced, or deleted the artifact since the read

A valid artifact whose saved rules no longer match the current rule-generation code is stale, not corrupt: bootstrap treats it as a cache miss and retrains against the store revision it read. Corruption is reserved for artifacts that fail schema validation.

These errors propagate through bootstrap and the engine's initialization and retraining methods. Trainer exceptions raise npll.TrainingError, preserving the original cause. Bootstrap can return a failed result for an empty snapshot or no generated rules; the public engine treats this as TrainingError too. Only explicit auto_train=False selects constant confidence. Retraining builds replacement serving components before changing the active engine state.

Scope and verification

Unit regressions cover same-count mutations, snapshot immutability, one-read bootstrap, 101 relations, 120 history entries, complete nested persistence, namespaces, corruption, and conflicting creation/replacement/forced retraining. Live checks in tests/integration/test_arango_backend_live.py create a disposable database and verify AQL identity, actual revision conflicts, complete artifact round-trip, and real training followed by reload and scoring.

The neural scorer learns observed triples against deterministic unobserved corruptions, including relation-conditioned scores. Such corruptions are training examples, not proof that a relationship is false. Learned edge confidence is not source truth or a calibrated probability. The E-M loop separately learns MLN rule weights. Its convergence report describes that loop, not scorer calibration.

Learned scorer tensors are persisted as a base64 binary state dictionary with a SHA-256 checksum, loaded with Torch weights-only deserialization and strict tensor name, shape and finiteness checks. This model state is separate from evidence records, which preserve application fields including vectors. Consumers apply their own model-input filtering. Reload initializes the architecture and loads tensors without replay or floating-point loss comparisons. Device and Torch version changes do not invalidate reuse. Semantic profile or recipe changes retrain. Cross-backend retrieval parity and Neo4j live tests are not established yet.

Run the live tests with an account allowed to create databases:

ODIN_TEST_ARANGO_URL=http://127.0.0.1:8529 \
ODIN_TEST_ARANGO_PASSWORD=your-test-password \
pytest tests/integration/test_arango_backend_live.py -v