[·] nescio docs
nescioDB documentation

The database that knows what it doesn't know.

nescio stores claims — who said what, when, and how reliable they are. Credible regions, entropy and answers are derived at query time, and they decay the way the real world does.

$ nescio bound herodb --entity villa --slot price --at 2026-06-25 replayed from 105 real queries
day 0 region (95%) 670k – 1,110k entropy 4.00 / 7.64 bits knowledge 48%
728 d
Evidence narrows the region. Time widens it again — the broker's half-life is 90 days. No TTL fired, nothing was deleted; the posterior just eroded.

startInstall

nescio is a single binary with an embedded engine — no daemon, no dependencies. A database is just a directory.

cargo install nescio

Or use it as a Rust library:

cargo add nescio

Requires Rust 1.75+. Sources are on GitHub; the crate API reference lives on docs.rs.

startFive-minute tour

Every output below is the actual output of nescio v0.8.0 — copy the commands and you will get the same numbers.

1 · Create a database

The real-estate template ships four slots and five sources of very different trustworthiness. (nescio templates lists all built-ins — osint and sensor too — and nescio templates --show NAME prints one as JSON to start your own schema from.)

$ nescio init mydb --template real-estate
initialized nescioDB in mydb
  slots:   condition, price, wants_to_sell, year_built
  sources: broker, land_registry, neighbor, notary, web_scraper

2 · Ingest a claim

You never write a value. You record that someone said something:

$ nescio ingest mydb --entity villa_1 --slot price \
    --interval 900000..1000000 --source broker --at 2026-06-25
ingested evidence for villa_1.price at 2026-06-25

3 · Ask what is known

$ nescio bound mydb --entity villa_1 --slot price --at 2026-07-03
BOUND villa_1.price as of 2026-07-03
  region (95%): [570000, 1210000]
  entropy: 4.20 of 7.64 bits (knowledge 45%)
  MAP estimate: 905000

The broker said 900–1,000k eight days ago with reliability 0.85 and a 90-day half-life — so the region is already wider than the claim. The database is honest about that.

4 · Time-travel

Ask the same question a year later. Nothing was written, nothing expired — the evidence just aged:

$ nescio bound mydb --entity villa_1 --slot price --at 2027-07-03
BOUND villa_1.price as of 2027-07-03
  region (95%): [220000, 1570000]
  entropy: 7.13 of 7.64 bits (knowledge 7%)
  MAP estimate: 905000

5 · Let the database plan its own procurement

Describe what evidence could be bought, and resolve picks the cheapest path to a knowledge target:

$ nescio resolve mydb --entity villa_1 --slot price \
    --target-bits 2.0 --actions actions.json --at 2026-07-03
RESOLVE villa_1.price: 4.42 bits now, target 2.00
  1. call the broker (slot price, cost 5) -> expected 2.57 bits
  2. pull the land registry (slot price, cost 40) -> expected 1.50 bits
  total cost 45 | greedy estimate 1.50 bits | MC-validated 1.31 bits

From here: read the concepts to understand the model, or jump straight to the verbs.


conceptsClaims, not values

A row in a classical database asserts a fact. A record in nescio asserts that a source made a claim:

{"entity": "villa_1",
 "claim": {"type": "interval", "slot": "price", "lo": 900000.0, "hi": 1000000.0},
 "source": "broker",
 "observed_at": 1782345600}

Three claim types cover continuous and categorical slots:

typesaysslot kind
interval"the value lies in [lo, hi]"continuous
value"the value is v"categorical
not_value"the value is not v"categorical

Each claim contributes a likelihood factor under a mixture model: with probability r the source is right (the value is uniform over what it asserted), with 1 − r the claim is noise (uniform over the whole domain). Two consequences fall out of this, for free:

  • Precision carries weight. A narrow interval is stronger evidence than a vague one, from the same source at the same time.
  • Contradiction is representable. Two sources can disagree; the posterior holds both, weighted by reliability.

Everything else — regions, entropy, answers — is derived at query time. There is no materialized value to become stale.

conceptsSources & decay

A source has a base reliability and, optionally, a half-life. Reliability erodes by physics, not by TTL:

r(t) = r0 · 0.5 ^ (age_days / half_life_days)

As r → 0 the claim's likelihood factor flattens toward uniform: old evidence loosens its grip on the region automatically. Deleting is unnecessary — and when you do delete (forgetting), regions widen correctly because nothing was ever aggregated.

$ nescio source mydb --name web_scraper --reliability 0.7 --half-life-days 45

The template's sources show the intended spread:

sourcer0half-life
land_registry1.0axiomatic: never decays, never wrong
notary1.0axiomatic
broker0.8590 d
web_scraper0.745 d
neighbor0.430 dgossip, weakly informative

Non-axiomatic reliability is capped just below 1.0 — two contradicting hard claims would otherwise annihilate the posterior. Only axioms may be absolute.

If the log contains ground truth (axiomatic evidence about the same slots a soft source reported on), nescio can learn a source's physics instead of you guessing them:

$ nescio recalibrate mydb --source web_scraper --apply

conceptsRegions & entropy

A slot's domain is discretized into cells (bins for continuous slots, one cell per value for categorical ones). The posterior over those cells yields everything bound reports:

  • Credible region — the smallest set of cells holding (by default) 95% of the probability mass, merged into intervals. A field without evidence is not NULL; it is a region covering the whole domain.
  • Entropy in bits — how ignorant the database really is. 7.64 bits is maximal for a 200-bin slot (log₂ 200); 0 is a collapsed point. The ratio is reported as knowledge.
  • MAP estimate — the single most probable cell, if you must have one number. It is deliberately the last line of the output.
A classical relational database is the special case where every claim is an axiom and every region is a point. nescio is a strict superset — you can run it as a normal database and pay nothing for the generality until you need it.

conceptsCouplings

Couplings let knowledge flow between slots of the same entity. The template couples condition ~ price: renovated villas center on 1.3M, derelict ones on 500k. Watch what a claim about the condition does to the price:

$ nescio ingest mydb --entity villa_1 --slot condition \
    --value renovated --source web_scraper --at 2026-06-20
$ nescio bound mydb --entity villa_1 --slot price --at 2026-07-03
BOUND villa_1.price as of 2026-07-03
  region (95%): [900000, 1550000]      ← was [570000, 1210000]
  entropy: 4.42 of 7.64 bits (knowledge 42%)
  MAP estimate: 995000                 ← was 905000

The region shifted upward — and note that knowledge dropped from 45% to 42%. The scraper's "renovated" pulls toward 1.3M while the broker's interval says 900–1,000k; the posterior honestly holds the tension between them. Evidence that disagrees with what you believed makes you less certain. That is a feature.

There are four coupling kinds — gaussian_by_category, step_threshold, matrix and table — each expecting specific domain kinds on slot_a / slot_b. They are declared once in schema.json and apply to every entity; the exact JSON for each kind is in the file-format reference.

conceptsAxioms & conflict

An axiomatic source has reliability 1.0 and no half-life: the land registry, a notary, a court ruling. Axioms are the only way to make something certain — and their regions never widen:

$ nescio bound mydb --entity villa_3 --slot price --at 2036-07-03
BOUND villa_3.price as of 2036-07-03
  region (95%): [940000, 990000]       ← identical ten years later
  entropy: 2.32 of 7.64 bits (knowledge 70%)

Two axioms that contradict each other are not a bug to paper over — they are a real state of the world, and nescio surfaces them as a conflict when you query the affected slot. Over HTTP that is a 409:

$ curl 'localhost:7777/bound?entity=villa_3&slot=price'
{"error":"axiom conflict: villa_3.price: axiomatic evidence conflicts"}
HTTP 409

conceptsPriors

When many entities share background knowledge ("prices in this district cluster around 800k"), register the prior once and reference it per entity — it is stored once, never copied:

$ nescio prior register mydb --name district_9 --slot price --gaussian 800000,150000
$ nescio prior use mydb --entity villa_2 --slot price --name district_9

Arbitrary shapes go in via --weights-file (one weight per cell of the slot's domain).

conceptsSchema evolution

Nobody knows their schema on day one. A live database can grow and shrink — and the model makes most of it cheap, because ignorance is the default state:

$ nescio schema add-slot mydb --name floor_area --continuous 20..600 --bins 116
slot "floor_area" added (continuous [20, 600], 116 bins); every entity starts at maximal entropy

$ nescio schema add-value mydb --slot condition --value gutted
value "gutted" added to slot "condition" (4 values now)

$ nescio schema add-coupling mydb --file coupling.json
coupling sell~price added; it applies to every entity immediately
  • Adding a slot needs no backfill — every existing entity simply starts at maximal entropy on it. There is no NULL to migrate.
  • Extending a categorical slot keeps history valid — the log stores values as strings, not indices. Coupling tables are recompiled (a category without an entry is uninformative), and priors on the slot are extended with the mean of their existing weights.
  • Changes are transactional — a coupling that fails validation (unknown slot, wrong domain kind, misshapen table) is refused with nothing committed. Like a contradicting axiom, a valid coupling that disagrees with existing evidence surfaces at query time, not at schema time.
  • Removing a slot cascades what is slot-local — its evidence is physically erased (log rewrite, same mechanism as forgetting) and its priors go with it. Anything cross-slot is refused instead: remove-slot on a coupled slot tells you to remove the coupling first.

The same operations over HTTP: POST /schema/add-slot, /schema/add-value, /schema/add-coupling, /schema/remove-coupling, /schema/remove-slot.

conceptsWatches & knowledge horizons

Decay is deterministic physics — which means "when will the database stop knowing enough?" has a computable answer. A watch is a standing question: name an entity's slot and a threshold, and nescio answers with the knowledge horizon — the exact date on which decay alone will push the slot past the threshold — the moment you register it:

$ nescio watch add mydb --name price_fresh --entity villa_1 --slot price --max-entropy 5.0
watch "price_fresh" added
  ok        price_fresh      villa_1.price  4.25 bits (threshold 5.00)  fires ~2026-08-10 without new evidence

$ nescio watch add mydb --name sell_known --entity villa_1 --slot wants_to_sell --min-knowledge 0.3
watch "sell_known" added
  TRIGGERED sell_known       villa_1.wants_to_sell  1.00 bits (threshold 0.70)

$ nescio watch check mydb --at 2026-09-01   ← exits 2 when anything fired: cron-ready
  TRIGGERED price_fresh      villa_1.price  5.38 bits (threshold 5.00)
  TRIGGERED sell_known       villa_1.wants_to_sell  1.00 bits (threshold 0.70)
  • Two ways to say the same thing--max-entropy BITS (absolute) or --min-knowledge RATIO (relative to the slot's maximal entropy). Exactly one per watch.
  • The horizon is a prediction, not a poll — no horizon exists when an axiomatic, non-decaying source pins the slot forever; new evidence moves it.
  • Conflict fires the watch — a standing question that cannot be answered (axiom conflict on the slot) is treated as triggered, with the error attached. Contradiction must surface.
  • Cron-friendlynescio watch check exits 0 when all is quiet, 2 when something fired; --json for machines.

Under nescio serve, a background evaluator re-checks the watches after every write and pushes transitions as Server-Sent Events — decay becomes a signal you subscribe to instead of a report you remember to ask for:

$ curl -N localhost:7777/watches/events
event: snapshot
data: {"as_of":1783281478,"watches":[{"name":"price_fresh","entity":"villa_1","slot":"price",
       "max_entropy_bits":5.0,"triggered":false,"entropy_bits":4.25,"threshold_bits":5.0,
       "knowledge":0.44,"horizon":1786305478,"horizon_date":"2026-08-10"}]}

event: triggered                                ← ~40 days later, no new evidence
data: {"name":"price_fresh","entity":"villa_1","slot":"price","triggered":true, …}

event: recovered                                ← fresh evidence arrived
data: {"name":"price_fresh","entity":"villa_1","slot":"price","triggered":false, …}

The matching HTTP routes: GET /watches (every watch with state and horizon), GET /watches/check (only the triggered ones), POST /watches / POST /watches/remove. Watches live in watches.json next to the schema (format).

conceptsForgetting

GDPR erasure is a first-class verb. Because nothing is aggregated — every answer is derived from the raw claims — physically removing a source's evidence makes every derived region widen correctly. There is no cached aggregate that could forget to forget:

$ nescio forget-source mydb --source web_scraper
physically erased 2 evidence records from source "web_scraper"; all derived regions widen

$ nescio bound mydb --entity villa_2 --slot price --at 2026-07-03
  entropy: 7.22 of 7.64 bits (knowledge 6%)   ← was 6.29 bits (18%)

the verbsbound

"What is known — and how ignorant are you, really?"

$ nescio bound mydb --entity villa_1 --slot price --credible 0.95 --at 2026-07-03
BOUND villa_1.price as of 2026-07-03
  region (95%): [570000, 1210000]
  entropy: 4.20 of 7.64 bits (knowledge 45%)
  MAP estimate: 905000

The credible level defaults to 0.95. Add --json for machine-readable output; the region of a categorical slot is a value set instead of intervals.

the verbssample

"Show me one world that is consistent with everything you know."

$ nescio sample mydb --entity villa_1 --seed 1 --at 2026-07-03
SAMPLE villa_1 (seed 1) as of 2026-07-03
  condition = renovated
  price = 916328.3849294278
  wants_to_sell = true
  year_built = 2006.148830186706

Samples respect couplings — a world where condition = derelict drags its sampled price down. The same seed always yields the same world, which makes downstream Monte-Carlo pipelines reproducible; different seeds explore the posterior.

the verbscertainly

"Is this true — and do you actually know it, or merely believe it?"

Three-valued logic as region containment: true if the whole region satisfies the predicate, false if none of it does, possible otherwise.

$ nescio certainly mydb --entity villa_3 --slot price --lt 1000000 --at 2026-07-03
true
$ nescio certainly mydb --entity villa_3 --slot price --gt 1000000 --at 2026-07-03
false
$ nescio certainly mydb --entity villa_1 --slot price --gt 950000 --at 2026-07-03
possible

villa_3's price rests on a notarial axiom, so hard answers exist. villa_1 rests on soft evidence — under the mixture model soft evidence never zeroes out any cell, so its predicates can only ever be possible. If you need certainty, you need an axiom; the database will not fake it.

Predicates: --gt, --lt, --between LO..HI (continuous), --is, --is-not (categorical).

the verbsfind

"Which entities certainly / possibly lie in this range?"

$ nescio find mydb --slot price --lo 900000 --hi 1000000 --mode possible --at 2026-07-03
villa_1
villa_2
villa_3
$ nescio find mydb --slot price --lo 900000 --hi 1000000 --mode certain --at 2026-07-03
villa_3

The two modes are the two useful questions: possible is recall (candidates you must not miss), certain is precision (matches you can act on without checking). A classical WHERE clause silently conflates them.

the verbsjoin

"Which pairs of entities relate — and how sure is that?"

Joining two regions is itself uncertain, so every match carries a probability (exact integral over the two posteriors, no sampling) and a three-valued certainty.

# comparable properties: |left.price − right.price| ≤ 150k
$ nescio join mydb --op approx --left price --right price --tol 150000 --at 2026-07-03
  villa_1                  villa_3                  p=0.885  possible
  villa_1                  villa_2                  p=0.164  possible
  villa_2                  villa_3                  p=0.163  possible
  3 pairs examined

# who is certainly / probably dearer than whom?
$ nescio join mydb --op gt --left price --right price --at 2026-07-03
  villa_3                  villa_2                  p=0.753  possible
  villa_1                  villa_2                  p=0.749  possible
  ...

Operators: gt, lt, approx (numeric, needs --tol) and same (categorical — entity resolution and dedup). Useful options:

option
--min-prob 0.8keep only matches at least this probable
--certainkeep only regionally-certain matches
--left-prefix a_restrict a side by entity-id prefix ("kinds" by naming convention)
--all-entitiesinclude entities with no evidence on the slot (default: excluded — they would match everything as "possible")
--limit 20cap returned matches, ranked by probability

Pairs are pruned to the regionally possible before any integral is computed. An unselective join is quadratic — that is fundamental to joins, SQL included — so evaluation is capped and the cap is reported (truncated: true), never silently applied.

the verbsresolve

"Which minimal-cost evidence would push entropy under a target?"

Describe the procurement actions you could take — each is "ask this source about this slot, at this cost":

# actions.json
[
  {"name": "call the broker",        "slot": "price",     "cost": 5,
   "source": {"name": "broker", "reliability": 0.85, "half_life_days": 90},
   "answer_width": 100000},
  {"name": "pull the land registry", "slot": "price",     "cost": 40,
   "source": {"name": "land_registry", "reliability": 1.0, "axiomatic": true},
   "answer_width": 20000},
  {"name": "ask the neighbor",       "slot": "condition", "cost": 1,
   "source": {"name": "neighbor", "reliability": 0.4, "half_life_days": 30}}
]
$ nescio resolve mydb --entity villa_1 --slot price \
    --target-bits 2.0 --actions actions.json --at 2026-07-03
RESOLVE villa_1.price: 4.42 bits now, target 2.00
  1. call the broker (slot price, cost 5) -> expected 2.57 bits
  2. pull the land registry (slot price, cost 40) -> expected 1.50 bits
  total cost 45 | greedy estimate 1.50 bits | MC-validated 1.31 bits

Three details worth knowing:

  • Actions on other slots count — couplings carry the information gain across slot boundaries. Asking the neighbor about the condition can reduce price entropy.
  • The greedy plan is Monte-Carlo validated over full sampled worlds (seeded, reproducible). The validated number is the one to trust.
  • If no action helps, the plan says so: "the DB cannot know more this way" is an answer, not a failure.

the verbsdecide

"Which evidence most improves the decision I actually face?"

Entropy is only the Bayes risk under log-loss. A cheap observation that halves the entropy is worthless if it never changes what you would do — and a tiny one that flips a high-stakes call is worth a lot. decide plans against an objective instead of bits: the true Value of Information.

objectiveyou commit torisk is measured in
{"kind": "entropy"}the full posteriorbits (classical resolve)
{"kind": "squared_error"}the posterior meanvariance
{"kind": "absolute_error"}the posterior medianexpected abs. error
{"kind": "decision", "loss": [[…]], "labels": […]}one of n decisionsexpected loss
$ nescio decide mydb --entity villa_1 --slot price \
    --objective objective.json --target 10000 --actions actions.json --at 2026-07-03
DECIDE villa_1.price [absolute_error]: risk 71027.9574 abs. error now, target 10000.0000
  would decide now:   estimate 955000
  1. call the broker (slot price, cost 5) -> expected risk 32243.8096 abs. error
  2. pull the land registry (slot price, cost 40) -> expected risk 5000.9287 abs. error
  would decide after: estimate 955000
  total cost 45 | greedy 5001.2186 abs. error | MC-validated 4664.3089 abs. error

Read it as: "your price estimate is currently off by ±71k in expectation; a 5-unit broker call cuts that to ±32k; adding the registry pull gets you under ±5k." The plan also reports what it would decide before and after — if the decision would not change, the evidence was not worth buying. Same endpoint shape as resolve: POST /decide with objective and target.


referenceHTTP API

One process owns the database directory:

$ nescio serve mydb --port 7777 --bind 127.0.0.1
nescioDB serving mydb on http://127.0.0.1:7777 (parallel reads, exclusive writes)

All responses are JSON. Queries — including the read-only POST /resolve, /decide and /join — run concurrently under a shared read lock; mutations take the write lock exclusively and are durable (fsynced) before they are acknowledged.

routeparameters / body
GET /health
GET /statusslots, couplings, sources, counts
GET /boundentity, slot [, credible=0.95] [, at]
GET /sampleentity [, seed=0] [, at]
GET /certainlyentity, slot, op=gt|lt|between|is|is_not [, value] [, lo, hi] [, at]
GET /findslot, lo, hi [, mode=possible|certain] [, at]
POST /join{predicate: {op, left, right, tol?}, options?, at?}
POST /resolve{entity, slot, target_bits, actions, max_steps?, mc?, seed?, at?}
POST /decide{entity, slot, objective, target, actions, max_steps?, mc?, seed?, at?}
POST /ingest{entity, claim, source, at?}
POST /ingest-batch[{entity, claim, source, at?}, …] — one group commit, one fsync
POST /sources{name, reliability, half_life_days?, axiomatic?}
POST /forget-source{source}
POST /recalibrate{source, apply?, min_truth_reliability?}
POST /priors/register{name, slot, weights}
POST /priors/use{entity, slot, name}
POST /schema/add-slot{name, domain} — domain as in schema.json
POST /schema/remove-slot{name} — erases the slot's evidence and priors; refused while coupled
POST /schema/add-value{slot, value} — categorical slots
POST /schema/add-coupling{slot_a, slot_b, compat, name?}
POST /schema/remove-coupling{name} — by label
GET /watches[at] — every watch with state + knowledge horizon
GET /watches/check[at] — only the triggered ones
GET /watches/eventsServer-Sent Events: snapshot, then triggered / recovered
POST /watches{name, entity, slot, max_entropy_bits? | min_knowledge?} — returns the initial state incl. horizon
POST /watches/remove{name}

at accepts "YYYY-MM-DD", a date-time, or unix seconds — in query strings and JSON bodies alike; it defaults to now. Status codes:

codemeaning
200ok
400invalid request — {"error": "…"} says what
404no such route
409axiom conflict — contradiction is a real state, not a server failure
413body over 8 MiB

The server binds to localhost by default and speaks plain HTTP without authentication — put it behind a reverse proxy if you expose it beyond the host.

referenceCLI

Every query command takes --at (time travel is a parameter, defaulting to now) and the verbs take --json for machine-readable output. nescio <command> --help shows every flag.

command
initcreate a database — --template NAME or --schema + --sources JSON files (formats)
templateslist built-in templates; --show NAME prints one as JSON
statusslots, couplings, sources, entity and record counts
sourceregister or update a source (--reliability, --half-life-days, --axiomatic)
ingestappend one claim: --interval LO..HI | --value V | --not-value V
boundcredible region + entropy + MAP (--credible 0.95)
sampleone consistent world, deterministic under --seed
certainlythree-valued predicate: --gt/--lt/--between/--is/--is-not
findentities in a range, --mode possible|certain
joinuncertain join, --op gt|lt|approx|same
resolveplan evidence to reach --target-bits, given --actions
decideplan evidence against an --objective, target in its risk units
prior register / useshared priors (--gaussian CENTER,SIGMA or --weights-file)
schema add-slot / remove-slot / add-value / add-coupling / remove-couplingevolve a live database — see schema evolution
watch add / rm / list / checkstanding questions with knowledge horizons — check exits 2 when triggered (watches)
forget-sourceGDPR erasure; all derived regions widen
recalibratelearn a source's r0 / half-life from ground truth (--apply)
import / exportJSONL in (one group commit) / JSONL out — dates or unix seconds, see formats
servethe HTTP API (--port 7777, --bind 127.0.0.1)

referenceFile formats

Every JSON shape nescio reads or writes, in one place — nothing here requires reading source code. The quickest living reference is nescio templates --show NAME, which prints a complete, valid schema + sources pair for any built-in template (real-estate, osint, sensor).

schema.json

Slots (the state spaces) and couplings (cross-slot correlations). Passed to init --schema; stored verbatim in the database directory.

{
  "slots": {
    "price":     {"type": "continuous", "lo": 0.0, "hi": 2000000.0, "n_bins": 200},
    "condition": {"type": "categorical", "values": ["renovated", "original", "derelict"]},
    "for_sale":  {"type": "categorical", "values": ["true", "false"]}
  },
  "couplings": [
    {"slot_a": "condition", "slot_b": "price", "name": "condition~price",
     "compat": {"kind": "gaussian_by_category",
                "centers": {"renovated": 1300000, "derelict": 500000},
                "sigma": 300000}}
  ]
}
  • continuous: hi > lo, n_bins ≥ 2. Resolution is (hi − lo) / n_bins — 200 bins over 2M means 10k steps.
  • categorical: at least two distinct values. Booleans are the two-value domain ["true", "false"].
  • couplings is optional; name defaults to "slot_a~slot_b" and must be unique (it is the handle for schema remove-coupling).

Coupling kinds

Slot order matters — each kind expects specific domain kinds on slot_a and slot_b:

kindslot_aslot_bfieldssemantics
gaussian_by_category categoricalcontinuous centers: {value: center}, sigma each category pulls slot_b toward its center, Gaussian-shaped; a category without a center is uninformative
step_threshold continuouscategorical threshold, below: {value: w}, above: {value: w} category weights switch at the threshold; missing entries are 1 (uninformative)
matrix categoricalcategorical weights: {a: {b: w}}, default (1) pairwise compatibility; missing entries fall back to default
table anyany rows (na × nb) fully explicit factor table, rows over slot_a's cells — maximal control, but fixed dimensions (it blocks schema add-value on its slots)

All weights must be finite and ≥ 0. A weight of 0 is a hard constraint — combined with an axiom it can produce a query-time conflict, exactly like contradicting axioms.

sources.json

What init --sources accepts — a JSON array, or the name-keyed map nescio itself writes into the database directory. Both work, so a file copied from an existing database is valid input.

[
  {"name": "land_registry", "reliability": 1.0, "axiomatic": true},
  {"name": "broker",        "reliability": 0.85, "half_life_days": 90.0},
  {"name": "web_scraper",   "reliability": 0.7,  "half_life_days": 45.0}
]
  • reliability ∈ (0, 1]. Non-axiomatic sources are capped just below 1.0 internally — only axioms may be absolute.
  • half_life_days optional, > 0; omitted means no decay.
  • axiomatic optional, default false.

Evidence records (JSONL for import / export)

One record per line. observed_at takes unix seconds or a readable date — and at is accepted as an alias, so hand-written files look like the CLI and the HTTP body. export always writes canonical unix seconds.

{"entity": "poi_7", "claim": {"type": "value", "slot": "role", "value": "courier"}, "source": "informant", "at": "2026-06-20"}
{"entity": "poi_7", "claim": {"type": "interval", "slot": "age", "lo": 25, "hi": 35}, "source": "social_media", "observed_at": "2026-06-28T14:30"}
{"entity": "poi_9", "claim": {"type": "not_value", "slot": "role", "value": "financier"}, "source": "news_wire", "observed_at": 1782345600}

Claim types: interval (continuous; lo ≤ hi, must intersect the domain), value and not_value (categorical; the value must be in the domain). The source must be registered before import.

Actions (resolve / decide)

A JSON array describing what evidence could be bought. The source is inline — it does not need to be registered, so you can plan with hypothetical sources.

[
  {"name": "call the broker", "slot": "price", "cost": 5,
   "source": {"name": "broker", "reliability": 0.85, "half_life_days": 90},
   "answer_width": 100000}
]
field
namelabel used in the plan output
slotwhich slot the answer lands on — need not be the target slot; couplings carry the gain across
costwhatever unit you optimize (money, minutes, API calls)
sourcean inline source object: reliability, half-life, axiomatic
answer_widthcontinuous slots only: the width of the interval the answer would arrive as

Objectives for decide are documented at the verb: {"kind": "entropy" | "squared_error" | "absolute_error"} or {"kind": "decision", "loss": [[…]], "labels": […]} with one loss row per decision and one entry per cell of the target slot.

priors.json

Managed via nescio prior register / use — you rarely write it by hand, but it is plain JSON:

{
  "registry":    {"district_9": {"slot": "price", "weights": [0.2, 0.4, "…one per cell…"]}},
  "assignments": {"villa_2": {"price": "district_9"}},
  "defaults":    {"price": [1.0, 1.0, "…applies to every unassigned entity…"]}
}

Weights are relative (they are normalized), must be finite and ≥ 0, not all zero, and their length must equal the slot's cell count (n_bins or the number of categorical values).

watches.json

Standing questions (watches), written by nescio watch add / POST /watches — you rarely edit this by hand, but it is plain JSON like everything else:

[
  {"name": "price_fresh", "entity": "villa_1", "slot": "price", "max_entropy_bits": 5.0},
  {"name": "sell_known",  "entity": "villa_1", "slot": "wants_to_sell", "min_knowledge": 0.3}
]

Exactly one of max_entropy_bits (bits) / min_knowledge (ratio in (0, 1]) per watch; names are unique, the slot must exist. The file is optional — no watches, no file.

referenceClient libraries

Typed clients for Python, TypeScript and Java. All three are zero-dependency by design and single-file vendorable: if you'd rather not add a package, copy the one source file into your project and you are done.

# pip install nesciodb   (Python 3.9+, urllib only — or vendor the one file)
from datetime import date
from nesciodb import NescioClient, claim, source, action, objective

db = NescioClient("http://localhost:7777")

db.ingest("villa_1", claim.interval("price", 900_000, 1_000_000),
          "broker", at="2026-06-25")

b = db.bound("villa_1", "price", at=date(2026, 7, 3))
print(f"{b.entropy_bits:.2f} bits, knowledge {b.knowledge_ratio:.0%}")
print(b.region)                          # [(570000.0, 1210000.0)]

# Entity handles for entity-centric code:
villa = db.entity("villa_1")
villa.certainly("price", lt=1_500_000)   # "true" | "possible" | "false"

# Value of Information: which evidence most improves the decision?
plan = villa.decide("price", objective=objective.absolute_error(), target=10_000,
    actions=[action("pull the land registry", "price", cost=40,
                    src=source("land_registry", 1.0, axiomatic=True),
                    answer_width=20_000)])
print(plan.recommended_now, "->", plan.validated_risk)

# Watches: fire when knowledge decays past a threshold
st = db.add_watch("price_fresh", "villa_1", "price", max_entropy_bits=5.0)
print(st.horizon_date)                   # the knowledge horizon: "2026-08-10"
for ev in db.watch_events():             # SSE: snapshot, then transitions
    if ev.event == "triggered":
        notify(ev.state)

What each client covers:

clientverbstimes (at)errors
Python all — incl. decide, schema evolution, watches + SSE stream, batch ingest, priors, recalibrate ISO string, unix seconds, date, datetime NescioError with .status
TypeScript all — incl. decide, schema evolution, watches + SSE stream, batch ingest, priors, recalibrate ISO string, unix seconds, Date NescioError with .status
Java boundresolve, ingest, sources, priors, recalibrate At.date(…), At.unix(…) NescioException with .status()

Python and TypeScript also ship builders for every wire format — claim.*, domain.*, coupling.*, objective.* — so the file-format rules (slot order, field names) are encoded in the type system instead of your memory.

referenceStorage & ops

A database is a directory. Config is human-readable JSON; the evidence log is a compact append-only binary (~2.6× smaller than JSONL, no parse cost on replay):

mydb/
  schema.json     slots and couplings
  sources.json    reliability, half-life, axiomatic
  priors.json     shared priors
  watches.json    standing questions (only if you add some)
  log.bin         the evidence log (append-only binary)

The binary log is never the only copy of the truth you can read — export reconstructs JSONL any time, and import goes the other way with a single group commit:

$ nescio export mydb | head -1
{"entity":"villa_1","claim":{"type":"interval","slot":"price","lo":900000.0,"hi":1000000.0},"source":"broker","observed_at":1782345600}

Performance

Measured on an M-series MacBook, 200,000 entities / 400,000 evidence records (cargo run --release --example bench):

ingest (group commit, one fsync)   ~1.1M records/s
open / log replay                  ~1.2M records/s
bound                              4.5 µs  (8.6 µs with couplings)
resolve                            < 1 ms

Every write is durable before it is acknowledged — a batch of any size costs one fsync.