Skip to main content

Install

Or, with uv:

Bootstrap a signing key

Generates an Ed25519 keypair at ~/.config/mareforma/key (mode 0600). With a key, every claim is signed and the first project you open auto-enrolls you as its root validator (the identity allowed to promote claims to ESTABLISHED). Do this before you start. Without a key, assert_claim still writes, but the claim is unsigned, so query hides it unless you pass include_unverified=True, and everything from “Bootstrap an ESTABLISHED upstream” onward raises: a seed claim, enroll_validator and validate each need a loaded signer. Independence and promotion key on the signing identity (asserter_keyid), which an unsigned claim does not have.

Open a graph

No project setup. No init command. graph.db is created in .mareforma/ under the current directory on first call.

Assert a claim, and record whether the data was there

A claim carries a classification the agent declares (INFERRED, ANALYTICAL, or DERIVED), but a declaration is only as good as the run behind it. So assert the claim from inside observe(): it watches the block, and Mareforma records what the run actually did next to what the agent said it did. Lead with this, not with a bare classification.
obs.verdict is GROUNDED if a read of dataset_alpha was observed inside the block, UNGROUNDED if none was, and OPAQUE if the read happened somewhere Mareforma could not see: a subprocess, a C extension, a warmed connection. The catch worth internalising is what a silent failure lands: when a cited read is attempted and fails, and the failures account for every open of that path, the scope lands UNGROUNDED, not a comfortable OPAQUE. So an agent that declares ANALYTICAL on a pipeline that quietly read nothing leaves the gap on the record instead of hiding it. claim_id is a UUID. The claim is immediately persisted to graph.db and backed up to claims.toml, a human-readable TOML file written after every mutation. If graph.db is ever deleted or corrupted, claims.toml preserves the full record. A claim is a falsifiable assertion with provenance: what was asserted, how the knowledge was derived (classification), from which data source, and building on which upstream claims (supports). Claims are append-only: once recorded, a claim is built upon, contradicted, or retracted, never overwritten. The verdict says a cited read happened, not that it was a read of this claim’s own data. assert_claim has no citation to bind it to, so the stored reason carries [no finding citation to bind]. Use assert_finding with data_source= when you want the verdict cross-checked against the data the claim rests on. See Grounding.

Query what is already known

Always query before asserting. If the graph already carries a well-supported finding on your topic, build on it; don’t duplicate it. Trust itself reads off the derived axes, not the stored label: graph.proposition_status(prop) returns the answer’s status and the question’s question_status, both computed on every read. The support_level column (PRELIMINARY -> REPLICATED -> ESTABLISHED) and the min_support= filter are the legacy promotion ladder, still functional this release but deprecated for v0.4.0. When the results will be spliced into an LLM prompt, use graph.query_for_llm(...) instead: it sanitizes free-text fields and wraps them in <untrusted_data>...</untrusted_data> delimiters so stored prompt-injection in earlier claim text cannot escape.

Bootstrap an ESTABLISHED upstream

REPLICATED requires at least one ESTABLISHED claim in the converging peers’ supports[], which stops replication of noise. On a fresh project, no claim is yet ESTABLISHED, so the first thing the root validator does is assert a seed claim: a claim that goes directly to ESTABLISHED via a signed seed envelope.

Let two agents converge

Independence is the signing key, not the generated_by label. Enroll a second validator and sign the second claim with its key, so the two lines carry distinct asserter_keyids.
Two claims under distinct signing keys, sharing an ESTABLISHED upstream → REPLICATED is set automatically at INSERT time. Independence is the signing key (asserter_keyid); generated_by is a display label only. No extra step required.

Promote to ESTABLISHED

Promotion needs a witness. No key can promote a claim it signed, and no key can promote a claim whose converging peer it signed, so neither your root key nor lab_b can do this step. Enroll a third key that asserted nothing and validate from it.
Only REPLICATED claims can be promoted. validate() is identity-gated: the loaded signing key must be enrolled in the project’s validators table, and it must not appear on the claim envelope or on any peer in the converging set. A key that promotes its own line raises SelfValidationError. The validation event itself is signed and persisted to the claim’s validation_signature column.

Close the graph

Or use a context manager; it closes automatically on exit:

Next

Concepts

What is trust, what is origin, what is the graph.

For agents

Full execution contract: methods, patterns, forbidden patterns.

API reference

Complete method signatures and return types.

Examples

Five runnable examples, from walkthrough to real AI scientists.