nova kg: the capsule knowledge graph
Part of the NovaFabric CLI reference. Both nova and novafabric run the same binary.
nova kg — Capsule Knowledge Graph (v0.17.0, ADR-0067)
Requires: pip install 'novafabric[scale-kg]' (kuzu>=0.11.3).
The KG is a secondary derived artifact — separate from the per-run lineage graph —
that aggregates observed entity relationships across all captured capsules:
which agents called which models (CALLS), which tools they used (USES_TOOL),
and which inference endpoints they routed to (ROUTES_TO).
Environment variable: NOVA_KG_PATH — override the default KuzuDB path.
nova kg init
Initialise the KG schema at the given path. Idempotent (safe to run multiple times).
nova kg init [--path PATH]| Flag | Default | Description |
|---|---|---|
--path |
.nova/kg/nova_kg.kuzu |
KuzuDB path (also reads NOVA_KG_PATH) |
nova kg init --path /data/nova/kg/nova_kg.kuzu
# KG schema initialised at /data/nova/kg/nova_kg.kuzunova kg status
Show KG store health, total edge count, and per-type node counts in a Rich text panel.
nova kg status [--path PATH]nova kg status
# KG store health: ok
# db_path: .nova/kg/nova_kg.kuzu
# edge_count: 1247
# ┌─ Node counts per layer ──────────────┐
# │ Node type │ Count │
# │ Agent │ 12 │
# │ Model │ 4 │
# │ Tool │ 31 │
# │ MCPServer │ 3 │
# │ InferenceEndpoint │ 2 │
# └──────────────────────────────────────┘nova kg ingest
Ingest events from a capsule directory (or all capsule directories) into the KG.
nova kg ingest [CAPSULE_DIR] [--all] [--source local-dir|nats] [--capsule-dir DIR]
[--path PATH] [--verified/--no-verified]
[--nats-url URL] [--nats-subject SUBJECT]| Argument / Flag | Description |
|---|---|
CAPSULE_DIR |
Path to a single capsule directory (local-dir mode). Omit when using --all or --source nats. |
--all |
Scan and ingest all subdirectories under --capsule-dir (or $NOVAFABRIC_CAPSULE_DIR / $NOVAFABRIC_HOME/capsules). |
--source local-dir|nats |
Ingest from capsule files (default) or drain a NATS JetStream subject once and exit. |
--capsule-dir DIR |
Base directory to scan when using --all. Defaults to $NOVAFABRIC_CAPSULE_DIR or $NOVAFABRIC_HOME/capsules. |
--path |
KuzuDB path (default: .nova/kg/nova_kg.kuzu). |
--verified |
Mark all events as NovaSeal-verified (sets confidence=1.0). |
--nats-url URL |
NATS server URL (used with --source nats). |
--nats-subject SUBJECT |
NATS JetStream subject to consume (used with --source nats). |
--source nats model/tool-call coverage (2026-07-30, ADR-0220 follow-up):
capture/orchestrator.py's real producer now re-emits each locally-captured
model/tool call as a ModelCallCompleted/ModelCallFailed/
ToolCallCompleted/ToolCallFailed spool event once the run finishes (no
*Started variant — the source data has one record per completed/failed
call, never a separate start event), so this ingestion path produces real
CALLS/USES_TOOL edges from real NATS traffic — verified end-to-end by
tests/kg/test_kg.py::test_real_producer_to_kg_pipeline_end_to_end.
EndpointRouted is still never emitted by any producer. local-dir and
--all read the same underlying model-calls.jsonl/tool-calls.jsonl
files directly and were unaffected either way.
# Single capsule
nova kg ingest .novafabric/runs/01HXAY7M --path /data/nova/kg/nova_kg.kuzu
# Ingested 42 events (0 skipped) → wrote 7 KG edges to /data/nova/kg/nova_kg.kuzu
# All capsules in the default capsule directory
nova kg ingest --all
# All capsules in a specific directory (e.g. after a nova-testbench run)
nova kg ingest --all --capsule-dir ~/novafabric-data/capsules
# Bulk ingest complete: 57 capsule(s) scanned · 1423 events ingested · 38 KG edges written · 0 skipped · 0 failed
# Drain a NATS JetStream subject once and exit (see known gap above)
nova kg ingest --source nats --nats-url nats://localhost:4222Supported event types (read from model-calls.jsonl, tool-calls.jsonl, or events.jsonl):
event_type |
Edge created |
|---|---|
ModelCallCompleted, ModelCallStarted |
CALLS (Agent → Model) |
ToolCallCompleted, ToolCallStarted |
USES_TOOL (Agent → Tool); if tool_name contains : (MCP format), also SERVED_BY (Tool → MCPServer) |
EndpointRouted |
ROUTES_TO (Agent → InferenceEndpoint) |
nova serve automatically ingests new capsules at the interval set by
NOVA_KG_INGEST_INTERVAL (default 60 seconds) without requiring manual
nova kg ingest calls. Use nova kg ingest --all (or the dashboard
Re-ingest All button) to trigger an immediate bulk ingest without waiting
for the next auto-ingest tick. Already-ingested directories are tracked in a
SQLite sidecar (ingest_tracker.db) that persists across server restarts.
nova kg build-provenance
Experimental (SPKG, ADR-0111; requires
pip install novafabric[spkg]). Map a capsule's lineage.jsonl to a W3C PROV-O RDF graph — the
canonical semantic layer of the Security & Provenance Knowledge Graph — and SHACL-validate it on the way
out (ADR-0111 R11: invalid provenance facts are rejected). This is the provenance-graph export step; to
populate the operational store used by nova kg detect, attack-path, and blast-radius, use
nova kg build.
nova kg build-provenance CAPSULE_DIR [-o OUTPUT] [--format turtle|nt|json-ld] [--validate/--no-validate]| Argument / Flag | Description |
|---|---|
CAPSULE_DIR |
Path to a capsule directory (reads its lineage.jsonl). |
--output, -o PATH |
Write the RDF graph to this file (default: stdout). |
--format |
RDF serialization: turtle (default), nt, or json-ld. |
--validate / --no-validate |
SHACL-validate the graph (default on). Exit 1 if invalid facts are found (R11 ingest gate). |
Exit codes: 0 (built, and SHACL-valid when --validate), 1 (SHACL validation failed, or the [spkg]
extra is not installed).
nova kg build-provenance .novafabric/runs/01HXAY7M
# ✓ SHACL-valid: 7 PROV-O triples from 01HXAY7M
# @prefix nf: <https://novafabric.io/ns/spkg#> . …
nova kg build-provenance .novafabric/runs/01HXAY7M -o prov.ttl --format turtleFindings emitted by nova kg detect must map to a MITRE ATT&CK technique and/or a D3FEND
countermeasure — a raw anomaly score alone is rejected by the nf:FindingShape SHACL constraint
(ADR-0111 R2).
nova kg build
Experimental (SPKG, ADR-0111; requires
pip install novafabric[spkg]). Build both SPKG layers for a capsule: the canonical W3C PROV-O
RDF (SHACL-gated) and the operational KùzuDB labeled-property graph (LPG) that powers attack-path /
blast-radius traversal. The canonical layer is validated first (ADR-0111 R11) — on failure nothing is
written to the operational store; the LPG is then rebuilt from the same capsule, so it holds no state not
derivable from a capsule (R4). Where build-provenance exports the RDF, build populates the stores.
nova kg build CAPSULE_DIR [--path KG_PATH] [--validate/--no-validate]| Argument / Flag | Description |
|---|---|
CAPSULE_DIR |
Path to a capsule directory (reads its lineage.jsonl). |
--path |
KùzuDB path for the SPKG operational graph, separate from the Capsule KG (default: .nova/kg/spkg.kuzu; env NOVA_SPKG_PATH). |
--validate / --no-validate |
SHACL-gate the canonical layer before writing the LPG (default on). Exit 1 if invalid facts are found (R11). |
Exit codes: 0 (both layers built), 1 (SHACL validation failed, or the [spkg] extra is not installed).
nova kg build .novafabric/runs/01HXAY7M
# ✓ SPKG built from 01HXAY7M (SHACL-valid): 7 PROV-O triples · 3 LPG edges → .nova/kg/spkg.kuzunova kg detect
Experimental (SPKG, ADR-0111). Rank a
capsule's most anomalous lineage edges with an unsupervised, label-free structural outlier detector:
it learns the fleet's own edge distribution (from --baseline capsules, or the target itself) and flags
edges with high combined surprisal (rare edge-type / entity / kind-triple). Every reported edge carries a
MITRE ATT&CK technique (ADR-0111 R2 — never a bare score). This is the dependency-free SP-2 baseline; the
PyGOD/TGN GNN detector is a later, resource-gated upgrade. Needs no optional extra (the detector is
pure standard-library).
nova kg detect CAPSULE_DIR [--baseline DIR ...] [--top/-k K] [--json] [-o OUTPUT]| Argument / Flag | Description |
|---|---|
CAPSULE_DIR |
Capsule directory to score (reads its lineage.jsonl). |
--baseline DIR |
Capsule dir(s) to learn "normal" from — repeatable. Default: self-baseline on the target. |
--top, -k |
Number of most-anomalous edges to report (default 5). |
--json |
Emit schema-valid AnomalyFinding records instead of the table. |
--output, -o PATH |
Write findings JSON here (with --json). |
Exit code 0 (an anomaly scan is informational — a finding is not a failure).
nova kg detect .novafabric/runs/01HXAY7M -k 10
# SPKG anomaly scan — 01HXAY7M (top 10, self-baseline) → ranked table with ATT&CK column
nova kg detect suspect/ --baseline normal-week-1/ --baseline normal-week-2/ --json -o findings.jsonnova kg attack-path
Experimental (SPKG, ADR-0111; requires
pip install novafabric[spkg]). Build the operational graph from a capsule's lineage, then run a bounded
shortest-path query between two entities (UC2 lateral-movement). Entities are kind:ref pairs, e.g.
run:attacker, dataset:aws_credentials. Informational — exit 0 whether or not a path exists.
nova kg attack-path CAPSULE_DIR --from KIND:REF --to KIND:REF [--max-depth N]| Argument / Flag | Description |
|---|---|
CAPSULE_DIR |
Capsule directory whose lineage.jsonl builds the SPKG graph. |
--from KIND:REF |
Source entity, e.g. run:attacker. Required. |
--to KIND:REF |
Target entity, e.g. dataset:aws_credentials. Required. |
--max-depth N |
Maximum path length to search (default: 6). |
nova kg attack-path .novafabric/runs/01HXAY7M \
--from run:attacker --to dataset:aws_credentials
# ⚠ Attack path found: run:attacker → … → dataset:aws_credentials in 3 hop(s)
nova kg attack-path .novafabric/runs/01HXAY7M \
--from agent:a1 --to artifact:report.md --max-depth 4
# ✓ No attack path from agent:a1 to artifact:report.md within 4 hop(s)nova kg blast-radius
Experimental (SPKG, ADR-0111; requires
pip install novafabric[spkg]). Build the operational graph from a capsule's lineage, then traverse the
impact / blast radius of an entity (UC3 supply-chain propagation). --downstream (default) lists
everything reachable from the entity — e.g. every run and artifact a poisoned model touched;
--upstream lists the entity's provenance instead. Prints a Rich table of affected entities (kind, ref).
nova kg blast-radius CAPSULE_DIR --entity KIND:REF [--downstream|--upstream] [--max-depth N]| Argument / Flag | Description |
|---|---|
CAPSULE_DIR |
Capsule directory whose lineage.jsonl builds the SPKG graph. |
--entity KIND:REF |
Entity to analyse, e.g. model:poisoned-model. Required. |
--downstream / --upstream |
--downstream (default): what this entity affects (blast radius, UC3). --upstream: what influenced it (provenance). |
--max-depth N |
Maximum traversal depth (default: 6). |
# What did the poisoned model touch? (downstream / impact)
nova kg blast-radius .novafabric/runs/01HXAY7M --entity model:poisoned-model
# Where did this artifact come from? (upstream / provenance)
nova kg blast-radius .novafabric/runs/01HXAY7M --entity artifact:report.md --upstreamnova kg query
Query models and tools called by a specific agent.
nova kg query AGENT_ID [--path PATH] [--output json|text]nova kg query my-agent --output json
# {
# "agent_id": "my-agent",
# "models": [
# {"model_id": "gpt-4o", "provider": "openai", "call_count": 42, "confidence": 1.0}
# ],
# "tools": [
# {"tool_id": "read_file", "tool_name": "read_file", "call_count": 18, "confidence": 0.0}
# ],
# "mcp_servers": [
# {"server_id": "filesystem", "server_name": "filesystem", "call_count": 18}
# ]
# }The mcp_servers key lists MCPServer nodes reachable via the 2-hop path
Agent → Tool → MCPServer. Empty when no MCP-namespaced tool calls were recorded.
Reference: src/novafabric/kg/, design/adr/0067-capsule-knowledge-graph-v1.md (private).
nova kg audit
Check KG store health: node/edge counts, orphaned edges, and zero-call-count anomalies.
nova kg audit [--path PATH] [--output json|text]| Flag | Default | Description |
|---|---|---|
--path |
.nova/kg/nova_kg.kuzu |
KuzuDB path (also reads NOVA_KG_PATH) |
--output, -o |
text |
Output format: text (Rich) or json |
nova kg audit
# KG store health: ok
# node_agent_count: 12
# node_model_count: 4
# edge_calls_count: 88
# No issues detected.
nova kg audit --output jsonExit code 0 = no issues. Exit code 1 = issues detected.
GET /api/kg/topology (dashboard API)
Returns all KG nodes and edges for multi-layer topology visualization. Requires nova serve to be running with a KG store already initialised.
GET /api/kg/topology?max_nodes=500
Authorization: Bearer <token>Response includes:
nodes— array of{id, type, name?, provider?, url?}for all 5 node types (Agent, Model, MCPServer, Tool, InferenceEndpoint)edges— array of{src, src_type, dst, dst_type, edge_type, call_count, confidence}for CALLS / USES_TOOL / SERVED_BY / ROUTES_TOnode_counts— per-type totals{Agent: N, Model: N, MCPServer: N, Tool: N, InferenceEndpoint: N}edge_counts— per-type totals
Used by the KGTab dashboard panel (Multi-Layer Topology section, lazy-loaded on demand). Response is cached for 30 seconds per serve process.
GET /api/kg/entity-queue (dashboard API)
Return all pending ReviewItem records from the Tier-3 human review queue. Requires nova serve.
GET /api/kg/entity-queue
GET /api/kg/entity-queue/stats
POST /api/kg/entity-queue/{item_id}/approve
POST /api/kg/entity-queue/{item_id}/rejectGET /api/kg/entity-queue returns { ok, count, items[] } where each item has item_id, candidate_name, entity_type, context_run_id, confidence, reason, status, created_at.
GET /api/kg/entity-queue/stats returns { ok, pending, approved, rejected }.
POST .../approve body: { "canonical": "resolved-entity-id", "resolved_by": "username" }.
POST .../reject body: { "resolved_by": "username" } (optional).
Used by the KGTab Entity Review Queue panel in the dashboard (v0.31.0).
GET /api/kg/aliases (dashboard API)
List or register KG alias-table entries. Requires nova serve to be running.
GET /api/kg/aliases[?canonical=<id>] # list all; optional canonical filter
POST /api/kg/aliases # register an aliasPOST body: { "alias": "gpt4", "canonical": "openai/gpt-4", "entity_type": "model" }
Optional POST fields: confidence (float, default 1.0), registered_by (string, default "api").
Used by the KGTab KG Alias Management panel in the dashboard.
nova kg alias list
List all aliases registered for a canonical entity name in the Tier-2 alias table (SQLite-backed, no KuzuDB dependency required).
nova kg alias list CANONICAL [--alias-db PATH] [--output json|text]| Argument / Flag | Default | Env var | Description |
|---|---|---|---|
CANONICAL |
(required) | — | Canonical entity name to look up |
--alias-db |
.nova/kg/alias.db |
NOVA_KG_ALIAS_DB |
SQLite path for the alias table |
--output, -o |
text |
— | Output format: text (Rich table) or json |
nova kg alias list gpt-4o
# Aliases for 'gpt-4o':
# Alias Entity Type Confidence Source Created At
# openai/gpt-4o model 1.000 manual 2026-05-10T…nova kg alias register
Manually register an alias → canonical mapping in the Tier-2 alias table.
nova kg alias register ALIAS CANONICAL [--type model|agent|tool|endpoint|mcp_server]
[--confidence FLOAT] [--alias-db PATH]| Argument / Flag | Default | Description |
|---|---|---|
ALIAS |
(required) | Alias form seen in capsule event payloads |
CANONICAL |
(required) | Canonical entity name to map to |
--type, -t |
model |
Entity type: model, agent, tool, endpoint, or mcp_server |
--confidence, -c |
1.0 |
Confidence score (0.0–1.0) |
--alias-db |
.nova/kg/alias.db |
SQLite path for the alias table |
nova kg alias register "openai/gpt-4o" "gpt-4o" --type model --confidence 1.0
# Registered: 'openai/gpt-4o' → 'gpt-4o' (type=model, confidence=1.000)
nova kg alias register "filesystem" "nova-mcp-filesystem" --type mcp_server
# Registered: 'filesystem' → 'nova-mcp-filesystem' (type=mcp_server, confidence=1.000)nova kg entity-queue list
List all pending review items in the Tier-3 entity human review queue (SQLite-backed).
nova kg entity-queue list [--queue-db PATH] [--output json|text]| Flag | Default | Env var | Description |
|---|---|---|---|
--queue-db |
.nova/kg/review_queue.db |
NOVA_KG_QUEUE_DB |
SQLite path for the review queue |
--output, -o |
text |
— | Output format: text (Rich table) or json |
nova kg entity-queue list
# Entity Review Queue (2 pending)
# Item ID Alias Type Suggested Canonical Confidence Created At
# uuid-… my-agent agent my-agent-v2 0.720 2026-05-15T…nova kg entity-queue approve
Approve a review item and assign its canonical name.
nova kg entity-queue approve ITEM_ID --canonical NAME [--by REVIEWER]
[--queue-db PATH]| Argument / Flag | Default | Description |
|---|---|---|
ITEM_ID |
(required) | UUID of the review item to approve |
--canonical, -c |
(required) | Canonical name to assign |
--by |
cli |
Reviewer identifier (name or user ID) |
--queue-db |
.nova/kg/review_queue.db |
SQLite path for the review queue |
nova kg entity-queue approve e3f9… --canonical "my-agent-v2" --by alice
# Approved: e3f9… → canonical='my-agent-v2' (resolved_by='alice')nova kg entity-queue reject
Reject a review item without assigning a canonical name.
nova kg entity-queue reject ITEM_ID [--by REVIEWER] [--queue-db PATH]| Argument / Flag | Default | Description |
|---|---|---|
ITEM_ID |
(required) | UUID of the review item to reject |
--by |
cli |
Reviewer identifier (name or user ID) |
--queue-db |
.nova/kg/review_queue.db |
SQLite path for the review queue |
nova kg entity-queue reject e3f9… --by alice
# Rejected: e3f9… (resolved_by='alice')nova kg entity-queue stats
Show pending / approved / rejected counts for the entity review queue.
nova kg entity-queue stats [--queue-db PATH] [--output json|text]| Flag | Default | Env var | Description |
|---|---|---|---|
--queue-db |
.nova/kg/review_queue.db |
NOVA_KG_QUEUE_DB |
SQLite path for the review queue |
--output, -o |
text |
— | Output format: text or json |
nova kg entity-queue stats
# Entity Review Queue Stats
# pending: 3
# approved: 18
# rejected: 2
nova kg entity-queue stats --output json
# {"pending": 3, "approved": 18, "rejected": 2}