Summary
The @ruvector/graph-node native binding (probed via @ruvector/graph-node-darwin-arm64@0.1.0 / etc.) exposes write-side primitives but no delete:
> Object.getOwnPropertyNames(GraphDatabase.prototype)
[ 'createEdge', 'createHyperedge', 'createNode', 'searchHyperedges' ]
That makes the binding a write-only graph store from a consumer's perspective — there's no way to remove a node, an edge, or a hyperedge once it's persisted.
Concrete downstream impact
In ruvnet/ruflo (issue #1784, PR #1789) the agentdb_hierarchical-store and agentdb_causal-edge MCP tools route writes through the bridge into either:
- (a) the AgentDB SQL fallback (
memory_entries table — soft-deletable), or
- (b) the
@ruvector/graph-node native backend when available (this binding).
When (b) is the active backend, the cli has no way to propagate a delete. The ruflo-adr plugin's /adr-index skill ends up with stale ADR nodes and dangling supersedes / amends / related / depends-on edges every time a docs/adr/*.md file is removed from disk. There's no first-class workaround at the cli's MCP layer.
We shipped a partial fix (PR #1789) that surfaces controller: "native-unsupported" honestly when delete is requested on the native backend, but the real fix has to happen here in the binding.
What's needed in @ruvector/graph-node
Three new methods on GraphDatabase.prototype:
// Delete a node and (optionally) cascade to its incident edges.
// Returns the count of deleted edges so callers can audit.
deleteNode(id: string, opts?: { cascade?: boolean }): Promise<{ deletedNode: boolean; deletedEdges: number }>;
// Delete a single edge by id.
deleteEdge(id: string): Promise<{ deleted: boolean }>;
// Delete a hyperedge by id.
deleteHyperedge(id: string): Promise<{ deleted: boolean }>;
Symmetric to the existing createNode / createEdge / createHyperedge. Cascade semantics on deleteNode are the most important — without them, callers have to manually enumerate edges before deleting the node, which is racy in any concurrent context.
Bonus (lower priority)
If feasible at the storage layer:
deleteEdgesByEndpoints(from: string, to: string, label?: string) — delete edges by (source, target, label) tuple. The cli currently has to materialize full edge IDs; a tuple-based delete would let the bridge avoid stale-ID lookups.
deleteHyperedgesContaining(nodeId: string) — useful when removing an agent from all swarm-team hyperedges in a single call.
Verification once landed
The cli's downstream PR (#1789) has stub branches at bridgeDeleteCausalNode / bridgeDeleteCausalEdge that try causalGraph.removeEdge() / db.deleteNode() etc. before falling back to SQL. Once the binding ships these, the cli can re-target those branches at the native API and the SQL fallback becomes a true fallback (only fires when the binding isn't loaded).
Affected versions
Probed against:
@ruvector/graph-node-darwin-arm64@0.1.0
@ruvector/graph-node@0.1.0 (parent meta)
Both are the latest at time of this report.
Repro
node -e "
const m = require('@ruvector/graph-node-darwin-arm64');
console.log(Object.getOwnPropertyNames(m.GraphDatabase.prototype));
// → [ 'createEdge', 'createHyperedge', 'createNode', 'searchHyperedges' ]
// (no delete*, no remove*)
"
References
Summary
The
@ruvector/graph-nodenative binding (probed via@ruvector/graph-node-darwin-arm64@0.1.0/ etc.) exposes write-side primitives but no delete:That makes the binding a write-only graph store from a consumer's perspective — there's no way to remove a node, an edge, or a hyperedge once it's persisted.
Concrete downstream impact
In
ruvnet/ruflo(issue #1784, PR #1789) theagentdb_hierarchical-storeandagentdb_causal-edgeMCP tools route writes through the bridge into either:memory_entriestable — soft-deletable), or@ruvector/graph-nodenative backend when available (this binding).When (b) is the active backend, the cli has no way to propagate a delete. The
ruflo-adrplugin's/adr-indexskill ends up with stale ADR nodes and danglingsupersedes/amends/related/depends-onedges every time adocs/adr/*.mdfile is removed from disk. There's no first-class workaround at the cli's MCP layer.We shipped a partial fix (PR #1789) that surfaces
controller: "native-unsupported"honestly when delete is requested on the native backend, but the real fix has to happen here in the binding.What's needed in
@ruvector/graph-nodeThree new methods on
GraphDatabase.prototype:Symmetric to the existing
createNode/createEdge/createHyperedge. Cascade semantics ondeleteNodeare the most important — without them, callers have to manually enumerate edges before deleting the node, which is racy in any concurrent context.Bonus (lower priority)
If feasible at the storage layer:
deleteEdgesByEndpoints(from: string, to: string, label?: string)— delete edges by(source, target, label)tuple. The cli currently has to materialize full edge IDs; a tuple-based delete would let the bridge avoid stale-ID lookups.deleteHyperedgesContaining(nodeId: string)— useful when removing an agent from all swarm-team hyperedges in a single call.Verification once landed
The cli's downstream PR (#1789) has stub branches at
bridgeDeleteCausalNode/bridgeDeleteCausalEdgethat trycausalGraph.removeEdge()/db.deleteNode()etc. before falling back to SQL. Once the binding ships these, the cli can re-target those branches at the native API and the SQL fallback becomes a true fallback (only fires when the binding isn't loaded).Affected versions
Probed against:
@ruvector/graph-node-darwin-arm64@0.1.0@ruvector/graph-node@0.1.0(parent meta)Both are the latest at time of this report.
Repro
References
ruvnet/ruflo/v3/@claude-flow/cli/src/ruvector/graph-backend.ts(already hasaddNode/addEdge/addHyperedgewrappers waiting fordeleteNode/deleteEdge/deleteHyperedgesiblings)