Files
nanobot-runtime/skills/llm-wiki/references/graph-workflow.md
2026-06-24 08:11:12 +02:00

7.6 KiB

Graph Workflow

The graph layer is the optional compiled index over the markdown wiki. It does not replace the wiki — it sits alongside it under cml/wiki/graph/ and is reproducible from the markdown at any time. The point is to make typed, provenance-backed relationships machine-queryable while keeping markdown canonical.

If cml/wiki/graph/ontology.yaml is absent, the wiki is pre-graph: don't run extract/lint/query and don't fabricate ontology files. Either propose adding the layer, or proceed without it.

What the graph captures

Three classes of edge come out of an extract:

  1. Typed semantic edges declared in a page's graph.relationships[] frontmatter. Examples: founded, proposed, depends_on. Each one carries an explicit source (a source-page slug), an evidence quote, a confidence (high/medium/low), and a status (current/historical/proposed/disputed/superseded). The extractor never invents these.
  2. mentions edges — one per body [[wikilink]] (deduplicated per page). Confidence is low; they accelerate navigation but should not be cited as evidence of a typed relationship.
  3. sourced_from edges — one per slug in a non-source page's frontmatter sources: list, pointing at the source page. summarizes_raw edges — one per source page's raw: field, with the raw file path as the (string-literal) object.

Frontmatter schema

graph:
  node_id: person:praney-behl       # optional; default <node_type>:<slug>
  node_type: person                  # optional; default mapped from type/kind via ontology
  canonical: true                    # mark canonical when multiple slugs alias the same entity
  aliases: [Praney, praney@example.com]
  relationships:
    - predicate: founded
      object: company:seedblocks
      source: praney-founder-context-dump
      evidence: "Solo technical founder and sole director..."
      confidence: high
      status: current
      # optional:
      # valid_from: 2025-01-15
      # valid_to: 2026-03-01
      # notes: "..."
      # raw_ref: "cml/raw/founder-dump.md#L42"
      # contradicts: <node-id-or-edge-id>
      # supersedes: <node-id-or-edge-id>

Required relationship fields: predicate, object, source, evidence, confidence, status.

node_id format is <node_type>:<slug>. The default is derived from the page's type/kind via the ontology's maps_from block. decision, claim, and raw are explicit-only — they don't have wiki pages, so they only show up as edge objects. If a typed edge points at one of these, the wiki_graph_lint.py flag for "broken object reference" will fire until either (a) you create a page for it, or (b) you add it to the ontology with explicit_only: true and accept that lint will continue to flag the reference.

The ontology

cml/wiki/graph/ontology.yaml is the contract. It declares:

  • node_types[*].maps_from — how page type/kind projects onto a node type.
  • predicates[*] — the allowed predicates, each with subject_types, object_types, and requires_evidence. "*" is a wildcard for either side.

Edit the ontology when you need a new domain predicate. Re-run wiki_graph_lint.py to validate; existing typed edges will be caught if they no longer match.

Add a typed edge when:

  • A specific source explicitly states the relationship.
  • You can quote a snippet of evidence.
  • The predicate is meaningful for downstream queries ("who founded what", "what did Stephanie propose").

Use a plain [[wikilink]] when:

  • The relationship is implicit, atmospheric, or you're hedging.
  • You cannot pin the claim to a single source quote.
  • The predicate would be mentions anyway.

When in doubt, write the wikilink and skip the typed edge. The lint surfaces missing evidence; it does not punish under-claiming.

Extract / lint / query loop

# Validate the typed metadata first; lint is conservative, never edits.
uv run skills/llm-wiki/scripts/wiki_graph_lint.py cml/wiki/

# Compile to nodes.jsonl, edges.jsonl, graph.sqlite, graph.graphml.
uv run skills/llm-wiki/scripts/wiki_graph_extract.py cml/wiki/

# Navigate.
uv run skills/llm-wiki/scripts/wiki_graph_query.py cml/wiki/ neighbors --node product:konvy
uv run skills/llm-wiki/scripts/wiki_graph_query.py cml/wiki/ edges    --subject person:stephanie-emmanouel
uv run skills/llm-wiki/scripts/wiki_graph_query.py cml/wiki/ path     --from person:praney-behl --to product:konvy
uv run skills/llm-wiki/scripts/wiki_graph_query.py cml/wiki/ facts    --about product:konvy

--json works on both lint and query commands.

Ingest workflow integration

After Step 6 of the standard ingest workflow (after surgical updates and source page creation), run:

  1. If new typed edges were added on the page being ingested, run wiki_graph_lint.py. Interactive: triage findings with the user before extract. Drain (headless): if lint is clean, proceed to extract; if lint reports errors, record them in log.md and skip extract for this batch — never silently rewrite typed edges, never block waiting for a user.
  2. Run wiki_graph_extract.py to refresh the compiled artifacts.
  3. Append a sub-line under the ingest's log.md entry: graph: +N nodes, +M typed edges (predicates: founded, contains_product, ...)

Skip extract if this ingest added no graph: metadata and created no new pages — the compiled artifacts are unchanged.

Query workflow integration

When the user asks a question that smells relational ("what's connected to X", "who proposed Y", "trace the path from A to B"):

  1. Read the index as usual.
  2. If cml/wiki/graph/graph.sqlite exists and is fresher than the latest log entry, query it for typed edges around the candidate pages — neighbors, edges, facts are the most useful.
  3. Read the wiki pages behind the relevant nodes/edges. Don't answer from graph rows alone for high-stakes claims; the evidence field is a hint, not the source of truth.
  4. Cite with [[wikilinks]] to wiki pages, not graph rows.

If graph.sqlite is stale (older than the most recent ingest in log.md), use it as-is and note the staleness — do not regenerate inline. Extract is a compile/drain step; the query turn stays read-only, and the background drain refreshes the graph after each ingest.

Generated artifact policy

File Canonical? Default tracking
cml/wiki/graph/ontology.yaml Yes — edit by hand Tracked
cml/wiki/graph/nodes.jsonl Generated Optional — cml/wiki/graph/.gitignore does not ignore it; track if you want graph diffs in PRs
cml/wiki/graph/edges.jsonl Generated Same as above
cml/wiki/graph/graph.sqlite Generated Gitignored by default (large, binary)
cml/wiki/graph/graph.graphml Generated Gitignored by default

The bootstrapped cml/wiki/graph/.gitignore ignores graph.sqlite and graph.graphml. Edit it if your team prefers different policy.

Anti-patterns

  • Typed edges without evidence. Defeats the entire point. Lint will flag them; do not silence.
  • Editing nodes.jsonl / edges.jsonl / graph.sqlite by hand. Edit the markdown; regenerate.
  • Inventing ontology entries to make a typed edge "fit". The ontology should reflect domain reality, not paper over a too-eager edge. Either add the predicate with proper subject_types/object_types, or use mentions.
  • Treating graph rows as evidence in answers. Always cite the wiki page; the graph just told you which wiki page to read.
  • Forgetting to regenerate after an ingest. The graph diverges silently. Tie extract to ingest in muscle memory.