06-consolidation-decision-tree

Dec 7, 2025

Decision Tree Logic

Phase 3.3 of the Gmail Memory-as-a-Tool system.

Overview

The Decision Tree is the core logic that determines what to do with a new memory extracted from an email:

  1. Compare the new memory against existing memories using similarity search

  2. Decide whether to CREATE, UPDATE, SKIP, or DELETE

  3. Execute the decision and log it for audit

Decision Types

Decision

When

Action

CREATE

No similar memory (score < 0.50) or related (0.50-0.80)

Create new MemoryNode

UPDATE

Similar memory exists (score 0.80-0.95)

Update existing MemoryNode

SKIP

Duplicate detected (score >= 0.95)

Do nothing, log only

DELETE

User/source deletion request

Soft delete MemoryNode

Decision Flow

New Memory (from extraction)
        
        
┌───────────────────┐
Similarity Search Compare against existing memories
└───────────────────┘
        
        
┌───────────────────┐
Get Best Match    Find highest scoring match
└───────────────────┘
        
        
┌───────────────────────────────────────────────────┐
Score Evaluation                   
├───────────────────────────────────────────────────┤
 >= 0.95 (DUPLICATE)  SKIP                       
0.80 - 0.95 (UPDATE) UPDATE existing memory     
0.50 - 0.80 (RELATED) CREATE + link to related  
 < 0.50 (UNRELATED)   CREATE new memory          
└───────────────────────────────────────────────────┘
        
        
┌───────────────────┐
Execute Decision  Write to graph DB
└───────────────────┘
        
        
┌───────────────────┐
Log Decision      Audit trail
└───────────────────┘

Example Scenarios

Scenario 1: CREATE (New Information)

Email:

Subject: Q1 마케팅 캠페인 킥오프
Content: Q1 마케팅 캠페인을 115일에 시작합니다. 예산은 5000만원입니다

Extracted Memory:

{
  "tempId": "temp_001",
  "content": "Q1 마케팅 캠페인: 시작일 1월 15일, 예산 5000만원",
  "category": "project",
  "importance": 0.8
}

Similarity Search Result:

Best match: None (no similar memories)
Score: N/A

Decision:

{
  "decision": "CREATE",
  "reason": "No similar memories found. Creating new memory.",
  "action": {
    "type": "create",
    "memoryData": { ... }
  }
}

Scenario 2: UPDATE (Information Changed)

New Email (follow-up):

Subject: Re: Q1 마케팅 캠페인 킥오프
Content: 예산이 6000만원으로 증액되었습니다

Similarity Search Result:

Best match: mem_001 (Q1 마케팅 캠페인)
Score: 0.87
Category: UPDATE
Factors:
  - content: 0.82 (semantically similar)
  - threadId: 1.0 (same thread)
  - people: 1.0 (same participants)

Decision:

{
  "decision": "UPDATE",
  "targetMemoryId": "mem_001",
  "reason": "High similarity (0.87) with existing memory. Updating with new info.",
  "action": {
    "type": "update",
    "targetId": "mem_001",
    "updates": {
      "content": "Q1 마케팅 캠페인: 시작일 1월 15일, 예산 6000만원 (증액)"
    },
    "createVersion": true
  }
}

Scenario 3: SKIP (Duplicate)

New Email (forwarded):

Subject: Fwd: Q1 마케팅 캠페인 킥오프
Content: (동일한 내용 포워드)

Similarity Search Result:

Best match: mem_001
Score: 0.97
Category: DUPLICATE

Decision:

{
  "decision": "SKIP",
  "targetMemoryId": "mem_001",
  "reason": "Duplicate detected (score: 0.97). Memory already exists.",
  "action": {
    "type": "skip",
    "duplicateOfId": "mem_001",
    "duplicateScore": 0.97
  }
}

Scenario 4: CREATE + LINK (Related but Different)

New Email (different topic, same project):

Subject: Q2 마케팅 예산 논의
Content: Q2 마케팅 예산으로 8000만원을 요청드립니다

Similarity Search Result:

Best match: mem_001 (Q1 마케팅 캠페인)
Score: 0.65
Category: RELATED

Decision:

{
  "decision": "CREATE",
  "reason": "Related content (0.65) but distinct topic. Creating new memory and linking.",
  "action": {
    "type": "create",
    "memoryData": { ... },
    "relatedMemoryIds": ["mem_001"],
    "relationshipType": "related_to"
  }
}

Scenario 5: DELETE (User Request)

User Input:

"Q1 마케팅 캠페인 관련 정보 삭제해줘"

Decision:

{
  "decision": "DELETE",
  "targetMemoryId": "mem_001",
  "reason": "User requested deletion",
  "action": {
    "type": "delete",
    "targetId": "mem_001",
    "trigger": "user_request",
    "softDelete": true,
    "cascadeMode": "preserve_edges"
  }
}

Conflict Resolution

When updating a memory, conflicts may arise if the new data has a lower priority source than the existing data.

Source Priority Order

Priority

Source Type

Example

1 (Highest)

USER_INPUT

User manually entered

2

BOOTSTRAPPED

Initial data import

3

TOOL_OUTPUT

MCP tool result

4 (Lowest)

REALTIME

Live email extraction

Resolution Rules

  1. Higher priority wins: If new data has lower priority, keep existing data

  2. Same priority: Use newer timestamp

  3. Override option: respectSourcePriority: false ignores priority

Example:

// Existing memory (from USER_INPUT, priority 1)
{ content: "예산 5000만원", sourceType: "user_input" }

// New extraction (from REALTIME, priority 4)
{ content: "예산 6000만원", sourceType: "realtime" }

// Result: Keep existing (user input has higher priority)
// Unless explicitly overridden

Merge Strategies

When updating, different fields can be merged differently:

Field

Strategy Options

Default

content

replace, append, keep_existing

replace

keywords

replace, merge, keep_existing

merge

importance

replace, max, keep_existing

max

Type Definitions

See: lib/types/consolidation.ts

Key Types

// Decision types
enum DecisionType {
  CREATE = 'create',
  UPDATE = 'update',
  SKIP = 'skip',
  DELETE = 'delete',
}

// Input context
interface DecisionContext {
  newMemory: NewMemoryInput;
  similarityResults: SimilarityResult[];
  bestMatch?: MemoryComparisonResult;
  trigger: ConsolidationTrigger;
  userId: string;
}

// Output result
interface DecisionResult {
  decision: DecisionType;
  targetMemoryId?: string;
  reason: string;
  action: DecisionAction;
}

Decision Logging

Every decision is logged for audit and debugging. See: lib/types/decision-log.ts

Log Entry Structure

interface DecisionLogEntry {
  id: string;
  timestamp: Date;
  userId: string;
  decision: DecisionType;
  inputMemoryId: string;
  targetMemoryId?: string;
  similarityScore?: number;
  reason: string;
  action: DecisionAction;
  status: 'success' | 'error' | 'skipped' | 'dry_run';
  processingTimeMs: number;
}

Querying Logs

// Find all SKIP decisions for a user
const skippedLogs = await logStorage.query({
  userId: 'user_123',
  decision: DecisionType.SKIP,
  dateRange: { from: lastWeek, to: now },
});

// Get analytics
const analytics = await logStorage.getAnalytics({
  userId: 'user_123',
});
// → { totalEntries: 150, byDecision: { create: 80, update: 45, skip: 20, delete: 5 }, ... }

Related Documentation

  • Similarity Types - Similarity scoring system

  • Conflict Resolution - Priority rules

  • Importance Scoring - How importance is calculated