A guess is not a fact: a type system for knowledge
Store a model's speculation in the same shape as a verified observation and retrieval will grant them equal authority — that is how a guess gets laundered into a decision. knomit's answer is the move programming languages made decades ago: type every fact by its epistemic status and its origin, and enforce what may be derived from what. A guess can be stored, retrieved, even confirmed later — but it cannot pass as an observation.
An agent, mid-task, needs a value it doesn’t have. It reasons: “the retry limit is probably three — most clients default to three.” Reasonable. It writes that to memory, because writing things to memory is what good agents do now. Three weeks later a different session retrieves it — top of the similarity ranking, phrased with the calm declarative confidence models phrase everything with — and cites it: the retry limit is three. A decision gets built on top. Nobody lied at any step. But a guess has become a citation, and the citation has become load-bearing.
The failure has a name: laundering. The moment a guess is written in the same shape as an observation, the distinction between them is unrecoverable — no amount of downstream diligence can restore information the storage format never captured. Retrieval ranks by similarity, similarity knows nothing about epistemic status, and a confident hallucination will outrank a hedged truth.
Most agent-memory systems have exactly this design. Every record is the same kind of record: an observation, a model’s speculation, a distilled summary, and a hand-written rule all become interchangeable blobs with embeddings. The question “is this something we observed or something we made up?” isn’t merely hard to answer in these systems — the schema doesn’t record the difference, so the question can’t be asked at all.
Programming solved this class of problem already
When a language lets you pass a string where a file handle belongs, you don’t fix that with reviewer vigilance. You fix it with a type system: make the illegal state unrepresentable, and the whole class of bug stops needing to be caught because it stops being expressible.
Knowledge has the same structure. A guess and an observation are different types. A rule and a description are different types. A fact a machine derived and a fact a person asserted have different provenance, whatever their content. Treating them uniformly isn’t a storage simplification — it is a type error, and it produces exactly what type errors produce: corruption that surfaces far from its cause.
So knomit types every fact, on three axes, and — this is the part that matters — makes the inference rules between types mechanically enforceable. What may be derived from what is checked in code, not policed by hope.
The three axes
Kind: epistemic vs pragmatic. Descriptive knowledge (“what is”) versus prescriptive knowledge (“what to do”). A policy is not true or false the way an observation is; conflating them corrupts both directions. And the pipeline enforces the boundary: pragmatic facts never enter synthesis at all — a rule is not evidence, so nothing can be “derived” from it.
Type: the epistemic leaf types. Within epistemic knowledge: observation, concept, process, principle, pattern, reference, synthesis (derived from other facts), insight, hypothesis, methodology. Pragmatic knowledge: policy, heuristic. Two deserve a closer look.
A hypothesis is a fabricated type — knowledge the system knows it made up. It must be forward-looking and falsifiable, with a concrete settlement criterion attached at creation: a date, a threshold, an observable. The creation rubric’s default answer is no — skipping is the expected outcome, and the type is designed to be transient: a hypothesis either settles into an observation or gets retracted. (As we draft this, knomit’s own corpus holds zero open hypotheses — which is the intended steady state, not a gap.)
A methodology makes reasoning-process knowledge first-class: not a claim about the code, but a lesson about how a conclusion was reached — retrieved and injected into future reasoning like any other fact. Almost no memory system represents this category at all.
Origin: authored, distilled, or discovered. Orthogonal to type: not what the fact claims but how it came to exist. Hand-written by a human or agent; produced by the synthesis pipeline from a cluster of sources; or generated by the discovery engine from a structural signal in the graph. Origin is stamped by how the candidate was formed, not by who happened to click save. This is the machine-generated-content provenance problem, solved at the schema level — here is a real distilled fact from knomit’s corpus, a cross-cutting pattern no single source fact states, machine-synthesized from four of them:
--- type: synthesisorigin: distilled confidence: 0.85 domain: [store, schema, architecture] entities: [GraphSchemaVersion, Rebuild, repopulateDomainTokens] refs: - kb/architecture/store/fast-rebuild-three-phase/0a1400eb.md ---
CROSS-CUTTING PATTERN (synthesized from the rebuild + domain-matching facts): every piece of SQLite-side state derived from the git fact tree is treated as rebuildable cache, never as a thing to migrate data into. Ship an empty-table migration, bump GraphSchemaVersion, and the next rebuild regenerates all derived state from source.
Its refs point at the four source facts it was distilled from; its commit
message reads synthesize-review: distill …. What it claims, where it came
from, and what it rests on are three separate, separately-queryable fields.
And the trust quantities stay separated. Confidence (how sure the believer is) is distinct from evidence weight (how well-sourced the claim is) is distinct from origin (where it came from). Collapsing these into one score is a category error most systems make: a 0.9-confidence hypothesis is still a guess, and a well-sourced synthesis of guesses is still guesswork. One scalar cannot encode that.
The inference rules
Here is where the type system earns its name. Each rule below is one sentence of policy, enforced in pipeline code — a typing judgment, not a guideline.
1. Hypotheses never seed synthesis. The distill step is forbidden from
using hypothesis-type facts as inputs for new derived facts. An unconfirmed
prediction cannot launder into established knowledge by being summarized —
hypothesis ⊬ synthesis, as a type rule would write it.
2. Guesses carry no evidentiary mass. A derived fact’s evidence weight counts only non-hypothesis sources; citing ten guesses adds exactly zero. This isn’t prompt guidance — it’s the weight computation skipping the type:
--- type: observation confidence: 0.95 domain: [synthesize, weight] entities: [WeightStrategy, SumProductNorm, computeWeight] refs: - src://knomit/internal/synthesize/weight.go@307b67d ---
computeWeight SKIPS hypothesis-type sources entirely — they carry uncertainty and can't contribute evidence weight to synthesized facts. SumProductNorm.Compute = sum(confidence × sources) / (sum + 1) — diminishing returns as evidence accumulates.
3. Discovered facts never seed discovery. The discovery engine excludes origin-discovered facts from its own seed pool — knomit calls this the idempotency rule. Machine output feeding machine input is the runaway loop every generative system has to break somewhere; breaking it in the type system means the system cannot compound its own speculation, by construction.
4. Reality outranks prediction on collision. When a new observation near-duplicates an existing hypothesis, the observation is written, the hypothesis is retracted, and the two are linked — a confirmed prediction, with the prediction’s provenance kept. In any dedup merge, non-hypothesis wins.
5. Promotion is a typed transition, and it’s recorded. A hypothesis settling into an observation is an explicit lifecycle event, detected by diffing the corpus across session commit ranges. Those transitions then feed a reflection step that reinforces or questions the methodologies that produced the predictions. “Is our reasoning improving?” becomes a query over typed transitions rather than an impression.
The payoff is the one type systems always deliver: entire classes of corruption stop being policed after the fact and become unrepresentable instead. You can watch the lifecycle running in our other posts — a hunch recorded honestly and upgraded when the evidence arrived, in how knomit holds its own shape, and the update/retract machinery that keeps settled facts true, in keeping facts honest after the code moves. This piece is the rulebook those stories run on. (All of it is stored as markdown in a git repository — why that substrate, and what it buys, is its own essay.)
Why this matters beyond one system
The industry is converging on agents that write their own long-term memory, and the default designs have no epistemic axis at all. RAG memory makes a verified fact and a stored guess the same row. Conversation-summary features store distilled content with no origin marking, silently re-cited as ground truth. And an agent that writes unlabeled memories and then retrieves them converges on its own biases — self-reinforcing and unauditable, not because the model is bad but because the store cannot represent the difference between what was observed and what was inferred.
The objections are worth taking seriously:
“Ten types is a taxonomy nobody will maintain.” Defaults do most of the work — observation is the default, and agents assign types at write time with good accuracy. The load-bearing distinctions are few: hypothesis versus everything else, pragmatic versus epistemic, and origin. The rest add precision, not burden.
“Why not just a confidence score?” Confidence measures the believer; type and origin describe the claim. A 0.9-confidence hypothesis is still a guess. A 0.6-confidence observation is still an observation. And no scalar can encode a quarantine rule — you cannot write “never synthesize from this” as a number.
“Models will mislabel.” Sometimes, yes. But a mislabeled fact is visible and correctable — the label is right there in the markdown, one edit away. Unlabeled knowledge is uncorrectable by construction: there is nothing to audit and nothing to fix. Perfect is not the bar; auditable is.
And one honest scope note: typing does not prevent wrong observations. An agent can observe carelessly and record something false, and the type system will not catch it — that’s what the maintenance machinery is for. What typing prevents is category corruption: guesses masquerading as knowledge, machine output masquerading as human assertion, rules masquerading as evidence.
Observed, derived, or made up
Agents are starting to write their own memories at scale. They will sometimes be wrong — no storage design changes that. The question is whether the system underneath can tell the difference between what it observed, what it derived, and what it made up. A store that can’t represent that difference will keep turning guesses into decisions no matter how good the model on top of it gets. A store that types its knowledge can hold a guess safely: labeled while it’s open, linked to the observation that settles it, and never mistaken for something it isn’t.
Read the code on GitHub.