Crux
GuidesRetrieval & Knowledge

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.

On this page