NewRelease 0.12.3 is out: Engine v0.12.3, Dashboard 0.5.2, Helm 0.12.3, the four SDKs, CLI 0.8.2 and MCP 0.12.3Release 0.12.3 is outSee what's new →
Advanced Lifecycle

Conflict Resolution

⏰ ~30 min to implement 📦 Requires: Dakera v0.11+

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 →
Prerequisites
  • Running Dakera server (Quickstart)
  • An agent ID with existing memories
  • Understanding of update_memory and update_importance SDK 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.

Three Resolution Strategies

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.

Diagram: Conflict Detection — Two Contradictory Memories

Sequence diagram of a write-time conflict check between your app, a contradiction check (an LLM or negation rules) and Dakera. Detect: the app calls recall with min_importance 0.3 and top_k 3 using a query from the new content, gets similar memories back, and asks the contradiction check about each of the top 3 candidates; for one it answers YES. Resolve with recency wins: update_importance drops the old memory to 0.15, update_memory prefixes its content with [SUPERSEDED], store_memory writes the new memory at 0.80, and store_memory adds a conflict-log entry at 0.4 with a 90-day TTL (ttl_seconds 7776000).Sequence diagram of a write-time conflict check between your app, a contradiction check (an LLM or negation rules) and Dakera. Detect: the app calls recall with min_importance 0.3 and top_k 3 using a query from the new content, gets similar memories back, and asks the contradiction check about each of the top 3 candidates; for one it answers YES. Resolve with recency wins: update_importance drops the old memory to 0.15, update_memory prefixes its content with [SUPERSEDED], store_memory writes the new memory at 0.80, and store_memory adds a conflict-log entry at 0.4 with a 90-day TTL (ttl_seconds 7776000).
Recall similar memories, confirm the contradiction, then demote, store and log.Full size ↗Full size ↗

Diagram: Resolution Strategy Decision Tree

Workflow for choosing a resolution strategy. A new memory with a similar match is classified as an update, a contradiction or additive. Additive or historical statements are not conflicts and are stored as usual. A conflict goes to a strategy router that picks by memory category: preferences and mutable facts use recency wins, where the old memory drops to 0.15; an importance gap of 0.15 or more uses confidence wins, where the loser is demoted; high-stakes facts such as address or medical details go to ask user, which falls back to recency after 72 hours in async channels, and the confirmed answer is stored and logged.Workflow for choosing a resolution strategy. A new memory with a similar match is classified as an update, a contradiction or additive. Additive or historical statements are not conflicts and are stored as usual. A conflict goes to a strategy router that picks by memory category: preferences and mutable facts use recency wins, where the old memory drops to 0.15; an importance gap of 0.15 or more uses confidence wins, where the loser is demoted; high-stakes facts such as address or medical details go to ask user, which falls back to recency after 72 hours in async channels, and the confirmed answer is stored and logged.
The memory category decides the strategy; only high-stakes facts wait for the user.Full size ↗Full size ↗

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

  1. Define your conflict detection scope
    Not 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"].
  2. Recall similar memories before storing
    Before every store_memory on a conflict-eligible type, call recall with a semantic query derived from the new content. Use min_importance=0.3 to catch even partially-decayed contradictions, and top_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.
  3. Classify the relationship
    A 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.
  4. Apply the resolution strategy
    Preferences: 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.
  5. Update winning memory and demote loser
    Call update_importance on the losing memory to drop it to 0.15, and update_memory to prefix its content with [SUPERSEDED]. Store the winner, or call update_memory on 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.
  6. Store a conflict audit entry
    After 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 by min_importance=0.5).

Before & After: Resolving a Preference Conflict

Before — two contradictory memories
[
  {
    "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
After — resolved (recency wins)
[
  {
    "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.

Start Building →

SDK Reference

MethodSDKPurpose
recall(agent_id, query, min_importance, top_k)PythonRetrieve similar memories to detect conflicts before storing
recall(agentId, query, {min_importance, top_k})TypeScriptRetrieve similar memories to detect conflicts before storing
update_memory(agent_id, memory_id, content)PythonMark the loser superseded, or revise the winner
updateMemory(agentId, memoryId, {content})TypeScriptMark the loser superseded, or revise the winner
update_importance(agent_id, memory_ids, importance)PythonDemote the losing memory
updateImportance(agentId, {memory_ids, importance})TypeScriptDemote the losing memory
store_memory(agent_id, content, importance, tags, ttl_seconds)PythonStore winning memory and TTL-limited audit log
storeMemory(agentId, {content, importance, tags, ttl_seconds})TypeScriptStore winning memory and TTL-limited audit log

Performance Considerations

2
Dakera calls on a write with no conflict (recall, store)
5
Dakera calls when recency wins (recall, two updates, two stores)
90 days
TTL on conflict-log entries (ttl_seconds=7776000)

Edge Cases

Edge Case 1: False Positive — Historical vs. Current Statements

"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.

Edge Case 2: Conflict Chains — Three or More Overlapping Memories

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.

Edge Case 3: Multi-Agent Race Condition on Shared Namespace

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).

Edge Case 4: Ask-User in Async Channels

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.

Edge Case 5: Genuine Ambiguity (User Has Not Decided)

"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

CategoryTagsDefault StrategyOverride Condition
UI preferencespreference, uiRecency winsNever override
Diet / lifestylepreference, lifestyleRecency winsNever override
Employmentfact, careerRecency winsConfidence if new importance is lower by 0.15 or more
Home addressfact, addressAsk user alwaysNever skip
Medical historyfact, medicalAsk user alwaysNever skip
Technical skillsfact, skillConfidence winsAdditive 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.

Try it live

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.