Incremental knowledge maintenance
Maintain changing knowledge
python -m examples.quickstarts.knowledge_maintenance
Mari’s shared update contracts now cover collection selection, indexed change propagation, stable group lineage, and reversible aggregates. These algorithms compose with existing atoms, structural references, and materialization receipts. The host owns models, authorization, persistence, scheduling, and transactions.
Select collections before consuming their members
An answer about all refund discussions depends on the selection rule as well as the discussions previously found. A new discussion can change the result even though it has no edge in the old dependency graph.
SelectionSpec identifies a scoped collection and versions its rule. Pass the
complete candidate partition to plan_selection. Each candidate stamp must
fingerprint every field the selector reads. Additional dependency stamps cover
policy, query configuration, index versions, or external selection inputs.
from mari_components import (
SelectionSpec, UpdateAction, complete_selection, plan_selection,
)
rule = SelectionSpec(
object=collection_ref,
implementation="refund-discussions:v1",
configuration={"query": "refund policy", "limit": 20, "ranker": "v2"},
)
selection = plan_selection(
rule, all_candidate_stamps,
dependencies=(policy_stamp,), previous=stored_selection,
)
if selection.update.action is UpdateAction.REBUILD:
selected_keys = host_select(selection.candidates)
receipt = complete_selection(selection, selected_keys)
host_commit_if_snapshot_current(receipt)
Insertions, deletions, candidate edits, rule changes, and policy changes trigger reevaluation. This includes nonwinners from a previous top-k selection. Semantic selection uses conservative invalidation, with no inferred exclusion bound. Only pass already-authorized material to model callbacks. A policy stamp tracks change and does not grant access.
The completed selection fingerprints ordered membership. consumer_inputs
contains that membership stamp plus the exact selected candidate stamps. If the
selector runs again and picks the same members, its unchanged membership can
stop propagation. An edit to a selected revision still reaches its consumers.
Do not make every consumer depend directly on the whole candidate universe.
The selector itself is application code. Declare deterministic tie-breaking and version its model, query, thresholds, limits, and configuration. A selection receipt records completed work, not predicted winners.
When the selection derivation belongs to the same dependency graph as its consumers, pass its completed materialization receipt to the planner. The membership output has that derivation as its producer, so do not also supply it as an external source. Supply the selected candidate stamps as current sources.
Maintain an indexed dependency frontier
DependencyIndex caches the same decisions as plan_dependency_updates and
adds a reverse dependency index. Source and receipt changes reevaluate direct
dependents, propagating changes in completed fingerprints, availability, or
blocked state. last_evaluated exposes the work inspected by the last update.
from mari_components import DependencyIndex
index = DependencyIndex(
sources=current_sources, derivations=recipes, materializations=receipts,
)
visited = index.apply(
sources=changed_stamps,
removed_sources=deleted_keys,
materializations=completed_receipts,
evicted_outputs=evicted_keys,
)
plan = index.plan(targets=(requested_output,))
for task in plan.ready:
host_execute_and_commit(task)
This delta API differs deliberately from the snapshot planner: omitted sources
are unchanged. Delete them explicitly with removed_sources. Evict a receipt
when its material is unavailable. Topology changes use derivations and
removed_derivations. These edits rebuild and cycle-check the index atomically.
Invalid conflicting edits leave its state intact.
Target selection returns the requested outputs and their ancestors. It limits returned work, not background index maintenance. Retirement hints belong to the complete index and are not caused by omitting a target.
Source/receipt updates avoid scanning unrelated derivations. Full plan rendering still enumerates the graph. Initial construction and topology changes are full passes. In the deterministic independent-branch test, changing one source evaluates one of 2,000 outputs. This is a work-count check, not a latency claim.
The index is a process-local, single-writer reference implementation. Restore it from source snapshots, derivations, and receipts. The host must atomically commit outputs and receipts conditional on their input snapshot, and serialize access to the in-memory index. It performs no background work or storage I/O.
Preserve identity through regrouping
reconcile_groups accepts old stable groups and new candidate groups in one
explicit scope and namespace. GroupIdentity.members uses stable ObjectRef
values, rather than revision IDs, so content edits can retain membership identity.
from mari_components import reconcile_groups
lineage = reconcile_groups(
old_groups, new_candidate_groups,
scope=scope, namespace="topic", generation=transaction_id,
)
for assignment in lineage.assignments:
host_record(assignment.group, assignment.predecessors, assignment.transitions)
Matching uses positive exact overlap, ordered by Jaccard similarity, intersection count, old identity, and candidate member keys. Greedy one-to-one assignment allows one split child to retain an old identity and one predecessor to survive a merge. All overlapping predecessors remain in the lineage report. Transitions identify creation, continuation, splitting, and merging. Unmatched old identities are retired hints, not deletion commands.
Supply a unique, persisted generation token for each regrouping transaction. Use the same token when retrying that transaction. New IDs include this token and membership so retired groups are not accidentally resurrected.
This is a deterministic overlap heuristic, not maximum-weight matching or a semantic truth judgment. Membership changes still invalidate computations under the retained identity. Similarity-based clustering can propose candidate groups upstream, but cannot establish byte equality or safe reuse.
Update reversible aggregates from keyed contributions
DeltaReducer defines pure zero, signed change, and finish operations.
DeltaAggregate retains immutable per-key contributions and applies additions,
replacement, and removal. Replayed identical upserts and repeated deletions are
idempotent. Invalid batches leave the aggregate unchanged.
Reducer |
Contribution |
Output |
|---|---|---|
|
Any JSON value per key |
Active contribution count |
|
Vector and positive weight |
Weighted sums, total weight, count, centroid |
|
Term-frequency map per document |
Corpus frequency, document frequency, total length, document count |
|
Unique target strings per source |
Target reference counts, with source targets retained in contributions |
from mari_components import DeltaAggregate, WeightedVectorReducer
aggregate = DeltaAggregate(WeightedVectorReducer(), scope=scope)
aggregate.apply(((atom_key, {"vector": [0.1, 0.2], "weight": 2}),))
aggregate.apply(removed=(deleted_atom_key,))
centroid = aggregate.value["centroid"]
output_stamp = aggregate.stamp(centroid_output_key)
Numeric accumulation uses exact rational arithmetic with a final float conversion. This avoids edit-order drift at reference-implementation cost. Numbers are interpreted through their decimal string representation. Immutable counter snapshots copy their mappings, so delta arithmetic does not imply constant-time snapshot creation or serialization. Reject nonfinite numbers and inconsistent dimensions. A new embedding space requires a new recipe and rebuilding its vector aggregate.
Lexical updates maintain statistics, not a complete incremental BM25 search engine. Membership target strings require host-defined scoped encoding. Custom reducers must preserve prior state and implement valid reversible operations. Generated summaries are not additive aggregates: rebuild them from declared inputs and compare actual output fingerprints.
Complete conversation maintenance example
Run python -m examples.quickstarts.knowledge_maintenance from the installed
repository. The fixture host connects:
current messages and access policy
→ collection selection
→ stable episodes and evidence-bound extraction
→ stable topics, membership, and topic briefs
→ separate summary text and vector derivations
→ current retrieval projection and aggregate statistics
All derivations use the same planner contracts. One edited message rebuilds one topic vector. A revision-only edit updates evidence but reuses unchanged text vectors. Topic splits and merges preserve explicit lineage. The example resolves source evidence again before serving search results and withholds a projection whose upstream build failed.
After each event, the example compares outputs and receipts with clean execution of the same graph and assigned identities. Tests cover insertions, deletions, empty collections, access changes, extraction and embedding recipes, regrouping, failure/retry, randomized planner deltas, and reversible aggregate updates. Grouping policy and model outputs are deterministic fixtures. These checks do not establish semantic extraction quality or production performance.
The older conversation and topic compilers retain their compatibility APIs. This example composes the new common contracts around episode parsing rather than silently replacing their caches. Existing atom stamps can enter exactly the same derivations and reducers.