b77f537dc6
- neuron-runtime, neuron-store, neuron-migrate: fix path deps (products/engram/ → foundation/engram/engrams/, products/el/ → foundation/el/engrams/) - neuron-rs workspace: fix axon-events/axon-protocol paths (../../../platform/ → ../../platform/, paths were off by one level) - neuron-lang/compiler/ removed (el self-hosting compiler now lives in foundation/el/el-compiler/)
118 lines
4.7 KiB
EmacsLisp
118 lines
4.7 KiB
EmacsLisp
// graph.el — Graph operations subsystem.
|
|
//
|
|
// Neuron's storage backend is Engram — a graph database where every entity
|
|
// (Memory, Knowledge, BacklogItem, etc.) is a node and every relationship
|
|
// is a typed edge. This subsystem exposes the raw graph API for operations
|
|
// that span entity types or need structural graph information.
|
|
//
|
|
// WHY expose graph ops directly?
|
|
// High-level tools like search_memories or retrieve_knowledge hide the graph
|
|
// structure. But some questions are inherently graph-shaped:
|
|
// "What entities are connected to this decision?"
|
|
// "How deep is the dependency chain for this backlog item?"
|
|
// "Are these two memories causally related?"
|
|
//
|
|
// Graph ops answer these questions without the overhead of entity-typed
|
|
// queries. They're the escape hatch when you need to see the whole picture.
|
|
//
|
|
// link_entities is a @manager because it mutates the graph topology.
|
|
// All reads are @accessor.
|
|
|
|
from types import {
|
|
GraphNode,
|
|
SearchResult,
|
|
NeuronError,
|
|
}
|
|
|
|
// ── Public API ────────────────────────────────────────────────────────────────
|
|
|
|
// inspect_graph — get a node and its immediate connections.
|
|
//
|
|
// entity_type: "Memory" | "Knowledge" | "BacklogItem" | "Context" | "Artifact" | ...
|
|
// entity_id: the UUID or string ID of the entity.
|
|
//
|
|
// Returns the node and its edges formatted as "relation:target_id" strings.
|
|
// Use this to understand what a node is connected to before traversing deeper.
|
|
@accessor
|
|
fn inspect_graph(entity_type: String, entity_id: String) -> Result<GraphNode, NeuronError> {
|
|
let node = native_graph_inspect(entity_type, entity_id)?
|
|
Ok(node)
|
|
}
|
|
|
|
// traverse_graph — walk the graph from a starting node up to depth hops.
|
|
//
|
|
// depth=1 returns immediate neighbors only (equivalent to inspect_graph).
|
|
// depth=2 returns neighbors of neighbors, etc.
|
|
// Default depth is 2 — deep enough for most dependency chains.
|
|
//
|
|
// Returns all visited nodes, not just leaves. This gives the caller the
|
|
// full subgraph around the starting node.
|
|
@accessor
|
|
fn traverse_graph(entity_id: String, depth: Int) -> Result<[GraphNode], NeuronError> {
|
|
let effective_depth = if depth == 0 { 2 } else { depth }
|
|
let nodes = native_graph_traverse(entity_id, effective_depth)?
|
|
Ok(nodes)
|
|
}
|
|
|
|
// link_entities — create a typed edge between two graph nodes.
|
|
//
|
|
// relation examples: "supersedes", "depends_on", "caused_by", "related_to",
|
|
// "implements", "references", "blocks"
|
|
//
|
|
// Edges are directional: source --relation--> target.
|
|
// Use causal links for reasoning chains, dependency links for work items,
|
|
// supersedes links for knowledge evolution.
|
|
@manager
|
|
fn link_entities(
|
|
source_id: String,
|
|
target_id: String,
|
|
relation: String,
|
|
) -> Result<GraphNode, NeuronError> {
|
|
native_graph_link(source_id, target_id, relation)?
|
|
native_emit("graph.linked", {"source": source_id, "target": target_id, "relation": relation})
|
|
// Return the source node with its updated edges.
|
|
let source_node = native_graph_inspect("", source_id)?
|
|
Ok(source_node)
|
|
}
|
|
|
|
// search_graph — semantic search across all node types in the graph.
|
|
//
|
|
// Unlike type-specific search (search_memories, search_knowledge), this
|
|
// searches the entire graph regardless of entity type. Useful for
|
|
// cross-cutting queries: "what's related to authentication across all
|
|
// memories, knowledge, and backlog items?"
|
|
@accessor
|
|
fn search_graph(query: String) -> Result<[SearchResult], NeuronError> {
|
|
let results = native_semantic_search(query, "", 20)?
|
|
Ok(results)
|
|
}
|
|
|
|
// rebuild_graph — trigger a full graph index rebuild.
|
|
//
|
|
// Used after bulk imports or when the semantic index drifts out of sync.
|
|
// This is an expensive operation — avoid in normal sessions.
|
|
// It's sealed because it modifies the graph infrastructure, not just data.
|
|
@manager
|
|
fn rebuild_graph() -> Result<String, NeuronError> {
|
|
sealed {
|
|
native_emit("graph.rebuild_requested", {})
|
|
Ok("graph rebuild initiated")
|
|
}
|
|
}
|
|
|
|
// pin_node — mark a graph node as pinned (exempt from compaction).
|
|
//
|
|
// Pinned nodes survive graph compaction even when their activation scores
|
|
// drop below the compaction threshold. Use for foundational knowledge nodes
|
|
// that must never be evicted.
|
|
@manager
|
|
fn pin_node(entity_id: String, entity_type: String) -> Result<GraphNode, NeuronError> {
|
|
sealed {
|
|
// Pinning is implemented as a special self-referential "pinned" edge.
|
|
native_graph_link(entity_id, entity_id, "pinned")?
|
|
native_emit("graph.node_pinned", {"id": entity_id, "type": entity_type})
|
|
let node = native_graph_inspect(entity_type, entity_id)?
|
|
Ok(node)
|
|
}
|
|
}
|