Skip to content

solvi.core.knowledge.store

The knowledge store: a hash-chained journal of facts, rules, skills, actions and episodes, with provenance, versions, the source check, disputes to a person, the retraction cascade, staleness flags and snapshots for decisions.

The knowledge store: what a system has learned, from whom, and what rests on it — a hash-chained journal of items.

from solvi.core.knowledge import KnowledgeStore
ks = KnowledgeStore("knowledge.jsonl")              # any TraceStorage backend (a path, a TraceStorage) or None
a = ks.add("fact", {"s": "abt:123", "r": "same_as", "o": "buy:456"}, source="person", by="ann")
b = ks.add("fact", {"s": "abt:123", "r": "brand", "o": "acme"}, source="outcome", derived_from=[a])
ks.retract(a, by="bob", why="wrong label")          # b goes with it; what a refuted or disputed comes back
ks.verify(); ks.fingerprint(); ks.rebuild(skip={a}).fingerprint()   # equal: a retraction is exact
ks.snapshot(about="abt:123")                        # what a decision is given: facts, hints, the fingerprint

An item is one of five kinds — fact (subject, relation, object), rule, skill, action (an action model's preconditions and effects), episode (one finished case) — with a source: "person", "outcome" (an observation: what really happened), "spec" (a written specification) or "verified" (a System 2 answer given alone under its guarantee, named by its stored decision of=). The system's own unverified answers are never admitted: the source check is part of the store, not a setting (SourceGate; solvi.core.sources.check_source).

State is a function of the live journal entries (JTMS-style): an item is present while it has a live assertion whose premises (derived_from) are present and not refuted. Per key (scope, subject, relation) the live assertions are walked in time order: the same value confirms; an outcome refutes the current value (the world changed); a higher-ranked source (outcome > person > spec > verified) refutes a lower one; supersedes (a producer's own new version) refutes the version it names; any other contradiction makes every side "disputed" and asks a person — one question per dispute EVENT (the journal position it opened at): the same two items disputed again after a resolution are asked again. resolve(key, value, by=) is the person's answer. A disputed item is never a fact.

Statuses: "active" (a fact System 1 may answer on), "hypothesis" (a hint: a behaviour-changing item not yet promoted by the write gates, a derived item on a premise that is not active, or an item under a pending flag), "disputed", "refuted", "expired" (its reconfirm_after ran out on the store's clock, or retired) and "retracted".

Staleness. flag(scope=..., why=...) (a drift or open-set flag) or reconfirm(id) marks items for re-confirmation: an item learned before the flag is a hint, not a fact, until a newer observation or person confirms it or the flag ends (end_flag). A flag is read where staleness is asked (stale(id)), not inferred from the item's status. rollback(id) (supervision undoing an update) restores the previous version as a hint, never as a fact, while a flag covering the rolled-back version or its scope is pending (a rollback during a pending drift flag once restored a stale threshold as a fact on a shifted stream, and a stale check that read the item status missed it). reconfirm_after per (kind, source) (RECONFIRM_DEFAULTS, the store's clock advanced by tick) expires items that weaker evidence keeps.

Every write — add, promote, refuse, retract, flag, resolve, tick, use — is a journal entry: in a TraceStorage (records of kind "knowledge", in the same hash chain as the decisions when it is the decisions' store) or in memory with its own hash chain. verify() checks the chain and that the state is what the journal gives; rebuild(upto=n, skip=ids) gives the store as it was at entry n, or as if ids had never been written; a store opened on a backend is replayed from its journal (an entry edited by hand breaks the chain). snapshot(...) returns what a decision reads, with the store's fingerprint and journal position, so every decision that is given it records the knowledge it used and replays; redecide(storage, retracted, decide) re-runs the decisions that rested on retracted items on their re-built snapshots and splits them into "answer changes" (to a reviewer) and "justification only" (recorded).

Measured by benchmarks/knowledge/retraction.py: 1,000 of 1,000 random retractions exact on a 10,000-item synthetic store (the store after a retraction has the fingerprint of the store rebuilt without it). Cost grows with the connected component a write touches (premise edges and shared keys); the store has been measured to 10,000 items, not 10⁵–10⁶.

Verdict dataclass

Verdict(admit: bool, reason: str = '', measured: dict | None = None, gate: str = '')

A write gate's answer about one proposed item: admitted or not, why, and what it measured (recorded).

SelfDefeating

Bases: ValueError

A derived item whose key is a key of one of its own premises with another value: it would refute its support.

KnowledgeStore

KnowledgeStore(storage=None, *, gates=None, defaults=None, _replay=True)

See the module docstring.

storage: where the journal is kept — None (in memory), a path (solvi.core.store.open_storage: .jsonl, .db, .duckdb, postgresql://) or a TraceStorage (the decisions' store itself, so knowledge and decisions share one hash chain); an existing journal there is replayed. gates: the write gates (solvi.core.knowledge.gates; default SourceGate and ConsistencyGate); the source check runs first whatever is given. defaults: reconfirm_after per (kind, source).

__len__

__len__()

The number of journal entries.

head

head()

{"count", "hash"}: the number of journal entries and the last entry's hash.

verify

verify()

Is the journal's hash chain whole (in memory: its own chain; on a backend: the backend's verify()), and is the state the one the journal gives (the store rebuilt from it has the same fingerprint)?

rebuild

rebuild(upto=None, skip=())

The store the journal gives, in memory: entries applied in order (upto: the first upto — the store as it was then); skip: item ids whose own entries (assertions, promotions, retractions) are left out — the store as if they had never been written. Premises naming a skipped item stay in the entries derived from it (dead).

batch

batch()

Several writes, one resolution at the end (a consolidation writes many items).

proposal

proposal(kind, body, scope=None, *, source, by=None, evidence=None, derived_from=(), supersedes=None, reconfirm_after=None, valid=None, level=None, of=None)

A proposed item as the write gates see it (a dict with the record's fields; nothing is written).

add

add(kind, body, scope=None, *, source, by=None, evidence=None, derived_from=(), supersedes=None, reconfirm_after=None, valid=None, level=None, of=None, shadow=None)

Propose an item → its id, or None when a gate refused it (a "refused" entry in the journal says why).

kind: one of KINDS; body: plain JSON (a fact: {"s", "r", "o"}); scope: a dict (a question, an environment); source: "person" | "outcome" | "spec" | "verified" (anything else is refused: never the system's own answers); by: who; evidence: stored ids, quotes, steps; derived_from: premise item ids (the retraction cascade follows them); supersedes: the id of the producer's previous version; reconfirm_after: clock units until it needs re-confirmation (default RECONFIRM_DEFAULTS); valid: (since, until) in the world, as you count time; level: a guarantee level (for "verified"); of: the stored decision a "verified" item is (checked in the store's TraceStorage); shadow: passed to the write gates (a shadow measurement for a behaviour-changing item).

A fact or an episode the gates admit is present at once; a rule, skill or action the gates admit is promoted (its verdicts recorded); one a later gate holds stays a hypothesis (a "held" entry says why).

promote

promote(iid, measured=None)

A behaviour-changing item passed its gates (or a person promotes it): it becomes a fact (measurements kept).

retract

retract(iid, *, by=None, why=None)

Take an item back, with everything derived from it (the cascade over derived_from); what it refuted or disputed comes back. Nothing is deleted: the journal keeps the entry and the chain stays whole. → {id: (status before, status after)} of every item whose status changed.

rollback

rollback(iid, *, by=None, why=None)

Supervision undoes an update: retract iid; the version it superseded or refuted comes back — as a fact, or only as a hint while a flag covering iid (or its scope) is pending: a stale version is never restored as a fact. → the status changes (as retract).

retire

retire(iid, *, why=None)

The item's producer no longer stands behind it (no longer compiled, no longer offered): expired.

reconfirm

reconfirm(iid, *, why=None, by=None)

Mark one item for re-confirmation (a flag on it alone) → the flag's id.

flag

flag(*, scope=None, ids=None, key=None, why=None, by=None)

A drift / open-set flag: the items it covers — ids, every item whose scope falls under scope, or the items on key — learned before it are hints, not facts, until a newer observation or a person confirms each (or end_flag). → the flag's id (its journal position).

end_flag

end_flag(flag, *, by=None, why=None)

The flag is resolved (re-confirmation done, a person cleared it): its items are facts again if nothing else holds them back.

resolve

resolve(key, value, *, by)

A person's answer to a dispute on key (an item's key, as disputes() lists it): value wins, the other sides are refuted.

tick

tick(k=1)

The store's clock moved by k (decisions on a stream, moves in an environment): reconfirm_after counts it.

used

used(ids, *, wrong=False)

Decisions used these items (wrong=True: and were found wrong): counts kept in each item's confidence.

note

note(**data)

A journal entry that changes nothing (a gate's rejected measurement, a reason): recorded and chained.

upstream

upstream(ids)

Items whose state can change the state of ids: premises (transitively) and same-key competitors.

component

component(ids)

The connected component(s) of ids: premise edges both ways and shared keys.

status

status(iid)

The item's status (STATUSES); "retracted" for an item with no live support (retracted, or derived from one).

stale

stale(iid)

Must this item be re-confirmed before System 1 answers on it alone? Reads the pending flags (and the clock's expiry) — not the status, which a later write could have set back.

usable

usable(iid)

Is the item a fact System 1 may answer on alone: active and not stale?

item

item(iid)

The record of one item (the schema of DESIGN_KM §4) → dict, or None.

find

find(*, kind=None, s=None, r=None, scope=None, status=None)

Records of the items that match every filter given (status: one or a tuple of STATUSES).

active

active(key)

The active item on a key, or None.

disputes

disputes()

The open disputes (person questions not resolved yet) → [{"question", "key", "items", "opened_at"}].

snapshot

snapshot(*, about=None, relation=None, scope=None, kinds=None)

What a decision is given, as one plain fact: the items that match (about: a subject or a list of them; relation; scope; kinds, default every kind) — "facts" (active and not stale) and "hints" (hypotheses, expired or stale items, with why) — the ids they rest on, the store's fingerprint and journal position, and the query (so redecide can rebuild it). Disputed, refuted and retracted items are not given.

fingerprint

fingerprint()

A hash of what the store believes: every present item with its status, rank, support and time range.

counts

counts()

{status: number of items} (absent items counted as "retracted").

report

report()

What was learned and from whom, what is refuted, retracted, expired, disputed, flagged → a dict.

redecide

redecide(storage, retracted, decide, *, fact='knowledge')

The stored decisions that rested on retracted items, re-run on their re-built snapshots.

storage: the decisions' TraceStorage; retracted: the retracted item ids; decide: init_state → {question: answer} (a function, or a System: its ask, not stored); fact: the given fact the snapshot was passed as. A decision is listed when its recorded snapshot changes once the retracted items' entries are left out of the journal up to its position; each is re-run on the old and the new snapshot. → (answer_changes, justification_only): lists of {"id", "old", "new"} — the first goes to a reviewer, the second is recorded.

key_of

key_of(body, scope)

Items whose body has a subject "s" and a relation "r" have a key: one value per (scope, s, r) at a time.

item_id

item_id(kind, body, scope)

The content id of an item: the same knowledge written twice is one item.

scope_matches

scope_matches(scope, pattern)

Does an item's scope fall under a flag's scope (every key of pattern has the same value in scope)?