Install
Bootstrap a signing key
~/.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
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 aclassification 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
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 oneESTABLISHED 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 thegenerated_by label. Enroll a
second validator and sign the second claim with its key, so the two lines
carry distinct asserter_keyids.
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 norlab_b can do this step. Enroll a third key that asserted nothing
and validate from it.
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
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.