{
"path": "/3mhrzf36xak2h",
"site": "at://did:plc:p7sxjpo2opcfkn7cgi5jqyqi/site.standard.publication/3m6ol2sdsnc2r",
"tags": [],
"$type": "site.standard.document",
"title": "From Property Graph to RDF: A Hands-On Translation Guide for LadybugDB",
"content": {
"$type": "pub.leaflet.content",
"pages": [
{
"id": "019d1eab-4e3f-7ff2-9652-63acd9dd632b",
"$type": "pub.leaflet.pages.linearDocument",
"blocks": [
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.image",
"image": {
"$type": "blob",
"ref": {
"$link": "bafkreig344n5ztsxlnivewmjb6mjdpgrjmzvl2m6xl7vmofy2pamevlzgu"
},
"mimeType": "image/webp",
"size": 710938
},
"aspectRatio": {
"width": 1536,
"height": 1024
}
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "You built your agent memory in LadybugDB. Typed node tables, polymorphic relationships, bipartite edge nodes — the whole Semantic Spacetime apparatus running on LadybugDB embedded engine. It works. It’s fast. It fits on your laptop."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Then someone asks:"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.blockquote",
"facets": [
{
"index": {
"byteEnd": 61,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#italic"
}
]
}
],
"plaintext": "“Can we export this to a knowledge graph? We use SPARQL.”"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 212,
"byteStart": 203
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 224,
"byteStart": 217
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 321,
"byteStart": 319
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#italic"
}
]
}
],
"plaintext": "This is not a betrayal of the property graph faith. It’s interoperability. And the good news is that LadybugDB’s bipartite architecture — where edges are first-class nodes mediated by polymorphic FROM_LINK / TO_LINK rel tables — maps to RDF more naturally than you'd expect. In fact, the bipartite pattern is reification. You've been writing triples all along."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "This article walks through the translation, node by node, rel by rel, from a working LadybugDB Cypher schema to Turtle (RDF’s most readable serialization). Every code block is real. Every mapping decision is explained."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"plaintext": "The Source Schema: Bipartite Semantic Spacetime"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Let’s start with a minimal but complete LadybugDB schema. This is the core of what we’ll translate — a bipartite graph with four Semantic Spacetime edge types."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "plaintext",
"plaintext": "-- Entity nodes: the \"nouns\" of the knowledge graphCREATE NODE TABLE EntityNode ( id STRING, label STRING, kind STRING, -- \"concept\" | \"actor\" | \"event\" | \"place\" layer STRING, -- \"core\" | \"domain\" | \"instance\" learned_at TIMESTAMP, expired_at TIMESTAMP, -- NULL = still valid PRIMARY KEY (id));"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "plaintext",
"plaintext": "-- Edge node: SIMILAR (γ₁ — near/similar)CREATE NODE TABLE SimilarEdge ( id STRING, layer STRING, kind STRING, -- \"semantic\" | \"structural\" | \"functional\" context STRING, -- \"embedding_v3\" | \"domain:medicine\" similarity DOUBLE, -- [0.0 … 1.0] learned_at TIMESTAMP, expired_at TIMESTAMP, PRIMARY KEY (id));-- Edge node: CONTAINS (γ₂ — containment/spatial)CREATE NODE TABLE ContainsEdge ( id STRING, layer STRING, kind STRING, -- \"part_of\" | \"member_of\" | \"scope\" context STRING, probability DOUBLE, learned_at TIMESTAMP, expired_at TIMESTAMP, PRIMARY KEY (id));-- Edge node: HAS_PROPERTY (γ₃ — expresses property)CREATE NODE TABLE HasPropertyEdge ( id STRING, layer STRING, kind STRING, property_name STRING, learned_at TIMESTAMP, expired_at TIMESTAMP, PRIMARY KEY (id));-- Edge node: LEADS_TO (γ₄ — causality/succession)CREATE NODE TABLE LeadsToEdge ( id STRING, layer STRING, kind STRING, -- \"causal\" | \"temporal\" | \"logical\" context STRING, learned_at TIMESTAMP, expired_at TIMESTAMP, PRIMARY KEY (id));-- TWO polymorphic rel tables enforce the bipartite ruleCREATE REL TABLE FROM_LINK ( FROM EntityNode TO SimilarEdge | ContainsEdge | HasPropertyEdge | LeadsToEdge);CREATE REL TABLE TO_LINK ( FROM SimilarEdge | ContainsEdge | HasPropertyEdge | LeadsToEdge TO EntityNode);"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "This is the schema we’ll translate. Two entity nodes connected through a reified edge node, mediated by exactly two rel tables. The bipartite constraint means: entities never touch entities; edge nodes never touch edge nodes."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"plaintext": "Step 0: Define Your RDF Namespace"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Before writing any triples, establish the vocabulary. In RDF, every concept needs a URI. In LadybugDB, concepts are table names and column names. The mapping is direct."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "plaintext",
"plaintext": "@prefix ldb: <http://ladybugdb.org/ontology#> .@prefix sst: <http://ladybugdb.org/sst#> .@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .@prefix owl: <http://www.w3.org/2002/07/owl#> ."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Two custom prefixes:"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.unorderedList",
"children": [
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 4,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "ldb: — LadybugDB ontology terms (node types, properties, structural predicates)"
}
},
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 4,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "sst: — Semantic Spacetime terms (the four edge types, layers, kinds)"
}
}
]
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Everything else maps to standard RDF/OWL vocabulary."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"plaintext": "Step 1: Translate Node Tables to RDF Classes"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 23,
"byteStart": 6
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 47,
"byteStart": 37
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 63,
"byteStart": 54
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 125,
"byteStart": 113
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "Each CREATE NODE TABLE becomes an rdfs:Class (or owl:Class if you want reasoning). Each column becomes an rdf:Property with a domain and range."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "plaintext",
"plaintext": "ldb:EntityNode a owl:Class ; rdfs:label \"Entity Node\" ; rdfs:comment \"The nouns of the knowledge graph — concepts, actors, events, places.\" .ldb:SimilarEdge a owl:Class ; rdfs:label \"Similar Edge Node\" ; rdfs:comment \"Reified γ₁ (near/similar) relation from Semantic Spacetime.\" .ldb:ContainsEdge a owl:Class ; rdfs:label \"Contains Edge Node\" ; rdfs:comment \"Reified γ₂ (containment/spatial) relation.\" .ldb:HasPropertyEdge a owl:Class ; rdfs:label \"Has Property Edge Node\" ; rdfs:comment \"Reified γ₃ (expresses property) relation.\" .ldb:LeadsToEdge a owl:Class ; rdfs:label \"Leads To Edge Node\" ; rdfs:comment \"Reified γ₄ (causality/succession) relation.\" .# --- Group edge node classes under a common superclass ---sst:EdgeNode a owl:Class ; rdfs:label \"SST Edge Node\" ; rdfs:comment \"Abstract superclass for all reified Semantic Spacetime relations.\" .ldb:SimilarEdge rdfs:subClassOf sst:EdgeNode .ldb:ContainsEdge rdfs:subClassOf sst:EdgeNode .ldb:HasPropertyEdge rdfs:subClassOf sst:EdgeNode .ldb:LeadsToEdge rdfs:subClassOf sst:EdgeNode ."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 38,
"byteStart": 23
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 232,
"byteStart": 223
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 240,
"byteStart": 233
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 308,
"byteStart": 296
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "Notice something? The rdfs:subClassOf hierarchy encodes what LadybugDB expresses through table naming conventions. In the property graph, all four edge node tables share the same structural role — they sit between two FROM_LINK/TO_LINK hops. In RDF, we make that shared role explicit with sst:EdgeNode."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"plaintext": "Step 2: Translate Column Definitions to RDF Properties"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 76,
"byteStart": 65
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "Each column in a node table becomes a property declaration. The PRIMARY KEY column maps to the node's URI itself — it doesn't need a separate property."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "plaintext",
"plaintext": "# --- Properties shared across all nodes ---"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "plaintext",
"plaintext": "ldb:layer a owl:DatatypeProperty ; rdfs:domain [ owl:unionOf (ldb:EntityNode sst:EdgeNode) ] ; rdfs:range xsd:string ; rdfs:comment \"Abstraction layer: core, domain, instance, meta.\" .ldb:kind a owl:DatatypeProperty ; rdfs:domain [ owl:unionOf (ldb:EntityNode sst:EdgeNode) ] ; rdfs:range xsd:string ; rdfs:comment \"Semantic subtype within a layer.\" .ldb:learnedAt a owl:DatatypeProperty ; rdfs:range xsd:dateTime ; rdfs:comment \"Timestamp when this knowledge was acquired.\" .ldb:expiredAt a owl:DatatypeProperty ; rdfs:range xsd:dateTime ; rdfs:comment \"Timestamp when this knowledge became invalid. Absent = still valid.\" .# --- EntityNode-specific ---ldb:label a owl:DatatypeProperty ; rdfs:domain ldb:EntityNode ; rdfs:range xsd:string .# --- SimilarEdge-specific ---sst:context a owl:DatatypeProperty ; rdfs:domain sst:EdgeNode ; rdfs:range xsd:string .sst:similarity a owl:DatatypeProperty ; rdfs:domain ldb:SimilarEdge ; rdfs:range xsd:double .# --- ContainsEdge-specific ---sst:probability a owl:DatatypeProperty ; rdfs:domain ldb:ContainsEdge ; rdfs:range xsd:double .# --- HasPropertyEdge-specific ---sst:propertyName a owl:DatatypeProperty ; rdfs:domain ldb:HasPropertyEdge ; rdfs:range xsd:string ."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 16,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#bold"
}
]
},
{
"index": {
"byteEnd": 21,
"byteStart": 19
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 107,
"byteStart": 105
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 146,
"byteStart": 144
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#italic"
}
]
},
{
"index": {
"byteEnd": 193,
"byteStart": 176
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 242,
"byteStart": 204
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "The key decision: id (the primary key) becomes the URI suffix, not a separate property. In LadybugDB, id is the identity. In RDF, identity is the URI. So an entity with id = \"concept_42\" becomes <http://ladybugdb.org/data/concept_42>. No redundancy."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"plaintext": "Step 3: Translate Polymorphic Rel Tables to RDF Object Properties"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 76,
"byteStart": 67
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 86,
"byteStart": 79
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "This is where it gets interesting. The two polymorphic rel tables (FROM_LINK, TO_LINK) encode the bipartite structure. In RDF, they become object properties linking entity nodes to edge nodes."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "plaintext",
"plaintext": "# --- Structural predicates (the bipartite backbone) ---"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "plaintext",
"plaintext": "ldb:fromLink a owl:ObjectProperty ; rdfs:domain ldb:EntityNode ; rdfs:range sst:EdgeNode ; rdfs:comment \"Connects a source entity to a reified edge node (FROM_LINK).\" .ldb:toLink a owl:ObjectProperty ; rdfs:domain sst:EdgeNode ; rdfs:range ldb:EntityNode ; rdfs:comment \"Connects a reified edge node to a target entity (TO_LINK).\" ."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 180,
"byteStart": 169
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 197,
"byteStart": 187
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "Two properties. That’s it. The bipartite rule from LadybugDB — “entities connect only to edge nodes, edge nodes connect only to entities” — is now encoded in rdfs:domain and rdfs:range constraints."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"plaintext": "Step 4: Translate Instance Data"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Now let’s populate. Suppose you have two concepts connected by a similarity relation in LadybugDB:"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "plaintext",
"plaintext": "-- Create entitiesCREATE (a:EntityNode { id: 'tcp_ip', label: 'TCP/IP Protocol', kind: 'concept', layer: 'domain', learned_at: timestamp('2024-01-15')});"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "plaintext",
"plaintext": "CREATE (b:EntityNode { id: 'http_protocol', label: 'HTTP Protocol', kind: 'concept', layer: 'domain', learned_at: timestamp('2024-01-15')});-- Create the reified edge nodeCREATE (s:SimilarEdge { id: 'sim_tcp_http_001', layer: 'domain', kind: 'structural', context: 'network_stack', similarity: 0.78, learned_at: timestamp('2024-01-15')});-- Wire it up through the bipartite backboneMATCH (a:EntityNode {id: 'tcp_ip'}), (s:SimilarEdge {id: 'sim_tcp_http_001'})CREATE (a)-[:FROM_LINK]->(s);MATCH (s:SimilarEdge {id: 'sim_tcp_http_001'}), (b:EntityNode {id: 'http_protocol'})CREATE (s)-[:TO_LINK]->(b);"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Here’s the same data in Turtle:"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "plaintext",
"plaintext": "@prefix ldb: <http://ladybugdb.org/ontology#> .@prefix sst: <http://ladybugdb.org/sst#> .@prefix data: <http://ladybugdb.org/data/> .@prefix xsd: <http://www.w3.org/2001/XMLSchema#> ."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "plaintext",
"plaintext": "# --- Entity nodes ---data:tcp_ip a ldb:EntityNode ; ldb:label \"TCP/IP Protocol\" ; ldb:kind \"concept\" ; ldb:layer \"domain\" ; ldb:learnedAt \"2024-01-15T00:00:00\"^^xsd:dateTime .data:http_protocol a ldb:EntityNode ; ldb:label \"HTTP Protocol\" ; ldb:kind \"concept\" ; ldb:layer \"domain\" ; ldb:learnedAt \"2024-01-15T00:00:00\"^^xsd:dateTime .# --- Reified edge node ---data:sim_tcp_http_001 a ldb:SimilarEdge ; ldb:layer \"domain\" ; ldb:kind \"structural\" ; sst:context \"network_stack\" ; sst:similarity \"0.78\"^^xsd:double ; ldb:learnedAt \"2024-01-15T00:00:00\"^^xsd:dateTime .# --- Bipartite wiring ---data:tcp_ip ldb:fromLink data:sim_tcp_http_001 .data:sim_tcp_http_001 ldb:toLink data:http_protocol ."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Read it aloud: “TCP/IP links-from a similarity edge node, which links-to HTTP.” The bipartite hop is visible. The edge node carries all the metadata — layer, kind, context, similarity score. No information is lost."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"plaintext": "Step 5: The RDF-star Shortcut (and Why You Might Skip It)"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 157,
"byteStart": 152
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "RDF 1.2 introduces triple terms (the successor to RDF-star’s quoted triples), which let you annotate edges without full reification. The syntax uses << >> delimiters:"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "plaintext",
"plaintext": "@prefix sst: <http://ladybugdb.org/sst#> .@prefix xsd: <http://www.w3.org/2001/XMLSchema#> ."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "plaintext",
"plaintext": "# RDF 1.2 triple term — compact but lossy<< data:tcp_ip sst:similarTo data:http_protocol >> sst:similarity \"0.78\"^^xsd:double ; sst:context \"network_stack\" ; ldb:layer \"domain\" ; ldb:kind \"structural\" ; ldb:learnedAt \"2024-01-15T00:00:00\"^^xsd:dateTime ."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "This is prettier. But there are three reasons your bipartite schema should probably stick with explicit reification:"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 12,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#bold"
}
]
},
{
"index": {
"byteEnd": 81,
"byteStart": 70
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 116,
"byteStart": 94
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "1. Identity. RDF-star triple terms don’t have their own URI. Your SimilarEdge node with id: 'sim_tcp_http_001' is a first-class citizen — you can link to it, query it, expire it independently. A triple term is anonymous. In agent memory architectures, addressable edges matter — you need to update a similarity score when embeddings change, without deleting and recreating the triple."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 21,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#bold"
}
]
},
{
"index": {
"byteEnd": 151,
"byteStart": 48
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "2. Multi-hop queries. In LadybugDB, you write MATCH (a)-[:FROM_LINK]->(e:SimilarEdge)-[:TO_LINK]->(b) WHERE e.similarity > 0.7 RETURN a, b, e.context. In SPARQL over explicit reification, this translates directly. With triple terms, filtering edge metadata requires nested patterns that are harder to compose."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 29,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#bold"
}
]
},
{
"index": {
"byteEnd": 57,
"byteStart": 52
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#italic"
}
]
},
{
"index": {
"byteEnd": 75,
"byteStart": 64
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 90,
"byteStart": 78
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 108,
"byteStart": 93
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 122,
"byteStart": 111
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 244,
"byteStart": 236
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "3. Semantic Spacetime typing. Your edge nodes are typed — SimilarEdge, ContainsEdge, HasPropertyEdge, LeadsToEdge. In RDF-star, the type of the annotation target is the triple itself, not a domain-specific class. You lose the rdf:type signal that makes SPARQL queries like \"find all containment relations in the meta layer\" trivial."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "The bipartite reification pattern and RDF-star serve different consumers. Use explicit reification as your canonical export; offer RDF-star as a convenience view for downstream tools that expect it."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"plaintext": "Step 6: Translate SPARQL ↔ Cypher Queries"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Now let’s make sure you can actually query the exported data. Here are the most common patterns, side by side."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"plaintext": "Find all entities similar to a given concept"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 19,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#bold"
}
]
}
],
"plaintext": "Cypher (LadybugDB):"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "plaintext",
"plaintext": "MATCH (a:EntityNode {id: 'tcp_ip'})-[:FROM_LINK]->(e:SimilarEdge)-[:TO_LINK]->(b:EntityNode)WHERE e.similarity > 0.7RETURN b.label, e.similarity, e.contextORDER BY e.similarity DESC;"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 27,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#bold"
}
]
}
],
"plaintext": "SPARQL (over exported RDF):"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "plaintext",
"plaintext": "PREFIX ldb: <http://ladybugdb.org/ontology#>PREFIX sst: <http://ladybugdb.org/sst#>PREFIX data: <http://ladybugdb.org/data/>"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "plaintext",
"plaintext": "SELECT ?label ?sim ?ctx WHERE { data:tcp_ip ldb:fromLink ?edge . ?edge a ldb:SimilarEdge ; ldb:toLink ?target ; sst:similarity ?sim ; sst:context ?ctx . ?target ldb:label ?label . FILTER (?sim > 0.7)}ORDER BY DESC(?sim)"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 45,
"byteStart": 36
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 64,
"byteStart": 52
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 74,
"byteStart": 67
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 91,
"byteStart": 81
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "The structure is almost identical. FROM_LINK → ldb:fromLink. TO_LINK → ldb:toLink. Filter on edge node properties translates directly."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"plaintext": "Find all containment paths (metagraph traversal)"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 7,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#bold"
}
]
}
],
"plaintext": "Cypher:"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "plaintext",
"plaintext": "MATCH path = (root:EntityNode {id: 'system_arch'}) (-[:FROM_LINK]->(e:ContainsEdge)-[:TO_LINK]->)+ (leaf:EntityNode)WHERE leaf.kind = 'component'RETURN [n IN nodes(path) | n.label] AS hierarchy;"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 29,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#bold"
}
]
}
],
"plaintext": "SPARQL (with property paths):"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "plaintext",
"plaintext": "PREFIX ldb: <http://ladybugdb.org/ontology#>PREFIX data: <http://ladybugdb.org/data/>"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "plaintext",
"plaintext": "SELECT ?leaf ?leafLabel WHERE { data:system_arch (ldb:fromLink/ldb:toLink)+ ?leaf . ?leaf a ldb:EntityNode ; ldb:kind \"component\" ; ldb:label ?leafLabel .}"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 121,
"byteStart": 106
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 220,
"byteStart": 208
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "Wait — this SPARQL property path skips the edge nodes. It jumps directly from entity to entity through fromLink/toLink composition. That's correct for reachability queries, but it loses the intermediate ContainsEdge metadata (layer, probability). For full traversal with edge data, you need recursive CTEs or unrolled patterns:"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "plaintext",
"plaintext": "# Explicit two-hop pattern for accessing edge node propertiesSELECT ?parent ?child ?prob ?layer WHERE { ?parent ldb:fromLink ?edge . ?edge a ldb:ContainsEdge ; ldb:toLink ?child ; sst:probability ?prob ; ldb:layer ?layer .}"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 175,
"byteStart": 170
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "This is the fundamental trade-off: property paths give you transitive closure; explicit patterns give you edge metadata. LadybugDB’s Cypher gives you both in a single MATCH because Kùzu's variable-length paths can filter on intermediate nodes. SPARQL makes you choose."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"plaintext": "Temporal queries: find knowledge valid at a point in time"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 7,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#bold"
}
]
}
],
"plaintext": "Cypher:"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "plaintext",
"plaintext": "MATCH (a:EntityNode)-[:FROM_LINK]->(e)-[:TO_LINK]->(b:EntityNode)WHERE e.learned_at <= timestamp('2024-06-01') AND (e.expired_at IS NULL OR e.expired_at > timestamp('2024-06-01'))RETURN a.label, labels(e)[0] AS relation_type, b.label;"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 7,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#bold"
}
]
}
],
"plaintext": "SPARQL:"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "plaintext",
"plaintext": "PREFIX ldb: <http://ladybugdb.org/ontology#>PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "plaintext",
"plaintext": "SELECT ?aLabel ?edgeType ?bLabel WHERE { ?a ldb:fromLink ?edge . ?edge ldb:toLink ?b ; rdf:type ?edgeType ; ldb:learnedAt ?learned . ?a ldb:label ?aLabel . ?b ldb:label ?bLabel . FILTER (?edgeType != owl:NamedIndividual) FILTER (?learned <= \"2024-06-01T00:00:00\"^^xsd:dateTime) FILTER NOT EXISTS { ?edge ldb:expiredAt ?expired . FILTER (?expired <= \"2024-06-01T00:00:00\"^^xsd:dateTime) }}"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 22,
"byteStart": 5
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 109,
"byteStart": 100
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 149,
"byteStart": 139
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 205,
"byteStart": 196
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "The FILTER NOT EXISTS pattern handles the \"NULL means still valid\" convention from LadybugDB. If expiredAt is absent (never set), the NOT EXISTS block succeeds — the edge is included. If expiredAt exists but is in the future relative to the query date, same result."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"plaintext": "Step 7: Write the Translator"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Here’s a minimal Python script that reads a LadybugDB (Kùzu) database and emits Turtle. This is not production code — it’s a starting point you can adapt."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "plaintext",
"plaintext": "import kuzufrom datetime import datetime"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "plaintext",
"plaintext": "def to_turtle(db_path: str, base_uri: str = \"http://ladybugdb.org/data/\") -> str: \"\"\"Export a LadybugDB bipartite graph to Turtle format.\"\"\" db = kuzu.Database(db_path) conn = kuzu.Connection(db) lines = [] lines.append(f\"@prefix ldb: <http://ladybugdb.org/ontology#> .\") lines.append(f\"@prefix sst: <http://ladybugdb.org/sst#> .\") lines.append(f\"@prefix data: <{base_uri}> .\") lines.append(f\"@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\") lines.append(f\"@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\") lines.append(\"\") # --- Map node table names to RDF classes --- table_class_map = { \"EntityNode\": \"ldb:EntityNode\", \"SimilarEdge\": \"ldb:SimilarEdge\", \"ContainsEdge\": \"ldb:ContainsEdge\", \"HasPropertyEdge\":\"ldb:HasPropertyEdge\", \"LeadsToEdge\": \"ldb:LeadsToEdge\", } # --- Property name mapping (Cypher column → Turtle predicate) --- prop_map = { \"label\": \"ldb:label\", \"kind\": \"ldb:kind\", \"layer\": \"ldb:layer\", \"learned_at\": \"ldb:learnedAt\", \"expired_at\": \"ldb:expiredAt\", \"context\": \"sst:context\", \"similarity\": \"sst:similarity\", \"probability\": \"sst:probability\", \"property_name\": \"sst:propertyName\", } # --- Export nodes --- for table_name, rdf_class in table_class_map.items(): try: result = conn.execute(f\"MATCH (n:{table_name}) RETURN n\") while result.has_next(): row = result.get_next() node = row[0] node_id = node[\"id\"] uri = f\"data:{node_id}\" props = [f\" a {rdf_class}\"] for col_name, predicate in prop_map.items(): if col_name in node and node[col_name] is not None: val = node[col_name] if isinstance(val, datetime): props.append( f' {predicate} \"{val.isoformat()}\"^^xsd:dateTime' ) elif isinstance(val, float): props.append( f' {predicate} \"{val}\"^^xsd:double' ) else: escaped = str(val).replace('\"', '\\\\\"') props.append(f' {predicate} \"{escaped}\"') lines.append(f\"{uri}\") lines.append(\" ;\\n\".join(props) + \" .\") lines.append(\"\") except Exception: pass # Table doesn't exist in this database # --- Export FROM_LINK relationships --- try: result = conn.execute( \"MATCH (a)-[r:FROM_LINK]->(e) RETURN a.id, e.id\" ) while result.has_next(): row = result.get_next() lines.append(f\"data:{row[0]} ldb:fromLink data:{row[1]} .\") except Exception: pass # --- Export TO_LINK relationships --- try: result = conn.execute( \"MATCH (e)-[r:TO_LINK]->(b) RETURN e.id, b.id\" ) while result.has_next(): row = result.get_next() lines.append(f\"data:{row[0]} ldb:toLink data:{row[1]} .\") except Exception: pass return \"\\n\".join(lines)if __name__ == \"__main__\": import sys db_path = sys.argv[1] if len(sys.argv) > 1 else \"./ladybug_db\" print(to_turtle(db_path))"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Run it:"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "plaintext",
"plaintext": "python ladybug_to_turtle.py ./my_memory_graph > export.ttl"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Load the result into any triple store — Apache Jena, Oxigraph, Stardog, GraphDB — and query with SPARQL."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"plaintext": "The Translation Cheat Sheet"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Here’s the complete mapping for quick reference."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 26,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#bold"
}
]
}
],
"plaintext": "Schema-level translations:"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 25,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 49,
"byteStart": 32
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "CREATE NODE TABLE X (...) → ldb:X a owl:Class — each table becomes a class."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 21,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 52,
"byteStart": 43
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 70,
"byteStart": 68
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#italic"
}
]
}
],
"plaintext": "id STRING PRIMARY KEY → the URI suffix data:{id} — identity is the URI, no separate property needed."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 12,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 34,
"byteStart": 19
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "label STRING → ldb:label \"...\" — string columns become datatype properties."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 20,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 60,
"byteStart": 27
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "learned_at TIMESTAMP → ldb:learnedAt \"...\"^^xsd:dateTime — temporal provenance with XSD typing."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 17,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 57,
"byteStart": 24
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "similarity DOUBLE → sst:similarity \"0.78\"^^xsd:double — numeric columns become typed literals."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 24,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#bold"
}
]
}
],
"plaintext": "Structural translations:"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 44,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 84,
"byteStart": 51
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "CREATE REL TABLE FROM_LINK (FROM A TO B|C|D) → ldb:fromLink a owl:ObjectProperty — polymorphism maps to union domain/range."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 42,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 80,
"byteStart": 49
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "CREATE REL TABLE TO_LINK (FROM B|C|D TO A) → ldb:toLink a owl:ObjectProperty — same pattern, reversed direction."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 21,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 54,
"byteStart": 28
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "(a)-[:FROM_LINK]->(e) → data:a ldb:fromLink data:e — a simple triple assertion."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 19,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 50,
"byteStart": 26
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "(e)-[:TO_LINK]->(b) → data:e ldb:toLink data:b — same, other side of the bipartite hop."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 25,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#bold"
}
]
}
],
"plaintext": "Query-level translations:"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 26,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 74,
"byteStart": 33
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "WHERE e.expired_at IS NULL → FILTER NOT EXISTS { ?e ldb:expiredAt ?x } — open-world absence handling."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 53,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 92,
"byteStart": 60
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "MATCH path = (a)(-[:FROM_LINK]->(e)-[:TO_LINK]->)+(b) → ?a (ldb:fromLink/ldb:toLink)+ ?b — property path composition, but note: this loses edge node data."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"plaintext": "What You Gain, What You Lose"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 8,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#bold"
}
]
}
],
"plaintext": "You gain interoperability with the entire Semantic Web stack — SPARQL federation, OWL reasoning, SHACL validation, linked data publication, and integration with knowledge graphs that speak RDF natively. If a collaborator uses Protégé, Wikidata, or any SPARQL endpoint, your LadybugDB memory graph becomes queryable without re-implementing the whole pipeline."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 8,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#bold"
}
]
},
{
"index": {
"byteEnd": 98,
"byteStart": 58
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 235,
"byteStart": 223
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "You gain a formal ontology. The OWL class hierarchy — SimilarEdge rdfs:subClassOf sst:EdgeNode — enables reasoning that LadybugDB's schema enforcement doesn't provide. An OWL reasoner can infer that any property of sst:EdgeNode applies to all four edge types without writing explicit queries."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 8,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#bold"
}
]
},
{
"index": {
"byteEnd": 78,
"byteStart": 72
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "You lose write performance. LadybugDB writes edge-node triples in one CREATE statement backed by columnar storage. A triple store turns that into 8–12 individual triples (class assertion + properties + two link assertions) indexed across multiple B-trees."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 8,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#bold"
}
]
},
{
"index": {
"byteEnd": 140,
"byteStart": 115
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 175,
"byteStart": 164
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 192,
"byteStart": 182
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 210,
"byteStart": 199
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#italic"
}
]
}
],
"plaintext": "You lose the bipartite guarantee at write time. In LadybugDB, Kùzu’s rel table constraints physically prevent EntityNode → EntityNode connections. In RDF, rdfs:domain and rdfs:range are descriptive — they inform reasoners but don't block invalid writes. You'd need SHACL shapes to enforce the bipartite constraint:"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "plaintext",
"plaintext": "@prefix sh: <http://www.w3.org/ns/shacl#> ."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "plaintext",
"plaintext": "ldb:BipartiteFromShape a sh:NodeShape ; sh:targetSubjectsOf ldb:fromLink ; sh:class ldb:EntityNode .ldb:BipartiteToShape a sh:NodeShape ; sh:targetObjectsOf ldb:toLink ; sh:class ldb:EntityNode ."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 8,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#bold"
}
]
}
],
"plaintext": "You lose nothing conceptually. The bipartite pattern, Semantic Spacetime’s four edge types, temporal validity, layered clustering — all of it roundtrips cleanly. The translation is lossless at the data level. The differences are in enforcement style and query ergonomics."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"plaintext": "When to Export, When to Stay"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Export to RDF when:"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.unorderedList",
"children": [
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "You need to publish your knowledge graph as linked data"
}
},
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Downstream tools expect SPARQL (most enterprise knowledge management platforms do)"
}
},
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "You’re federating with external knowledge graphs (Wikidata, DBpedia, domain ontologies)"
}
},
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "You want OWL reasoning or SHACL validation on your schema"
}
}
]
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Stay in LadybugDB when:"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.unorderedList",
"children": [
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Your graph is an agent’s local memory (embedded, fast, single-writer)"
}
},
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "You need sub-millisecond traversal for real-time decision-making"
}
},
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "The bipartite constraint is safety-critical and must be enforced at write time"
}
},
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "You’re running on an edge device where a triple store won’t fit"
}
}
]
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "The two are not mutually exclusive. LadybugDB is the engine; RDF is the export format. Build your agent memory in the property graph. Publish it to the knowledge graph when the world needs to see it."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 30,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#italic"
}
]
}
],
"plaintext": "This article is a companion to"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"src": "https://leanpub.com/ladybugdb?source=post_page-----7c5eb1f09c7c---------------------------------------",
"$type": "pub.leaflet.blocks.website",
"title": "LadybugDB for Edge Agent AI memory",
"description": "Seasoned Developer's Journey from COBOL to Web 3.0, SSI, Privacy First Edge AI, and Beyond",
"previewImage": {
"$type": "blob",
"ref": {
"$link": "bafkreigjfy2z727eph5khsjw6oh3vkp7bqfuvsmfvec5ob4dm4gx64rzue"
},
"mimeType": "image/png",
"size": 38679
}
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 204,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#italic"
}
]
}
],
"plaintext": "which covers the full LadybugDB schema design — bipartite graphs, Semantic Spacetime ontology, Promise Graphs, hypergraphs, metagraphs, and agentic memory architecture. The Leanpub edition gets updates "
}
}
]
}
]
},
"bskyPostRef": {
"cid": "bafyreihnkcgwaunc2up5sbrzu3uxi6wzxxpt7poro5rtgniklamrp2fli4",
"uri": "at://did:plc:p7sxjpo2opcfkn7cgi5jqyqi/app.bsky.feed.post/3mhrzfb2k6c2h",
"commit": {
"cid": "bafyreibiip27ipmbs7c5auso7lbu4n7l2lzdyfhrgchfyrlbynyq5hwrra",
"rev": "3mhrzfb4ztm2x"
},
"validationStatus": "valid"
},
"publishedAt": "2026-03-24T07:09:38.640Z"
}