TruthMaintenanceSession keeps derived facts synchronized with changing external evidence. It maintains the invariant:
Reasoner, actions, RETE, and Datalog behavior is unchanged.
When to Use It
UseTruthMaintenanceSession when:
- Facts come from external evidence that can be corrected or withdrawn (e.g. a document revision replaces one assertion with another).
- You need derived conclusions to be removed only when they lose all support, not on any deletion.
- You want a batch update that publishes the final state and net changes, instead of replaying intermediate states.
- You are hitting the limits of
RetractAction: it removes only the requested fact and leaves unsupported conclusions behind, while a naive deletion cascade would wrongly remove conclusions that still have alternative derivations or explicit support.
Getting Started
Employed(Alice) remains supported and Eligible(Alice) keeps its derivation.
Core Concepts
- Support: an external fact assertion, identified by a caller-provided
support_id. A support ID is permanently bound to one fact for the session lifetime; withdrawing and later reactivating the same association is allowed, but binding the same ID to a different fact raisesValidationError. - Derivation: a rule application with rule identity, ordered premises, variable bindings, and conclusion. Independent derivations of the same conclusion are all retained; duplicate matches cannot inflate support.
- Version: a monotonic commit counter. It increments once per batch that changes the support set (even if the fact set is unchanged) and stays unchanged for no-ops.
- Truth semantics: losing all support means “not currently derivable”, not “logically false”.
Session API
TruthMaintenanceSession(rules=...)
Constructs the session from an iterable of existing Rule objects. The rules are copied into immutable internal snapshots: later mutation of the original Rule objects does not affect the session.
The rule set must be:
- Positive, function-free implications with at least one body atom.
- Range-restricted: every head variable occurs in the body.
- Acyclic: the predicate dependency graph must not contain direct or indirect recursion; cyclic rule sets are rejected at construction.
- Side-effect free: actions, handlers, and non-implication rule types are rejected;
confidencemust be1.0.
apply(assertions=..., retractions=...)
Atomically applies one batch:
assertions: iterable ofFactSupport.retractions: iterable of support-ID strings.
MaintenanceDelta with version, added_facts, removed_facts, added_supports, and removed_supports (net values, observed after commit).
Validation and error semantics:
- Re-asserting an already active support with the same fact is a no-op; retracting an unknown or inactive support is a no-op.
- A support ID bound to a different fact, the same ID in both assertions and retractions of one batch, or any malformed input raises the existing
ValidationError. - Unexpected internal failures raise
ProcessingErrorwith the original cause attached. - Any failed batch leaves the committed facts, support catalog, derivations, and version unchanged.
explain(fact)
Returns a FactExplanation for the current state:
fact: the canonical fact string.active: whether the fact currently holds.explicit_support_ids: sorted IDs of external supports.derivations: all retained direct derivations (rule ID, ordered premises, bindings).
Read-only views
facts: frozenset of all currently believed facts (explicit plus derived).version: the commit counter described above.snapshot(): returns aTruthMaintenanceSnapshot— a frozen dataclass withversion,facts, andactive_supports(tuple sorted by support ID). The snapshot is a full detached copy: laterapply()batches never change an existing snapshot.
facts, version, snapshot(), explain()) leave the
session state unchanged. Returned snapshots (MaintenanceDelta,
FactExplanation, Derivation, TruthMaintenanceSnapshot) are frozen
dataclasses with immutable collections; mutating caller-owned rules or
previously returned results cannot mutate the session.
Checkpoint export and restore
Useto_checkpoint() and from_checkpoint() when an application must resume
the same logical session in a later process. These methods are a serialization
boundary, not a storage engine.
to_checkpoint()returns a new JSON-compatibledictcontaining the fixed rules captured at construction, the complete support catalog (including withdrawn supports), the active support IDs, and the committed session version. It does not save derived facts or internal indexes.from_checkpoint(payload)accepts the result ofjson.loads()— not a JSON string or file path — validates the complete payload, rebuilds derived facts, direct derivations, and dependency indexes, and returns a new session only after reconstruction succeeds.
snapshot() remains a read-only query view of the current state. A checkpoint
is different: it is a restart format that preserves the support-ID bindings and
arity constraints needed to continue updates after serialization. The
session_version in a checkpoint is only a local commit counter. It does not
authorize reusing old snapshot-provider identities, temporal adapter history,
or external caches.
The fixed v1 schema is:
format_version must be integer 1;
session_version must be a non-negative integer. A non-empty support catalog
requires a positive version because it represents at least one committed
update; an empty catalog requires version zero, and a non-empty catalog with no
active supports requires at least two commits. Rules and catalog facts are
semantic values, so invalid atoms, variables in facts, recursive rules, and
arity conflicts are rejected
with the same ValidationError used by construction and apply().
Unsupported future formats are also rejected, and restore never returns a
partially reconstructed session.
Applications choose where to store the JSON. The minimal example below is
intended only to show the API; Path.write_text() does not provide a
crash-safe atomic commit. Use an atomic file replacement or transactional
database commit if losing or partially writing a checkpoint is unacceptable.
JSONDecodeError from the application’s own json.loads() call;
from_checkpoint() validates the resulting structured object and raises
ValidationError for malformed checkpoint data.
Cost Model
- Deletion propagation and affected-rule matching are targeted: a deletion-only batch uses dependency indexes to compute the affected fact set, then re-matches only the rules whose body predicates intersect that set (the affectedness scan itself walks the rule list, so cost still grows with rule count); insertions reevaluate only rules reachable from newly active predicates.
- Staging copies the whole session state for each effective
apply()batch to guarantee atomic commits (an empty or already-applied batch is a no-op and stages nothing), and the session retains the support catalog in memory for the session lifetime (including withdrawn supports, so IDs stay bound). - Each
snapshot()call copies the full committed state again (facts, active supports, version), so consumers such asTruthMaintenanceContextFilterpay one whole-state copy per filtered retrieval. Do not call it in a tight loop when a single shared read is enough. - The copy-on-update and catalog costs are per-batch whole-state costs; do not assume end-to-end latency is strictly proportional to the affected subgraph. Measure before relying on performance.
- Checkpoint payloads grow with the fixed rules and the complete historical support catalog, not just the active supports. Export also pays a deterministic sorting cost for the catalog and active IDs.
- Restore is a full reconstruction: it validates every catalog entry, combines rule and catalog arity constraints, and rebuilds the complete closure and direct explanations by replaying only the active supports. Join matching can produce many intermediate matches, memory grows with those matches and the copied candidate state, and no constant-time or active-support-only cost bound is promised.
Limitations
- Non-recursive rules only; the constructor rejects cyclic predicate dependencies.
- Narrow atom syntax: ASCII predicate/variable identifiers and unquoted scalar symbol constants, including zero-arity atoms. No quoted strings, whitespace-bearing constants, nested terms, comparisons, aggregation, negation, or disjunction. Exactly one arity per predicate within the session.
- Boolean support semantics; no confidence propagation, no probabilistic reasoning.
- No side effects: the session never executes actions or handlers and creates no LLM or store clients.
- No automatic storage or recovery: checkpoints are an explicit serialization format, but the session never writes to a file or database and does not recover automatically after a crash. The application owns storage, atomic commit, and choosing the last good checkpoint.
- No thread-safety or concurrent writers: access remains caller-serialized in a single process, and checkpointing does not add a distributed locking or multi-writer protocol.
- No temporal validity or cross-store synchronization guarantees. Restoring a checkpoint does not restore temporal adapter history, snapshot-provider identities, or external caches.
- Fixed rules: rule changes require constructing a new session.
- No historical proof archive:
explain()describes the current state only.
Links
- Temporal Truth Maintenance — an opt-in graph adapter for independent valid/known-time slices, expiration and late corrections.
- Reasoning — the rule engines this session builds on, including the
Rulerepresentation. - Context —
TruthMaintenanceContextFilterconsumes immutable snapshots to filter retrieved context by active support. - Ontology — ontology axioms and SHACL constraints.
