mixle.substrate package¶
Typed, provenanced, scoped storage for local knowledge and artifacts.
The substrate stores raw data, documents, model artifacts, harvested traces,
ontology triples, simulation outputs, and context packets as
SubstrateItem objects. Each item carries a kind, provenance, access
scope, tags, links, and a retrievable text surface.
- class Substrate(root=None, *, secret_policy='redact')[source]
Bases:
objectA local shard of the knowledge substrate: a filesystem-backed store with typed retrieval.
put/get/remove/allmanage items;searchretrieves thekmost relevant items for a query, filtered by kind and scope, ranking text items semantically (a learned embedding over the current text corpus) and everything else lexically.save/loadpersist the shard as oneitems.jsonlunderroot.Immutability contract: every item that crosses the store boundary is a defensive copy.
putcopies the object you hand it before storing it;get/all(and thereforesearch) return copies of what is stored. Mutating an object you obtained from – or are about to pass to – this store never affects it: the store’s own state, and everything derived from it (the semantic index), is independent of what any caller does with its own reference afterward. The only way to change a stored item isput()(replace wholesale) orupdate()(change named fields); both revalidate the result and correctly invalidate the semantic index, exactly like a fresh write.Scope isolation contract:
search(..., scope=X)ranks using an embedding index fit ONLY on scopeX’s own text items (seereindex()) – a scoped query’s results, and the model that produced them, never depend on any other scope’s content, so the presence or content of items in a scope you cannot pass never leaks through a scope you can.scope=None(the default) is the one deliberate exception: it asks for every item across every scope, so its index is fit over everything, same as asking for all of it directly throughall()/get()would show you.Secret-handling contract (MXR-080-0262): every write –
put(), andupdate(), which routes through it – scansitem.text,item.payload(recursively), anditem.tagsfor known secret shapes (mixle.substrate.security) using exactly the surface_lexical_score()serializes for search, BEFORE anything reachesself._itemsor either index. This is unconditional: there is no path into the store that skips it, so the redact-before-store guardsafe_text()always offered is no longer opt-in.secret_policy(set at construction, or overridden per call) picks what happens when a secret is found:"redact"(the default) masks it in place so only the masked form is ever stored, embedded, or served;"reject"raisesSecretPolicyErrorand stores nothing.- Parameters:
root (str | None)
secret_policy (SecretPolicy)
- put(item, *, secret_policy=None)[source]
Add or replace an item; returns its id and schedules semantic-index rebuilds for text items.
Stores a defensive copy of
item(see the class docstring’s immutability contract) – the caller’s own object is never aliased into the store, so mutating it after this call has no effect. Dirty whenever this put could change what_text_items()(the embedding index’s real inclusion rule – ANY kind with a truthy.text, not just a fixed subset) would return: the new item itself carries text, OR it replaces an item that used to. That second half matters as much as the first – clearing an existing item’s text (or replacing it with a no-text item) shrinks the indexed corpus exactly as much as adding text grows it, and without it the stale embedding for the old text would keep matching queries after the text is gone.Secret-handling (MXR-080-0262, see the class docstring’s secret-handling contract): before anything is stored or indexed, this scans
item.text,item.payload(recursively), anditem.tags– the same surface_lexical_score()serializes for search – for known secret shapes, and acts persecret_policy(this call’s override, elseself.secret_policy, default"redact"):"redact"masks detected secrets in place before storing;"reject"raisesSecretPolicyErrorand stores nothing. This is unconditional – there is no way to reachself._itemsthat skips it.
- add(kind, text='', **kw)[source]
Convenience: build a
SubstrateItemandput()it.
- get(item_id)[source]
Return a defensive copy of the item stored as
item_id, orNonewhen it is absent.Mutating the returned object does not change what is stored – call
update()(orput()a full replacement) to make a change land, so index invalidation stays correct.- Parameters:
item_id (str)
- Return type:
SubstrateItem | None
- remove(item_id)[source]
Remove an item by id and return whether anything was deleted.
- all(*, kind=None, scope=None)[source]
Return defensive copies of stored items, optionally filtered by kind and scope.
As with
get(), mutating a returned item never affects the store; useupdate().
- update(item_id, *, secret_policy=None, **fields)[source]
Change named fields on the stored item
item_id, the supported way to edit in place.Mutating an object returned by
get()/all()never reaches the store – both return defensive copies, so the store’s own state (and anything derived from it, like the semantic index) is untouched by that.updateis how a change actually lands: it rebuilds the stored item withfieldsapplied, re-runsSubstrateItem’s own validation (__post_init__, e.g. rejecting an unknownkind) viadataclasses.replace(), and routes the result back throughput()’s storage path so the same dirty/index-invalidation bookkeeping AND the same secret-handling guard (see the class docstring’s secret-handling contract;secret_policyhere overridesself.secret_policyfor this call only) fire as they would for any other write – a change that affects what_text_items()covers (new text, cleared text, a different scope for a text item) correctly schedules a reindex instead of leaving the semantic index silently stale, and a change that introduces a secret into any field is masked (or rejected) exactly like a freshput()would.Returns a defensive copy of the item as actually stored (post-redaction, consistent with
get()/all()– NOT the raw pre-policyfields-applied object).- Raises:
KeyError –
item_idis not stored.ValueError –
fieldstries to changeid– update changes an item’s content, not its identity;puta new item andremovethe old one for that.SecretPolicyError –
secret_policy="reject"(orself.secret_policyis) and the update introduces a secret anywhere in the surface – nothing is changed.
- Parameters:
- Return type:
SubstrateItem
- reindex()[source]
(Re)fit one embedding index PER visibility domain. Idempotent, lazy-called.
MXR-080-0237: a single embedder fit over every scope’s text pooled together makes a scoped query’s transform and ranking depend on inaccessible scopes’ content – a membership side channel (whether some distinctive text exists in a scope you can never read is inferable from how it shifts your OWN scoped query’s scores). Fixed by fitting one embedder per distinct scope value present, each ONLY on that scope’s own text items, plus one unrestricted index (key
None) fit over everything for callers that passscope=None– that caller is asking for every scope by construction, so pooling there asserts no isolation boundary to violate.search()picks the index matching its ownscopeargument, so a scope’s results and the model that produced them never see a single byte of another scope’s text.- Return type:
None
- search(query, k=5, *, kind=None, scope=None)[source]
The
kmost relevant items toqueryas(item, score), filtered by kind/scope.Text-bearing items rank by cosine similarity in the learned embedding space fit for THIS
scopealone (seereindex()); when there are too few same-scope items to learn one (or for a non-text query), ranking falls back to a lexical token overlap. Structured items with no text always rank lexically over their serialized payload + tags.kmust be an exact, non-negativeint(MXR-080-0236, see_require_count()): “return up tokresults,” never a negative-indexing slice trick. Checked beforereindex()(a potentially expensive rebuild) ever runs, so an invalidkfails fast.retrieve()’s diversified and flat merge paths, andtop(), all validate through this same function, so a result count means exactly the same thing – and an invalid one is rejected exactly the same way – at every entry point across the retrieval surface.
- save(root=None)[source]
Persist the shard to
{root}/items.jsonl(one item per line), atomically.The full snapshot is written to a sibling temp file in
targetfirst, fsynced, then published with a singleos.replace– mirrorsmixle.task.artifact._atomic_json_dump()andmixle.system.registry.Registry._write_index()’s temp-file-plus-os.replaceconvention. A plainopen(path, "w")truncatesitems.jsonlBEFORE the new content is written, so any failure partway through this method (a crash, a disk-full write, a non-serializable item further down the store) would destroy the last good shard instead of just failing the save; staging the new snapshot off to the side first means the write is all-or-nothing and the previousitems.jsonlis untouched by a failed attempt.
- load(root=None)[source]
Load items from
{root}/items.jsonlinto this shard, all-or-nothing.Every row is parsed into a private, freshly-built dict first; only once the ENTIRE file has parsed and validated cleanly does that dict become
self._items. A malformed or invalid row anywhere in the file raises – naming the file, the 1-based line number, and a preview of the offending row – and leaves whatever this shard held before the call completely untouched: never a partial store holding only the rows that happened to precede the bad one.- Parameters:
root (str | None)
- Return type:
None
- class SubstrateItem(kind, text='', payload=<factory>, provenance=<factory>, scope='local', tags=<factory>, links=<factory>, derived_from=<factory>, id=<factory>, created_at=<factory>)[source]
Bases:
objectOne typed, provenanced, scoped item in the substrate.
Edge typing (MXR-080-0261):
linksandderived_fromare both lists of item ids, but they are NOT interchangeable.linksis the generic, untyped KG-relation surface – “related to”, “mentions”, “co-occurs with”, anythingkg_rag/multihopwant to associate two items by.derived_fromis narrower and load-bearing: it is the ONLY place genuine provenance/ancestry (“this item was derived from that one”) is recorded, and it is the only fieldverify_lineage()ever traverses. Before this field existed, lineage verification walkedlinksitself, so a merely-related entity was indistinguishable from a true derivation parent – putting a citation-worthy relation intolinkssilently made it certifiable ancestry. Keeping the two lists separate (rather than tagging each entry with a type) is a deliberate minimal-diff choice: every existing reader oflinks(kg_rag,multihop,freshness,context,governance,spaces) keeps reading exactly the same untyped list it always has, with no change to its shape or meaning.- Parameters:
- ingest_documents(substrate, docs, *, source='documents', scope='local', indices=None)[source]
Add text passages to the substrate as
kind="text"items. Returns anIngestReceipt(the new/revised item ids, plus any per-doc failures – see its docstring; behaves exactly like the historical barelist[str]for every existing caller).Each doc is a string, or a
{"text": ..., "tags": [...], "payload": {...}}dict for metadata. Anything else is reported on.failedrather than crashing the whole batch (MXR-080-0269).Stable, revisioned identity and integrity (MXR-080-0268): each doc’s id is derived from
(source, its position)–indices[i]when the caller supplies one (soingest_file()can pass through a.jsonlfile’s true line numbers even after splitting mixed content into docs/records), else the plainenumerateposition. Re-ingesting the SAMEsourcereuses the SAME ids, turning a repeat call into a revision of the existing records (see_put_revisioned()) rather than a fresh random-id duplicate every time. Whensourcenames a real, readable file, every doc’s provenance also carries that file’s full, algorithm-labelledcontent_hash(the exact digestcheck_freshness()later compares against) –sourceis often just a label with nothing to hash (e.g. the"documents"default), so nocontent_hashis recorded for those.
- ingest_artifacts(substrate, registry_root, *, scope='local')[source]
Index every deployed artifact under
registry_root(dirs containing amanifest.json). Returns anIngestReceipt(behaves exactly like the historical barelist[str]).The item’s text surface is a human summary of the manifest (kind, io, meta); its payload REFERENCES the artifact directory (
{"ref": path}) rather than copying it – unchanged, since other readers (e.g.mixle.reason.receipt.decision_receipt()) depend onrefnaming the directory, not the manifest file – and provenance carries the manifest’s lineage fields when present.Integrity, identity, and validation (MXR-080-0268/0269): provenance also carries the manifest FILE’s full, algorithm-labelled
content_hash(content_hash()) and its ownmanifest_path, for anyone who wants to verify it directly – note that becausepayload["ref"](a directory) is whatcheck_freshness()actually re-hashes, an artifact item’s freshness verdict is UNVERIFIABLE by construction (a directory cannot be read as file bytes), which is the documented, intentional behavior of that check, not a gap in this one; recording the hash here is still correct and useful (forensic verification, and a future reader can compare it against a fresh hash of the same manifest file by hand). Each artifact’s id is derived from its directory path (see_stable_id()), so re-ingesting the SAME artifact directory revises the existing record instead of duplicating it. A manifest that fails to read, fails to parse, or parses to valid JSON of the wrong shape (anything other than a JSON object) is reported on.failed– checked BEFORE any.get()is called on it and BEFORE anything is stored for it – rather than raising and aborting every artifact discovered after it (MXR-080-0269).
- ingest_traces(substrate, jsonl_path, *, source=None, scope='local')[source]
Index a harvested
.jsonlof{"input": ..., "answer"/"label"/"call": ...}rows as traces. Returns anIngestReceipt(behaves exactly like the historical barelist[str]).Integrity, identity, and validation (MXR-080-0268/0269): every row’s provenance carries the source file’s full, algorithm-labelled
content_hash(one read of the whole file up front, reused for every row – all rows from the same file share the same recorded hash, since a byte anywhere in the file invalidates every row’s “as ingested” guarantee equally). Each row’s id is derived from(source, its line number), so re-ingesting the SAME file revises the existing per-row records instead of duplicating them. A line that fails to parse, or parses to valid JSON that is not a JSON object (the documented row shape requires.get()-able fields), is reported on.failed– checked BEFORE any.get()is called on it – rather than raising and aborting every row after it in the file (MXR-080-0269, the exact “trace code assumes every row is a mapping” bug).
- ingest_file(substrate, path, *, kind=None, source=None, scope='local')[source]
Ingest a data file into the substrate. Format inferred from the extension unless
kindforces it. Returns anIngestReceipt(behaves exactly like the historical barelist[str])..txt/.md-> onetextitem per non-empty line;.jsonl-> one item per JSON line (a string /{"text": ...}becomes a text item, any other object a record item);.csv-> onerecorditem per row keyed by the header.sourcedefaults to the file path, so every resulting item picks up that file’scontent_hashand a stable, revisioned identity (seeingest_documents()/ingest_records()) automatically.A malformed
.jsonlline is reported on the returned receipt’s.failed(naming its 1-based line number) rather than silently dropped (MXR-080-0268) or allowed to abort every line after it (MXR-080-0269); the.jsonlbranch preserves each row’s TRUE original line number as its stable identity even after splitting mixed content into a docs sub-batch and a records sub-batch (viaIngestReceipt.merge(), which – unlike plain list concatenation – keeps both sub-batches’.failedentries instead of silently dropping one side’s).
- ingest_records(substrate, records, *, source='records', scope='local', text_fields=None, indices=None)[source]
Add a sequence of records (dicts or tuples) to the substrate as
kind="record"items. Returns anIngestReceipt(behaves exactly like the historical barelist[str]).Each record’s payload is stored structured; its retrievable text surface is the
text_fields(for dict records) joined, else the whole serialized record – so records are searchable by content.Stable, revisioned identity and integrity (MXR-080-0268), mirroring
ingest_documents(): each record’s id is derived from(source, its position)–indices[i]when supplied (seeingest_file()), else the plain position – so re-ingesting the SAME source revises the existing records instead of duplicating them, and a real, readablesourcefile’scontent_hashis recorded on every record’s provenance. One record’s storage failure (e.g. a rejected secret undersecret_policy="reject") is reported on.failedrather than aborting the rest of the batch.
- class ContextPacket(task, items=<factory>, scores=<factory>, budget=<factory>, n_candidates=0, texts=<factory>, compressed=False)[source]
Bases:
objectA budgeted, provenanced view of the substrate assembled for one target + task.
textsholds the text actually used per item – the full item surface, or (when the packet was compressed) an extractive summary that keeps only the query-relevant sentences.preservationreceipts how much of each item’s query-relevant content survived, so compression is measured, not trusted.- Parameters:
- render(*, header=True)[source]
The assembled context string the target consumes (respecting the budget shape).
- property used_chars: int
The TRUE size of this packet’s default (header-included) rendering – always derived from the actual
render()output, never an independently-settable estimate that can silently drift from what a receiver actually gets (MXR-080-0239, MXR-080-0240).
- preservation()[source]
Per item, the fraction of the task’s query terms retained in the used text (1.0 = all kept).
The receipt for compression: a value near 1.0 means the summary kept what the query cares about; a low value flags an item whose relevant content was squeezed out.
- property compression_ratio: float
Used chars / full chars over the selected items (1.0 = uncompressed).
- provenance()[source]
Where every included piece came from – ids, kinds, sources, relevance scores.
- to_knowledge_dict(*, id, project_id, target_kind, target_id=None, expected_output_schema=None, factuality=None)[source]
Return a plain dict shaped like
mixle_knowledge.contracts.ContextPacket.The exported fields cover
id,project_id,task,target_kind,target_id, token and byte budgets, evidence item identifiers, constraints, citations,expected_output_schema, andpayload. Constructing a validated pydantic object is the receiving package’s responsibility; core mixle intentionally keeps this as a dependency-free dictionary so platform contract packages can depend on core rather than the reverse.When
factualityis aFactualityReceipt, it is included inpayload["factuality"]so receivers can inspect grounding metadata before trusting the packet.
- to_knowledge_bundle_dict(*, id, project_id, target_kind, target_id=None, expected_output_schema=None, gaps=None, required_capability_ids=None, handoff_policy=None, continuation=None)[source]
Return a plain dict shaped like
mixle_knowledge.contracts.KnowledgeBundle(IC-13, M0b).Unlike
to_knowledge_dict()(kept, unchanged, as a deprecated compatibility view – M1c must never use it as canonical state), this bridge carries each selected item’s OWN canonical structured payload/artifact_ref/relations (substrate_item_to_knowledge_dict()) instead of flattening everything into one rendered text string: a graph stays a graph, a typed table stays a typed table, an image keeps its artifact ref.self.render()and the compression receipt are only a disposable legacy view, kept underrenderings["legacy_text"]; they may be recomputed or dropped at will without touching any item’s canonical payload or hash.As with
substrate_item_to_knowledge_dict(), core never importsmixle_knowledge– the caller validates the result (e.g. withKnowledgeBundle.model_validate).- Parameters:
- Return type:
- class ContextBudget(max_chars=2000, max_items=20, shape='passages')[source]
Bases:
objectWhat a target can take – the DeviceSpec of context.
shapehints the rendering style.
- class ReceiverProfile(name, max_chars=2000, max_items=20, shape='passages', compress=False)[source]
Bases:
objectA named receiver’s capacity – what
assemble_for_receivers()budgets and shapes for it.A frontier LM and a local student are not the same target: the LM affords a large, prose-shaped context; the student needs a small, feature-shaped one.
ReceiverProfilenames that difference so it is set once per receiver, not re-derived ad hoc at every call site.- to_budget()[source]
Convert this receiver profile to a context budget.
- Return type:
ContextBudget
- assemble_context(substrate, task, *, budget=None, kind=None, scope=None, compress=False, telemetry=None)[source]
Assemble the best-affordable
ContextPacketfortaskfromsubstrate.Retrieves relevant items (
Substrate.search()), then packs them in descending relevance until the character budget or item cap is reached. The packet’s default rendering – header, separators, provenance tags, and all – is GUARANTEED to fitbudget.max_chars: every candidate is checked against its actual rendered length before being kept, not an estimate that can silently diverge from whatContextPacket.render()later returns (MXR-080-0239). When even the single most relevant item does not fit whole, its text is shrunk instead of dropped – extractively withcompress=True, by hard truncation otherwise – so a small-but-feasible budget still yields something, exactly as before, except the result now genuinely fits rather than silently overflowing.budget.max_charsmust be enough to hold at least the empty rendering fortask(the header alone); if it cannot, this raises immediately rather than doing retrieval work for a budget no selection could ever satisfy.With
compress=True, items too large to fit whole are extractively summarized to their query-relevant sentences instead of dropped;packet.preservation()reports what was kept. Emits acontextevent when telemetry is supplied.
- assemble_for_receivers(substrate, task, receivers, *, kind=None, scope=None, telemetry=None)[source]
Assemble ONE task-conditioned
ContextPacketper named receiver – the concrete receiver-conditioned compression path.Two receivers reading the same substrate for the same task get genuinely different renderings: budget, shape, and, via
compress, which sentences survive. The result is not the same blob truncated to fit each consumer.packets = assemble_for_receivers(substrate, task, [ ReceiverProfile("frontier_llm", max_chars=2000, shape="passages"), ReceiverProfile("local_student", max_chars=200, shape="features", compress=True), ]) packets["frontier_llm"].render(), packets["local_student"].render()
receiversmust have unique, non-empty names – the result is keyed by name, so a duplicate would otherwise silently discard an earlier receiver’s packet AFTER its retrieval, compression, and telemetry work has already run (MXR-080-0241). Validated upfront, before any of that work starts.
- compress_text(text, task, max_chars)[source]
Extractive, torch-free summary of
textkeeping the sentences most relevant totask, withinmax_chars(the standalone compressor used byassemble_context()withcompress=True).max_charsmust be a positive int – raises otherwise; the returned string’s length is always<= max_chars(MXR-080-0239).
- retrieve(substrate, query, *, k=8, kinds=None, weights=None, diversify=True, scope=None, telemetry=None)[source]
Plan a cross-kind retrieval for
query(see module docstring).- Parameters:
k (int) – total items to return. Must be an exact, non-negative
int(MXR-080-0236, see_require_count()) –booland fractional values are rejected, and a negativekraises rather than silently slicing from the end. Validated once, up front, before any per-kind search runs, and used identically by both the diversified and flat merge paths below so a givenkmeans the same thing regardless ofdiversify.kinds (list[str] | None) – restrict to these substrate kinds (default: every kind present).
weights (dict[str, float] | None) – per-kind score multipliers (e.g.
{"artifact": 1.2}to favor deployable models). Every weight must be finite and non-negative (MXR-080-0248) – a negative weight would silently reverse that kind’s relevance ordering, and a NaN/infinite weight would destabilize the merged order; both are rejected up front, before any score is multiplied.diversify (bool) – when True (default), interleave the top hits of each kind so the result spans modalities; when False, take a flat merged top-k (whichever kind scores highest wins).
scope (str | None) – restrict to a team/access scope.
substrate (Substrate)
query (str)
telemetry (Any)
- Return type:
Retrieval
- class Retrieval(query, items=None, scores=None)[source]
Bases:
objectA planned, cross-kind retrieval result: items in merged relevance order, grouped by kind.
Construction contract (MXR-080-0248): built from aligned, validated
(item, score)pairs (seeRetrievedHit) – not independently-settable parallel lists.items/scoresstay available as read-only derived views for every existing reader (r.items,r.scores,zip(r.items, r.scores),r.scores[0]); what changed is that aRetrievalcan no longer be constructed into a state where they disagree on length, or where a score is NaN/inf.- property items: list[SubstrateItem]
Retrieved items in merged relevance order (a read-only derived view of
hits).
- property scores: list[float]
1 with
items(a read-only derived view ofhits).- Type:
Scores aligned 1
- by_kind()[source]
Group retrieved items by substrate kind.
- top(n)[source]
Return the top
nretrieved items.nsharesSubstrate.search()’s exact-non-negative-integer contract (MXR-080-0236) – no bool, no fractional value, no negative-indexing trick.
- provenance()[source]
Return compact provenance records for retrieved items.
- to_context(task=None, **assemble_kw)[source]
Budget THIS retrieval’s own ordered, weighted hits into a
ContextPacket(MXR-080-0249).Previously this copied
self.itemsinto a throwawaySubstrateand calledassemble_context(), which ran ITS OWN fresh, unweighted search over that tiny shard – discarding this retrieval’s cross-kind weights, diversification order, and scores entirely, and substituting whatever a brand-new lexical/cosine fit over just these few items happened to produce instead. A weighted retrieval that had ranked an artifact above a text item (scores 50 and 1) came back from that fresh search reordered text-before-artifact, rescored 1 and 0.5 – the weighting had silently vanished.This instead packs
self.hits– already in this retrieval’s own decided order, at their own decided scores – directly against the budget, with the same greedy, fit-checked packingassemble_context()itself uses (built on the same publicContextPacket/ContextBudget/compress_textsurface, so the fit guarantee – the default rendering is always checked against its TRUE length, never an estimate – holds here too), but never re-scoring, re-ordering, or re-searching.Accepts the same
budget/compress/telemetrykeywordsassemble_contextdid.kind/scopeare no longer accepted: there is no fresh search left for them to filter – restrictkinds=/scope=on the originalretrieve()call instead.
- eig_retrieve(substrate, belief, evidence_fn, *, k=8, kind=None, scope=None, seed=None)[source]
Greedily pick up to
ksubstrate items by expected posterior-entropy reduction againstbelief.evidence_fn(item)turns a candidate item into whateverbelief.update(...)expects (e.g. a per-hypothesis log-likelihood vector for aCategoricalBelief`), OR – when the item’s true evidence is not yet known – anEvidenceOutcomesdescribing what it could turn out to be. Each round, every remaining candidate is scored by the expected entropy reductioncurrent.entropy() - E[updated.entropy()](MXR-080-0250; the exact realized reduction in the common deterministic-evidence case, since that is the same formula’s single-outcome degenerate case); the best-scoring item is taken – but only if its gain clears zero, otherwiseeig_retrieve()stops rather than hand back a pick that would increase uncertainty (or add none) dressed as evidence – the running belief moves to its post-update state, and scoring repeats against the shrunk pool, so an item whose evidence is redundant with an already-picked item scores near zero on its next look, the direct fix for similarity retrieval pulling in near-duplicates.beliefitself is never mutated: every hypothetical update runs against a fresh clone (MXR-080-0251), so a candidate that turns out not to be chosen – or aBeliefStaterealization whoseupdate()does not honor the documented “returns a new belief” contract – can never contaminate scoring for the rest of the round.evidence_fnshould raiseValueErrorto signal “no usable evidence for this item” (mirroring whatbelief.update()itself raises for incompatible evidence); that is the only exception type treated as a skip – every skip is recorded, with its reason, on the returnedEigRetrievalrather than silently dropped, and any other exception is a genuine bug and propagates.seed(anintornumpy.random.RandomState) is used only when a chosen candidate carries genuine outcome uncertainty, to draw which outcome is treated as realized for the next round’s baseline.Returned as an
EigRetrieval(queryis a fixed marker, not a text query) so it composes with the sameto_context/by_kindsurface as cosine retrieval, plus.skippedand.stop_reasondiagnostics cosine retrieval has no equivalent of.
- class EigRetrieval(query, items=None, scores=None, *, skipped=None, stop_reason=None)[source]
Bases:
RetrievalA
Retrievalfromeig_retrieve(), plus the diagnostics a similarity retrieval has no use for: which candidates scoring skipped (and why), and whether the run stopped early because no remaining candidate cleared the admissible-gain bar (MXR-080-0250/0251).Deliberately a plain subclass with its own
__init__rather than a second@dataclasslayer:Retrievalitself is@dataclass(init=False)with a hand-written constructor that builds its validatedhitstuple fromitems/scores(MXR-080-0248, e.g. rejecting a length mismatch or a non-finite score) – re-declaringEigRetrievalas its own@dataclasswould let dataclass field inheritance synthesize a DIFFERENT__init__over the raw field list instead (query, hits=(), skipped=..., stop_reason=...), silently bypassing that validation. Delegating toRetrieval.__init__viasuper()keeps construction going through the one real validation path regardless of howRetrievalrepresents itself internally.
- class EvidenceOutcome(probability, evidence)[source]
Bases:
objectOne possible realization of a candidate’s evidence, weighted by its probability of occurring.
- class EvidenceOutcomes(outcomes)[source]
Bases:
objectA candidate’s predictive distribution over what its evidence could turn out to be (MXR-080-0250).
Return this from
evidence_fninstead of a bare evidence value when a candidate’s true evidence is NOT yet known at scoring time – e.g. a not-yet-run experiment or a diagnostic whose result is uncertain until performed.outcomesis the finite set of what it could resolve to, each paired with its probability under the current belief’s own predictive distribution;eig_retrieve()scores the candidate by the true expectation,H[belief] - E[H[belief | outcome]], over this set, rather than the entropy change from a single realized update. Validated eagerly, the same disciplineCategoricalBeliefapplies to its own probabilities: everyprobabilitymust be finite and non-negative, and they must sum to 1 (within floating-point tolerance) – a caller that gets this wrong finds out immediately, at the point they built the outcome set, not several stack frames away inside a confusing entropy computation.- Parameters:
outcomes (Sequence[EvidenceOutcome])
- class SkippedCandidate(item, reason)[source]
Bases:
objectA substrate item scoring could not use, and why (MXR-080-0251) – surfaced, never just dropped.
- Parameters:
item (SubstrateItem)
reason (str)
- multihop(substrate, query, *, max_hops=2, seeds=3, branch=2, max_items=12, min_score=0.0, scope=None, telemetry=None)[source]
Chain typed hops from
queryacross the substrate, recording the evidence path (see docstring).- Parameters:
max_hops (int) – how many hops out from the seeds to expand.
seeds (int) – how many top matches to start from (depth 0).
branch (int) – how many neighbors to expand per frontier item per hop.
max_items (int) – overall cap on the chain size.
min_score (float) – relevance floor for SEED and CONTENT hops – a match must score strictly above it to enter the chain (LINK hops are explicit edges and always followed). Keeps a fuzzy retriever from chaining on near-zero-similarity noise; raise it for a dense embedder.
scope (str | None) – restrict to a team/access scope.
substrate (Substrate)
query (str)
telemetry (Any)
- Raises:
TypeError –
max_hops/seeds/branch/max_itemsis not a plain, exactint(bools and fractional floats included).ValueError – any of them is negative.
- Return type:
HopChain
- class HopChain(query, steps=<factory>)[source]
Bases:
objectA multi-hop retrieval result: the items found and the evidence PATH to each.
- property items: list[SubstrateItem]
Retrieved items in hop-chain order.
- path_to(item_id)[source]
The evidence chain from a seed to
item_id– the trace the reasoner cites.
- provenance()[source]
Return compact provenance records for every hop step.
- class HopStep(item, depth, via, parent_id, score=0.0)[source]
Bases:
objectOne item in the chain plus how it was reached: the provenance of a retrieval decision.
- answer_from_substrate(substrate, question, answerer, *, budget=None, hops=1, min_evidence=1, min_confidence=0.1, compress=True, scope=None, telemetry=None)[source]
Answer
questionfromsubstrateviaanswerer, or abstain when evidence is too thin.- Parameters:
answerer (Callable[[str, str], str]) –
(question, context_text) -> answer_str– any model/rule; called only when there is enough evidence above the confidence floor (so a weak retrieval never fabricates).budget (ContextBudget | None) – the context budget handed to the answerer (default 2000 chars).
hops (int) – 1 = single-shot
retrieve(); >1 =multihop()chaining that many hops.min_evidence (int) – minimum evidence items the answerer must actually receive to attempt an answer – checked against
packet.items(what survives context budgeting), never the pre-budget retrieved/hop-chain count. A small budget can drop most retrieved items before the answerer ever sees them; gating on the pre-budget count would let a caller believe far more evidence was used than the answerer actually got (MXR-080-0257).min_confidence (float) – retrieval-strength floor below which it abstains rather than guess.
compress (bool) – compress the context to fit more sources under budget.
scope (str | None) – restrict to a team/access scope.
substrate (Substrate)
question (str)
telemetry (Any)
- Return type:
Answer
- class Answer(question, answer, abstained, confidence, context, note='', evidence=<factory>)[source]
Bases:
objectA cited answer or abstention with the evidence it rests on and a confidence.
- Parameters:
- citations()[source]
Where the answer’s evidence came from – the provenance the answer must be checkable against.
- measure_flywheel(sub, questions, answer_fn, assimilate_batch, *, k=5, min_credence=None, reset_answer_fn=None, trials=1)[source]
Measure
answer_fnagainstquestionsbefore and afterassimilate_batch(sub)adds a batch of calibrated beliefs (returning the ids it touched), with a THIRD measurement that isolates exactly the batch’s own contribution – the attribution control.answer_fnand every other piece of the system stay fixed throughout: only the store’s content (and what it makes retrievable) changes.Withheld isolation is delta-level, not belief-level (MXR-080-0246):
assimilate_batchmay UPDATE a belief that already existed – and was already visible to thebeforepass – rather than create one from nothing. An id it returns that was already present pre-batch is rolled back, for the duration of the withheld measurement only, to exactly its pre-batch snapshot (its pre-existing evidence, unmodified); an id that did not exist pre-batch is excluded from retrieval outright, same as before, since there is no pre-batch state for it to roll back to. Either waysubis restored to its true post-assimilation content before this function returns – the rollback is an internal, temporary device for taking one measurement, never an observable side effect of calling this function.answer_fnmust be a pure function of(question, context), ORreset_answer_fnmust rewind whatever internal state it has to an identical starting point (MXR-080-0247): when given, it is called immediately before every individual measurement – every trial of every one of the three passes – so a stateful evaluator (a cache, a call counter, a seeded sampler) cannot drift across before/after/withheld and masquerade as a store-content effect.trials(requiresreset_answer_fnwhen greater than 1 – without a reset, repeated calls just accumulate whatever stateanswer_fncarries between them rather than sampling independent replicates) repeats each pass that many times and keeps every individual rate alongside the mean, turning the attribution check into a paired comparison across matched replicates instead of one unrepeated scalar.
- class FlywheelReport(before, after, withheld, attribution_confirmed)[source]
Bases:
objectBefore/after/withheld flywheel measurements with an attribution check.
- Parameters:
before (FlywheelMeasurement)
after (FlywheelMeasurement)
withheld (FlywheelMeasurement)
attribution_confirmed (bool)
- class FlywheelMeasurement(solve_rate, grounded_fraction, trial_solve_rates=<factory>, trial_grounded_fractions=<factory>)[source]
Bases:
objectHeld-out answer quality and grounding rate for one flywheel measurement.
trial_solve_rates/trial_grounded_fractionscarry one entry per replicate trial (MXR-080-0247): a 1-tuple equal tosolve_rate/grounded_fractionfor the defaulttrials=1, longer whenmeasure_flywheel()is asked to repeat each pass. Exposed raw – rather than collapsed into one particular spread statistic – so a caller can compute whatever uncertainty measure (stderr, a confidence interval, a paired significance test) actually fits its ownanswer_fn, instead of this module asserting one test is correct for an arbitrary evaluator.
- class QAItem(question, answer)[source]
Bases:
objectOne held-out question:
answer_fnis judged correct on it if it producesanswerfrom the retrieved context alone.
- investigate(question, actions, answerer, *, budget_cost=None, min_evidence=1, min_confidence=0.15, target_confidence=None, max_actions=None, scorer=None, telemetry=None)[source]
Answer
questionby firing evidence-acquiringactionsunder a cost budget, or abstain.Actions are ordered by
scorer(defaultscore_action(), EIG-per-cost) and fired highest-first. The loop stops early once it holds at leastmin_evidencefragments and confidence clearstarget_confidence(default:min_confidence). It also stops at the cost budget ormax_actions.scorercan be replaced with a learned acquisition policy such asmixle.inference.learn_action_policy(). Theanswereris called only when the evidence clears the bar. The returnedInvestigationcarries the ordered action trace as provenance, and each fired action can emit telemetry for later policy learning. Each action’s score is evaluated exactly once – up front, cached, and reused for both the ordering and the fire/skip decision – so a stateful or stochasticscorercan never produce a fired order inconsistent with the score recorded on its Step (MXR-080-0256).Every action’s cost is validated (finite, non-negative) up front, before any ranking or accounting runs (MXR-080-0254). An action that raises during
run()is recorded as an explicit failedStep(empty fragments,failed=True, the exception onerror) instead of being silently coerced into an empty success; ananswererthat raises is likewise recorded on the returnedInvestigation(abstained=True,failed=True,errorset) instead of propagating with no investigation record at all (MXR-080-0256).- Parameters:
- Return type:
Investigation
- class Investigation(question, answer, abstained, confidence, steps=<factory>, note='', factuality=None, proposal=None, failed=False, error='')[source]
Bases:
objectA cited answer (or abstention) plus the sequence of actions that acquired its evidence.
- Parameters:
- property spent: float
Total action cost spent by the investigation.
- trace()[source]
The actions taken, in order – the provenance the answer must be checkable against.
Each entry’s
failed/errordistinguish “ran, found nothing” from “raised” – collapsing those into the same baren_fragments: 0is exactly how an action failure used to disappear from the trace (MXR-080-0256).
- class Action(name, kind, run, cost=1.0, description='', base_score=0.0, earned_relevance=None)[source]
Bases:
objectOne evidence-acquiring move: run it on a question, get back evidence fragments, at a cost.
- class Step(action, kind, fragments, cost, score, relevance=0.0, failed=False, error='')[source]
Bases:
objectA fired action and what it yielded – the audit trail behind an investigated answer.
- score_action(action, question)[source]
EIG-per-cost proxy: lexical relevance of the action to the question, divided by its cost.
This heuristic can be replaced with a learned or calibrated expected-information-gain estimate. Retrieval-style actions carry a
base_scorefloor because retrieval is always at least weakly informative.Zero-cost actions have nothing to divide by: a free, on-topic action has no cost tradeoff to weigh against anything else, so it scores
+infand sorts first; a free but wholly irrelevant action (zero numerator) scores0.0, same as any other non-informative action, rather than becoming an unbounded favorite.Action.__post_init__already guaranteescostis finite and non-negative, so these are the only two cases – no clamped division ever runs on a negative or non-finite value (MXR-080-0254).
- relevance_of(action, question)[source]
How on-topic an action is for a question (lexical overlap + its base floor), ignoring cost.
This is a PROSPECTIVE estimate – “how promising does this mounted capability look” – used both as
score_action’s routing prior and, elsewhere, to rank untried acquisition strategies (seemixle.scientist.Scientist.propose()). It is deliberately NOT what confidence is earned from once an action has actually run; see_relevance_for().
- action_features(action, question)[source]
The features a learned acquisition policy keys on: the action’s kind, cost, and query overlap.
- retrieve_action(substrate, *, name='retrieve', k=6, scope=None, cost=1.0, min_score=0.0)[source]
A retrieve action over a
Substrate(the always-available floor action).min_scorefilters out weak matches: a small embedder returns a result for every query, so a positive floor keeps genuinely-irrelevant items from becoming false evidence. It defaults to 0.0, but the comparison is a strict>regardless of the floor chosen: an exact-zero score means no overlap at all, which is never usable evidence, so it is always excluded – not admitted just because the caller left the default floor at its lowest setting (MXR-080-0255). A small positivemin_scoremakes retrieval conservative still, on a noisy index where near-zero (but not exactly zero) scores are also unreliable.Confidence is earned separately from ordering:
base_scoregives retrieval a routing prior forscore_action()(“always at least weakly worth trying”), but the fired Step’s confidence-bearing relevance is the TRUE top calibrated score among the results that survived the floor (0.0 when nothing did) – never the fixed routing prior. A store with nothing relevant now reports low or zero confidence instead of borrowing retrieval’s routing floor as if it were support (MXR-080-0255).
- compute_action(skill, *, name=None, cost=1.0, description=None)[source]
A COMPUTE action that runs a
Skilland reports its result.
- simulate_action(simulator, field_index, scenario, *, name=None, cost=2.0, description='')[source]
A SIMULATE action that runs a what-if scenario and reports the simulated outcome mean.
- create_action(build, *, name='create', cost=4.0, description='', report=None)[source]
A CREATE action that builds a model / dataset on demand and reports what it made.
build(question) -> artifactfits/synthesizes something (e.g. viamixle.inference.create()ormixle.inference.synthesize());reportrenders the artifact to an evidence fragment (default: a certificate/guarantee summary when present, elserepr). Creation is the most expensive action, so it defaults tocost=4– the reasoner reaches for it only when cheaper retrieve/compute/simulate actions cannot answer.
- delegate_action(delegate, *, name='delegate', cost=8.0, description='', priced=True)[source]
A DELEGATE action that hands the question to an external worker (pool job / remote tool / agent).
delegate(question) -> answeris any priced external capability. This is the reasoner’s most expensive move (defaultcost=8) and the escalation of last resort: it fires only when nothing local clears the bar, honoring the 99%-local topology.priced=Truerecords that the call incurs real spend (the pool/interop layers own the actual budget-reject + confirm rails).
- class Reasoner(answerer, *, substrate=None, skills=None, actions=None, budget_cost=None, min_confidence=0.15, retrieve_min_score=0.0, scorer=None, telemetry=None)[source]
Bases:
objectA configured reasoner: a knowledge store + skills + actions, asked questions through one method.
- Parameters:
- property actions: list[Action]
The current action space (retrieve + one compute per skill + attached simulators/creators).
- add_action(action)[source]
Attach an extra action (a simulator, creator, or delegate); returns self for chaining.
- Parameters:
action (Action)
- Return type:
Reasoner
- use_policy(scorer)[source]
Route by a learned acquisition policy instead of the lexical prior. Chainable.
- ask(question, *, verify=False, actions=None, **overrides)[source]
Answer
questionover the configured action space, or abstain.overridespass through toinvestigate()(e.g.budget_cost,min_confidence,target_confidence,max_actions).actions, when given, is used for THIS call only, instead ofself._actions– e.g. a caller-owned immutable view such asHarness’s per-request whitelist snapshot. It never reads or mutatesself._actions, so passing it cannot corrupt the reasoner’s own configured action list (or any other in-flight call). DefaultNonekeeps prior behavior: the reasoner’s own action list.With
verify=Trueand a substrate configured, the (non-abstained) answer is run back throughcheck_factuality()and theFactualityReceiptis attached toInvestigation.factuality– the reasoner grounds its own answer’s claims and reports which it can cite. It does not suppress the answer; the receipt is there for the caller to gate on.
- check_factuality(substrate, answer, *, extract=None, corroborates=None, min_score=0.2, k=4, scope=None)[source]
Ground each claim of
answeragainstsubstrate, returning aFactualityReceipt.- Parameters:
extract (Callable[[str], list[str]] | None) –
answer -> [claim, ...](defaultmixle.reason.llm.sentence_claims()).corroborates (Callable[[str, str], Corroboration | bool] | None) –
(evidence_text, claim) -> Corroborationclassifying retrieved evidence against a claim as SUPPORTED / CONTRADICTED / UNVERIFIED (default: lexical candidacy gated by a negation/polarity check – see_default_corroborates(); pass a real NLI/entailment model for stronger grounding). A plainboolis still accepted for compatibility:Truemaps to SUPPORTED,Falseto UNVERIFIED.min_score (float) – retrieval-score floor; evidence below it doesn’t count (guards low-signal embedder noise).
k (int) – evidence items retrieved per claim.
scope (str | None) – restrict retrieval to a team/access scope.
substrate (Substrate)
answer (str)
- Return type:
FactualityReceipt
- class FactualityReceipt(answer, verdicts=<factory>)[source]
Bases:
objectA per-claim grounding of an answer against the substrate – the receipt behind ‘is this true?’.
- property grounded_fraction: float | None
Fraction of extracted claims supported by substrate evidence.
Nonewhen the answer had no extractable claims at all (empty, unparseable, or evasive) – there is nothing that was checked, so this is UNKNOWN rather than a vacuous 1.0.
- unsupported()[source]
The claims the substrate could not corroborate – exactly what to flag or retract.
- Return type:
list[ClaimVerdict]
- contradicted()[source]
Claims where retrieved evidence actively disagreed – worse than merely uncited.
- Return type:
list[ClaimVerdict]
- is_grounded(threshold=1.0)[source]
True iff the grounded fraction meets
threshold(default 1.0: every claim must be cited).Fails closed when there is nothing to assess: an answer with no extractable claims is never reported as “grounded”, regardless of
threshold– a full-grounding gate must not wave through silence just because a vacuous fraction happens to clear the bar.
- class ClaimVerdict(claim, supported, score, citations=<factory>, contradicted=False, contradictions=<factory>)[source]
Bases:
objectOne claim from an answer, with what the substrate said about it and the evidence either way.
supportedis True only when some qualifying evidence was judgedCorroboration.SUPPORTED(entailment-aware – see_default_corroborates()– never bare lexical overlap).contradictedis True when some qualifying evidence was judgedCorroboration.CONTRADICTED– a stronger and distinct signal from merely being uncited: the substrate does not just fail to back this claim, something in it disagrees. The two are not mutually exclusive: conflicting evidence for the same claim (one item backs it, another disputes it) sets both.
- class Space(substrate, team, *, shared=(PUBLIC,), policy=None)[source]
Bases:
objectA team’s scoped view over a shared substrate: its own items plus what has been shared to it.
teamdoubles as this space’s authorization principal (MXR-080-0264): its home scope, perAccessPolicy, is the scope of the same name. Construction validates every entry ofsharedagainstpolicy– PUBLIC always passes; anything else, including another team’s scope, needs an explicit grant, or construction raisesAccessDeniedErrorrather than silently trusting the caller-supplied tuple. Reads re-check live (seescopes), so revoking a grant after construction takes effect immediately rather than requiring a newSpace.- property scopes: set[str]
its own scope plus whichever of
sharedthe policy currently authorizes – re-checked live (not just once at construction), so a grant revoked later stops being visible immediately rather than requiring a newSpace.- Type:
Scopes actually visible to this team right now
- all(*, kind=None)[source]
Every visible item (optionally of one kind) – never another team’s private knowledge.
- add(*, scope=None, **kw)[source]
Add an item to this team’s own scope by default (pass
scope=PUBLICto share immediately, or any other scope this principal holds a write grant for). MXR-080-0264: no longer writes into an arbitrary scope – in particular another team’s – on the caller’s say-so alone.
- retrieve(query, *, k=8, **kw)[source]
Retrieve over exactly the team’s visible set (own scope ∪ shared), with cross-kind diversity.
- publish(ids, *, to=PUBLIC, by=None)[source]
Share this team’s items into a common scope (audited). Only own-scope items are publishable.
by(default:self.team) is also the authorization principal for this call (MXR-080-0264): overriding it to a different principal requires that principal to hold its own grants onself.policy, sinceSpacecannot vouch for an identity other than the team it represents.
- publish(substrate, ids, *, to=PUBLIC, by, from_scope=None, policy=None)[source]
Share items into a common scope, authorized (MXR-080-0264).
Re-scopes each item in
idstotoand recordspublished_by/published_fromin its provenance. Returns the ids actually published; missing ids, ids whose scope doesn’t matchfrom_scope(an additional, OPTIONAL caller assertion), and idsbyis not authorized to move are all skipped rather than aborting the whole batch.bymust be a non-empty, authenticated principal, andpolicy(anAccessPolicy; a fresh, all-deny-but-home-scope-and-PUBLIC one when omitted) must authorize it to WRITEto– checked once, up front, for the whole call – AND to READ each item’s ACTUAL stored scope, checked per item. That second check is what closes the holefrom_scopealone never did: it fires even whenfrom_scopeis omitted entirely, so a known item id from a scopebycannot read is never movable just by leaving the filter off.Every published item’s pre-publish state is persisted as an immutable, content-addressed revision (MXR-080-0265) before it is overwritten;
version_historyentries carry that revision’s digest, recoverable withrevision()– so “a re-published item never silently overwrites its predecessor, the prior state is always recoverable” is an enforced property, not just a claim.
- visible_scopes(team, *, shared=(PUBLIC,))[source]
The scopes a team may read: its own id plus the shared scopes (never another team’s private one).
A pure helper – it does not itself enforce anything.
Spaceis the enforcement point: it validatessharedagainst anAccessPolicybefore this ever runs.
- merge_versions(substrate, keep_id, other_id, *, by=None, prefer=MergeStrategy.LATEST, policy=None, confirm=False)[source]
Reconcile two versions of the same knowledge into one, keeping full lineage (no silent loss, P2).
Merges
other_idintokeep_id: unions tags and links, keeps the text/payload of whicheverprefer(aMergeStrategy) selects, bumps the surviving item’s version, records BOTH parents in the history, and removes the merged-away item. Returns the surviving id, orNoneif either id is missing (checked first, before any authorization – a merge naming a nonexistent id is a no-op, not a permission question).Authorized common lineage, required (MXR-080-0266):
bymust be a non-empty, authenticated principal, andpolicy(anAccessPolicy; a fresh, all-deny-but-home-scope-and-PUBLIC one when omitted) must authorize it to WRITE bothkeep’s andother’s current scopes – a caller can no longer reconcile, and thereby delete, another principal’s item merely by knowing its id.keepandothermust also share the samekind: merging e.g. atextitem into animageitem is never a legitimate reconciliation of the same knowledge, so this is a hard reject with no override. This is deliberately not a full lineage-graph check (nolinks/common-ancestor requirement): P1’s own cross-team-fork use case reconciles items that were never formally linked to each other; kind agreement plus dual-scope write authorization is the minimum coherent gate that rejects genuinely unrelated items (different kind, unauthorized principal) without breaking that legitimate use case.prefer(unrecognized values now raiseValueErrorinstead of silently behaving like"keep") and both items’ pre-merge states are persisted as immutable, content-addressed revisions (MXR-080-0265) beforekeepis overwritten andotheris deleted – recoverable withrevision()even thoughotherno longer exists as a live item. The delete is real and permanent as a LIVE item, so it requires an explicitconfirm=Trueacknowledgment; without it this raisesValueErrorrather than silently performing the destructive step.
- class MergeStrategy(*values)[source]
Bases:
StrEnumWhich side’s text/payload survives a merge – a closed, validated vocabulary (MXR-080-0266): an unrecognized strategy now raises instead of silently behaving like
KEEP.
- history(substrate, item_id)[source]
The full publish/merge history of an item: every version with who changed it, from where, and to where, plus (MXR-080-0265/0266) a
"revision"(publish entries) orparents[]["revision"](merge entries) content-hash digest thatrevision()resolves to the complete immutable prior item state – not just this bookkeeping.
- revision(substrate, digest)[source]
The complete immutable snapshot recorded under content hash
digest(MXR-080-0265), orNoneif no such revision was ever persisted. Returns the full priorSubstrateItemstate as a JSON-compatible dict (kind, text, payload, provenance, tags, links, scope, created_at) – not just the who/from/to bookkeepingversion_historyalone would give you. The returned dict is independent of the stored snapshot (a deep copy), so a caller mutating it can never corrupt the persisted revision.
- version_of(item)[source]
The share version of an item (0 if never published) – a monotonic counter bumped by each publish.
- class AccessPolicy(_read=<factory>, _write=<factory>)[source]
Bases:
objectThe centralized access-policy decision point (MXR-080-0264): the one place that decides whether a principal may read or write a scope.
A principal always has read+write access to the scope sharing its own name (
Spaceis constructed withteamdoubling as that team’s principal id, so a team’s own scope is always its home scope) and toPUBLIC, the explicit sharing commons – this module’s whole design is that nothing else crosses a boundary until someone deliberately publishes into a common scope. Every OTHER scope, in particular another team’s private scope, is denied until explicitly granted withgrant_read()/grant_write()/grant().Space,publish(), andmerge_versions()all consult anAccessPolicyinstead of trusting caller-supplied scope parameters (shared=,to=,from_scope=) as though they were already-validated permissions – closing the concrete leak where a team-A space configured withshared=("team-b",)simply read team-B’s private items with no check at all.- grant_read(principal, scope)[source]
Grant
principalread access toscopebeyond its home scope and PUBLIC (chainable).
- grant_write(principal, scope)[source]
Grant
principalwrite access toscopebeyond its home scope and PUBLIC (chainable).
- grant(principal, scope)[source]
Grant
principalboth read and write access toscope(chainable).
- revoke(principal, scope)[source]
Revoke both read and write grants of
scopefromprincipal(chainable).A principal’s home scope and PUBLIC are intrinsic, not grant-dependent, so this can never revoke those – only a scope previously given via
grant_read()/grant_write().
- can_read(principal, scope)[source]
Whether
principalmay read items inscope.
- can_write(principal, scope)[source]
Whether
principalmay write (add or publish) items intoscope.
- require_read(principal, scope)[source]
Raise
AccessDeniedErrorunlessprincipalmay readscope.
- exception AccessDeniedError[source]
Bases:
PermissionErrorRaised when a principal is not authorized for the scope it tried to read, write, or publish.
- verify_lineage(substrate, item_id, *, max_depth=20, scope=None, policy=None)[source]
Walk
item_id’s ancestry viaderived_fromONLY, returning a three-state verdict (cycle-safe).Traverses exclusively
derived_from(MXR-080-0261) – never the generic, untypedlinksKG edges (seeSubstrateItem) – so a merely-related entity can no longer be certified as a derivation ancestor just by existing.scope, when given, doubles as the authorization principal (the same conventionbeliefuses for the identical problem): every ancestor, at every hop, is resolved throughpolicy(a fresh, home-scope-plus-PUBLIC-onlyAccessPolicywhen omitted). An ancestor outside that authorized view can neither ground anINTACTverdict nor be silently skipped as though absent-therefore-fine – it is named inLineageReport.unverified. An ancestor id that does not exist at all and one that exists but is private to a scopescopecannot read are DELIBERATELY indistinguishable here: both land inunverified, neverdangling– telling them apart would let a caller use their own scoped audits as an existence oracle for another scope’s content, the same side channel MXR-080-0237 closed for semantic search and MXR-080-0244 closed for belief evidence.scope=None(the default) is the one deliberate exception, matchingSubstrate.all()/Substrate.search()’s own convention: an unrestricted caller has no boundary to hide behind, so a missing ancestor is unambiguously reportable asLineageState.BROKEN(dangling) – the legacy behavior.max_depthbounds pathological chains, but reaching it is no longer silent (MXR-080-0260): a node at the cap that still has its OWN unexploredderived_fromentries is named inLineageReport.truncatedand the verdict isLineageState.UNVERIFIED, neverINTACT– an unvisited tail is never certified. A node at the cap with no further edges (a genuine leaf) is not truncated.A back-edge into an ancestor already on the CURRENT root-to-node path is a genuine cycle (an item cannot legitimately derive, even transitively, from itself) and is named in
LineageReport.cycles, forcingBROKEN. Reaching an already-fully-resolved ancestor via a DIFFERENT branch (a shared ancestor / diamond) is ordinary DAG structure, not a cycle, and is not re-walked.Precedence when a walk has more than one kind of finding:
BROKEN(a confirmed defect) outranksUNVERIFIED(an open question) outranksINTACT– the legacy “one break anywhere fails the whole chain” discipline, extended to the two new states. A missing or unauthorized ROOT item is handled the same way:BROKENwhen unrestricted (scope=None),UNVERIFIEDwhen scoped.
- audit_substrate(substrate, *, scope=None, policy=None)[source]
A knowledge-integrity sweep: how many items are intact/broken/unverified, every one named.
MXR-080-0261:
scope/policyare threaded into everyverify_lineage()call, so a scoped audit (scopenotNone) can only ever certify a chain using ancestors that SAME scope is authorized to read. Previously this filtered its own item list byscopebut then handed each id to an unscopedverify_lineage, which walked every scope regardless – a scoped audit could be, and was, validated by (or leak the internal integrity of) items it had no business seeing at all.Returns
{n_items, n_intact, n_broken, n_unverified, broken: [...], unverified: [...]}.brokennames every item with a confirmed structural defect (dangling edge and/or cycle) within its authorized visibility;unverifiednames every item whose chain could not be fully checked (depth-cap truncation and/or an ancestor outsidescope’s authorized visibility) – kept separate frombrokenbecause “could not verify” is never the same claim as “confirmed defective.”
- class LineageReport(item_id, state, n_links, dangling=<factory>, cycles=<factory>, unverified=<factory>, truncated=<factory>, depth=0, visited=0)[source]
Bases:
objectWhether an item’s provenance chain resolves end to end – and exactly why, if it doesn’t.
stateis the headline three-state verdict (seeLineageState); every other field names precisely which ancestor ids drove it, so a caller can always see WHY, not just THAT.- Parameters:
- property intact: bool
TrueiffstateisLineageState.INTACT.Never itself the source of truth –
stateis (MXR-080-0260/0261): a bare boolean cannot distinguish a confirmed break from an open question, which is exactly what these findings fix. Kept only so a caller that wants a single coarse check still gets a CORRECT one: bothBROKENandUNVERIFIEDnow readFalsehere, where the pre-fix code could readTruefor a truncated or cross-scope chain it had never actually verified.- Type:
Read-only convenience view
- class LineageState(*values)[source]
Bases:
StrEnumThe three-state verdict of a lineage verification (MXR-080-0260/0261) – a closed vocabulary, the same discipline
MergeStrategyapplies to merge strategies. Never a bare boolean: a bareintactcannot distinguish “checked everything, it’s fine” from “stopped looking partway and don’t actually know” – exactly the overclaim these findings fix.- INTACT: the full chain was traversed – every ancestor authorized and resolved, no cycle, no
depth-cap truncation, no dangling edge. The only state that certifies anything.
- BROKEN: a confirmed structural defect within the verified region – a dangling edge (an ancestor
id that resolves to nothing, where the caller had legitimate visibility to know that for a fact) or a cycle (an ancestor that is, directly or transitively, its own ancestor).
- UNVERIFIED: the chain could not be fully checked –
max_depthwas reached with further, unexplored edges beyond it, or an ancestor lies outside the caller’s authorized scope and could be neither confirmed present nor absent. Never silently promoted to INTACT.
- class Governance(_approvers=<factory>, _grants=<factory>)[source]
Bases:
objectWho may approve promotions into which scope – the org-governance ACL.
approversis the live, queryable ACL;grantsis its append-only audit trail (who was granted, into which scope, and who authorized it). Both are READ-ONLY views: they were public mutable containers, so authority could be granted straight into the dict with no authorization check and no audit entry, and the trail recording that could itself be edited (MXR-080-1883).grant()is the only way in, and it both checks and records.A scope with no approvers yet is unowned: the first grant into it may come from anyone, so an approver set can be bootstrapped from nothing. That remains a genuine hole – anyone may claim an unowned scope – and it is now marked
bootstrap: Truein the audit trail rather than being indistinguishable from an authorized grant. Rooting the bootstrap in a declared authority is outstanding. Once a scope has at least one approver, only an existing approver of THAT scope may grant further ones into it –grant()raisesGovernanceAuthorizationErrorfor anyone else.- property approvers: Mapping[str, frozenset[str]]
The live ACL, as a read-only view.
This was a public mutable dict, so
governance.approvers.setdefault("secret", set()).add(...)granted approval authority over a scope with no authorization check and no entry in the audit trail that exists to record exactly that (MXR-080-1883).grant()is the only way in, and it both checks and records.
- property grants: tuple[Mapping[str, Any], ...]
The append-only grant audit trail, as a read-only view.
An audit trail a caller can edit or truncate is not one; it was a public
list.
- may_approve(who, scope)[source]
Return whether
whois allowed to approve (or reject) promotion intoscope.
- grant(who, scope, *, by)[source]
Add
whoas an approver forscope(chainable) – audited, and gated once owned.bymust already be an approver ofscope, unlessscopecurrently has none at all (the bootstrap case: someone has to seed the first approver). RaisesGovernanceAuthorizationErrorotherwise.
- exception GovernanceAuthorizationError[source]
Bases:
PermissionErrorRaised when an actor attempts a governance transition they are not authorized to make.
- propose(substrate, ids, *, to, by)[source]
Mark items as pending promotion to scope
to; they are not yet visible there. Returns the ids.Each call appends a NEW entry to the item’s append-only
proposal_history– it never overwrites an existing entry, so a prior decision’s audit record (who rejected or approved it, and why) always survives, even when the item is proposed again later. An item that already has a PENDING proposal is skipped (left out of the returned ids): resolve that in-flight review withapprove()orreject()first, rather than silently replacing it with a second one.
- approve(substrate, item_id, *, by, governance, to=None)[source]
Promote a pending item into its proposed scope – IFF
bymay approve for that scope (the gate).On success the item is published into the target scope (via P1
publish(), so it inherits the versioned/audited share) and a new APPROVED entry is appended to itsproposal_historynaming the approver – the PENDING entry it decides is left untouched, never overwritten. Returns False (no change, not a security event) if the item has no pending proposal. RaisesGovernanceAuthorizationErrorifbyis not an approver authorized for the target scope; no mutation happens in that case.
- authorization_decision(substrate, item_id, *, capability_id, version, digest=None)[source]
Adapt a completed legacy scope proposal to the shared authorization contract.
Pending or missing proposals have no decision and raise
ValueError. This adapter keeps the substrate API compatible while allowing orchestrators to exchange one authorization representation with other Mixle projects.
- reject(substrate, item_id, *, by, governance, reason='')[source]
Refuse a pending promotion – the item stays in its origin scope; the refusal is recorded.
bymust be either the proposal’s own proposer (a self-withdrawal) or an actorgovernanceauthorizes to approve into the proposed scope (a governance refusal). Anyone else raisesGovernanceAuthorizationError– an unrelated third party can no longer reject someone else’s proposal. A new REJECTED entry is appended to the item’sproposal_history; the PENDING entry it decides is left untouched, never overwritten. Returns False (no change, not a security event) if the item has no pending proposal to refuse.
- pending(substrate, *, to=None)[source]
Items awaiting approval (optionally only those proposed to scope
to).
- detect_secrets(text)[source]
Scan
textfor well-known secret shapes; return aSecretScannaming each finding.- Parameters:
text (str)
- Return type:
SecretScan
- redact_secrets(text, *, mask='[REDACTED:{rule}]', keep_prefix=0)[source]
Return
textwith every detected secret replaced by a rule-labelled mask (destructive to secrets).keep_prefixleaves that many leading characters of the secret visible (0 = fully masked) so a reader can still tell which credential it was without recovering it.Applied to a FIXED POINT, because one pass did not deliver what this docstring promises (MXR-080-1882). A rule may match only the credential and leave its assignment context in place:
token=ghp_...masks totoken=[REDACTED:github_token], which still matches thesensitive_assignmentrule, so the “redacted” text was still detected as carrying a secret. That only became visible once the store began re-scanning what it was about to write; before that, the single pass simply returned text nobody checked again.Iteration is bounded and monotone in practice – each pass replaces at least one match with a mask that is not itself that rule’s shape – but the bound is explicit rather than assumed, and a text that has not converged is refused instead of returned dirty.
- safe_text(text)[source]
Redact-before-store guard: mask any secrets so they are never indexed or served.
- scan_item(item)[source]
Scan a substrate item’s full indexed/serialized surface (text + payload + tags) for secrets.
- Parameters:
item (Any)
- Return type:
SecretScan
- scan_substrate(substrate, *, scope=None)[source]
Sweep a substrate for leaked secrets and report which stored items triggered rules.
Returns
{n_items, n_dirty, dirty: [{item_id, rules}]}for compatibility with existing callers; entries indirtyare the items that matched one or more secret-detection rules.
- class SecretScan(findings=<factory>)[source]
Bases:
objectThe result of scanning a text: whether anything leaked and every finding.
- Parameters:
findings (list[SecretFinding])
- property clean: bool
Whether the scan found no secrets.
- class SecretFinding(rule, start, end, preview)[source]
Bases:
objectOne detected secret: which rule matched, where, and a safe preview (the value stays masked).
- class ExternalModel(generate, *, calibration_prompts=None, equivalent=None, max_entropy=None, alpha=0.1, samples=8)[source]
Bases:
objectAn external
generatecallable wrapped so each answer carries semantic-entropy UQ.- Parameters:
generate (Callable[[Any], Any]) –
prompt -> answer(an external agent / LLM / remote tool). Called multiple times per query to measure how much its meaning varies (the uncertainty signal).calibration_prompts (Any) – example prompts whose (1-alpha) semantic-entropy quantile becomes the “too uncertain” cutoff – a calibration policy. Required unless
max_entropyis given directly: with neither (or with calibration prompts too few/degenerate to yield a finite quantile), no calibration policy exists at all, and every answer’sstateisConfidence.UNCALIBRATED(MXR-080-0270) – never silently treated as confident just because its entropy happened to be finite.equivalent (Callable[[Any, Any], bool] | None) –
(a, b) -> boolmeaning-equivalence for clustering samples (default: exact match).max_entropy (float | None) – an explicit uncertainty cutoff, overriding the calibrated one. Must be finite and non-negative – entropy is never negative, so a non-finite or negative cutoff is always a caller error, not a valid (if strict) policy.
samples (int) – how many draws to sample per
answer()call. The first draw becomes the returned answer; entropy is measured over the same batch it came from (MXR-080-0270).alpha (float)
- property max_entropy: float | None
Semantic-entropy cutoff used to decide confidence, or
Noneif no calibration policy is configured (MXR-080-0270).Nonewhenever neithercalibration_promptsnor an explicitmax_entropyproduced a finite cutoff – callers must fail closed onNone, never treat a missing cutoff as “anything finite is confident” the way an unguardedentropy <= infsilently would.
- answer(prompt)[source]
Sample the external model; the first draw is the returned answer (MXR-080-0270).
Every draw – the one returned and every other sample used to measure entropy – comes from the same batch, so the confidence verdict genuinely describes the returned text’s own semantic position in the cluster, rather than a separately generated call that was never actually compared to anything. Fails closed to
Confidence.UNCALIBRATEDwhen no calibration policy (calibration_promptsor an explicitmax_entropy) is configured, however low the measured entropy happens to be.- Parameters:
prompt (Any)
- Return type:
ExternalAnswer
- class ExternalAnswer(prompt, answer, entropy, state)[source]
Bases:
objectAn external model’s answer plus its self-measured uncertainty (semantic entropy).
answeris itself one of the samples measured forentropy(MXR-080-0270), drawn from the same batch rather than a separate, unassessed call – sostategenuinely describes the returned text’s own membership in the measured cluster.- property confident: bool
TrueiffstateisConfidence.CONFIDENT.Never itself the source of truth –
stateis (MXR-080-0270): bothConfidence.UNCERTAINandConfidence.UNCALIBRATEDreadFalsehere, so an uncalibrated model can no longer read as confident just because its entropy happened to be finite.- Type:
Read-only convenience view
- property calibrated: bool
TrueunlessstateisConfidence.UNCALIBRATED.
- external_action(model, *, name='external', cost=8.0, description='', trust_uncertain=False)[source]
A reasoner delegate action backed by a UQ-wrapped external model (see module docstring).
By default (
trust_uncertain=False) the action contributes evidence only when the external model’s answer isConfidence.CONFIDENT; anUNCERTAINorUNCALIBRATEDanswer yields no fragment, so the reasoner treats it as no answer rather than a guess (MXR-080-0270: a never-calibrated model is withheld exactly like a genuinely self-contradicting one – both are “we cannot vouch for this”, not “confident”). The fragment carries the model’s entropy and verdict so the trace records how sure the external source was, and why. Cost defaults high – external calls are the escalation of last resort.
- kg_action(triples, *, ontology=None, types=None, name='kg', cost=1.0, description='', k=8)[source]
A reasoner RETRIEVE action over a knowledge graph (typed facts, not passages).
Contributes one fragment per fact (
head relation tail); nothing links -> no evidence, so the reasoner falls through honestly instead of forcing a match. Relevance comes from the action’sdescriptionplus the KG’s own entity inventory (queries naming a known entity score).triplesis materialized exactly once, here, into an immutable, arity-validated structure (MXR-080-0253, see_materialize_triples()) – both the inventory built below and every later_runcall read from that same materialized data, so a one-shottriplesgenerator can no longer be exhausted by the inventory pass and come up empty when the action actually runs.ksharesretrieve_triples()’s exact-non-negative-integer contract, checked up front so a badkfails at construction rather than silently inside a fired action.
- link_entities(question, entities)[source]
The entity-linking leaf: which KG entities does the question mention?
Matches each entity’s normalized name as a token subsequence of the question, longest name first so multi-word entities win over their substrings. Returns the linked entities in match order.
- retrieve_triples(triples, question, *, ontology=None, types=None, k=8)[source]
Typed KG retrieval: link the question’s entities, return the (schema-valid) facts about them.
Returns
{entities, facts, rejected}–factsare the triples touching a linked entity (head or tail), at mostk; when anontology(+ entitytypes) is supplied, schema-violating triples are excluded and reported underrejectedwith named reasons, so an unvalidated store cannot inject a type-invalid fact as evidence.kmust be an exact, non-negativeint(MXR-080-0253, see_require_count()) – a negativekused to fall through to ordinary Python slicing (hits[:-1]silently drops just the last hit) instead of being rejected.triplesis materialized once, validated, into an immutable structure before any lookup runs (MXR-080-0253, see_materialize_triples()) – safe no matter how many timestriplesis a one-shot iterable and no matter how many times this function is called with it.
- check_freshness(substrate, item_id, *, max_age_s=None, now=None)[source]
Audit one item for the staleness signals (see module docstring), returning a three-state
FreshnessStateverdict (MXR-080-0267) – never a bare boolean. Missing item -> stale.now(default: the current wall clock, injectable for deterministic tests) andmax_age_sare caller-supplied ARGUMENTS, validated up front: NaN or infinite (and, formax_age_s, negative) values are always a caller bug, so an invalid one raisesValueErrorimmediately rather than being silently clamped or ignored. By contrast, a NaN or future-dateditem.created_atis substrate DATA being audited, exactly like a moved file or a changed hash: it is named in the result (stateFreshnessState.STALE) rather than raised, so one corrupted record can never abort afreshness_report()sweep over everything else.Precedence when more than one kind of finding applies to the same item (mirrors
verify_lineage()’s BROKEN-outranks-UNVERIFIED rule):FreshnessState.STALE(a confirmed signal) outranksFreshnessState.UNVERIFIABLE(an open question) outranksFreshnessState.FRESH.
- freshness_report(substrate, *, max_age_s=None, scope=None, now=None)[source]
Sweep a store for stale/unverifiable knowledge (MXR-080-0267 three-state; mirrors
audit_substrate()’s broken/unverified split):{n_items, n_fresh, n_stale, n_unverifiable, stale: [...], unverifiable: [...]}– the monitor feed.stalenames every item with a confirmed signal (moved/changed/superseded/aged/invalid timestamp);unverifiablenames every item whose referenced content exists and carries a recorded hash but could not be read to check against it – kept separate fromstalebecause “could not verify” is never the same claim as “confirmed defective.”
- content_hash(path)[source]
The full, algorithm-labelled digest of a file’s bytes (
"sha256:<64-hex>"), orNoneif unreadable – record this verbatim at ingest, and compare it verbatim at audit time.MXR-080-0267: previously a bare hex string truncated to the first 32 characters (128 bits), with no algorithm recorded – collision resistance silently weaker than sha256’s own, and no way for a future reader to know what algorithm produced a recorded value (or to add a second scheme without guessing which one an old value used). The label makes the digest self-describing and algorithm-agile; the digest itself is never truncated.
- class Freshness(item_id, state, signals=<factory>, age_s=0.0)[source]
Bases:
objectOne item’s freshness verdict: a three-state
state, never a bare boolean (MXR-080-0267; mirrorsLineageReport). Every signal – confirmed or unverifiable – is named insignals.- property fresh: bool
TrueiffstateisFreshnessState.FRESH.Never itself the source of truth –
stateis (MXR-080-0267): bothFreshnessState.STALEandFreshnessState.UNVERIFIABLEreadFalsehere, where the pre-fix code could readTruefor content it had never actually been able to check against its recorded hash – a directory (or any other unreadable path) with a deliberately wrong recorded hash was reported fresh.- Type:
Read-only convenience view
- class Harness(reasoner, *, name, description='', validate=None, allowed_kinds=None, escalate=None, min_confidence=0.15, on_result=None)[source]
Bases:
objectSchema + whitelist + guardrails + escalation around a reasoner (see module docstring).
- Parameters:
reasoner (Reasoner) – the configured
Reasoner(answerer + substrate + skills + actions).description (str) – identity, used by the registry.
validate (Callable[[str], str | None] | None) –
(request) -> None | str– return an error string to REFUSE the request before any model runs (the input schema, as a callable so any validator plugs in).allowed_kinds (tuple[str, ...] | None) – action kinds this harness may fire (whitelist; None = all).
escalate (Callable[[str, Any], str] | None) –
(request, result) -> str– called on abstention with the REDACTED request and a redactedInvestigation; its return is the escalated answer handed back (e.g. a ticket id). None = abstentions surface as ‘escalated’ with no handler note.min_confidence (float) – the answer bar (passed through to
ask).on_result (Callable[[HarnessResult], None] | None) – optional UI hook, called with every HarnessResult (fire-and-forget).
name (str)
description
Action-view immutability:
reasoner._actionsis a shared, mutable structure – otherHarnessinstances built from the same reasoner, or a laterreasoner.add_action()call, can change it at any time. So that one harness’s whitelist can never be corrupted by another harness’s construction, or bypassed by a lateradd_action(), aHarnessnever mutatesreasoner._actionsand never re-readsreasoner.actionsafter__init__: it snapshots its own immutable action view – the whitelist-filtered (or, withallowed_kinds=None, full) action list, each action wrapped to redact its own evidence – exactly once, at construction time, and every request the harness ever serves uses only that view. Attach every action a harness should be able to use to the reasoner BEFORE constructing that harness;reasoner.add_action()calls (or another harness’s construction) after that point affect only the shared reasoner and any harnesses built afterward, never one already built. The frozen view is available for inspection asharness.actions.- property actions: tuple[Action, ...]
This harness’s own immutable, redaction-wrapped action view (frozen at construction).
- class HarnessResult(status, answer=None, reason='', investigation=None, redactions=0)[source]
Bases:
objectOne request’s outcome: which gate decided (refused/answered/escalated), and the evidence.
- support_triage_harness(substrate, answerer, *, escalate=None, max_chars=2000)[source]
Support triage: retrieve-only over the team’s knowledge, refuse empty/oversized requests, escalate anything the knowledge base cannot support – the canonical ‘never guess at a customer’.
- monitoring_harness(reasoner, *, escalate=None)[source]
Monitoring/alerting: compute + simulate allowed (run checks, what-ifs), no delegation out.
- register_harness(substrate, harness, *, scope='local')[source]
Index a harness on the substrate as a scoped artifact – discoverable and shareable (P-scoped).
- find_harnesses(substrate, query='', *, scope=None)[source]
Discover registered harnesses (optionally by query / scope). Returns their manifests.
- harvest_knowledge(model_output, *, source, extract=None)[source]
Split
model_outputinto atomic claims, each stamped withsource(which model produced it, its confidence, etc). Default extraction is sentence-level (mixle.reason.llm.sentence_claims()); passextractfor a different atomic-proposition splitter.
- assimilate(sub, claim, evidence, *, scope='local', policy=None)[source]
Bayesian-ish update of the belief in
claimfromevidence– never a binary write.Finds or creates the belief item (keyed on normalized claim text) and appends each evidence entry (
{"source_id", "tier", "direction": "+"/"-", "weight"});tiermust be one of_TIER_STRENGTH("model_assertion"plusmixle.doe.oracle.VERIFIABILITY_TIERS), anddirection, when given, must be"+"or"-"(MXR-080-0242).Verification (MXR-080-0243): a tier above
MODEL_ASSERTIONonly earns its claimed strength whensource_idresolves to a real substrate item through an authorized view ofscope(see_resolve()); an unresolved, fabricated, or omittedsource_idis recorded as unverified and contributes atMODEL_ASSERTION-strength regardless of what it claims.Anti-laundering (MXR-080-0244): an entry whose
source_idresolves back to THIS belief (a cycle) or to another belief item with no independent (non-model-assertion, verified) support of its own is stored with an effective weight of zero – it cannot move the credence, though it stays in the trail for audit. Resolution – both the verification check above and the laundering check – goes throughpolicy(a fresh, home-scope-plus-PUBLIC-onlyAccessPolicywhen omitted), never a raw global lookup, so citing another scope’s private belief is indistinguishable from citing something that does not exist at all: neither the credence it produces nor anything else observable here can be used to learn whether that other scope’s belief exists or is grounded.
- retract(sub, source_id, *, scope=None)[source]
Remove every evidence entry citing
source_id, recomputing credence, and CASCADE: if that removal causes a belief to lose its only independent support, also strip citations of THAT belief from whatever cited it, recursively. Returns every belief item touched by the cascade.
- retrieve_beliefs(sub, query, *, k=8, min_credence=None, scope=None)[source]
Beliefs relevant to
query, optionally thresholded onmin_credenceand re-ranked byrelevance * credence– so a caller can weight by, or hard-filter on, how much the store actually believes each item (never a “fact” vs “non-fact” partition, only credence).MXR-080-0245: the belief tag and
min_credenceare filtered over the FULL"record"-kind candidate domain, not a fixed-size prefetch – a store where ordinary (non-belief) records or low-credence beliefs rank higher by raw relevance can no longer crowd out qualifying beliefs that rank lower but still exist.sub.searchstill does the actual relevance scoring (this module never touches core.py’s ranking); the fix is asking it for every"record"-kind candidate in scope rather than an arbitrary3*kcut of them.
- credence_from_history(evidence_history)[source]
The credence implied by an evidence history alone – a pure function, so replaying a belief’s stored
evidence_historythrough this always reproduces its currentcredenceexactly.An entry’s claimed
tieronly counts at its claimed strength whenEvidenceEntry.verifiedis True (MXR-080-0243); an unverified claim of a strong tier contributes atMODEL_ASSERTION-strength instead, and does not count towardhas_real_support(so it cannot, by itself, lift a belief pastMODEL_ASSERTION_CAP) – otherwise merely CLAIMING a strong tier would escape the cap even though its actual numeric contribution never did.
- class BeliefItem(id, claim, credence, evidence_history=<factory>, scope='local')[source]
Bases:
objectA claim’s current credence plus the full evidence trail that produced it.
- class Claim(text, produced_by=<factory>, quantity=None)[source]
Bases:
objectOne atomic proposition (or typed quantity) pulled from a model’s output.
- class EvidenceEntry(source_id, tier, direction='+', weight=1.0, time=<factory>, verified=True)[source]
Bases:
objectOne piece of evidence that moved a belief’s credence, in the order it was applied.
verifiedrecords whethersource_idresolved to something real, through an authorized scoped view, at the moment this entry was assimilated (MXR-080-0243/0244) – computed once, byassimilate(), and carried on the entry itself socredence_from_history()can replay it without ever touching the substrate again. Direct construction (tests, deserializing an already-decided entry) defaults toTrue, matchingdirection/weight/time’s own “already-legitimate input” defaults;assimilate()is the untrusted-input boundary and always computes and passes this explicitly rather than relying on the default.
Submodules¶
- mixle.substrate.accum module
- mixle.substrate.act module
- mixle.substrate.answer module
- mixle.substrate.belief module
- mixle.substrate.context module
- mixle.substrate.core module
- mixle.substrate.eig_retrieve module
- mixle.substrate.factuality module
- mixle.substrate.freshness module
- mixle.substrate.governance module
- mixle.substrate.harness module
- mixle.substrate.ingest module
- mixle.substrate.interop module
- mixle.substrate.kg_rag module
- mixle.substrate.multihop module
- mixle.substrate.reasoner module
- mixle.substrate.retrieve module
- mixle.substrate.security module
- mixle.substrate.spaces module
- mixle.substrate.trust module