The Concept Ontology
The Concept Ontology is a version-controlled graph that provides the semantic stratum between source code and learning content. It is the foundation for the Dream Pipeline's deterministic documentation gap detection.
The Problem It Solves
The GapInferenceEngine (Phase 4 of the Dream Pipeline) needs to detect which parts of Neo.mjs lack adequate documentation. The original approach used regex-based token matching against file paths:
// OLD: Fragile regex token matching
const hasGuide = guideFilePaths.some(p => nodeTokens.some(term => regex.test(p)));This fails structurally — "Reactivity.md" never token-matches "Neo.button.Base".
The Concept Ontology solves this by introducing CONCEPT nodes as first-class entities
that bridge the gap between implementation files and learning guides:
// NEW: Graph traversal
const explanations = conceptService.getEdges(conceptId, 'EXPLAINED_BY');
const hasGuide = explanations.length > 0;A concept has a GUIDE_GAP if it has zero EXPLAINED_BY edges. This is deterministic,
semantically correct, and requires no embedding comparison.
What Is a Concept?
A concept is an abstract architectural idea that:
- Has a name and a hierarchical position in the knowledge tree
- Can be implemented by one or more source files
- Can be explained by one or more learning guides
- Has a tier reflecting its importance to the platform's identity
- Is connected to other concepts via typed relationships
Concepts vs. Classes
| Class | Concept | |
|---|---|---|
| Identity | Neo.core.Base |
"Instance Lifecycle" |
| Nature | Implementation artifact | Architectural idea |
| Guides map to | ❌ Not directly (many-to-many) | ✅ Directly (1-to-many) |
| Source files map to | ✅ 1-to-1 | ✅ 1-to-many |
Every class doesn't deserve a guide, but every concept deserves at least one. The concept layer is the intermediary that makes gap detection semantically meaningful.
The Teaching Test
A concept is included in the ontology only if it passes all three criteria:
- A developer needs to understand it to use Neo.mjs productively
- It cannot be learned by simply reading one API doc page
- It answers "how" or "why" questions, not just "what" questions
| ✅ Passes | ❌ Fails |
|---|---|
| "Two-Tier Reactivity" (architectural model) | Neo.util.Array (utility, API-doc-sufficient) |
| "Off-Main-Thread Execution" (mental model shift) | "Portal App About Us View" (app-specific) |
| "Config Descriptors & Merge Strategies" (complex system) | afterSetWidth (lifecycle hook instance) |
Storage Format
The concept graph is stored as JSONL files at .neo-ai-data/concepts/:
.neo-ai-data/concepts/ ├── nodes.jsonl # One concept node per line └── edges.jsonl # One relationship edge per line
Why JSONL, Not JSON or SQLite
- Git-friendly: Each line is an independent record. Adding a concept = adding a line. No structural merge conflicts.
- PR-reviewable:
git diffshows exactly which concepts were added/modified/removed. - Streaming: Can be processed line-by-line without loading the entire graph into memory.
- Decoupled: Independent of the Native Edge Graph (SQLite), which is in flux due to the Multi-Tenant Memory Core migration.
Node Schema
Each line in nodes.jsonl is a JSON object:
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | ✅ | Kebab-case unique identifier (e.g., "multi-threading") |
name |
string | ✅ | Human-readable display name |
tier |
number | ✅ | Importance tier (see Tiering System below) |
description |
string | ✅ | One-paragraph explanation of the concept |
uniqueToNeo |
boolean | ✅ | true if architecturally unique to Neo.mjs |
tags |
string[] | ✅ | Categorization tags for search and filtering |
aliases |
string[] | ❌ | Alternative terms that refer to the exact same concept (O(1) lookup) |
verifiedAt |
string | null | ❌ | ISO date string for the last source-grounded verification, or null / missing when never explicitly verified |
extraction_metadata |
object | null | ❌ | Present only on LLM-mined candidate rows (written by ConceptDiscoveryService): the extraction pass's objective self-report {missing_fields, ambiguous_references, confidence_score}, denormalized onto each candidate it produced. JSONL-only — not projected to graph node properties or the ConceptIngestor payload hash; legacy rows without it load unchanged. |
{"id":"off-main-thread","name":"Off-Main-Thread Execution","tier":1,"description":"Application business logic runs inside a dedicated App Worker.","uniqueToNeo":true,"tags":["architecture","workers"],"aliases":["off the main thread","OMT"],"verifiedAt":null}
{"id":"mined-example","name":"Mined Example","tier":3,"description":"An LLM-mined candidate awaiting curator review.","uniqueToNeo":false,"tags":["mined-candidate"],"aliases":[],"verifiedAt":null,"extraction_metadata":{"missing_fields":[],"ambiguous_references":["'the module' — three modules exist"],"confidence_score":0.7}}[!IMPORTANT] Aliases are strict synonyms within Neo.mjs. A term qualifies as an alias only if it refers to the exact same architectural concept. Cross-framework terms (e.g., "ViewModel" for State Provider, "JSX" for JSON VDOM) are not aliases — they belong in
ANALOGOUS_TOedges.
[!IMPORTANT] Freshness metadata is non-destructive.
verifiedAtexists to build a review queue: concepts withnull, missing, invalid, or older-than-90-day values emit aCONCEPT_REVERIFY_DUEhandoff signal. This must not fade graph nodes, weaken edges, reduce concept weight, or auto-retire concepts. A stale verification date means "check this against current repo reality"; it is not evidence that the concept lost value. Existing committed ontology nodes start with explicitverifiedAt: nullso the first source-grounding pass can be queried directly from the data file.
Auto-Extracted Concept Provenance
Concepts in the Native Edge Graph arrive via two distinct paths, and consumers MUST be able to distinguish them when scoring, filtering, or ranking results.
| Path | Trigger | Node property | Edge weight |
|---|---|---|---|
| Manual / curated | Operator or agent calls addMessage({taggedConcepts: [...]}) (or any other concept-tagging API) with explicit concept IDs |
absent (concept node may pre-exist with no auto_extracted flag) |
1.0 |
| Auto-extracted | MailboxService.addMessage runs SemanticGraphExtractor.extractMessageConcepts(body) as a fire-and-forget post-write; LLM-derived CONCEPT:* / CLASS:* IDs are upserted with properties.auto_extracted: true |
properties.auto_extracted = true on the CONCEPT or CLASS node when the node is freshly created by this path |
0.8 on the TAGGED_CONCEPT edge |
Write Path
MailboxService.addMessagepersists the MESSAGE node + recipient edges.- Synchronously links manual
taggedConceptswithTAGGED_CONCEPTweight1.0. - Asynchronously fires
SemanticGraphExtractor.extractMessageConcepts(bodyText)— LLM call against the OpenAI-compatible chat provider. Returns 1-5 inferred concept IDs. - For each extracted ID: upsert the concept node with
properties.auto_extracted: true(only when freshly created — pre-existing nodes are NOT re-stamped), then link withTAGGED_CONCEPTweight0.8.
The two paths emit the same edge type (TAGGED_CONCEPT); provenance lives in the edge weight + the node-side auto_extracted flag.
Read-Time Consumer Pattern
Downstream consumers (DreamService topology synthesis, Librarian sub-agent traversal, future GraphRAG query layer) reading concept nodes from the graph SHOULD inspect both signals when ranking or filtering:
- For ranking — weight curated concepts higher than auto-extracted (use the edge weight directly: 1.0 vs 0.8 is already calibrated for this).
- For filtering — to exclude auto-extracted entirely, filter
node.properties.auto_extracted !== true. To include only auto-extracted (e.g., to audit LLM output), filternode.properties.auto_extracted === true. - For provenance audits —
auto_extracted: trueis the durable signal that the concept entered the graph via LLM inference rather than human/agent curation. Useful for post-incident reasoning about graph noise.
Edge-Weight Convention Rationale
The 0.8 / 1.0 split is deliberate: a TAGGED_CONCEPT edge from a curated source is 20% stronger than an auto-extracted edge with the same source MESSAGE node. This calibration matches the operator-observed truthfulness gap between LLM concept inference (high recall, moderate precision) and human/agent curation (lower recall, near-perfect precision).
Consumers SHOULD prefer reading the edge weight over the node-side flag when both are available, since edge weights propagate naturally through graph traversal scoring (vs the flag requiring an extra lookup at scoring time). Reserve the node-side flag for filtering and provenance audits.
[!IMPORTANT] Pre-existing concept nodes retain their original provenance. When
extractMessageConceptsreturns aCONCEPT:*/CLASS:*ID that already exists in the graph (e.g., a high-tier concept seeded innodes.jsonl), the upsert path does NOT overwriteproperties.auto_extracted. The flag is set only when the node is freshly created by the auto-extraction path. This preserves curated concepts' status as authoritative even when they're subsequently mentioned by LLM-extracted MESSAGE bodies.
Edge Schema
Each line in edges.jsonl is a JSON object:
| Field | Type | Required | Description |
|---|---|---|---|
source |
string | ✅ | Source node ID (concept or file reference) |
target |
string | ✅ | Target node ID (concept, file reference, or ext: external ID) |
type |
string | ✅ | Relationship type (see Edge Types below) |
note |
string | ❌ | Architectural distinction note (used with ANALOGOUS_TO) |
Edge Types
| Type | Direction | Meaning |
|---|---|---|
PARENT_CONCEPT |
parent → child | Hierarchical grouping |
IMPLEMENTED_BY |
concept → file | Source file that implements the concept |
EXPLAINED_BY |
concept → file | Guide/doc that explains the concept |
EXEMPLIFIED_BY |
concept → file | Example that demonstrates the concept |
REQUIRES |
concept → concept | Prerequisite (must understand A before B) |
ANALOGOUS_TO |
concept → ext:id | Cross-framework analogue (not equivalence) |
File Reference Format
File targets use the file: prefix with a repository-relative path:
{"source":"push-reactivity","target":"file:src/Neo.mjs","type":"IMPLEMENTED_BY"}
{"source":"push-reactivity","target":"file:learn/guides/coreengine/ConfigSystem.md","type":"EXPLAINED_BY"}External Reference Format
External (cross-framework) targets use the ext: prefix to prevent collision with
internal concept IDs:
{"source":"state-provider","target":"ext:react-context","type":"ANALOGOUS_TO","note":"Both provide hierarchical state, but Neo.mjs providers use bind:{} on reactive configs — no subtree re-rendering."}[!WARNING]
ANALOGOUS_TOexpresses architectural similarity, not equivalence. Thenotefield must explain how the Neo.mjs concept differs from its cross-framework analogue. Never use this edge to suggest that concepts are interchangeable.
Tiering System
| Tier | Weight | Description | Gap Severity |
|---|---|---|---|
| 0 | — | System anchor (Neo.mjs itself) | N/A |
| 1 | ≥ 0.9 | Platform identity concepts | CRITICAL if undocumented |
| 2 | 0.5–0.8 | Major subsystem concepts | HIGH if undocumented |
| 3 | 0.1–0.4 | Implementation-level concepts | MEDIUM if undocumented |
The Concept Hierarchy (Abbreviated)
✅ = has at least one EXPLAINED_BY edge. Missing ✅ = GUIDE_GAP candidate.
Contributing a Concept
- Add a single line to
nodes.jsonlfollowing the node schema - Add
PARENT_CONCEPTedge(s) toedges.jsonlto place it in the hierarchy - Add
EXPLAINED_BYedges for any existing guides that cover the concept - Add
IMPLEMENTED_BYedges for source files that implement it - Verify the concept passes the Teaching Test
JSONL Format Rules
- One JSON object per line — no multi-line JSON
- No trailing commas — strict JSON per line
- Git-friendly — each line is an independent record, minimizing merge conflicts
- Append-only preferred — add new lines rather than reordering existing ones