Search and Retrieval

This guide covers how to find things in khive — from keyword search to vector similarity to graph traversal — and when to use each approach.

Five ways to retrieve

khive offers five retrieval verbs, each suited to a different question shape:

Verb Question shape Example
get(id) “I have a UUID, give me the record” Fetch a known entity after a link operation
search(kind, query) “Find things about X” Discover entities matching a topic
list(kind, filters) “Show me all Y” Browse all concepts, all edges from a node
neighbors(node_id) “What connects to this?” One-hop graph exploration
traverse(roots) “What is reachable within N hops?” Multi-hop lineage, clusters
query(gql) “Pattern match over the graph” Complex structural queries

search combines full-text search (FTS5 trigram) with vector similarity (embedding-based) using Reciprocal Rank Fusion (RRF).

request(ops="search(kind=\"entity\", query=\"memory efficient attention\")")

Returns a ranked list. This abbreviated example shows the ranking fields:

[
  {
    "id": "a1b2c3d4",
    "name": "FlashAttention",
    "rank_score": 0.0325,
    "rank_score_kind": "rrf",
    "signals": { "vector_similarity": 0.842, "keyword_score": 18.4 },
    "score": 0.0325
  },
  {
    "id": "e5f6a7b8",
    "name": "PagedAttention",
    "rank_score": 0.0161,
    "rank_score_kind": "rrf",
    "signals": { "keyword_score": 12.1 },
    "score": 0.0161
  }
]

Search notes

request(ops="search(kind=\"note\", query=\"tiling recomputation\")")

Note search automatically excludes superseded notes (notes targeted by a supersedes edge). This is a view-layer filter — the old notes still exist.

Narrow by entity kind, type, or tags:

request(ops="search(kind=\"entity\", query=\"attention\", entity_kind=\"concept\", tags=[\"ml\"])")

Score interpretation

rank_score is the deterministic ordering value for this query and strategy. It is not a probability, a percentage match, or comparable across queries. rank_score_kind names the strategy: rrf, vector, keyword, weighted, or union. Entity exact-title boosts and note salience weighting affect the rank score while retaining the rrf kind.

signals contains available component evidence before fusion and modifiers: vector_similarity and/or keyword_score. Missing evidence has no key; it is never filled with zero. Scores belong to the backend and embedding model that produced them, so vector similarities from different models are not comparable. Canonical responses carry an empty object when no evidence was retained; Agent presentation omits that empty object and rounds numeric scores.

Use min_rank_score for an inclusive, strategy-local floor in [0,1]. The server compares fixed-point scores after fusion and modifiers, before the final result limit, including when sorting by creation or update time. This input range does not make the score a calibrated relevance measure.

In v0.8, score is a deprecated exact alias of rank_score, and min_score is a deprecated exact alias of min_rank_score. Supplying both parameter names is an error, even when equal. Prefer the canonical names in new callers. knowledge.search and memory.recall retain their separate scoring contracts.

Structured browse: list

list returns records matching structured filters, without text similarity:

request(ops="list(kind=\"entity\", entity_kind=\"concept\", limit=20)")
request(ops="list(kind=\"edge\", source_id=\"<uuid>\")")
request(ops="list(kind=\"note\", note_kind=\"decision\", limit=10)")

Use list when you want categorical browsing, not similarity ranking.

Graph navigation: neighbors and traverse

One-hop: neighbors

request(ops="neighbors(node_id=\"<uuid>\", direction=\"both\")")

Direction options: out, in, both (default). Omitting direction returns edges in both directions; pass out or in when you specifically want only one side.

Filter by relation:

request(ops="neighbors(node_id=\"<uuid>\", direction=\"in\", relations=[\"extends\", \"variant_of\"])")

Multi-hop: traverse

request(ops="traverse(roots=[\"<uuid>\"], max_depth=3, relations=[\"extends\", \"variant_of\"])")

Returns paths — each path is a list of nodes from root to leaf. Use include_roots=false to exclude the starting nodes from results.

Traverse is BFS-based. It respects direction (default: both) and relations filters. limit defaults to 100 non-root nodes per root and is capped at 1,000; the full request also has fixed root, depth, work, and time bounds documented in the API reference.

Pattern matching: query

For complex structural questions, use GQL or SPARQL:

GQL

request(ops="query(query=\"MATCH (a:concept)-[:extends]->(b:concept) WHERE b.name = 'LoRA' RETURN a\")")
request(ops="query(query=\"MATCH (p:document)<-[:introduced_by]-(c:concept)<-[:implements]-(impl:project) RETURN c.name, impl.name\")")

SPARQL

request(ops="query(query=\"SELECT ?a WHERE { ?a :extends+ ?b . ?b :name 'LoRA' . } LIMIT 10\")")

Both syntaxes compile to the same SQL backend. Use whichever feels natural.

Knowledge search: rerank and decompose

The knowledge.search verb adds two capabilities on top of base search:

Reranking

request(ops="knowledge.search(query=\"memory efficient attention mechanisms\", rerank=true)")

Reranking uses a cross-encoder model to re-score results after the initial retrieval pass. This produces clean [0,1] scores instead of raw RRF values. Reranking is on by default for knowledge.search.

Query decomposition

request(ops="knowledge.search(query=\"compare LoRA and QLoRA fine-tuning approaches\", decompose=true)")

Decomposition splits multi-concept queries into sub-queries, runs them independently, and merges the results. This avoids FTS edge cases where compound queries miss relevant documents.

Use decompose=true when your query mentions multiple distinct concepts.

Memory recall

memory.recall is a specialized search over memory notes with decay-weighted scoring:

request(ops="memory.recall(query=\"attention optimization\", limit=5)")

See Memory and Recall for the full scoring formula and usage patterns.

Choosing the right retrieval

You want to… Use
Find entities about a topic search(kind="entity", query="...")
Find notes about a topic search(kind="note", query="...")
Browse all entities of a kind list(kind="entity", entity_kind="concept")
See what connects to a node neighbors(node_id="...", direction="both")
Explore multi-hop paths traverse(roots=["..."], max_depth=3)
Structural pattern matching query(query="MATCH ...")
Find knowledge atoms with scoring knowledge.search(query="...", rerank=true)
Recall agent memories memory.recall(query="...")

Performance notes

  • Cold start: the first search in a session loads the ANN index and embedding model. The daemon keeps these warm for subsequent calls.
  • Daemon: khive auto-spawns kkernel mcp --daemon on first request. The daemon keeps indexes hot across sessions.
  • Vector search without embeddings: if running with --no-embed, only FTS results are returned (no vector similarity).

See also


Raw markdown for this page: /md/search.md