Skip to content
docs
Arxo ↗

Facts and context

For LLMs5 sections

A question carries two things: the facts of the case and the context they are evaluated in. Facts arrive as plain predicates with plain values; the facade wraps each value by the type the canon declares for that parameter. Nothing is guessed from the shape of the value.

The frist scenario needs three context fields and two facts — nothing else on this page is required to ask it:

JavaScript
const caseInput = {
legalTime: '2026-09-17',
timezone: 'Europe/Berlin',
deadlinePolicy: 'urn:de:corpus:clir:bgb-fristen#BGB_FRISTEN_TAG',
answers: [
{ predicate: 'frist_ereignis', args: ['frist', '2026-03-06'] },
{ predicate: 'frist_dauer_tage', args: ['frist', '14 calendar_day'] },
],
};
const r = await fristen.truth('frist_ende', ['frist', '2026-03-20'], caseInput);
// r.evaluationStatus === 'COMPUTED', r.truthStatus === 'TRUE_ONLY'
  • legalTime is the date on which the law is read: required.
  • timezone says how dates are read.
  • deadlinePolicy names the canon’s rule for counting days.
  • answers holds the facts: the event date and the duration.

Drop frist_dauer_tage from answers and the same call answers COMPUTED with NEITHER: the engine supports neither the claim nor its negation, because one premise is simply absent.

The full declarations follow. Their fields fall into three groups: the facts and the main context, which every question uses; extra parameters that only some tasks need; and data provenance and low-level integration, which matter when you record where facts came from or build the engine’s input yourself.

TypeScript
export type BareValue = string | number | boolean;
export type FactArg = Arg | BareValue;
export interface Fact {
predicate: string;
args: FactArg[];
/** `yes` (default when omitted), `no` (an explicit negative), `unknown` (no assertion). */
state?: Tristate;
id?: string;
package?: string;
origin?: string;
adjudicated?: boolean;
evidence?: string[];
}

Python sends the same shape as a dict: predicate, args, and the optional state ("yes", "no", "unknown"), id, package, origin, adjudicated, and evidence keys.

FieldMeaning
predicateThe relation name: a local name (frist_ereignis), a qualified name (de.bgb.core::anspruch_entstanden_am), or a full StableId (urn:...#local)
argsOne value per parameter, in order; a bare value or a pre-built Arg, which passes through unchanged
stateyes when omitted; no asserts the negation; unknown asserts nothing

The remaining fields (id, package, origin, adjudicated, evidence) describe provenance; they are listed in the last group below.

Bare values are wrapped by the declared type

Section titled “Bare values are wrapped by the declared type”
TypeScript
/**
* A bare value in an argument position is wrapped by the DECLARED type of
* the parameter: `'2026-03-06'` for a Date, `'14 calendar_day'` for a
* Quantity, `'730000 KZT'` for Money, `'Abschlagszahlung'` for an enum
* variant, `'frist'` (short) or `'urn:…'` (full) for an entity. An `Arg`
* object is passed through unchanged. Nothing is defaulted.
*/

The canon declares each parameter’s type, and the manifest (questions() in TypeScript) exposes it with a widget — entity, date, quantity, and the rest — so a form knows what to collect. Your code passes the plain string and the facade does the wrapping:

JavaScript
{ predicate: 'frist_ereignis', args: ['frist', '2026-03-06'] },
{ predicate: 'frist_dauer_tage', args: ['frist', '14 calendar_day'] },

The first fact’s second argument is a date; the second fact’s is a quantity of fourteen calendar days. A value that does not fit the declared type is a FactError, not a silent coercion, and the error names the path and the nearest valid names.

Entity ids are short names (frist) or full URNs; dates are calendar strings (2026-03-06); quantities carry their unit exactly as the canon declares it (14 calendar_day); money carries amount and currency (730000 KZT); enum variants use the canon’s own spelling.

TypeScript
export interface CaseInput {
/** The date the question is asked on the legal axis. Required, never "today". */
legalTime: string;
timezone?: string;
decisionTime?: string;
knowledgeTime?: string;
deadlinePolicy?: string;
/** Calendar snapshot the case is evaluated with; sent as `options.calendarSnapshot`. */
calendar?: string;
namespace?: string;
caseName?: string;
queryId?: string;
answers?: Fact[];
/** A case already lowered (assertions with `kind: 'assertion'`) is passed through. */
assertions?: unknown[];
constants?: Record<string, unknown>;
options?: Record<string, unknown>;
context?: Record<string, unknown>;
free?: unknown;
}
FieldMeaning
legalTimeThe date the law is projected on. Required; there is no default and “today” is never implied
timezoneThe time zone dates are read in, e.g. Europe/Berlin — not a holiday calendar
deadlinePolicyHow days are counted: a StableId declared by the canon, e.g. urn:de:corpus:clir:bgb-fristen#BGB_FRISTEN_TAG
calendarA calendar-data snapshot the case is evaluated with (weekends, holidays)
answersThe facts, as Fact above

legalTime deserves emphasis: every question is asked on the legal axis at an explicit date, because the canon in force on one date may differ from the canon in force on another. The application always supplies it.

The periods canon declares two counting policies: BGB_FRISTEN_TAG counts from the next day and moves an end that falls on a weekend or holiday to the next working day, while BGB_FRISTEN_KALENDER steps by the calendar with the last-day-of-month rule and no roll. A question about a period names one of them; without the policy the engine cannot count, and the answer says the policy is missing.

Three independent context elements, three different jobs — do not merge them:

  • time zone (timezone, e.g. Europe/Berlin): how dates are read. It never supplies holidays.
  • calendar data (calendar): the weekend/holiday snapshot the computation evaluates with.
  • counting policy (deadlinePolicy): the rule for beginnings, endings, and rolls (BGB_FRISTEN_TAG vs BGB_FRISTEN_KALENDER).

Choosing Europe/Berlin alone does not give the computation a holiday calendar; that comes only from the calendar data the case carries.

state: 'no' asserts the negation of the fact — the event did not happen on that date — and the engine can conclude from it. Omitting the fact asserts nothing: rules that need it stay undetermined, and a truth question over the gap answers NEITHER. The query chapter shows how why_not names exactly which premises were missing, so the application can ask the user for them instead of guessing.

These CaseInput fields matter only when the canon’s rules or the question kind read them:

FieldMeaning
decisionTimeWhen the deciding act happened, for rules that read it
knowledgeTimeWhat was known when, for rules that read it
constantsNamed constants the query may refer to
freeThe free position of a collect, when not named inline with ?name
namespaceThe namespace bare names resolve in; defaults to the canon’s
caseNameA name for the case, used in query ids
queryIdAn explicit id for the question
optionsEngine options for this evaluation
contextFurther context the rules may read

Provenance fields on a Fact say where the fact came from; they are carried into the proof:

FieldMeaning
idA stable handle for the fact, used when provenance is attached
packageDisambiguates a short name shared by several packages in the world
originWhere the fact came from, carried into the proof
adjudicatedWhether a court established this fact
evidenceDocument handles the provenance refers to

Two inputs bypass the facade’s own assembly:

  • assertions on CaseInput takes a case your code has already converted into the engine’s input form — a list of assertions marked kind: 'assertion' — instead of answers. The facade passes it through untouched: what you built is what the engine sees. Most applications never need it; answers is the normal route.
  • A pre-built Arg object in args passes through unchanged — the facade never re-wraps it — so a hand-built Arg must already be well formed.
  • Argument order follows the canon’s parameter order; the manifest lists it per relation.
  • A pre-built Arg object or an assertions list skips the facade’s checks and wrapping; the engine receives it as given.

Documentation for Arxo. Writings — blog.arxo.io.

Anonymous visit counts on stats.arxo.io, no cookies.