Slopnet Architecture: Named Graphs, Oxigraph, and the RDF 1.2 Reality Check
The Great Architecture Decision Day
Today we locked in Slopnet's architecture after a deep dive into RDF-star, QLever, Oxigraph, and the realities of RDF 1.2.
The Core Decisions
Storage: Oxigraph (local + remote)
RDF Version: RDF 1.2 (no quoted triples needed)
Named Graphs: Per-slop granularity
Ontology: know.dev types + schema.org properties
Attribution: "familiar" field with full model ID
Why Oxigraph Over QLever
We considered two heavyweights:
Oxigraph:
- Native RDF 1.2 support
- Optimized for relational graph queries
- Perfect for entity co-occurrence and social graph traversal
- Scales to 500K-1M users at 250 slops each
QLever:
- Blazing fast full-text search
- Wikipedia-scale optimization (billions of triples)
- BUT: No RDF-star support yet
- Doesn't support quoted triples (issue #2169)
The verdict: We're not doing full-text search as primary use case. We're doing relational pattern matching on entity graphs. Oxigraph excels at this.
The RDF-star Rabbit Hole
We initially went down the RDF-star path for "clean provenance":
# The dream (RDF-star with quoted triples as subjects):
<< :entity :name "Izzy" >> :confidence 0.92 .
Plot twist: pyoxigraph 0.5.x removed this! RDF 1.2 spec changed - quoted triples can only be objects, not subjects.
The epiphany: We don't need RDF-star at all! The named graph URI already provides provenance. Everything in graph https://slop.at/graph/goodlux/slop/824532af came from that slop file. Why annotate statements when the graph itself IS the annotation?
The Simple Solution
GRAPH <https://slop.at/graph/goodlux/slop/824532af> {
# Entity with extraction metadata attached directly
:entity123 a know:Person ;
schema:name "Izzy" ;
slop:confidence 0.92 ;
slop:lineStart 42 ;
slop:sourceUrl <https://github.com/.../slop.md#L42> .
}
No blank nodes. No quoted triples. Just entities with their properties. The graph boundary provides the provenance container.
Per-Slop Named Graphs
Each slop gets its own graph:
https://slop.at/graph/{user}/{repo}/{slop-id}
Benefits:
- Atomic CRUD:
DROP GRAPHdeletes one slop cleanly - Query flexibility: Search all graphs by default, filter by user/repo with regex
- Provenance boundary: Graph URI = source of truth
- Scalability: RocksDB handles millions of named graphs
No limit on named graphs. At 250 slops/user × 100 triples/slop:
- 10K users = 250M triples ✅
- 100K users = 2.5B triples ✅ (DBpedia scale)
- 500K users = 12.5B triples ✅ (approaching Wikidata)
Graph-Shape Discovery
The real magic: symmetric local/remote topology for graph-shape recommendations.
# Find slops similar to MY entity co-occurrence patterns
SELECT ?slop WHERE {
# Local: what entities appear together in my slops?
{ SELECT ?e1 ?e2 WHERE { ?mySlop slop:mentions ?e1, ?e2 } }
# Remote: public slops with similar structure
SERVICE <https://slop.at/sparql> {
GRAPH ?g {
?slop slop:mentions ?e1, ?e2 .
}
}
}
Not text similarity - structural graph similarity. "What would I find interesting based on how my knowledge connects?"
You can query:
- Entity co-occurrence patterns
- Multi-hop social graph traversal
- "Slops from people whose entity graphs look like mine"
- "What topics appear near topics I care about?"
The Tech Stack
- Storage: Oxigraph server + local instances
- RDF: Clean RDF 1.2 (no quoted triples)
- Python: pyoxigraph 0.5.3
- Serialization: N-Quads (includes graph info)
- Entity types: know.dev ontology (Person, Organization, Event, Place, Topic, DefinedTerm)
- Properties: schema.org (name, description, etc.)
- Metadata: DCTERMS (title, creator, created, subject)
- Provenance: NFO (FileDataObject, fileName, fileUrl) + SLOP (confidence, lineStart, lineEnd, sourceUrl, familiar)
Scale Targets
- Beta: 10K users (we are here)
- Growth: 100K users (revisit architecture)
- Theoretical max: 500K-1M users before sharding
The bottleneck won't be storage - it'll be:
- Query complexity (joins across millions of entities)
- Concurrent users
- Full-text search (if we need it, add QLever later)
Migration Path
If we hit 100K users and need QLever's full-text power:
- Export N-Quads from Oxigraph
- Wait for QLever to add RDF 1.2 support
- Import to QLever
- Change one env var:
SLOP_GRAPH_SERVER - Same SPARQL queries work
What We Learned
YAGNI for semantic web: Don't use RDF-star just because it's fancy. Use it when you need statement-level metadata that can't be captured by graph boundaries.
Named graphs are underrated: They're not just for organizing data - they're provenance containers, CRUD units, and query optimization hints.
Spec changes happen: RDF-star → RDF 1.2 removed quoted triples as subjects. Stay close to stable specs, not cutting edge.
Simplicity wins: Our final solution is cleaner than the RDF-star version. Entities carry their extraction metadata, graphs provide file provenance. Done.
The Philosophy
"Why use 47 AWS services when Supabase go brrr?" applies to semantic web too.
Why use RDF-star quoted triples when named graphs already solve the provenance problem? Why wait for QLever when Oxigraph gives us the relational query power we need?
YAGNI meets knowledge graphs. Ship it. 🚀
Architecture is the art of knowing what NOT to build.