Concepts
The mental model behind agent-knowledge, in the order you'll meet it.
Claims — the unit of knowledge
A claim is a signed statement with pedigree. Its body is immutable — revision means
a new claim that supersedes the old one. The claim id is content-addressed:
claim_id = sha256( signing_input("claim", body) ) # hex
Derived, never chosen. Two identical bodies are one claim, which makes duplicate proposals idempotent. Topics are lowercased, sorted, and deduped at construction. The author address is derived from the signing key, so a claim can never carry an author its signer doesn't control.
Claim kinds: semantic (what is true) · procedural (how to do things) · relational
(how things relate).
Provenance and pedigree
Every claim carries:
episodes— the observations it was distilled from. Episodes are cheap, unsigned, append-only capture (observation | outcome | correction | surprise). They are fuel for consolidation and are never returned by briefings directly.refs—ArtifactRefs naming the artifacts the claim is about. These double as invalidation hooks (see below).supersedes— an optional revision chain.
Episode and supersedes references may not cross the space boundary (ScopeError).
Confidence ⊥ Staleness
Trust has two independent axes, both derived at read time from the endorsement record — never persisted as authoritative.
Confidence ∈ [0, 1] — "was this ever true." Reference formula:
confidence = (1 + c) / (2 + c + k·d)
where c = distinct corroborating identities, d = distinct contradicting identities,
k = contradiction weight (default 2 — one contradiction outweighs one corroboration).
No endorsements → prior of 0.5. Implementations may substitute the formula; the fields
are the standard.
Staleness — whole seconds since the last corroborating encounter (asserted_at counts
as the first). Corroboration resets it. Contradictions never touch staleness — they're
about truth, not freshness.
Standing positions supersede, not accumulate: an identity's most recent endorsement is its position, and self-corroboration never stacks.
Ranking applies freshness damping: horizon / (horizon + staleness) — 1.0 when fresh,
0.5 at the horizon (default 7 days).
Provenance-linked invalidation ("the signature move")
kb.invalidate(scope, artifact_uri="file://pricing-model.md", changed_at=now)
records that an artifact changed. Any claim whose refs include that artifact — with a
change newer than its last corroboration — becomes suspect. The flag flips the moment
the artifact changes and clears on the next corroboration. Knowledge about things that
changed announces itself instead of quietly rotting.
Contradiction attaches, never edits
A contradiction is a signed endorsement on the claim. It lowers confidence and stays
visible in every briefing. Silent last-write-wins is forbidden by construction. When
distinct contradictors reach the threshold (2), a ConflictEvent is surfaced for
humans — the library never auto-resolves disputes.
Proposal-governed promotion
Knowledge enters a scope only by proposal, decided under the scope's Policy:
| Mode | Behavior |
|---|---|
auto (default) |
Accepts immediately — unless a claim topic is in protected_topics, which queues it pending |
review |
Pending until required_approvals distinct approving identities; any reject → rejected |
Rules enforced in code, not convention:
- The proposer cannot review their own promotion.
- Each identity reviews once.
- Verdicts are admitted atomically only while pending (checked and stored under one store lock) — concurrent quorum can't strand a promotion or record a vote on a decided one.
- Status leaves
pendingexactly once.
All governance records are append-only and queryable: the audit trail is the data structure.
Retirement — the only exit
retire() is the only way a claim leaves briefings, and it is author-only. The record
itself is never deleted. Cross-author disagreement must use contradiction (which keeps the
claim visible) — unrestricted retirement would be a hidden-censorship primitive. Supersession
is likewise author-only.
Assembled briefings
brief() is deterministic and model-free:
rank = relevance (refs-overlap boosted) × confidence × freshness damping
The retriever supplies relevance (reference implementation: Okapi BM25 over Porter-stemmed,
stopword-filtered tokens); the core owns trust and assembly. verify_first fires on items
that made the briefing while suspect or staler than the scope's horizon. An empty query
("list everything") ranks by trust alone. Default limit: 12 items.
The three seams
Everything above is the fixed normative core. Three things are deliberately pluggable:
Store— persistence. ShipsSQLiteStore(WAL) andInMemoryStore, tested for parity.Retriever— relevance. ShipsKeywordRetriever(BM25); bring your own embeddings.SourceWeight— trust. ACallable[[address], float]mapping authors to weights. Absent it, every distinct identity is one equal vote. This is the injection point for reputation or stake-weighted Sybil resistance, which a wire standard cannot itself provide.
Fine print worth knowing
- Zero-relevance claims are dropped, not just down-ranked. For a non-empty query, a claim with zero retriever relevance and no ref overlap is omitted from the briefing entirely.
- Two "conflicts" with different thresholds.
Briefing.conflictslists briefed items with ≥1 contradictor;kb.conflicts(scope)surfaces scope-wideConflictEvents at the ≥2 distinct-contradictor threshold. - Idempotency has one exception. Re-proposing an identical body returns the existing
promotion — unless that promotion was
rejected, in which case a fresh one opens. - Segments. A claim with
segment=Noneis visible in every segment of its space; a segment-scoped claim is visible only in its own segment. - Stateful retrievers get
index(claim)on acceptance andremove(claim_id)on retirement from the facade.
Signing domain
Records sign under the domain fg-agent-knowledge/v1 with per-record contexts
(claim, endorsement, verdict, retirement):
signing_input = uint16be(len(tag)) || tag || canonical_json(body)
Same construction as agent-id but a distinct domain — a knowledge signature can never be
replayed as an identity artifact. Canonical JSON forbids floats anywhere. Timestamps are
RFC 3339 UTC with 3 fractional digits. Future timestamps beyond a 300s skew window
(MAX_CLOCK_SKEW_SECONDS) are rejected on propose(asserted_at) and endorse(issued_at);
the spec requires this for every signed record, and the reference implementation does not
yet enforce it on observe/review/retire paths. Signed text fields are bounded at 65,536 bytes.