iris-vector-graph
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.
Getting Started
Five minutes from zero to running graph queries.
1. Start IRIS
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 minute
Starts 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.
2. Install
pip install iris-vector-graph
3. Run your first query
import 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.
What It Does
| 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 |
Performance
Hardware: M3 Ultra, Community IRIS 2026.1, ARM64 Docker.
Query latency
| 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).
Algorithm comparison (vs Neo4j GDS and networkx)
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.
Architecture
┌─────────────────────────────────────────────────────────────────────┐
│ 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 │
│ │
└─────────────────────────────────────────────────────────────────────┘
Full schema and ObjectScript class reference: docs/architecture/ARCHITECTURE.md.
Semantic Layer (RDF / SHACL / PROV-O)
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.
Named Graphs
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.
FHIR Repository as a Graph
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 reached
The 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.
Revision Ledger (Transaction-Time History)
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) writes
Existing 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).
Non-USER Namespace Deployment
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 controls
| 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.
Documentation
| 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 |
AI Agent Development
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-dev
This 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
License
MIT. See LICENSE.
Metadata
Release files for iris-vector-graph 4.4.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| iris_vector_graph-4.4.2.tar.gz | 33.5 MB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| iris_vector_graph-4.4.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 34.6 MB
Release files / iris_vector_graph-4.4.2.tar.gz
| Download URL | iris_vector_graph-4.4.2.tar.gz |
|---|---|
| Size | 33.5 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5a0ab737ddef38022f63faae479f7cd1ce083bf36d7f3af73249bce29091eed5
|
|
BLAKE2b-256 checksum How to use checksums |
bfdb5f8f9b1329ab9b20f471d5c2a84b5f2b9822bcc9bfd384f6d324059554d2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.13
|
Release files / iris_vector_graph-4.4.2-py3-none-any.whl
| Download URL | iris_vector_graph-4.4.2-py3-none-any.whl |
|---|---|
| Size | 1.1 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9047583dd605b44d5db3e3276524f4cfe5c969a8b9fbe292bca7b8c4f7aa9716
|
|
BLAKE2b-256 checksum How to use checksums |
0357294f171e212b1a648820c796af591695976099e5f733bbc3197847532b8b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.13
|
Security for iris-vector-graph 4.4.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.