Turn InterSystems IRIS into a graph + vector database β openCypher queries, HNSW/BM25 hybrid search, temporal property graph, RDF/SHACL/PROV-O semantic layer, and Neo4j Bolt protocol compatibility.
Five minutes from zero to running graph queries.
git clone https://github.com/intersystems-community/iris-vector-graph && cd iris-vector-graph
docker compose up -d
docker compose ps # wait until iris_vector_graph shows (healthy), about a minuteStarts IRIS Community Edition on localhost:1972. No license required.
Default credentials: _SYSTEM / SYS. engine.initialize_schema() in step 3 creates the tables and deploys IVG's
ObjectScript classes (Graph.KG.*) into the namespace; after upgrading the package, run it again to redeploy them.
pip install iris-vector-graphimport iris
from iris_vector_graph.engine import IRISGraphEngine
conn = iris.connect("localhost", 1972, "USER", "_SYSTEM", "SYS")
engine = IRISGraphEngine(conn, embedding_dimension=768)
engine.initialize_schema()
engine.create_node("alice", labels=["Person"], properties={"name": "Alice"})
engine.create_node("bob", labels=["Person"], properties={"name": "Bob"})
engine.create_edge("alice", "KNOWS", "bob")
result = engine.execute_cypher(
"MATCH (a {node_id:$id})-[:KNOWS]->(b) RETURN b.name AS name",
{"id": "alice"}
)
print(result["rows"]) # [['Bob']]Note: On a stock image
initialize_schema()logs a fewIVG setup:lines, and none of them is an error. The MCP classes (Graph.KG.MCPService,MCPToolSet,MCPTools) need%AIpackages the image does not ship, and are not required. The^KGadjacency index stays empty until data is loaded.
| Feature | Notes |
|---|---|
| openCypher | MATCH, CREATE, MERGE, DELETE, WITH, UNWIND, variable-length paths, subqueries |
| Temporal property graph | Time-windowed edges, pre-aggregated bucket analytics, O(1) window queries |
| Vector search | HNSW (native IRIS VECTOR), IVFFlat, PLAID multi-vector, BM25 full-text |
| Graph analytics | Betweenness, closeness, eigenvector, degree centrality; Leiden community detection; SCC; k-core; PPR |
| Shortest path | Unweighted BFS (shortestPath), weighted Dijkstra (ivg.shortestPath.weighted) |
| Native Cypher | Supported reads, most variable-length included, run inside IRIS over ^KG (on by default), a read with hops as a class compiled for its shape (4.4.0); openCypher TCK 3896/3896 |
| Bulk loader | Writes rows, 6β14K edges/s (chunk sizes); ^KG follows via the adjacency index; incremental ^NKG rebuild |
| FHIR graph | An ISC FHIR repository as a named graph; concept β resource β patient ranking (guide) |
| Bolt protocol | neo4j-driver compatible wire protocol (TCP + WebSocket) |
| Embedded Python | Graph algorithms run server-side via IRIS embedded Python (igraph, leidenalg) |
| IPM / ZPM | ObjectScript-only install via InterSystems Package Manager |
| RDF export | export_rdf() β full or filtered graph to Turtle/NT/NQuads/JSON-LD |
| SHACL validation | validate_shacl() β SHACL Core via PySHACL; ValidationReport dataclass |
| PROV-O | prov_export() β temporal edges as W3C PROV-O provenance graph |
Hardware: M3 Ultra, Community IRIS 2026.1, ARM64 Docker.
| Query | Latency | Notes |
|---|---|---|
| 1-hop neighbor lookup | ~0.4ms | $Order on ^KG |
NKG fast-path: [*1..N] via ^NKG BFS, hops 2β5 (2026-06, before native Cypher) |
1.4β2.0ms | 4.9β13.4x faster than the SQL path then |
| IC3 2-hop with LIMIT (LDBC SF10) | 1.2ms | 3.5x faster than GES/GraphScope |
| IC13 shortest path (LDBC SF10) | 2.1β3.2ms | Comparable to GES at SF1000 on cluster |
| HNSW vector search (768-dim) | 1.7ms | Native IRIS VECTOR index |
| BM25 full-text (174 nodes, 3-term) | 0.3ms | Posting-list $Order |
| Temporal window query | 0.1ms | O(results), B-tree |
| Pre-aggregated bucket (24hr/288 buckets) | 0.16ms | O(buckets), not O(edges) |
Against Neo4j and Memgraph on the LDBC Social Network Benchmark at scale factor 10 (AWS r7g.8xlarge,
2026-10-23, native Cypher with compiled plans), IVG's median time over 39 queries is 0.90x Neo4j's and 0.57x
Memgraph's (Memgraph from the previous SF10 run on the same instance type)
(report).
IVG is competitive with or faster than Neo4j GDS on degree centrality, betweenness, and Leiden community detection, producing results identical to networkx (Pearson r = 1.0). Validated on DRKG biomedical KG (~97K nodes / ~5.9M edges).
Full methodology and numbers: docs/performance/BENCHMARKS.md and docs/performance/GRAPH_ALGORITHMS.md.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β iris-vector-graph β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β βββββββββββββββββ βββββββββββββββββ βββββββββββββββββββββ β
β β Python SDK β β Cypher/AQL β β Bolt (wire) β β
β β IRISGraph β β translator β β neo4j-driver β β
β β Engine β β + executor β β compatible β β
β βββββββββ¬ββββββββ βββββββββ¬ββββββββ ββββββββββ¬βββββββββββ β
β ββββββββββββββββ¬βββββ β β
β βΌ β β
β ββββββββββββββββββββββββββ β β
β β GraphStore protocol ββββββββββββββββ β
β β (pluggable backend) β β
β βββββββββββββ¬βββββββββββββ β
β β β
β ββββββββββββββββΌβββββββββββββββ β
β βΌ βΌ βΌ β
β βββββββββββββββ ββββββββββββ βββββββββββββββββ β
β β SQL layer β β ^KG β β ^NKG β β
β β Graph_KG.* β β globals β β integer adj β β
β β (nodes, β β (edges, β β index β β
β β edges, β β temp, β βββββββββ¬ββββββββ β
β β vectors) β β PPR) β β β
β βββββββββββββββ ββββββββββββ β β
β βΌ β
β ββββββββββββββββββββββ β
β β Algorithm tiers β β
β ββββββββββββββββββββββ€ β
β β 1. Rust acceleratorβ β fastest β
β β (rayon parallel)β β
β β 2. ObjectScript β β
β β parallel 8Γ β β
β β 3. Python LazyKG β β always worksβ
β ββββββββββββββββββββββ β
β β
β Centrality: betweenness (Brandes) Β· closeness Β· eigenvector β
β degree β
β Community: Leiden Β· triangle count Β· SCC Β· k-core β
β Search: vector (HNSW/IVF/PLAID) Β· BM25 Β· temporal Β· PPR β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
The Rust tier is used for an algorithm only when the loaded library passes IVG's reader gate for it; as of arno-callout 1.2.0, pagerank, ppr, wcc, cdlp and subgraph do not, and answer from ObjectScript (KNOWN_ISSUES, "Arno callout defects"). Full schema and ObjectScript class reference: docs/architecture/ARCHITECTURE.md.
pip install 'iris-vector-graph[rdf]'# Export graph as Turtle (full or filtered)
engine.export_rdf("kg.ttl")
engine.export_rdf("proteins.nt", format="nt", label_filter=["Protein", "Disease"])
engine.export_rdf_from_cypher("MATCH (p:Patient)-[r]->(e) RETURN p,r,e", "patients.ttl")
# Persistent namespace prefixes
engine.register_namespace("fhir", "http://hl7.org/fhir/")
# SHACL Core validation
report = engine.validate_shacl("shapes/patient.ttl")
if not report.conforms:
for v in report.violations:
print(f"{v.focus_node}: {v.message} [{v.severity}]")
# PROV-O temporal provenance
engine.prov_export("provenance.ttl", ts_start=1700000000)
prov = engine.prov_as_dict(edge_id=42)Every write to ivg is stored as W3C-aligned SPO triples (rdf_edges, rdf_props, rdf_labels)
with OWL 2 RL inference, named graph support, and RDF-star style edge qualifiers.
See docs/SEMANTIC_LAYER.md for the full guide: format
reference, SHACL shape writing, PROV-O vocabulary mapping, and integration patterns.
Scope nodes and edges to a named graph β useful for multi-tenant data, staging vs. production snapshots, or materializing a ledger reconstruction into an isolated subgraph.
# Create a node in the "umls" named graph
engine.create_node("C0027051", labels=["Concept"], graph="umls")
# Create an edge in the same named graph
engine.create_edge("C0027051", "ISA", "C0085580", graph="umls")
# Cypher: USE GRAPH targets the context for CREATE and MERGE
engine.execute_cypher("USE GRAPH umls CREATE (n:Concept {id: 'C0001234'})")
# Import an NDJSON file into a named graph
engine.import_graph_ndjson("export.ndjson", graph="staging")
# Erase one graph's content from every store: SQL rows, ^KG adjacency,
# the degree cache, all five temporal trees. drop_graph() is a deprecated alias.
engine.erase_graph("staging")
# Erase every graph, including the stores no per-graph erase can reach
engine.erase_all()
# Independent per-graph, bidirectional drift check between SQL and ^KG
engine.verify_graph("umls")["ok"]
# delete_edge defaults to the default graph; pass all_graphs=True to sweep all
engine.delete_edge("C0027051", "ISA", "C0085580", graph="umls")
engine.delete_edge("C0027051", "ISA", "C0085580", all_graphs=True)Nodes in the default graph use graph="" (empty-string sentinel); the BFS
adjacency index (^KG) partitions by graph key so traversals stay within
their graph. Existing code that never passes graph= is unaffected.
See docs/USER_GUIDE.md for the full guide.
Run IVG in an ISC FHIR namespace (HS.FHIRServer, R4 JsonAdvSQL) and the repository
becomes the named graph fhir:<NAMESPACE>:<package>: one node per live resource, one
edge per indexed reference. Codes and properties stay in the repository.
reg = engine.fhir_graph_register() # creates the fhir_* tables on first use
g = reg["graph_id"] # e.g. fhir:IVGFHIR:X0001
engine.fhir_graph_rebuild(g)
engine.fhir_graph_schedule(g, interval_s=60) # incremental sync from the Task Manager
# Map a code to a concept node, then rank patients around the concept
engine.code_crosswalk_add("http://hl7.org/fhir/sid/icd-10-cm", "E11.9", "C0011860",
target_graph="umls", relation="exact")
ranked = engine.fhir_concept_ppr(g, "umls", ["C0011860"], params="clinical",
group_by="patient", top_k=10)
engine.fhir_coverage_report(g) # what the interpretation reachedThe same from a shell: ivg fhir register|rebuild|sync --once|status|schedule|links.
See docs/FHIR_GRAPH.md for the model, the interpretation
contract (base R4, no profiles), sync, reports and limits.
The structural graph (nodes, labels, properties, relationships, qualifiers) can
be versioned with an opt-in, immutable revision ledger. A changeset applies
atomically and produces exactly one revision; a failed operation applies
nothing. This is transaction time (when the ledger recorded a change) and is
independent of the temporal property graph's event time (ts on temporal
edges), which is unchanged and unversioned.
from iris_vector_graph.ledger import Changeset
genesis = engine.ledger.enable() # idempotent; captures the existing graph
head = engine.ledger.head()
cs = Changeset(actor="ingest:etl-42", actor_type="ingest",
message="equipment sync",
expected_head=head.revision_id, # optimistic concurrency
idempotency_key="etl-42-2026-09-05") # safe retry
cs.create_node("pump-7", labels=["Equipment"], properties={"status": "ok"})
cs.create_node("tank-2")
r = cs.create_relationship("pump-7", "FEEDS", "tank-2", qualifiers={"weight": "1.0"})
cs.set_qualifier(r, "capacity", "100")
result = engine.ledger.commit(cs) # StaleHeadError if another writer won
engine.ledger.history(limit=20) # deterministic, pageable, filterable
engine.ledger.get_revision(result.revision.revision_id).records
engine.ledger.diff(genesis.revision_id, result.revision.revision_id)
engine.ledger.reconstruct(genesis.revision_id) # read-only graph as of a revision
engine.ledger.export_reconstruction(result.revision.revision_id, "at-rev.ndjson")
engine.ledger.verify() # replay from genesis == current tables?
engine.ledger.verify(adopt=True) # record any unrecorded (legacy) writesExisting APIs keep working with a ledger enabled; writes made through them are
"unrecorded" and are surfaced by verify(). enable(strict=True) (or
set_strict(True)) rejects non-ledger structural writes with
LedgerStrictModeError; reads, temporal writes, and index maintenance are
unaffected. Relationships get a stable statement identity that survives
qualifier changes and snapshot restore. Counters are in
engine.status().ledger, the Graph_KG.ledger_stats table, and a metrics hook
(engine.ledger.register_metrics_hook, see
docs/ledger-prometheus-hook.md).
By default IVG connects to the USER namespace. When deploying against a
HealthShare, Ensemble, or multi-tenant IRIS instance where data lives in a
different namespace, pass namespace= at engine construction:
from iris_vector_graph import IRISGraphEngine
import iris.dbapi as dbapi
conn = dbapi.connect(hostname="...", port=1972, namespace="MYGRAPH", ...)
engine = IRISGraphEngine(conn, namespace="MYGRAPH")MYGRAPH is a placeholder β use a namespace you own and have deployed IVG
into. A namespace an installed product manages (a HealthShare/Ensemble
platform or customization namespace) is the wrong home for IVG's schema: that
product's own upgrade process owns the class and table set there.
On first use, the engine probes $Data(^KG("out")) to verify the ^KG
globals are accessible. If they are absent a WARNING is logged naming the
namespace and the fix. Set IVG_STRICT_NAMESPACE=1 to upgrade the warning
to a raised NamespaceMismatchWarning.
A namespace is an IVG namespace only once the Graph.KG.* classes are deployed
into it: a DDL-only schema, and a CPF global mapping alone, look right and behave
differently. How to check a namespace and how to map ^KG:
Admin Guide Β§3.
| Env var | Effect |
|---|---|
| (neither) | logging.warning on mismatch, execution continues |
IVG_STRICT_NAMESPACE=1 |
logging.warning + raises NamespaceMismatchWarning |
IVG_IGNORE_NAMESPACE_CHECK=1 |
Skip probe entirely (HealthShare platform-managed namespaces) |
IVG_IGNORE_NAMESPACE_CHECK=1 takes precedence over IVG_STRICT_NAMESPACE=1.
| Document | Contents |
|---|---|
| User Guide | Cypher examples, named graphs, ledger, temporal edges, vector search |
| Admin Guide | Container setup, schema management, index rebuilding |
| Admin API | Python API reference for engine administration |
| Benchmarks | Full methodology, LDBC SNB results, ingestion throughput |
| Graph Algorithms | Centrality and community detection benchmark details |
| Semantic Layer | RDF export, SHACL validation, PROV-O provenance |
| FHIR Graph | FHIR repository as a graph: quick start, interpretation contract, reports |
| openCypher TCK | How the TCK figure is produced, what a pass checks, how to rerun it |
| Changelog | Full version history |
When building agents that use IVG as a knowledge graph backend, install the iris-agentic-dev MCP toolkit for ObjectScript compilation, global inspection, and IRIS method invocation from your agent's tool loop:
pip install iris-agentic-devThis gives your agent iris_compile, iris_execute, iris_global, and related tools
that work alongside IVG's Python API.
Skill files with agent-oriented quickstarts:
skills/iris-vector-graph/SKILL.mdβ IVG API, temporal edges, SHACL, globals, container setup, common gotchasskills/ivg-arno/SKILL.mdβ Arno Rust acceleration, BFS at scale, enterprise container, ASQ query syntax
MIT. See LICENSE.