Skip to content

RFC 019: Extended Semantic Annotations & Graph Enrichments

RFC 019: Extended Semantic Annotations & Graph Enrichments

Section titled “RFC 019: Extended Semantic Annotations & Graph Enrichments”

With the removal of the hardcoded entities registry, the compiler achieved schema neutrality. However, the current inline annotation for speakers (e.g., `dhritarashtra `uvacha) is suboptimal due to scope bleeding, text pollution, and addressing limitations. Furthermore, we need a robust, standard mechanism to store graph enrichments post-publication, especially for complex texts with nested narratives (like the Yogavāsiṣṭha).

This design proposes a dual-path architecture for defining semantic annotations and leveraging standard graph query languages for enrichments.


1. Core Architecture: Dual-Path Semantic Annotations

Section titled “1. Core Architecture: Dual-Path Semantic Annotations”

To satisfy both rapid authoring and large-scale lineage management, Vyasa will support both inline and out-of-band annotations. Both paths will compile down into the exact same underlying SQLite representation.

Section titled “1.1 Out-of-Band Annotations (Recommended for Lineage)”

Out-of-band files (e.g., annotations.vy) allow assigning metadata to explicit URN ranges without polluting the source text.

`annotate-range {
scope = "1:3-1:5"
type = "speaker"
id = "sanjaya"
}

1.2 Inline Annotations (Syntactically Native)

Section titled “1.2 Inline Annotations (Syntactically Native)”

For small works or rapid authoring, linguists require an inline shorthand. However, defining complex key-value maps inline pollutes the primary stream corpus.

We will introduce a Semantic Alias syntax that implicitly binds only to the immediately following addressable block. Users define the complex mapping once out-of-band, and use a highly compact single-line marker in the text.

Out-of-band definition:

`define-alias {
id = "sanjaya.uvaca"
type = "event"
speaker = "sanjaya"
action = "uvaca"
}

Inline usage (Single Block):

`[[ sanjaya.uvaca ]]
`v 3 [
dṛṣṭvā tu pāṇḍavānīkaṁ ...
]

If the open bracket [ is not present, the alias applies only to the immediately following block.

Inline usage (Multi-Block Scope): Vyasa grammar naturally supports block delimiters. We will extend the alias syntax to act as a block boundary itself, allowing users to neatly wrap multiple verses:

`[[ sanjaya.uvaca
`v 3 [ ... ]
`v 4 [ ... ]
]] sanjaya.uvaca

This multi-block scoping keeps the grammar exceptionally clean while preventing users from having to repeat the alias 50 times for a long speech.


2. Interstitial Addressing (The pre vs Numeric Debate)

Section titled “2. Interstitial Addressing (The pre vs Numeric Debate)”

We need a way to address narrator text or interstitial paragraphs that sit outside numbered verses.

Proposed Resolution: We allow the syntactic sugar pre and post in the Vyasa source files (e.g., 1:5:pre). Internally, the compiler translates pre into segment 15 of the prior block. For example, 1:5:pre becomes a shorthand for 1:4.15. This unifies interstitial text and segment addressing seamlessly.

To support intra-block targeting (e.g. half-verses or specific lines), we will enforce a strict limit of 15 segments per content-block.

2.2 High-Performance URN Integer Sorting (Bitwise Encoding)

Section titled “2.2 High-Performance URN Integer Sorting (Bitwise Encoding)”

Graph CTEs execute millions of joins. Using floating point numbers (e.g., 1:4.15) for URN sorting introduces precision risks and is materially slower to index/sort than raw integers. Base-10 multiplier formulas (e.g., Verse * 16) waste space and create gaps.

Instead, the internal URN sorting index uses Bitwise Allocation packed into a highly compact 32-bit integer (u32). The implementation allocates the 32 bits as follows:

  • Segments (Leaf): 4 bits (up to 15 segments/block, with 15 reserved for pre/post)
  • Blocks (Verses): 12 bits (up to 4,095 verses/chapter)
  • Chapters: 8 bits (up to 255 chapters/book)
  • Books: 8 bits (up to 255 books/volume)

By packing the URN via bit-shifting ((Book << 24) | (Chapter << 16) | (Verse << 4) | Segment), we guarantee mathematically pristine, lightning-fast CTE graph queries and range scans, while keeping the database index as compact as possible.


Augmented annotations require a robust graph representation. Rather than inventing a new domain-specific language within Vyasa, we will adopt an industry standard: GQL / SQL:PGQ (Property Graph Queries).

Since Vyasa heavily relies on SQLite, SQL:PGQ allows us to expose our existing relational blocks and streams as a Property Graph without requiring a completely new database engine. Users can author standard .gql files which the Vyasa compiler will validate against known URNs.

3.1 Vyasa Core Graph Ontology (Standard Types)

Section titled “3.1 Vyasa Core Graph Ontology (Standard Types)”

To ensure GQL engines can efficiently index and query the data, we must not leave graph schemas as open-ended strings. Vyasa will define a foundational set of strict Node and Edge types, accommodating Indic taxonomy.

Proposed Node Types:

  • Entity (Super-type for Person, Being, non-human characters, or concepts)
  • Place (Spatial context)
  • Time (Temporal scale or epoch, e.g., Yuga, Kalpa)
  • Event (An action involving participants, strictly associated with a Place and Time. e.g., Akhyana, Charita, Gatha)
  • NarrativeFrame (A story context or factual occurrence. Supports distinguishing Itihāsa vs. Purāna vs. Modern History)

Proposed Edge Types:

  • PARTICIPATES_IN (Entity -> Event)
  • OCCURS_AT (Event -> Place)
  • OCCURS_DURING (Event -> Time)
  • NARRATES (Entity -> NarrativeFrame)
  • CONTAINS (NarrativeFrame -> NarrativeFrame)

3.2 Solving Recursion and Scale (e.g., Yogavāsiṣṭha)

Section titled “3.2 Solving Recursion and Scale (e.g., Yogavāsiṣṭha)”

Texts like the Yogavāsiṣṭha feature “dreams within dreams” and nested stories where characters in a sub-story might share names with characters in the parent story, operating on vastly different scales of space and time.

Using the NarrativeFrame and CONTAINS edge allows arbitrary recursion.

  • An Entity (“Vasistha-Dream”) exists within NarrativeFrame (“Leela’s Story”), which is CONTAINED by NarrativeFrame (“Primary Discourse”).
  • An Event maps to a specific NarrativeFrame, preventing namespace collisions of identical characters across different conceptual layers.

Derived publications must be able to fork or augment annotations (e.g., translating “krishna” to “śrī-bhagavān”).

Because Entity definitions are now compiled as a single distinct record in the graph_nodes table, handling aliases no longer requires complex cross-referencing against downstream dictionary dependencies.

Resolution: Entity Property Aliasing The compiler will natively support aliasing by packing alternative names directly into the properties JSON payload of the Entity node. For example, the out-of-band definition for Kṛṣṇa can define an array of aliases:

{
"aliases": ["śrī-bhagavān", "keshava"]
}

This means the Frontend Viewer and Search features can trivially map an alias back to the canonical ID using a simple database query on the single node record, keeping the compiler fast and linear while fully supporting domain-specific nomenclature.

  1. Zero Runtime Compiler Errors: The core graph processing (vyasac) must remain strongly typed with validation pushed to compilation time.
  2. Unified WASM Runtime: Any graph traversal required by the viewer must be executable via the compiled WASM footprint or pure SQL execution over the SQLite package.
  3. URN Relative Paths: URN paths internal to the core must be fully numeric, representing relative block sequence logic for fast structural sorting (e.g., 1:5.9).
  4. Engine-Agnostic User Investment: Users will invest significant effort in authoring out-of-band semantic manifest files and complex queries. We MUST protect this investment by ensuring that our out-of-band format (.vy-graph) remains decoupled from any specific third-party GQL engine compliance (e.g., DuckDB, Neo4j). Our schema guarantees cross-engine portability by compiling down to standard relational tables and CTEs.

Appendix A: Prototype Findings & Validation

Section titled “Appendix A: Prototype Findings & Validation”

A standalone prototype was built to validate this schema using a mock dataset scaled to the Yogavāsiṣṭha (40,000 nested events, hierarchical NarrativeFrames, and Being participants).

Engine Pivot (DuckDB -> SQLite CTEs): During execution, we discovered that DuckDB’s duckpgq extension is experimental and currently fails to distribute cleanly for all environments. Rather than relying on a brittle third-party extension, we validated the graph schema by running native SQLite Recursive CTEs (WITH RECURSIVE). Because SQL:PGQ natively compiles down to Recursive CTEs, executing this successfully proves the schema’s layout efficiency for any standard GQL engine.

Performance Benchmarks (8MB SQLite cache limit):

  1. Complex Graph Traversal (35.1ms): Multi-hop filtering query over 40,000 nodes (Find all events involving Vasistha-Dream that specifically occurred inside Leela’s Dream) completed and returned 2,678 exact matches in ~35ms.
  2. Recursive Containment (0.16ms): Traversing CONTAINS edges from the primary discourse to find all nested narrative frames at any depth completed in 168 microseconds.

Conclusion: The unified graph_nodes and graph_edges (with properties JSON) mapping is highly viable. The architecture handles massive recursive texts efficiently, proving the design is sound and ready for robust GQL querying while allowing users to safely invest in out-of-band models.