The page-scoped prompt: the unit of work is the term the reader is looking at.
vaelii.impl.llm.selection prompts about lines the reader picked and asks for them
back edited. This namespace prompts about a term page — /term?q=penguin — and asks
for knowledge the KB does not have yet:
genl neighbourhood licenses
(vaelii.impl.llm.inventory) — arity and argument types included,Three things differ from the edit path, each because it was measured:
output-schema, Ollama's format). On generation this
rescues models that otherwise answer in markdown prose; on the edit path the same
parameter silently drops lines, so the two contracts are deliberately different and
are not unified.genl edge or a
rule, not a fact about an individual, so the prompt asks for those shapes and shows
them.The prompt's one job beyond the shape is to stop the model coining vocabulary — see
vaelii.impl.llm.inventory, which is where both guards against that live.
The page-scoped prompt: **the unit of work is the term the reader is looking at.**
`vaelii.impl.llm.selection` prompts about lines the reader picked and asks for them
back edited. This namespace prompts about a *term page* — `/term?q=penguin` — and asks
for knowledge the KB does not have yet:
1. what the page already says about the term, as bare sentences,
2. the vocabulary that term's `genl` neighbourhood licenses
(`vaelii.impl.llm.inventory`) — arity and argument types included,
3. the reader's free-text instruction ("flesh out the capabilities of this").
Three things differ from the edit path, each because it was measured:
* **The context is dropped.** A page is already about one context, so the caller
supplies it and the model writes bare sentences. That removes a whole class of
answer the model gets wrong for no gain — and it shortens every line it writes.
* **Decoding is constrained** (`output-schema`, Ollama's `format`). On generation this
*rescues* models that otherwise answer in markdown prose; on the edit path the same
parameter silently drops lines, so the two contracts are deliberately different and
are not unified.
* **The content is type-level.** Common sense about a *kind* is a `genl` edge or a
rule, not a fact about an individual, so the prompt asks for those shapes and shows
them.
The prompt's one job beyond the shape is to stop the model coining vocabulary — see
`vaelii.impl.llm.inventory`, which is where both guards against that live.(assertion-entry sentence context strength)One read sentence -> the [sentence context opts?] entry vaelii.core/edit! takes,
with the caller's context supplied. strength rides across as
{:strength :monotonic} when the model claimed the assertion is known-true; a default
needs no opts, since that is assert's own default.
One read sentence -> the `[sentence context opts?]` entry `vaelii.core/edit!` takes,
with the caller's context supplied. `strength` rides across as
`{:strength :monotonic}` when the model claimed the assertion is known-true; a default
needs no opts, since that is `assert`'s own default.The JSON schema Ollama's format constrains decoding to — the contract on this
path, unlike the edit path where it is an opt-in optimization.
Measured: constrained decoding is what makes generation work at all on a local model.
Models that answered a generation request with a markdown essay produce clean
s-expressions under this schema, and the strongest coder model went from nothing usable
to a full answer. An object per assertion, never a bare string, so the sampler
cannot choose its own line shape; sentence is one s-expression and carries no
context — the caller supplies that.
The JSON schema Ollama's `format` constrains decoding to — **the contract on this path**, unlike the edit path where it is an opt-in optimization. Measured: constrained decoding is what makes generation work at all on a local model. Models that answered a generation request with a markdown essay produce clean s-expressions under this schema, and the strongest coder model went from nothing usable to a full answer. An **object per assertion**, never a bare string, so the sampler cannot choose its own line shape; `sentence` is one s-expression and carries **no context** — the caller supplies that.
(page-context rows given)The context new assertions are filed in: the caller's :context when given, else the
context most of the page's own sentexes are in, else UniverseContext.
A modal context is the honest default — a page about penguin whose sentexes sit in
OrganismContext is a page about OrganismContext — and ties break on the context name so
the choice cannot depend on arrival order. The vocabulary head is never chosen: a term
carries derived bookkeeping there ((arity penguin 1), (unaryPredicate penguin)) which can
outnumber its definitional sentexes, and new domain knowledge does not belong in the
context that defines the vocabulary. Reported back by
vaelii.impl.llm.session/propose-page, because which context knowledge lands in is a
decision a reviewer must see.
The context new assertions are filed in: the caller's `:context` when given, else the context most of the page's own sentexes are in, else `UniverseContext`. A modal context is the honest default — a page about `penguin` whose sentexes sit in `OrganismContext` is a page about `OrganismContext` — and ties break on the context name so the choice cannot depend on arrival order. The **vocabulary head is never chosen**: a term carries derived bookkeeping there (`(arity penguin 1)`, `(unaryPredicate penguin)`) which can outnumber its definitional sentexes, and new domain knowledge does not belong in the context that defines the vocabulary. Reported back by `vaelii.impl.llm.session/propose-page`, because which context knowledge lands in is a decision a reviewer must see.
(stored-heading rows)How many of the term's stored lines the prompt is showing — 40, 40 of 137, or
40 of more than 137.
A bare count of what is on screen reads as the whole of what is stored, and the model
is asked in the same breath not to repeat anything already there. Where the cut or
stored-lines' scan bound bit, saying so is what keeps that instruction honest: a
model shown a sample and told it is one writes about the gaps rather than assuming it
has seen them. Reads stored-lines' metadata, and answers the plain count for rows
built without it.
How many of the term's stored lines the prompt is showing — `40`, `40 of 137`, or `40 of more than 137`. A bare count of what is on screen reads as the whole of what is stored, and the model is asked in the same breath not to repeat anything already there. Where the cut or `stored-lines`' scan bound bit, saying so is what keeps that instruction honest: a model shown a sample and told it is one writes about the gaps rather than assuming it has seen them. Reads `stored-lines`' metadata, and answers the plain count for rows built without it.
(stored-lines kb term)(stored-lines kb
term
{:keys [max-lines max-scan] :or {max-lines 40 max-scan 4000}})What the term page shows, as [{:handle :sentence :context :line} …] — the stored
sentexes mentioning the term, deduped and sorted by content before :max-lines (40)
cuts, so which of what was scanned the model is shown is a function of the KB rather
than of the order the knowledge arrived in.
Rendered as bare sentences: the context is deliberately absent, because the answer
shape has no context in it either and showing one invites the model to write one. A
rule keeps its set/*Rule wrappers, since that is how its direction and defeasibility
are written. An exceptWhen meta-sentex is left out — it names the rule it qualifies by
raw handle, which is engine bookkeeping and not something to show a model, let alone ask
it to imitate.
:max-scan (4000) is the cost bound, and it is the one thing here that is not a
content choice. Content order is what a cap has to key on, so the sort must happen
over everything the cut chooses between — which means fetching it. Below the bound the
page is therefore a function of the knowledge alone: the same sentences loaded in any
order give the same lines. At the bound it is a sample, and scanned decides which
one — the term's earliest mentions, since what a sentence says cannot be known without
fetching it and fetching all of them is the cost the bound refuses. A page term
mentioned more often than that is a term whose page is already a sample, so the scan
stops and the answer says so: the metadata carries :found (distinct lines within the
scan) and :truncated?, and user-turn writes both into the heading rather than
letting 40 lines read as the whole of what is stored. Ten record fetches per line shown
is noise beside the model call this prompt is built for; a million is not.
What the term page shows, as `[{:handle :sentence :context :line} …]` — the stored
sentexes mentioning the term, deduped and **sorted by content before `:max-lines` (40)
cuts**, so which of what was scanned the model is shown is a function of the KB rather
than of the order the knowledge arrived in.
Rendered as **bare sentences**: the context is deliberately absent, because the answer
shape has no context in it either and showing one invites the model to write one. A
rule keeps its `set/*Rule` wrappers, since that is how its direction and defeasibility
are written. An `exceptWhen` meta-sentex is left out — it names the rule it qualifies by
raw handle, which is engine bookkeeping and not something to show a model, let alone ask
it to imitate.
**`:max-scan` (4000) is the cost bound, and it is the one thing here that is not a
content choice.** Content order is what a cap has to key on, so the sort must happen
over everything the cut chooses between — which means fetching it. Below the bound the
page is therefore a function of the knowledge alone: the same sentences loaded in any
order give the same lines. At the bound it is a **sample**, and `scanned` decides which
one — the term's earliest mentions, since what a sentence says cannot be known without
fetching it and fetching all of them is the cost the bound refuses. A page term
mentioned more often than that is a term whose page is already a sample, so the scan
stops and the answer says so: the metadata carries `:found` (distinct lines within the
scan) and `:truncated?`, and `user-turn` writes both into the heading rather than
letting 40 lines read as the whole of what is stored. Ten record fetches per line shown
is noise beside the model call this prompt is built for; a million is not.The instruction half of the page-scoped turn — static, because everything KB-specific rides in the user turn.
It carries four things the model gets wrong without them: that a claim about a kind
is a rule or a genl edge rather than a sentence with a loose variable, that the
context is not the model's to write, that detail belongs in arguments and not in
predicate names, and that the vocabulary on the card is there to be reused.
The instruction half of the page-scoped turn — static, because everything KB-specific rides in the user turn. It carries four things the model gets wrong without them: that a claim about a *kind* is a rule or a `genl` edge rather than a sentence with a loose variable, that the context is not the model's to write, that detail belongs in **arguments** and not in predicate names, and that the vocabulary on the card is there to be reused.
(user-turn kb term rows context instruction)(user-turn kb
term
rows
context
instruction
{:keys [max-assertions] :or {max-assertions 24} :as opts})The volatile half: the page's term, its own stored sentences, the vocabulary card, the context new assertions land in, and the reader's instruction — instruction last, so it is the newest thing in the window.
opts is passed through to vaelii.impl.llm.inventory/inventory and render
(:max-predicates, :max-types, :max-tokens, …) and to stored-lines
(:max-lines, :max-scan). :max-assertions (24) caps what is asked for, which is
what bounds the answer's length and so its latency.
The stored block's heading is stored-heading's rather than a bare count, so a capped
page reads as capped.
The volatile half: the page's term, its own stored sentences, the vocabulary card, the context new assertions land in, and the reader's instruction — instruction last, so it is the newest thing in the window. `opts` is passed through to `vaelii.impl.llm.inventory/inventory` and `render` (`:max-predicates`, `:max-types`, `:max-tokens`, …) and to `stored-lines` (`:max-lines`, `:max-scan`). `:max-assertions` (24) caps what is asked for, which is what bounds the answer's length and so its latency. The stored block's heading is `stored-heading`'s rather than a bare count, so a capped page reads as capped.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |