brain-write — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited brain-write (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
The write surface is small but consequential — every fact you record gets scored by the conflict resolver, may supersede or compete with existing facts, and stays in the audit trail forever. This skill covers when to reach for each tool and how to set the inputs so the resolver does what you mean.
record_factingest_document — prefer it over record_fact for anything longer than one claimlink_entities with kind: identity_ofpaid_for, mentioned_in, worked_with, …) → link_entitiesretract_factdetect_contradictionrecord_feedbackDo not use for:
forget_entity (admin scope; see brain-mcp-setup)All write tools require the brain:write scope on the API key. forget_entity requires brain:admin.
record_fact({
entityRef: { vertical: "rent", id: "cust_42" }, // or { entityId: "..." }
predicate: "tier",
object: "platinum",
validFrom: "2026-05-01T00:00:00Z",
validUntil: undefined, // optional — leave open-ended unless the user said "until X"
confidence: 0.9, // 0..1
sourceVertical: "rent",
userId: "u_42", // the person whose memory this is; omit = shared with the whole tenant
// Grounding inputs (claim-grounding plane) — name the observation
// behind the claim so the fact counts as GROUNDED:
conversationId: "conv-2026-05-01-support", // the conversation you observed it in
evidence: [ // ≤10 typed pointers, stored verbatim
{ kind: "message", ref: "msg_9f3", note: "user stated tier explicitly" },
],
})An MCP agent CAN name the observation behind its claim — always do. Pass conversationId (the conversation the fact was observed in) and/or evidence[] (up to 10 typed pointers: event / message / conversation / url / document / commit / other, each { kind, ref, note? }). Their presence marks the fact grounded on the claim-grounding plane: servers running EVIDENCE_GROUNDING_STAMP stamp groundingStatus: 'grounded' on the row, and downstream gates (ungrounded-exclusion from consolidation, the ungrounded_evidence serving abstention) treat it as observation-backed. A bare record_fact with neither input is permanently observation-free — the fact still records, but it can be excluded from long-term consolidation and can cause a strict server to abstain rather than serve answers resting only on such claims. Verify what landed with get_fact (shows groundingStatus) and get_fact_provenance (shows the grounding turns), both available when the server runs FACTS_API_ENABLED.
Confidence is not a vote: the conflict resolver decides by valid time (a later validFrom supersedes, a restated value corroborates, a same-moment disagreement goes to fact resolution) and never by score. Confidence is recorded on the fact as provenance; readers and consolidation use it, and so does the human adjudicating a dispute. Pick it honestly:
| Situation | confidence |
|---|---|
| User said "Alice is platinum tier" explicitly | 0.9 — 1.0 |
| Inferred from LLM extraction over a transcript | 0.5 — 0.7 |
| System observation (counted event, signed signature) | 0.95 — 1.0 |
| Vague phrasing ("I think she's gold?") | 0.3 — 0.5 |
A confidence of 1.0 implies a system-grade ground truth source — don't claim it for an LLM extraction.
When the cost of a contested write is high (e.g. agent loops that pay an ingest credit, or actions that would surface a CHANGED notification), check first:
detect_contradiction({
entityRef: { vertical: "rent", id: "cust_42" },
predicate: "tier",
object: "platinum",
validFrom: "2026-05-01T00:00:00Z",
confidence: 0.9,
sourceVertical: "rent",
})Returns { wouldOutcome, reasoning, opposingFacts, predicatePolicy }:
wouldOutcome | Meaning | What to do |
|---|---|---|
INSERTED | No overlapping prior; safe to write. | Just call record_fact. |
SUPERSEDED | Would close a prior fact; resolver picks the new one as winner. | OK if the user knows the prior is outdated; surface a confirmation if it's user-visible. |
COMPETING | Would land alongside a prior in COMPETING status — the resolver couldn't pick. | Either ask the user to disambiguate, or proceed and surface to a reviewer queue via get_competing_facts. |
REJECTED | Score below reject threshold (too unconfident, too low-trust). | Either raise confidence (only if honest), drop the fact, or ask for a stronger source. |
The dry-run is JS-side approximation of fn::resolve_fact — small fidelity gap on source_trust (uses the seed table, not the per-tenant learned rate) but matches the resolver's logic on every other axis.
For anything longer than one claim (meeting transcript, email body, markdown), don't loop record_fact — feed the whole document through the Source → Indexer → Candidates → Brain pipeline. ingest_document takes no userId: with a key not bound to one user, what it writes is shared with the whole tenant, so don't feed it one person's private material that way.
ingest_document({
kind: "chat", // chat | email | markdown | pdf | …
text: "<normalized document text>",
title: "Support call with Alice", // optional
occurredAt: "2026-05-01T14:00:00Z", // the document's own timestamp → facts' validFrom
vertical: "rent",
indexers: "general", // 'auto' also routes installed domain packs
toolObservationRef: "tool_observation:abc123", // optional provenance hop, see below
})The document is stored (content-hash deduped), read by the indexer, staged as candidates, and committed through the same conflict resolution as record_fact — extraction and superseding are handled for you. The tool is registered only when the server runs DOCUMENT_INGEST_ENABLED.
toolObservationRef closes the tool-result → document → fact loop: when the document you're ingesting was derived from a tool result the server observed (servers running TOOL_OBSERVATIONS_ENABLED record content-free observation rows per MCP tool call), pass the tool_observation:<id> so every committed fact's source.evidence[] carries a tool_observation entry pointing back at it.
After using a fact from search_knowledge / synthesize, report how it went:
record_feedback({
factId: "knowledge_fact:01HXYZ...",
verdict: "incorrect", // 'helpful' | 'not_helpful' | 'incorrect'
reason: "Tier was gold, not platinum — confirmed by billing.", // optional
})helpful — the fact answered the question (positive reliability signal)incorrect — the fact is wrong; counts against its source's learnedreputation at the nightly refit
not_helpful — irrelevant hit; stored, but not a reliability signalOne standing vote per caller key per fact — repeat calls replace your previous verdict. Note the trust effect is prospective: it shifts the SOURCE's trust for facts ingested after the refit, it does not demote the flagged fact itself (retract it if it's wrong).
link_entities({
from: { vertical: "rent", id: "cust_42" },
to: { vertical: "shop", id: "buyer_18" },
kind: "identity_of", // 'identity_of' merges; other kinds add typed edges
weight: 1.0, // 0..1, optional
sourceVertical: "rent",
})kind: identity_of — cross-vertical mergeidentity_of is special — it triggers a cascade that merges from into to:
from is reparented to tofrom.mergedAt + from.mergedInto are set so the redirect is auditableCycle guards:
from == to) is rejected.A → B → A) is rejected.B.mergedInto = C) follows the redirect — you end up merging into C, not B. The semantic is "merge the whole identity cluster", not "merge into the row by id".Typed edges (paid_for, mentioned_in, worked_with, manages, etc.) just add a row in knowledge_edge. They're surfaced by find_related_entities and contribute to PPR / SubgraphRAG context. Free-vocabulary; tenants extend the edge taxonomy without a migration.
retract_fact({
factId: "knowledge_fact:01HXYZ...",
reason: "Source misattributed; was Bob, not Alice.",
})What happens:
status flips to retracted, retractedAt is set, retractionReason recorded.derivedFrom — any fact derived from this one is also retracted (depth-first).status='active', validUntil restored to its pre-supersede value). The audit trail keeps the supersede/revive chain.The row stays for audit. To actually delete (GDPR), use forget_entity — admin scope only.
Three predicate classes require brain:admin for retract, not just brain:write:
billing_event — affects downstream invoicing auditshuman_declared — represents operator-attested ground truthsource.kind === 'legal' — regulator-visibleIf the API key has only brain:write, the retract on these falls through with a 403. Don't pre-validate this in the agent loop; let brain enforce it and tell the user the scope they need.
| Question | retract | forget |
|---|---|---|
| "Was this ever true?" | Yes — row stays | No — row gone, only HMAC tombstone left |
| "Affects audit trail?" | Walks supersede + cascade chains | Hard delete of the entity and all its facts |
| "GDPR Art. 17 right-to-erasure?" | No — facts remain | Yes — this is the intent |
| "Reversible?" | Yes — re-record the fact | No — fact-level rehydration impossible |
| "Required scope?" | brain:write (admin for some predicates) | brain:admin |
Default to retract. Reach for forget only when a DSAR or tenant-offboarding event genuinely requires erasure.
validFrom. If the user said "Alice is platinum", validFrom is the moment of conversation. If they said "Alice has been platinum since April", validFrom is April. Be precise.sourceVertical describes WHO is asserting the fact — usually the vertical the agent is operating on behalf of ("rent", "shop"), not the vertical the agent runtime lives in ("chat"). Get this wrong and the conflict resolver's source_trust math goes sideways.identity_of is a merge directive. For "are connected", use a typed edge like worked_with or interacted_with.detect_contradiction — preflight before record_factget_competing_facts — see what's already unresolved on this entity (see brain-conflict)get_source_reputation — the learned trust profile of a source vertical; check it when detect_contradiction's seed-table approximation isn't precise enoughsearch_knowledge / get_entity_profile — find the existing fact before retracting (see brain-search, brain-recall)get_fact / get_fact_provenance — read back the full trust record (incl. groundingStatus) and the grounding turns behind a fact you just recorded or are about to retract (FACTS_API_ENABLED servers)memory_diff — confirm the change landed (see brain-bitemporal)~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.