Conflict Resolution
Handle contradictory memories correctly when users change preferences, correct past information, or update facts. Choose between recency-wins, confidence-wins, and explicit user confirmation strategies — or combine all three based on the memory category.
Start Free →- Running Dakera server (Quickstart)
- An agent ID with existing memories
- Understanding of
update_memoryandupdate_importanceSDK methods
The Problem: Contradictory Memories Erode User Trust
Real users change. "I prefer dark mode" becomes "actually I switched back to light mode." "I work at Acme Corp" becomes "I moved to BuildCo in January." A diet preference changes. A home city is updated. A phone number is corrected.
Without conflict resolution, your agent stores both the old and new facts. A recall query returns both — and the agent confidently uses the wrong one. Users who see their AI reference an outdated preference lose trust immediately. Worse, the agent does not know it is wrong. The solution is to detect contradictions at write time and resolve them deterministically, not probabilistically.
Recency wins: The most recently stored memory on a topic wins automatically. Best for preferences and mutable facts. Confidence wins: The memory with the higher importance score wins. Best when explicit corrections should override incidental mentions. Ask user: Surface the conflict to the user and request confirmation. Best for high-stakes facts like home address, medical information, and financial details.
Architecture
Conflict detection happens at write time, in your application. When storing a new preference or factual memory, it recalls the most similar existing memories with recall and asks a contradiction check (an LLM call or negation rules) about each one. If a candidate contradicts the new memory, a conflict is flagged and the resolution strategy is applied before the write completes. Recall scores blend relevance, importance and recency (smart_score), so they rank candidates; they are not a raw similarity to cut on.
Dakera does part of this on its own: when a newer memory is very similar to an older one (cosine similarity of at least 0.92) and shares an entity tag with it, the knowledge graph links them with a supersedes edge and recall demotes the outdated fact. That needs entity extraction on the namespace and does not catch contradictions phrased differently, which is why this pattern resolves them explicitly.
- Detect: Before storing, recall similar memories and check for semantic contradiction
- Classify: Determine if the new memory updates, supersedes, or merely extends the existing one
- Resolve: Apply your chosen strategy: recency-wins, confidence-wins, or ask-user
- Update: Use
update_importanceto demote the loser andupdate_memoryto mark it superseded; store the winner (or revise it withupdate_memorywhen it already exists) - Audit: Store a conflict log entry at low importance for traceability without polluting recall
Diagram: Conflict Detection — Two Contradictory Memories
Diagram: Resolution Strategy Decision Tree
Real-World Scenario: Personal Assistant Tracking Updated Preferences
Scenario: Clara AI builds a personal assistant that tracks user preferences across hundreds of interactions. User Sofia says "I'm vegetarian" in January. In April, Sofia mentions "I actually eat fish now." Without conflict resolution, Clara has two contradictory diet memories and the next restaurant recommendation may be wrong.
Clara implements write-time conflict detection: before storing any preference memory, it recalls the top 3 similar memories and checks each for a contradiction. If one contradicts the new preference, it applies recency-wins: the new preference becomes active at importance 0.80, and the old one drops to 0.15 and keeps decaying from there, so it no longer competes in recall. A conflict audit entry is stored at importance 0.4 for traceability.
Result: 94% of user preference updates are resolved automatically. Only 6% — high-stakes facts like home address or medical conditions — escalate to explicit user confirmation. User trust scores improved by 28% after deployment.
Step-by-Step Implementation
-
Define your conflict detection scopeNot all memories can conflict. Only mutable facts and preferences can be contradicted. Immutable facts (birth year, historical events) and raw episodic memories (specific conversation turns) should bypass conflict detection. Define a list of memory tags eligible for conflict checking:
["preference", "fact", "profile"]. -
Recall similar memories before storingBefore every
store_memoryon a conflict-eligible type, callrecallwith a semantic query derived from the new content. Usemin_importance=0.3to catch even partially-decayed contradictions, andtop_k=3. Pass every candidate to the contradiction check; if you also want a score cut, calibrate it on your own data, because recall scores mix relevance with importance and recency. -
Classify the relationshipA similar memory may be an update ("I moved to Berlin" — same topic, new value), a contradiction ("I like coffee" vs. "I don't drink coffee"), or additive ("I like coffee" + "I also like tea" — no conflict). Use a lightweight LLM call or negation-pattern matching to distinguish these cases before triggering resolution.
-
Apply the resolution strategyPreferences: recency-wins automatically. High-stakes facts (address, medical): ask-user. Importance-discrepant memories: confidence-wins. Document which strategy applies to which memory category and enforce it in a strategy router function — never decide strategy ad-hoc at the call site.
-
Update winning memory and demote loserCall
update_importanceon the losing memory to drop it to 0.15, andupdate_memoryto prefix its content with[SUPERSEDED]. Store the winner, or callupdate_memoryon it if it already exists and its content needs updating. The loser keeps decaying from 0.15; decay lowers importance toward a floor of 0.01 but never deletes. Never hard-delete the losing memory immediately — it may be needed for audit or recovery within the next 30 days. -
Store a conflict audit entryAfter resolution, store a brief audit memory tagged with
"conflict-log"at importance 0.4 with a 90-day TTL. This allows you to trace the history of any fact change without surfacing it in normal recall (filtered out bymin_importance=0.5).
Before & After: Resolving a Preference Conflict
[
{
"id": "m-a1",
"content": "User prefers light mode",
"importance": 0.75,
"memory_type": "semantic",
"tags": ["preference", "ui"],
"created_at": "2026-01-15T10:00:00Z"
},
{
"id": "m-b1",
"content": "User switched back to dark mode",
"importance": 0.70,
"memory_type": "semantic",
"tags": ["preference", "ui"],
"created_at": "2026-05-20T14:22:00Z"
}
]
// recall("UI theme preference") returns BOTH
// Agent uses wrong memory ~50% of the time
// Users report "it keeps forgetting" — trust erodes
[
{
"id": "m-b1",
"content": "User prefers dark mode",
"importance": 0.80,
"tags": ["preference", "ui"],
"created_at": "2026-05-20T14:22:00Z"
},
{
"id": "m-a1",
"content": "[SUPERSEDED] User preferred light mode",
"importance": 0.15,
"tags": ["preference", "ui"],
"created_at": "2026-01-15T10:00:00Z"
},
{
"id": "m-log-1",
"content": "CONFLICT LOG: light->dark mode, recency, 2026-05-20",
"importance": 0.4,
"tags": ["conflict-log"],
"ttl_seconds": 7776000
}
]
// recall("UI theme", min_importance=0.5) returns m-b1 only
// Correct preference on every recommendation
Implementation
# 1. Recall similar memories before storing the new preference
curl -X POST http://localhost:3000/v1/memory/recall \
-H "Authorization: Bearer dk-..." \
-H "Content-Type: application/json" \
-d '{"agent_id": "assistant", "query": "UI theme display mode", "min_importance": 0.3, "top_k": 3}'
# 2. Your contradiction check says m-a1 conflicts: demote the old memory
curl -X PATCH http://localhost:3000/v1/memories/m-a1/importance \
-H "Authorization: Bearer dk-..." \
-H "Content-Type: application/json" \
-d '{"agent_id": "assistant", "importance": 0.15}'
# 3. Mark the old memory as superseded
curl -X PUT "http://localhost:3000/v1/memory/update/m-a1?agent_id=assistant" \
-H "Authorization: Bearer dk-..." \
-H "Content-Type: application/json" \
-d '{"content": "[SUPERSEDED] User preferred light mode"}'
# 4. Store the new winning memory
curl -X POST http://localhost:3000/v1/memory/store \
-H "Authorization: Bearer dk-..." \
-H "Content-Type: application/json" \
-d '{
"agent_id": "assistant",
"content": "User prefers dark mode (updated May 2026)",
"importance": 0.80,
"memory_type": "semantic",
"tags": ["preference", "ui"]
}'
# 5. Store a conflict audit entry (90-day TTL)
curl -X POST http://localhost:3000/v1/memory/store \
-H "Authorization: Bearer dk-..." \
-H "Content-Type: application/json" \
-d '{
"agent_id": "assistant",
"content": "CONFLICT LOG: light-mode -> dark-mode, recency strategy, 2026-05-20",
"importance": 0.4,
"memory_type": "episodic",
"tags": ["conflict-log"],
"ttl_seconds": 7776000
}'from dakera import DakeraClient
from datetime import datetime
import openai
client = DakeraClient(base_url="http://localhost:3000", api_key="dk-...")
oai = openai.OpenAI()
AGENT = "personal-assistant"
CONFLICT_ELIGIBLE_TAGS = {"preference", "fact", "profile"}
HIGH_STAKES_TAGS = {"medical", "address", "financial"}
def detect_contradiction(existing: str, new: str) -> bool:
"""LLM-based contradiction check (application logic, not a Dakera call)."""
resp = oai.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user",
"content": f"Do these two statements contradict? YES or NO.\nA: {existing}\nB: {new}"}]
)
return resp.choices[0].message.content.strip().upper() == "YES"
def resolve_conflict(existing_mem, new_content: str, new_importance: float, new_tags: list):
"""Apply recency-wins, or escalate to ask-user for high-stakes facts."""
if HIGH_STAKES_TAGS.intersection(new_tags):
return {"action": "ask-user", "existing": existing_mem.content, "proposed": new_content}
# Recency wins: demote the loser and mark it superseded
client.update_importance(
agent_id=AGENT,
memory_ids=[existing_mem.id],
importance=0.15
)
client.update_memory(
agent_id=AGENT,
memory_id=existing_mem.id,
content=f"[SUPERSEDED] {existing_mem.content}"
)
# Store the new winner
client.store_memory(
agent_id=AGENT,
content=new_content,
importance=new_importance,
memory_type="semantic",
tags=new_tags
)
# Audit entry (90-day TTL)
client.store_memory(
agent_id=AGENT,
content=f"CONFLICT LOG: '{existing_mem.content[:50]}' -> '{new_content[:50]}', recency, {datetime.now().date()}",
importance=0.4,
memory_type="episodic",
tags=["conflict-log"],
ttl_seconds=7776000
)
return {"action": "resolved-recency"}
def store_with_conflict_check(content: str, importance: float, tags: list, memory_type: str = "semantic"):
"""Write-time conflict detection and resolution."""
if not CONFLICT_ELIGIBLE_TAGS.intersection(tags):
return client.store_memory(agent_id=AGENT, content=content,
importance=importance, memory_type=memory_type, tags=tags)
# Recall scores rank candidates; the contradiction check makes the decision
candidates = client.recall(agent_id=AGENT, query=content, min_importance=0.3, top_k=3)
for mem in candidates.memories:
if detect_contradiction(mem.content, content):
return resolve_conflict(mem, content, importance, tags)
return client.store_memory(agent_id=AGENT, content=content,
importance=importance, memory_type=memory_type, tags=tags)
# Usage: a preference update triggers conflict detection
store_with_conflict_check(
content="User prefers dark mode (confirmed May 2026)",
importance=0.80,
tags=["preference", "ui"]
)import { DakeraClient } from '@dakera-ai/dakera';
import OpenAI from 'openai';
const client = new DakeraClient({ baseUrl: 'http://localhost:3000', apiKey: 'dk-...' });
const oai = new OpenAI();
const AGENT = 'personal-assistant';
const CONFLICT_ELIGIBLE = new Set(['preference', 'fact', 'profile']);
const HIGH_STAKES = new Set(['medical', 'address', 'financial']);
// Application logic: Dakera does not judge contradictions
async function detectContradiction(a: string, b: string): Promise<boolean> {
const r = await oai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: `Contradict? YES or NO.\nA: ${a}\nB: ${b}` }]
});
return r.choices[0].message.content?.trim().toUpperCase() === 'YES';
}
async function storeWithConflictCheck(
content: string, importance: number, tags: string[], memory_type: 'episodic' | 'semantic' = 'semantic'
) {
if (!tags.some(t => CONFLICT_ELIGIBLE.has(t))) {
return client.storeMemory(AGENT, { content, importance, memory_type, tags });
}
const candidates = await client.recall(AGENT, content, { min_importance: 0.3, top_k: 3 });
for (const mem of candidates.memories) {
if (!(await detectContradiction(mem.content, content))) continue;
if (tags.some(t => HIGH_STAKES.has(t))) {
return { action: 'ask-user', existing: mem.content, proposed: content };
}
// Recency wins: demote and mark the loser, store the winner, log it
await client.updateImportance(AGENT, { memory_ids: [mem.id], importance: 0.15 });
await client.updateMemory(AGENT, mem.id, { content: `[SUPERSEDED] ${mem.content}` });
await client.storeMemory(AGENT, { content, importance, memory_type, tags });
await client.storeMemory(AGENT, {
content: `CONFLICT LOG: '${mem.content.slice(0, 50)}' -> '${content.slice(0, 50)}', recency, ${new Date().toISOString().split('T')[0]}`,
importance: 0.4, memory_type: 'episodic', tags: ['conflict-log'], ttl_seconds: 7776000
});
return { action: 'resolved-recency' };
}
return client.storeMemory(AGENT, { content, importance, memory_type, tags });
}
// Usage
await storeWithConflictCheck('User prefers dark mode (May 2026)', 0.80, ['preference', 'ui']);use dakera_client::memory::{UpdateImportanceRequest, UpdateMemoryRequest};
use dakera_client::{DakeraClient, MemoryType, RecallRequest, StoreMemoryRequest};
let client = DakeraClient::builder("http://localhost:3000").api_key("dk-...").build()?;
let agent = "personal-assistant";
let new_content = "User prefers dark mode (May 2026)";
// 1. Recall similar memories as conflict candidates
let candidates = client.recall(
RecallRequest::new(agent, "UI theme display mode preference")
.with_min_importance(0.3)
.with_top_k(3),
).await?;
// 2. Your contradiction check decides; recall scores only rank the candidates
for mem in &candidates.memories {
if !is_contradiction(&mem.content, new_content) {
continue;
}
// Demote the loser and mark it superseded
client.update_importance(agent, UpdateImportanceRequest {
memory_ids: vec![mem.id.clone()],
importance: 0.15,
}).await?;
client.update_memory(agent, &mem.id, UpdateMemoryRequest {
content: Some(format!("[SUPERSEDED] {}", mem.content)),
..Default::default()
}).await?;
// Store the new winner
client.store_memory(
StoreMemoryRequest::new(agent, new_content)
.with_type(MemoryType::Semantic)
.with_importance(0.80)
.with_tags(vec!["preference".into(), "ui".into()]),
).await?;
// Audit entry with a 90-day TTL
client.store_memory(
StoreMemoryRequest::new(agent, "CONFLICT LOG: light-mode -> dark-mode, recency, 2026-05-20")
.with_type(MemoryType::Episodic)
.with_importance(0.4)
.with_tags(vec!["conflict-log".into()])
.with_ttl(7_776_000),
).await?;
break;
}package main
import (
"context"
"fmt"
"time"
dakera "github.com/dakera-ai/dakera-go"
)
func storeWithConflictCheck(ctx context.Context, client *dakera.Client, agent, content string, importance float32, tags []string) error {
minImp := float32(0.3)
candidates, err := client.Recall(ctx, agent, dakera.RecallRequest{
Query: content, MinImportance: &minImp, TopK: 3,
})
if err != nil {
return err
}
for _, mem := range candidates.Memories {
// Your contradiction check decides; recall scores only rank the candidates
if !isContradiction(mem.Content, content) {
continue
}
if isHighStakes(tags) {
fmt.Printf("ASK USER: '%s' vs '%s'\n", mem.Content, content)
return nil
}
// Demote the loser and mark it superseded
if err := client.UpdateImportance(ctx, agent, dakera.UpdateImportanceRequest{
MemoryIDs: []string{mem.ID}, Importance: 0.15,
}); err != nil {
return err
}
superseded := "[SUPERSEDED] " + mem.Content
if _, err := client.UpdateMemory(ctx, agent, mem.ID, dakera.UpdateMemoryRequest{Content: &superseded}); err != nil {
return err
}
// Store the winner
if _, err := client.StoreMemory(ctx, agent, dakera.StoreMemoryRequest{
Content: content, Importance: &importance, MemoryType: "semantic", Tags: tags,
}); err != nil {
return err
}
// Audit entry with a 90-day TTL
logImp, ttl := float32(0.4), 7776000
_, err := client.StoreMemory(ctx, agent, dakera.StoreMemoryRequest{
Content: fmt.Sprintf("CONFLICT LOG: recency resolved %s", time.Now().Format("2006-01-02")),
Importance: &logImp, MemoryType: "episodic",
Tags: []string{"conflict-log"}, TTLSeconds: &ttl,
})
return err
}
_, err = client.StoreMemory(ctx, agent, dakera.StoreMemoryRequest{
Content: content, Importance: &importance, MemoryType: "semantic", Tags: tags,
})
return err
}One current value per preference
A recall, a contradiction check and two updates remove the most damaging class of memory bugs.
SDK Reference
| Method | SDK | Purpose |
|---|---|---|
recall(agent_id, query, min_importance, top_k) | Python | Retrieve similar memories to detect conflicts before storing |
recall(agentId, query, {min_importance, top_k}) | TypeScript | Retrieve similar memories to detect conflicts before storing |
update_memory(agent_id, memory_id, content) | Python | Mark the loser superseded, or revise the winner |
updateMemory(agentId, memoryId, {content}) | TypeScript | Mark the loser superseded, or revise the winner |
update_importance(agent_id, memory_ids, importance) | Python | Demote the losing memory |
updateImportance(agentId, {memory_ids, importance}) | TypeScript | Demote the losing memory |
store_memory(agent_id, content, importance, tags, ttl_seconds) | Python | Store winning memory and TTL-limited audit log |
storeMemory(agentId, {content, importance, tags, ttl_seconds}) | TypeScript | Store winning memory and TTL-limited audit log |
Performance Considerations
ttl_seconds=7776000)- LLM contradiction detection is the bottleneck. Each check adds a model round trip to every conflict-eligible write. For high-volume systems, replace it with a local negation-detection model or a rules-based approach (keyword negation: "not", "no longer", "switched away from").
- Cache recent recall results. If storing multiple memories in rapid succession (e.g., at session start), the recall step returns the same candidates repeatedly. Cache results keyed by semantic query hash for 10 seconds at the application layer to avoid redundant Dakera calls.
- Conflict log TTL prevents unbounded accumulation. With a 90-day TTL (
ttl_seconds=7776000) on audit entries, the conflict log stays bounded. Without TTL, a busy personal assistant agent accumulates thousands of log entries in a year, adding noise at low-importance recall thresholds.
Edge Cases
"I used to hate dark mode" and "I prefer dark mode" share high similarity but are not contradictions — the first is historical, the second is current. Always include temporal marker detection: statements containing "used to", "previously", "back when I", or "before" should be classified as historical context, not conflicting facts, and bypass the conflict resolver.
A user may have a chain: light mode (Jan) → dark mode (Jun) → light mode again (Feb next year). If the resolver only checks the single highest-similarity match, it may resolve against the June memory and miss the January one. Process all candidates in descending importance order, demoting each loser before storing the winner.
Two agents simultaneously writing conflicting facts both pass the recall check before either write completes. Both write "wins." Dakera updates have no compare-and-set, so serialize conflict-resolution writes per agent in your application, for example with a distributed lock (Redis, database advisory lock).
When ask-user triggers in an async channel (email, Slack webhook), the user's response arrives minutes to hours later. Store a pending conflict memory tagged ["conflict-pending"] at importance 0.6. When the response arrives, find it with a batch recall filtered on that tag, apply the confirmed resolution, and forget the pending marker. Add a 72-hour timeout that defaults to recency-wins if the user does not respond.
"I'm not sure if I prefer Python or Go for this project" is not a conflict — it is genuine ambiguity. Train your contradiction classifier to distinguish contradiction from uncertainty. Ambiguous states should be stored as a single "user is evaluating X vs Y" memory. Tag it ["ambiguous"] to suppress future conflict detection on that topic until the user resolves it explicitly.
Advanced Configuration: Strategy Matrix & Confidence Scoring
Strategy Selection by Memory Category
| Category | Tags | Default Strategy | Override Condition |
|---|---|---|---|
| UI preferences | preference, ui | Recency wins | Never override |
| Diet / lifestyle | preference, lifestyle | Recency wins | Never override |
| Employment | fact, career | Recency wins | Confidence if new importance is lower by 0.15 or more |
| Home address | fact, address | Ask user always | Never skip |
| Medical history | fact, medical | Ask user always | Never skip |
| Technical skills | fact, skill | Confidence wins | Additive if >30% semantically different |
Confidence-Wins Threshold
Only apply confidence-wins when importance scores diverge by at least 0.15 (existing=0.9 vs new=0.6 means existing wins; existing=0.6 vs new=0.9 means new wins). When the difference is less than 0.15, fall back to recency-wins to avoid over-riding genuinely uncertain cases.
Run this pattern against a real Dakera.
The Playground executes live API calls in your browser. No account, no install. Then run it on your own server in five minutes.