Assertions
Publish typed evidence-backed propositions and resolve conflicts without mutating source records.
Use assertions() when indexing should extract propositions your application
can list, stream, resolve, or inject as prompt context. Assertions are useful
for requirements, limits, dates, exclusions, coverage rules, product facts, and
other statements where evidence and conflict handling matter.
Use plain retrieval when the model should read source text directly. Use assertions when a proposition needs a stable identity and visible evidence.
Author Assertion Types
Each assertion type is a Zod schema. A stage has a stable id and version, and
uses exactly one production mode: deterministic run or model-backed
extraction.
import { indexingPipeline } from "@use-crux/core/indexing";
import { assertions, knowledgeBase } from "@use-crux/core/knowledge";
import { z } from "zod";
const policyFacts = assertions({
id: "policy-facts",
version: 1,
types: {
requirement: z.object({
application: z.string(),
jurisdiction: z.string(),
text: z.string(),
}).describe("A documented policy requirement."),
},
run: ({ chunks }, api) => {
for (const chunk of chunks) {
if (!chunk.content.includes("proof of coverage")) continue;
api.emit(
"requirement",
{
application: "auto",
jurisdiction: "CA",
text: "Applicant must provide proof of coverage.",
},
{
evidence: {
kind: "chunk",
sourceId: chunk.sourceId,
chunkId: chunk.chunkId,
},
provenance: "exact",
},
);
}
},
});
const docs = knowledgeBase({
id: "docs",
storage,
pipeline: indexingPipeline({
derive: [policyFacts],
}),
});Evidence is required. Supports must resolve to indexed chunks before an assertion is published.
Model-Backed Assertions
Use model mode when the assertion shape cannot be extracted reliably with code.
import { assertions, knowledgeModel } from "@use-crux/core/knowledge";
const extractor = knowledgeModel({
name: "policy-fact-extractor",
version: "2026-07",
generateText: retrievalModel.generateText,
generateObject: retrievalModel.generateObject,
});
const extractedFacts = assertions({
id: "policy-facts",
version: 1,
model: extractor,
instructions: "Extract requirements stated in the source. Do not infer unstated rules.",
types: {
requirement: z.object({
application: z.string(),
jurisdiction: z.string(),
text: z.string(),
}),
},
});Model-backed assertion stages validate output, retry repair once, drop invalid items with warnings, and cache valid claims by source and stage fingerprint.
Prompt Bounds
Model-mode assertion extraction batches source chunks deterministically. Chunks
are sorted by ordinal and assigned whole, in order, to batches bounded by the
internal MAX_DERIVE_BATCH_CHARS = 12000 budget. Each batch makes one model
call, so calls scale with source size and every chunk is covered.
The stage vocabulary, instructions, source id, and document title repeat for each batch. A bounded excerpt of the document body appears only in the first batch. Routine per-chunk truncation is not used; truncation happens only when a single chunk is too large to fit in one batch.
When an oversized single chunk is truncated, indexing returns a warning in
result.knowledge. The same summary is recorded on the mutation effect receipt
evidence. Each warning names the stage, source id, chunk id, original length,
and bounded length.
const result = await docs.reindex(sources);
console.log(result.knowledge?.stages[0]?.warnings);Right-size chunking so ordinary chunks fit comfortably within the 12000-character batch budget. The parent-child chunker already uses a 900-character child default, so most sources batch without truncation.
Consume Assertion Sets
Read assertions from a knowledge base or a view. The types option narrows the
returned item type.
const requirements = docs.assertions(policyFacts, {
types: ["requirement"],
});
const page = await requirements.list({ limit: 25 });
for await (const item of requirements.stream()) {
console.log(item.type, item.data.text, item.evidence);
}Assertion sets go directly into use. The prompt context is bounded and
summarized as assertion lines, not raw source records. Use asContext() only
when you need to override the defaults, such as the item limit or priority.
import { prompt } from "@use-crux/core";
const answer = prompt({
id: "answer-policy",
use: [requirements],
system: "Answer from the documented requirements.",
prompt: "Which requirements apply?",
});Relate Assertions
Deterministic assertion runs can emit relations between assertions. Relations
are separate assertion records, not graph edges. expandRelations() does not
traverse them.
const current = api.emit("requirement", {
application: "auto",
jurisdiction: "CA",
text: "Applicant must provide proof of coverage.",
}, { evidence });
api.relate(
"supersedes",
current,
{
type: "requirement",
data: {
application: "auto",
jurisdiction: "CA",
text: "Applicant may provide proof after approval.",
},
},
{ evidence },
);Supported assertion relation types are supports, amends, supersedes,
narrows, and conflictsWith.
Inspecting relations
Use relations() when you need the persisted assertion relation records
themselves, including endpoints, evidence support refs, provenance, and stage
identity. The read is lazy and cursor-paginated like list().
const page = await docs.assertions(policyFacts).relations({
types: ["supersedes", "conflictsWith"],
limit: 25,
});
for (const relation of page.items) {
console.log(relation.type, relation.from.assertionId, relation.to.assertionId);
console.log(relation.evidence);
}View-bound assertion sets apply the view membership boundary to relation evidence. A relation is visible through a view only when at least one of its support refs is visible through that view.
Resolve Conflicts
Resolution partitions visible assertions into four groups:
const resolved = await docs
.assertions(policyFacts)
.resolve()
.result();
console.log(resolved.selected);
console.log(resolved.superseded);
console.log(resolved.contested);
console.log(resolved.unresolved);
console.log(resolved.trace);Explicit supersedes relations put the target assertion in superseded.
Explicit conflictsWith relations put both sides in contested. A custom
resolution policy can add selected, superseded, contested, or unresolved
decisions.
const resolvedRequirements = docs.assertions(policyFacts).resolve({
id: "prefer-current",
version: 1,
run: ({ assertions }, decision) => {
for (const assertion of assertions) {
decision.select(assertion, "No conflicting relation was found.");
}
},
});
const promptWithResolvedFacts = prompt({
id: "resolved-policy-answer",
use: [resolvedRequirements],
system: "Prefer selected requirements and mention contested items.",
});Resolution is lazy and cached per handle by generation, view revision, selected types, and policy fingerprint. It never mutates source assertions.
Views And Supports
View-bound assertion sets first resolve the view revision. Supports outside the visible member set are excluded. Assertions with no visible supports are hidden. Assertion relations are visible when their evidence supports intersect the visible member set; the public relation read returns endpoint refs, not hydrated assertion content.