Semantic-search requirements¶
Status: lexical is implemented and semantic reads a committed index; the
toolchain that produces one is not wired together — see
issue 0011.
Semantic search is the one HQL feature whose answer depends on things outside
the binary: a vault, an index, and a model that computed it. These are its
requirements, stated as what has to be true before a semantic query can
answer, and who is responsible for each.
A card requires a vault, and a vault is a .hmd/ directory¶
A card is a document with a place in a namespace, and the namespace is HMD's.
The marker is .hmd/ at the vault root, which is what hmd walks up to find.
.hmd/config.toml inside it is not required to read a vault: where it is
absent, its defaults are assumed, and they are the values hmd init writes —
the wiki root, the namespace name, autodiscovery and its mode. A vault is
therefore usable before anyone has configured it, and configuring it later
changes behaviour rather than enabling it.
The directory itself is required, and increasingly so. It is where vault state
lives, it is what makes a root discoverable from a working directory rather than
supplied by hand, and it is the only thing that distinguishes a vault from a
folder of Markdown. Working with .hmd cards without one fails semantic search
first and more later, so the long-term requirement is the directory, with the
config file remaining optional.
Three parts, and what each is responsible for¶
| Part | Responsibility | May assume |
|---|---|---|
hmd (Python, package hypermarkdown) |
the vault: root discovery, .hmd/config.toml, the namespace |
nothing about HQL |
contrib/semantics/ (Python) |
the index: read the vault, embed each card and each query, write .hql/index.sqlite |
hmd is installed |
hql (Rust) |
the query: score a stored query against stored vectors | nothing but the index file |
The dependency runs one way. A script around semantic search may assume the
hmd CLI is installed and treat it as a dependency, which is the default case
for HmdCards. The Rust binary may assume neither Python nor hmd: it consumes
a committed index, and cargo test must keep running with no Python present.
What must hold before a query can answer¶
- The vault exists and is one —
.hmd/is present. hql.tomlnames an index, and the file it names exists.- The index describes this vault: every card present on both sides, and every
content hash still matching.
cli.py verifyis what says so. - The index holds the query. The binary has no model, so it cannot embed a
question; a query the index does not carry is an error naming the command
that would add it, never a fallback to
lexical.
A failure of any of these names the part that failed. Semantic search never degrades into lexical search silently, because a worse answer that looks like an answer is the failure mode this design exists to avoid.
The text contract¶
The text the producer embeds and the text Document::text() hashes must be
byte-identical, or the index scores text nobody wrote and every score is quietly
wrong. This is why contrib/semantics/vault.py reimplements the loader's header
parser instead of using a YAML library, and why a change to either side moves
both in one commit. The indexed text is the title, then the authored header's
strings in key order, then the body; where a document sits is a field rather
than a header entry and is not indexed at all, so moving a card between
directories does not change its score.
Out of scope¶
The model in the Rust binary, embedding at query time, multiple indexes, hybrid
retrieval and approximate search. See
HQL-0002 for the schema and the full list,
and semantic search for what a Hit
carries.