Skip to content

Unified Data Store ​

One engine. Four databases eliminated. Zero sync bugs.

Purple8 Hyper Graph is a Hyper Graph DB — a new category of database that unifies four traditionally separate systems into a single embedded engine. It is not a graph database with plugins. It is not a vector database with a graph layer. It is a purpose-built engine where graph, vector, document, and full-text are equal citizens sharing one storage layer, one write path, and one query engine.

CapabilityWhat it replacesPurple8 module
Graph DatabaseNeo4j, FalkorDB, Kùzucore.engine — property graph with nodes, edges, labels, adjacency indexes
Vector DatabasePinecone, Weaviate, Qdrant, Milvuscore.vector — native HNSW index with multi-vector slots, int8/binary quantization, ANN pre-filtering
Document StoreMongoDB, Postgres JSONB, DynamoDBcore.models.Node.properties — schemaless dict[str, Any] on every node, arbitrary JSON depth
Full-Text SearchElasticsearch, OpenSearch, Typesensefulltext — BM25 inverted index with configurable tokenisation, stopwords, and hybrid text+vector search

All four live in the same BrickCoreStorage (Cortex) instance, encrypted at rest with the same KMS envelope, backed up in one call, and queryable through a single REST API and Cypher endpoint. Durability is provided by a Write-Ahead Log (WAL) with fdatasync — there is no RocksDB and no Raft consensus layer.


Why this matters ​

For the first SDK-only RDF/LPG integration slice, see Native Semantic Graph Foundation. It uses canonical statements and governed projections; SPARQL and general Cypher traversal of those projections are not yet implemented.

The infrastructure sprawl problem ​

A typical RAG or AI-powered application today looks like this:

App → Postgres (documents)
    → Pinecone  (vectors)
    → Elasticsearch (keyword search)
    → Neo4j (relationships)
    → sync layer (to keep them consistent)

That is four connection strings, four auth configurations, four backup strategies, four failure modes, and a custom synchronisation layer that you wrote and now have to maintain forever. Every new developer on the team has to understand all five systems. Every deployment is a multi-container orchestration. Every data migration touches four schemas.

The Purple8 answer: a Hyper Graph DB ​

App → Purple8 Hyper Graph (everything)

One pip install. One storage directory. One backup command. One encryption key. One auth layer. One container. Zero synchronisation bugs.

We call this a Hyper Graph DB because it goes beyond what any single database category offers. It is not "a graph database with vector search bolted on." It is a new kind of engine where all four modes — graph relationships, vector embeddings, document properties, and keyword search — are first-class citizens with independent storage paths, shared atomicity, and a single query surface.


How the four engines are isolated ​

A natural concern: "If everything runs in one process, won't a heavy document write stall my vector search?" The answer is no — because the four storage modes use physically separate data structures that don't compete for the same resources:

Storage-level isolation ​

┌──────────────────────────────────────────────────────────┐
│                     Purple8 Hyper Graph                        │
│                                                          │
│  ┌──────────────────────┐    ┌──────────────────────┐    │
│  │  BrickCoreStorage      │    │     HNSW Index       │    │
│  │  (Cortex — graph+docs)│    │  (vectors — hnswlib) │    │
│  │                       │    │                      │    │
│  │  n: nodes + props     │    │  Separate memory     │    │
│  │  e: edges             │    │  Separate files      │    │
│  │  o: outgoing adj      │    │  (hnsw.bin)          │    │
│  │  i: incoming adj      │    │                      │    │
│  │  l: label index       │    │  No Cortex writes    │    │
│  │  p: property index    │    │  during search       │    │
│  │  m: metadata          │    │                      │    │
│  └──────────────────────┘    └──────────────────────┘    │
│                                                          │
│  ┌──────────────────────┐    ┌──────────────────────┐    │
│  │  Full-Text Index      │    │    LRU Caches        │    │
│  │  (BM25 — in-memory    │    │                      │    │
│  │   inverted index)     │    │  Traversal cache     │    │
│  │                       │    │  Vector cache        │    │
│  │  No Cortex writes     │    │  Label allow-set     │    │
│  │  during search        │    │  cache               │    │
│  └──────────────────────┘    └──────────────────────┘    │
└──────────────────────────────────────────────────────────┘
ComponentStorage mediumContention with graph writes?
Graph topology (nodes, edges, adjacency)BrickCoreStorage (Cortex) key prefixes (n:, e:, o:, i:)Write lock serialises mutations only
Document propertiesStored inside the node record (same Cortex key)Same write as node — no extra I/O
Vector index (HNSW)Separate hnswlib data structure + hnsw.bin fileNo Cortex I/O — reads are pure in-memory ANN
Full-text index (BM25)Separate in-memory inverted indexNo Cortex I/O — reads are pure in-memory BM25 scoring
Property indexesCortex p: prefix keysRead-only lookups — O(k) not O(n)

Write path isolation ​

All writes go through a single _write_lock (reentrant threading.RLock). This guarantees atomicity across all four modes — when you call add_node(), the graph topology, document properties, vector embedding, and full-text tokens are all written in one atomic operation. There is no eventual consistency. There is no sync layer.

But critically, the write lock only blocks other writes. Read operations — vector_search(), full_text_search(), traverse(), get_node() — are lock-free and run concurrently without blocking.

python
# All four storage modes updated atomically in one call:
fte.add_node("doc-1", labels=["Contract"], properties={
    "title": "Master Services Agreement",
    "body": "This agreement is entered into by...",
    "clauses": [{"id": "2.3", "text": "Limitation of liability..."}]
}, embedding=contract_embedding)

# Under the hood:
# 1. Cortex (BrickCoreStorage) write: node record + properties + adjacency  ← graph + document
# 2. hnswlib insert: add vector to HNSW graph                     ← vector (no Cortex I/O)
# 3. BM25 index: tokenise + update inverted index                 ← full-text (no Cortex I/O)
# All three happen inside the same write lock — atomically.

What this means in practice ​

  • A bulk document ingest (10,000 nodes with properties) does not degrade vector search latency — because vector search reads from a separate in-memory HNSW structure, not Cortex.
  • A heavy vector search workload does not slow down graph traversals — because traversals use Cortex prefix-seek reads while vector search uses hnswlib.
  • A full-text search does not touch Cortex at all — it scans the in-memory inverted index.
  • Property index lookups are O(k) reads from Cortex's p: prefix — they don't compete with graph topology reads on n: or e: prefixes.

Document ingestion pipeline ​

Purple8 includes a full document ingestion pipeline that flows cleanly into the unified store:

┌──────────┐   ┌───────────┐   ┌──────────┐   ┌───────────┐
│  Source   │ → │  Extract  │ → │ Compare  │ → │  Publish  │
│ (upload / │   │ (DocIntel │   │ (diff vs │   │ (commit   │
│  URL /    │   │  parses + │   │  existing│   │  to graph │
│  paste)   │   │  chunks)  │   │  graph)  │   │  store)   │
└──────────┘   └───────────┘   └──────────┘   └───────────┘

How it works ​

  1. Source — Upload a file (PDF, DOCX, IFC, DXF, images with OCR, and 70+ formats across 75+ languages), paste a URL, or paste raw text.
  2. Extract — Purple8 DocIntel parses the document, detects entities and relationships, and chunks the content using one of 8 strategies (fixed, recursive, semantic, sentence window, parent-child, markdown header, late chunking, or syntactic).
  3. Compare — The extracted entities are diffed against the existing graph. You see what's new, what's updated, and what conflicts before anything is committed.
  4. Publish — Commit to the graph with one click. Entities become nodes, relationships become edges, document text becomes properties, and embeddings are indexed — all in one atomic operation.

What the ingestion pipeline stores ​

When a document is ingested, Purple8 creates one node per entity with:

  • Labels — ["Document"], ["Person"], ["Company"], etc.
  • Properties — the full extracted content as schemaless JSON (title, body, metadata, source info, parsed fields)
  • Embedding — vector embedding for semantic search
  • Edges — typed relationships linking entities to each other

All four storage modes are populated in a single atomic write. There is no two-phase commit, no message queue, no background sync job.


Index management ​

Purple8 provides editable, user-controlled indexes — much like creating indexes in MongoDB or DynamoDB, but covering all four storage modes from one interface.

Property indexes (document-style queries) ​

Build secondary indexes on any node property for O(k) lookups instead of O(n) full scans:

python
# Create indexes — like MongoDB's createIndex() or DynamoDB's GSI
engine.build_property_index("Invoice", "vendor")
engine.build_property_index("Invoice", "status")
engine.build_property_index("Contract", "effective_date")

# Fast equality lookup — O(k), not O(n)
invoices = engine.find_nodes_by_property("Invoice", "vendor", "Acme Corp")

# Multi-filter query with automatic index selection
# Purple8 picks the most selective indexed property as the primary key,
# then post-filters the rest in memory — just like a query planner.
results = engine.find_nodes(
    "Invoice",
    filters={"vendor": "Acme Corp", "status": "pending", "currency": "USD"},
    limit=50,
)

# List all indexes
indexes = engine.list_property_indexes()
# [("Contract", "effective_date"), ("Invoice", "status"), ("Invoice", "vendor")]

Vector index & hardware-aware auto-configuration ​

The HNSW vector index and Cortex write buffer are tunable manually or solved automatically for the host hardware via CortexAutoConfig (auto_config=True):

python
# Hardware-aware parameter solver (CortexAutoConfig):
# Bounds peak process RSS to alpha * available_ram (default alpha=0.75) via the
# brick seal-and-evict cycle, and auto-detects multi-socket CPU/NUMA topology
# (recommended_shards), Intel AMX/XPU, and NVIDIA Tensor Core GPUs up to
# Blackwell Ultra B300 (288 GB HBM3e, FP4).
engine = GraphEngine("./data", embedding_dimension=1536, auto_config=True)

# Query-time: ef_search controls accuracy/speed tradeoff
engine._vector_index.ef_search = 100  # higher = better recall, slower search

# Quantization: 4× RAM savings (int8) or 32× (binary)
# Set via env var: P8G_VECTOR_QUANTIZATION=int8

# Backend: default hnswlib (C++) or pure-Rust HNSW
# Set via env var: P8G_VECTOR_BACKEND=rust

# Auto-compaction: tombstoned vectors trigger background rebuild
# after P8G_VECTOR_COMPACT_THRESHOLD deletions (default: 500)
# or 10% of index size — whichever is smaller

Full-text index configuration ​

python
from purple8_graph.fulltext import FullTextEngine

fte = FullTextEngine(
    engine,
    index_fields=["title", "body", "tags", "clause_text"],  # choose what to index
    k1=1.5,     # BM25 term frequency saturation
    b=0.75,     # BM25 document length normalisation
)

# The full-text index auto-updates on add_node / update_node / delete_node —
# no manual reindexing required.

REST API for index management ​

All indexes are manageable via the REST API — no code changes needed:

bash
# Build a property index
POST /api/indexes
{"label": "Invoice", "property": "vendor"}

# List all indexes
GET /api/indexes

# Configure RAG retrieval settings (chunking, embedding, reranking)
PUT /rag/config
{
  "chunking_strategy": "semantic",
  "embedding_model": "text-embedding-3-small",
  "retrieval_k": 15,
  "min_similarity": 0.7,
  "reranker": "cohere",
  "hybrid_weight": 0.5
}

Querying: the MongoDB / DynamoDB experience ​

Purple8's document store provides querying capabilities comparable to MongoDB or DynamoDB — but with graph traversals, vector search, and full-text search available in the same query:

Simple document queries (MongoDB-style) ​

python
# Find by property (like MongoDB's find())
invoices = engine.find_nodes("Invoice", filters={
    "vendor": "Acme Corp",
    "status": "pending",
})

# Find by property with limit (like DynamoDB's query with Limit)
recent = engine.find_nodes("Contract", filters={
    "effective_date": "2026-04-01",
}, limit=10)

# Get a single document by ID (like MongoDB's findOne / DynamoDB's GetItem)
doc = engine.get_node("invoice-42")
print(doc.properties["line_items"][0]["sku"])  # "WDG-001"

# Update properties (like MongoDB's updateOne / DynamoDB's UpdateItem)
engine.update_node("invoice-42", properties={
    "status": "approved",
    "approved_by": "alice@acme.com",
    "approved_at": "2026-04-07T10:30:00Z",
})

Full-text search (Elasticsearch-style) ​

python
# Keyword search with BM25 scoring
hits = fte.full_text_search("limitation liability indemnification", top_k=20)

# Hybrid: BM25 + vector similarity (like Elasticsearch kNN + text)
hits = fte.hybrid_text_vector_search(
    query="limitation of liability",
    embedding=query_embedding,
    top_k=10,
    text_weight=0.3,
    vector_weight=0.7,
)

Graph traversals (Neo4j-style) ​

python
# Follow relationships
results = engine.traverse("contract-101", edge_types=["SIGNED_BY"], max_depth=1)

# Cypher queries — combine everything
results = engine.execute_cypher("""
    CALL db.vector.search('Contract', $vec, 10) YIELD node, score
    WHERE score > 0.8
    MATCH (node)-[:SIGNED_BY]->(person:Person)
    MATCH (person)-[:WORKS_AT]->(company:Company)
    WHERE company.industry = 'Financial Services'
    RETURN node.title, person.name, company.name, score
    ORDER BY score DESC
""", {"vec": query_embedding})

What you cannot do with separate databases ​

None of these queries are possible when your data lives in four different systems:

python
# "Find contracts similar to this one, then follow the supersession chain,
#  then return all parties involved in the latest version"
chain = engine.hybrid_search(
    query_embedding, top_k=5,
    expand_edges=["SUPERSEDES"], max_depth=3,
)

# With separate databases, this requires:
# 1. Pinecone query → get similar doc IDs
# 2. Neo4j query → traverse SUPERSEDES edges
# 3. Neo4j query → get party nodes
# 4. Postgres query → get document properties
# 5. App-level join code to stitch results together
# 6. Hope nothing changed between steps 1 and 5

Impact on developers ​

Before Purple8 (4 systems) ​

python
# Connection setup
pg = psycopg2.connect("postgres://...")
pinecone.init(api_key="...", environment="...")
es = Elasticsearch(["https://..."], api_key="...")
neo4j_driver = GraphDatabase.driver("bolt://...", auth=("...", "..."))

# Ingest a document — 4 systems, 4 transactions, no atomicity
pg.execute("INSERT INTO documents ...")       # 1. store doc
pinecone.upsert(vectors=[(id, embedding)])    # 2. index vector
es.index(index="docs", body=doc_text)         # 3. index text
neo4j_driver.session().run("CREATE (d:Doc)…") # 4. create graph node

# What if step 3 fails? Now your data is inconsistent.
# You need: retry logic, dead letter queues, reconciliation jobs.

With Purple8 (1 system) ​

python
from purple8_graph import GraphEngine
from purple8_graph.fulltext import FullTextEngine

engine = GraphEngine("./data")
fte = FullTextEngine(engine)

# One call. Atomic. Done.
fte.add_node("doc-1", labels=["Document"],
    properties={"title": "MSA", "body": "..."},
    embedding=vec)

Developer experience wins ​

Concern4 systemsPurple8
Lines of setup code20+ (4 clients, 4 configs)3
Dependencies in requirements.txt8+ (psycopg2, pinecone-client, elasticsearch, neo4j, ...)1 (purple8-hyper-graph)
Error handlingCompensating transactions, DLQs, reconciliationSingle try/except
Schema migrations4 migration scripts per changeNone — schemaless by default
Local developmentDocker Compose with 4+ servicespip install purple8-hyper-graph
Integration testsMock or spin up 4 servicesOne in-memory engine instance
New developer onboardingLearn 4 query languages + sync layerLearn one SDK
Time to first working prototypeDays (infra setup)Minutes

Impact on non-developer users ​

Purple8's web UI (RAG Studio) makes the unified store accessible to non-technical users — no code required:

Document ingestion ​

The 4-step Ingest wizard (Source → Extract → Compare → Publish) lets business users upload documents (PDF, DOCX, spreadsheets, even CAD files) and see the extracted entities and relationships before committing them to the graph. No CLI, no API calls.

Search and query ​

RAG Studio's Query tab lets users ask questions in natural language. Under the hood, Purple8 searches the vector index, the full-text index, and the graph simultaneously — but the user just types a question and gets an answer with source citations.

Configuration ​

The Configure tab exposes all RAG settings (chunking strategy, embedding model, retrieval parameters, reranker) through a graphical interface. Changes take effect immediately — no redeploy needed.

What this means for teams ​

  • Data analysts can upload CSVs and immediately query relationships
  • Legal teams can search contracts by meaning, not just keywords
  • Compliance officers can trace audit trails through the graph without writing Cypher
  • Product managers can prototype RAG features without waiting for engineering

Performance characteristics (v0.72.0) ​

Read performance (no contention) ​

Operation / BenchmarkMeasured ResultHow
Query P99 @ 100K0.33 msCortex (BrickCoreStorage) + HNSW in-process
HTTP P95 (1024d)23 msFull HTTP + auth + vector retrieval stack
VectorDBBench (1M × 768d)916 QPS, 0.946 recall@10Segmented HNSW index
get_node(id)~0.1 msCortex (BrickCoreStorage) point read
vector_search(vec, top_k=10)~0.3–3 msIn-memory HNSW — no Cortex I/O
full_text_search(query, top_k=10)~0.5–2 msIn-memory BM25 — no Cortex I/O
find_nodes_by_property(label, prop, val)~0.2 msCortex p: prefix seek
traverse(node, depth=2)~2–10 msCortex o:/i: prefix seeks
hybrid_search(vec, top_k=10, expand=2)~3–12 msHNSW + Cortex traversal

Write & scale performance ​

Operation / BenchmarkMeasured ResultHow
Batch ingest40,551 docs/sRust DashMap write buffer + BrickWAL batch fdatasync
Largest single-machine corpus8.84M MS MARCO docs in ~9 GB peak RSSCortexAutoConfig brick seal-and-evict cycle on a 24 GB machine
Sustained soak test1.18M requests, 500 users, 0% errors30-minute continuous mixed read/write load
add_edge>200k edges/minCortex prefix-key writes

The critical insight: write operations hold the lock briefly (microseconds for Cortex + HNSW insert), then release it. Read operations never acquire the lock. So even during a 100k-document bulk ingest:

  • Vector search latency stays at ~1–3 ms (reads don't block on writes)
  • Full-text search latency stays at ~0.5–2 ms (separate in-memory index)
  • Graph traversal latency stays at ~2–10 ms (Cortex concurrent reads are lock-free)

The only potential contention is write-write: if two threads try to write simultaneously, one waits for the other. This is intentional — it's what gives you atomicity. For write-heavy workloads, Purple8 offers horizontal sharding with per-shard write locks, so writes to different shards proceed in parallel.


De-risking: "All eggs in one basket?" ​

The most common objection to a unified store is: "Aren't I putting all my eggs in one basket?" Here is how Purple8 de-risks this:

1. Raft consensus replication ​

Purple8's Raft implementation replicates all four storage modes across multiple nodes. Every write — graph mutations, document updates, vector inserts, full-text index changes — is committed to the Raft log and applied to all replicas after majority quorum.

Client write → Leader (RaftNode)
                 │
                 ├──append_entry──► Follower 1  ✓
                 ├──append_entry──► Follower 2  ✓
                 │   ← majority ACK ───────────
                 ▼
              Apply to ShardedGraphEngine (all 4 storage modes)
  • Leader election: If the leader goes down, a follower is elected within 150–300 ms.
  • Linearisability: Committed entries are applied synchronously in tick order — no stale reads.
  • WAL persistence: The Raft log is backed by a WALWriter with fsync — log entries survive process restarts.

2. Backup and restore ​

One backup captures everything — graph, documents, vectors, indexes, metadata:

python
from purple8_graph.production import BackupManager, BackupConfig

mgr = BackupManager(BackupConfig(
    backup_dir=Path("./backups"),
    compress=True,
    checksum=True,     # SHA-256 integrity verification
    max_backups=10,    # auto-rotate old backups
))

# One call backs up all four storage modes
manifest = await mgr.create_backup(engine, description="Pre-migration snapshot")
print(f"Backed up {manifest.node_count} nodes, {manifest.edge_count} edges")
print(f"Size: {manifest.size_bytes / 1024 / 1024:.1f} MB (compressed)")

# Restore — verified with SHA-256 checksum; streams gzip/JSONL and checks
# only the 4-byte P8KM envelope header before KMS decryption
await mgr.restore_backup(manifest.backup_id, engine, verify_checksum=True)

Compare this with 4-system backup:

Concern4 systemsPurple8
Backup commandspg_dump + Pinecone export + ES snapshot + Neo4j dumpmgr.create_backup()
Restore commands4 restore scripts, ordered correctlymgr.restore_backup()
Point-in-time consistencyVery hard — 4 systems at different timesGuaranteed — one atomic snapshot
Integrity verification4 separate checksumsOne SHA-256
Storage overhead4× metadataOne compressed archive

3. Horizontal sharding ​

For deployments that need write scale beyond a single node, Purple8 supports horizontal sharding with per-shard isolation:

python
from purple8_graph import GraphEngine
from purple8_graph.sharding import ShardedGraphEngine, HashPartitioner

# Each shard is an independent GraphEngine with its own BrickCoreStorage (Cortex), HNSW, and full-text index
shards = [GraphEngine(f"./shard_{i}") for i in range(4)]
engine = ShardedGraphEngine(shards, HashPartitioner(num_shards=4))

# Writes route to the correct shard via consistent hashing
# Reads scatter-gather across all shards
# Per-shard write locks — writes to different shards proceed in parallel

4. Data portability ​

Purple8 is not a lock-in:

  • Export: Backup data is plain JSON (or compressed JSON) — you can read it with any tool.
  • Cypher: Standard Cypher queries work — migrating to/from Neo4j is straightforward.
  • REST API: Standard HTTP endpoints — any client can integrate.
  • Open-core: The engine is open-source. You can inspect every line of storage code.

Impact on time to market ​

Traditional architecture (weeks) ​

Week 1: Set up Postgres — schema design, migrations, connection pooling
Week 1: Set up Pinecone — create index, configure dimensions, API key management
Week 2: Set up Elasticsearch — cluster, mappings, analyzers, index templates
Week 2: Set up Neo4j — Docker, Cypher schema, driver config
Week 3: Build sync layer — message queues, change data capture, reconciliation
Week 3: Write integration tests — mock all 4 services, test failure scenarios
Week 4: Deploy — multi-container orchestration, health checks, monitoring
Week 4: Fix sync bugs — the first batch that slipped through

Purple8 architecture (hours) ​

Hour 1: pip install purple8-hyper-graph
Hour 1: engine = GraphEngine("./data")
Hour 2: Build ingestion pipeline using Purple8 DocIntel
Hour 3: Add vector search + graph traversal
Hour 3: Deploy — one container, one volume, one health check

Why it is faster ​

PhaseTraditionalPurple8Savings
Infrastructure setup2 weeks1 hour~99%
Data model design4 schemas × 1 day0 — schemaless~100%
Sync layer development1 week0 — atomic writes~100%
Integration testing1 week1 day (one engine mock)~80%
Deployment and opsOngoingOne container~90%
Total to MVP4–6 weeks1–2 days~95%

The four engines in detail ​

1. Graph Store ​

The property graph is the backbone. Every entity is a Node with labels and typed relationships (Edges):

python
from purple8_graph import GraphEngine

engine = GraphEngine("./data")

# Create nodes with labels
engine.add_node("doc-1", labels=["Document", "Contract"],
                properties={"title": "NDA v3", "status": "active"})

engine.add_node("person-1", labels=["Person"],
                properties={"name": "Alice Chen", "role": "Legal"})

# Create a typed relationship
engine.add_edge("doc-1", "person-1", edge_type="AUTHORED_BY",
                properties={"date": "2026-03-15"})

# Traverse: who authored this document?
results = engine.traverse("doc-1", edge_types=["AUTHORED_BY"], max_depth=1)

The graph layer uses Cortex (BrickCoreStorage) key prefixes with prefix-seek indexes:

  • n: — node records (properties stored inline)
  • e: — edge records
  • o:source:type — outgoing adjacency index
  • i:target:type — incoming adjacency index
  • l:label — label-to-node index
  • p:label|prop|value — secondary property index

All reads are O(log N + k) via Cortex prefix seeks — not O(N) full scans.

2. Document Store ​

Every node's properties field is a schemaless JSON document — dict[str, Any] with arbitrary nesting:

python
engine.add_node("invoice-42", labels=["Invoice"], properties={
    "vendor": "Acme Corp",
    "amount": 15400.00,
    "currency": "USD",
    "line_items": [
        {"sku": "WDG-001", "qty": 100, "unit_price": 154.00},
        {"sku": "WDG-002", "qty": 50,  "unit_price": 0.00},
    ],
    "metadata": {
        "source": "SAP",
        "imported_at": "2026-04-07T10:30:00Z",
        "raw_text": "Full OCR text of the scanned invoice..."
    },
    "tags": ["procurement", "q2-2026", "approved"]
})
TypeExample
Strings"title": "Annual Report"
Numbers"amount": 15400.00
Booleans"approved": true
Nested objects"address": {"city": "SF", "zip": "94105"}
Arrays"tags": ["urgent", "legal"]
Mixed arrays"data": [1, "two", {"x": 3}]
Null"deleted_at": null

No depth limit. No schema constraint (unless you opt in via SchemaValidator). All property data is serialised with orjson (4–10× faster than stdlib json) and stored in BrickCoreStorage (Cortex) with optional AES-256-GCM envelope encryption.

3. Vector Store ​

Every node can carry one or more embedding vectors, indexed in an HNSW graph:

python
# Single embedding (default slot)
engine.add_node("doc-1", labels=["Document"],
                properties={"title": "Climate Report"},
                embedding=embedding_vec)

# Multi-vector: separate embeddings for title vs body
engine.add_node("doc-2", labels=["Document"],
                properties={"title": "Q2 Earnings", "body": "Revenue grew..."},
                embeddings={"title": title_vec, "body": body_vec})

# Semantic search
results = engine.vector_search(query_vec, top_k=10, labels=["Document"])

# Hybrid: vector + graph traversal
results = engine.hybrid_search(query_vec, top_k=10,
                               expand_edges=["CITES"], max_depth=2)

Vector features:

  • Multi-vector slots per node
  • ANN pre-filtering by label (cached label allow-set — O(1) after first call)
  • Quantization: int8 (4× RAM savings) or binary (32×)
  • DiskANN backend for larger-than-RAM indexes
  • Auto-compaction of tombstoned vectors

Built-in BM25 inverted index — no Elasticsearch required:

python
from purple8_graph.fulltext import FullTextEngine

fte = FullTextEngine(engine, index_fields=["title", "body", "tags"])

# Auto-indexed on write
fte.add_node("doc-1", labels=["Document"],
             properties={"title": "Climate Change Report",
                         "body": "Global temperatures rose by 1.2C..."})

# BM25 keyword search
results = fte.full_text_search("climate temperature", top_k=10)

# Hybrid: BM25 + vector similarity
results = fte.hybrid_text_vector_search(
    query="climate report",
    embedding=query_vec,
    top_k=10,
    text_weight=0.3,
    vector_weight=0.7,
)

Full-text features:

  • Configurable tokenisation and stopword lists
  • Per-field indexing (choose which properties to index)
  • BM25 with tunable k1 and b parameters
  • Automatic index updates on add/update/delete
  • Hybrid text + vector fusion with configurable weights

Summary: before and after ​

ConcernWithout Purple8With Purple8
Document storagePostgres / MongoDBnode.properties
Vector searchPinecone / Weaviateengine.vector_search()
Keyword searchElasticsearchfte.full_text_search()
RelationshipsNeo4j / manual joinsengine.add_edge() / engine.traverse()
Property indexes4 separate index configsengine.build_property_index()
Encryption at rest4 per-system configsOne KMS envelope
Backup4 separate backup jobsOne mgr.create_backup() call
Replication4 replication topologiesOne Raft log
Auth / tenancy4 auth configsOne multi-tenant auth layer
Deployment4+ containers1 container
ConsistencyApp-level sync codeSingle-writer, atomic writes
Time to MVP4–6 weeks1–2 days
Dependencies8+ packages1 package
New developer ramp-upLearn 4 systems + syncLearn 1 SDK

One import. One storage directory. One backup. One encryption key. Zero synchronisation bugs.

Purple8 Hyper Graph v0.72.0 · AI-Native Execution Infrastructure · NVIDIA Inception Member · Red Hat Technology Partner.