brain-conflict
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.
git clone --depth 1 https://github.com/inite-ai/inite-brain-service /tmp/brain-conflict && cp -r /tmp/brain-conflict/skills/brain-conflict ~/.claude/skills/brain-conflictSKILL.md
# brain-conflict
Brain's conflict resolver runs on every `record_fact`. Most of the time it picks a winner cleanly — the new fact either INSERTED (no overlap), SUPERSEDED (clearly beats the prior), or REJECTED (too unconfident). When it can't pick — when two facts overlap in valid-time, are too semantically close, and score within margin — they BOTH stay alive in COMPETING status. This skill is how you handle those.
## What COMPETING means
The conflict resolver writes `status='competing'` when:
- The predicate is `bitemporal` (`status`, `intent`, `address`, …) — `single_active` predicates always supersede.
- A prior fact's valid-time interval overlaps the new fact's.
- The two facts are cosine-similar above `CONFLICT_SIMILARITY_THRESHOLD` (semantically close enough that one might be a rephrase of the other).
- The score gap between them is ≤ `CONFLICT_MARGIN_SUPERSEDE` — neither dominates.
The resolver refuses to pick. Both rows stay searchable but are surfaced as a known disagreement. The dreams loop's resolver pass (`/v1/dreams/run`) periodically wakes up and asks an LLM judge to break ties for pairs older than the min-age gate; multi-way conflicts (3+) escape auto-resolution and stay for human adjudication.
## Detecting disagreements — get_competing_facts
```ts
get_competing_facts({
entityId: "knowledge_entity:01HXYZ...",
predicate: undefined, // optional — filter to one predicate
asOf: undefined, // optional — what was competing at this moment
})
```
Returns groups keyed by `(entityId, predicate)`:
```json
{
"entityId": "knowledge_entity:01HXYZ...",
"groups": [
{
"key": "knowledge_entity:01HXYZ...::status",
"predicate": "status",
"facts": [
{ "factId": "...", "object": "active", "recordedAt": "...", "source": ... },
{ "factId": "...", "object": "churned", "recordedAt": "...", "source": ... }
]
}
]
}
```
- A group of **2 facts** is a pair the resolver left for adjudication — the dreams loop will try to break it, or a human will.
- A group of **3+ facts** is a multi-way disagreement the resolver escalated. Dreams resolver won't touch these; they wait for an operator.
`asOf` filters to what was competing at that moment — useful for postmortems ("what was this customer's status disagreement on April 1?").
## Preflight before recording — detect_contradiction
Before you `record_fact` on a contentious predicate, ask brain what would happen:
```ts
detect_contradiction({
entityRef: { vertical: "rent", id: "cust_42" },
predicate: "status",
object: "active",
validFrom: "2026-05-01T00:00:00Z",
confidence: 0.9,
sourceVertical: "rent",
})
```
Returns:
```json
{
"wouldOutcome": "COMPETING",
"reasoning": "bitemporal predicate, strongest similar prior (cosine 0.91) score 0.612 too close to candidate 0.624 (gap 0.012) within margin; both would remain active in COMPETING status.",
"opposingFacts": [
{ "factId": "...", "object": "churned", ... }
],
"predicatePolicy": { "semantics": "bitemporal", "decayHalfLifeDays": 60, "piiClass": "none" }
}
```
The reasoning string names the rule that fired and the numerical gap. Read it before deciding what to do.
## Adjudication workflows
### LLM auto-resolution (dreams loop)
For low-stakes pairs aged past the min-age window, the dreams loop's resolver does this:
1. Pulls competing pairs (`status='competing'` AND age ≥ `DREAMS_RESOLVE_MIN_AGE_DAYS`)
2. Walks neighbouring context facts on the entity
3. Asks an LLM judge to pick `a_wins | b_wins | unsure`
4. On confident verdict, marks the loser superseded; on `unsure`, leaves the pair
This is fully automated and writes through `retract_fact` with `reason: "dreams_resolver:a_wins"` style. The audit row stays — operators can always see "this was resolved by automated judgement".
### Human-in-the-loop (your job as the agent)
For high-stakes pairs or 3+ multi-way groups:
1. `get_competing_facts` to find the disagreement.
2. Present the user the COMPETING pair with timestamps + sources. Phrase it neutrally — "brain has two records for this; which do you want to keep?"
3. On user verdict:
- Loser → `retract_fact({ factId: <loser>, reason: "operator verdict: <text>" })`
- Winner stays as-is. The retract revives the supersede chain if needed.
4. Optional: record a fresh fact reinforcing the user's verdict with `human_declared` source — that source class has the highest trust weight and tilts future conflicts the same way.
### Recording a new fact that touches the disagreement
When the user wants to record something new on a predicate that's already in COMPETING, run `detect_contradiction` first. If the dry-run says the new fact would also land COMPETING, surface that to the user before you write — "this would create a 3-way disagreement; would you rather pick one of the existing values first?"
## What NOT to do
- **Don't auto-pick by recency.** "Newer = right" is the resolver's *recency* weight, not a UI rule. If the resolver decided not to pick, your agent shouldn't override silently.
- **Don't forget to surface the source.** A competing pair where one source is `human_declared` and the other is `inbox_extraction` is almost always resolvable by the user; the disagreement is between a person and an LLM. Surface that.
- **Don't retract one side of a 3-way without picking the winner.** Retracting one of three competing facts leaves a 2-way pair the resolver still can't break. Either retract two (leaving a single active) or convert the survivors to a single `human_declared` fact via record_fact.
- **Don't ignore `asOf` when adjudicating historical conflicts.** Two facts that competed on April 1 may have been resolved by May — call `get_competing_facts({ asOf: "2026-04-01..." })` to see the historical state, not the current one.
## Pitfalls
- **`single_active` predicates never produce COMPETING by overlap alone.** They auto-supersede. If you see COMPETING on a `single_acHow 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").
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.
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).