brain-write
How to write to the INITE Brain knowledge graph from an agent loop — record_fact, link_entities, retract_fact, and the detect_contradiction preflight. Covers confidence picking, retract vs forget semantics, identity_of cycle-guards. Use when the user explicitly wants to record, merge, or revise structured knowledge from a conversation (not when they're just asking a question).
git clone --depth 1 https://github.com/inite-ai/inite-brain-service /tmp/brain-write && cp -r /tmp/brain-write/skills/brain-write ~/.claude/skills/brain-writeSKILL.md
# brain-write
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.
## When to use
- The user says "remember that …" / "make a note that …" / "let's record …" → `record_fact`
- A fact you have on hand says "X is the same person as Y" → `link_entities` with `kind: identity_of`
- A typed edge between two known entities (`paid_for`, `mentioned_in`, `worked_with`, …) → `link_entities`
- Something brain previously believed is now known to be wrong → `retract_fact`
- "Before I save this, would it conflict with anything?" → `detect_contradiction`
Do **not** use for:
- Bulk ingest from a vertical's event stream — that's a /v1/ingest path, not an agent loop
- GDPR-grade hard delete → `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 — recording one fact
```ts
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",
})
```
### Picking confidence
The conflict resolver scores each candidate against existing facts using `confidence × CONFLICT_WEIGHT_CONFIDENCE + sourceTrust × CONFLICT_WEIGHT_SOURCE_TRUST + recency + authority`. Confidence is the per-fact axis you control; 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.
### Preflight with detect_contradiction
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:
```ts
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.
## link_entities — declaring a typed edge
```ts
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 merge
`identity_of` is special — it triggers a cascade that merges `from` into `to`:
- Every fact on `from` is reparented to `to`
- `from.mergedAt` + `from.mergedInto` are set so the redirect is auditable
- The conflict resolver runs over the union (so two source-of-truth facts on the same predicate compete properly)
Cycle guards:
- A self-merge (`from == to`) is rejected.
- A merge that would create a cycle (`A → B → A`) is rejected.
- A merge against an already-merged entity (`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".
### Non-merge kinds
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 — walking a belief back
```ts
retract_fact({
factId: "knowledge_fact:01HXYZ...",
reason: "Source misattributed; was Bob, not Alice.",
})
```
What happens:
- The fact's `status` flips to `retracted`, `retractedAt` is set, `retractionReason` recorded.
- The cascade walks `derivedFrom` — any fact derived from this one is also retracted (depth-first).
- If this fact had previously superseded another, the predecessor is REVIVED (`status='active'`, `validUntil` restored to its pre-supersede value). The audit trail keeps the supersede/revive chain.
- Cross-vertical changefeed picks up the change so dependent systems are notified.
The row stays for audit. To actually delete (GDPR), use `forget_entity` — admin scope only.
### Predicate-class admin escalation
Three predicate classes require `brain:admin` for retract, not just `brain:write`:
- `billing_event` — affects downstream invoicing audits
- `human_declared` — represents operator-attested ground truth
- Any fact whose `source.kind === 'legal'` — regulator-visible
If the API key has only `brain:write`, the retract on these falls through wiHow to query the INITE Brain knowledge graph across time — the asOf parameter, validFrom/validUntil semantics, reading retracted facts, and the memory_diff "what changed between two cursors" surface. Use when the user's question has a temporal dimension ("on X date", "before Y", "what's new since last conversation").
How to detect and resolve conflicting beliefs in the INITE Brain knowledge graph — the COMPETING fact status, get_competing_facts, detect_contradiction preflight, and the human-in-the-loop adjudication workflow. Use when the timeline shows two facts disagreeing on the same predicate, or when an agent needs to decide what to record without making the disagreement worse.
Walk a developer through connecting a fresh MCP client (Claude Desktop, Cursor, Goose v2, Aider, Continue.dev, n8n, or a raw @modelcontextprotocol/sdk client) to the INITE Brain service. Covers obtaining an API key, the per-tenant URL shape, config snippets per client, the scope matrix for all 14 brain tools, and the smoke test. Use when the user says "add brain MCP", "connect brain to Claude", "set up brain for Cursor", or names any MCP-capable client.
Recall everything brain knows about one specific entity — current profile, full bitemporal timeline, graph neighbours, and unresolved disagreements. Use when the user names a person/company/thing and asks "tell me about them", "what's their history", or "what do we still disagree about?". For a single-shot LLM briefing without three round-trips, reach for summarize_entity instead.
Semantic search over the INITE Brain knowledge graph via the search_knowledge MCP tool. Use when the user wants to find facts, entities, or evidence about people/companies/objects in their tenant, especially when the question is fuzzy or natural-language. Supports bitemporal "as of" queries.