OTO
deployment · from ARCHITECTURE.md at v0.6.1

Neo4j, the production store

SQLite
build/one file, no server, on a laptop
oto servereads it directly
Neo4j
--target neo4jthe build loads it, with a read-back
serve.backenda shared server for a team, no silent fallback
Two stores, one answer: an equivalence battery proves both answer every query the same way.

The engine reads through one interface, Store in serve/store.py, and the answer text is built from the rows it returns.

SqliteStore

is the development store, the plugin's store on every machine, and the fallback: the build's .db, loaded into memory. Neo4jStore is the production store: a live connection to a self-hosted Neo4j that the build loads and that one server, or many, can share.

tests/test_store_equivalence.py

builds one project into both, runs every tool through each, and diffs the text; passage search is the one tool allowed to differ, because FTS5 and Lucene tokenize differently, and its result sets must overlap instead.

A project chooses with "serve": {"backend": "neo4j"} in project.config.json; oto serve --backend and OTO_STORE override it per process. With Neo4j the engine verifies the connection at startup, checks that the database holds a load of this project at a schema it understands, and says which it is serving on stderr.

If the database is unreachable it keeps running, answers every call with the reason, and retries on the next call; it never falls back to the local file on its own, because a production server that silently answered from a stale copy would be worse than an honest error.

Pre-flight refuses the combination "serve from Neo4j, never load it", and oto status reports whether Neo4j is reachable and loaded.

The load itself runs when the project sets "targets": ["sqlite", "neo4j"] and "neo4j": {"uri": ..., "database": ...}, with NEO4J_USER and NEO4J_PASSWORD in the environment (NEO4J_URI overrides the uri, so CI and production can point one config at different servers).

It writes (:Entity:<Class>) nodes keyed <project>:<id>, so several projects share one database, with declared attributes in their types and the authored attributes as JSON text beside them; relationships named after the relations with kind asserted or derived; SUPERSEDES between versions; :Evidence, :Source, :DerivedAttribute and :PolicyFinding nodes; and the rows serving needs that are not graph: :Passage with a full-text index, :Lexicon, :Changelog, and :OtoProject carrying the build sequence and schema version.

Every element carries the load's number; once the new facts are in, the previous load's are deleted, so a fact retired in the graph is retired in Neo4j. oto build --target neo4j --verify forces the load and compares the database with the build.

The driver is the neo4j extra; the base install never imports it. Cypher, the full-text indexes and the Graph Data Science library are then available to any other tool.