Skip to content

Relationship Operations

The /associate endpoint creates typed relationships between memories to build a knowledge graph in FalkorDB. These associations enable graph traversal during recall, allowing multi-hop reasoning and discovery of related context beyond direct semantic similarity.

Unlike semantic similarity (which is computed automatically via vector embeddings), associations capture explicit logical relationships such as causation (LEADS_TO), evolution (EVOLVED_INTO), or contradiction (CONTRADICTS).

Associations serve two primary purposes:

  1. Structural Knowledge Graph — Organize memories into a queryable graph with semantic meaning
  2. Enhanced Recall — Surface related memories during graph expansion that semantic search alone would miss

POST /associate
Content-Type: application/json
Authorization: Bearer <AUTOMEM_API_TOKEN>

Key characteristics:

  • Creates directed edges (memory1 → memory2)
  • Requires both memories to exist in FalkorDB
  • Stores relationships as graph edges, not separate nodes
  • Accepts 11 authorable semantic relationship types
  • Optional strength score (0.0–1.0) for weighting connections
  • System-generated relations (SIMILAR_TO, PRECEDED_BY) can appear in recall and graph results but are not valid inputs to this endpoint; DISCOVERED is system-internal (public_visible=False) — excluded from recall results and the /graph/relations type list, but may still appear in raw graph traversal responses (/graph/snapshot, /graph/neighbors)

FieldTypeRequiredDescription
memory1_idstringYesSource memory UUID
memory2_idstringYesTarget memory UUID
typestringNoRelationship type (must be in AUTHORABLE_RELATIONS; default: RELATES_TO)
strengthfloatNoEdge weight 0.0–1.0 (default: 0.5)
Terminal window
curl -X POST https://your-automem-instance/associate \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"memory1_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"memory2_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"type": "LEADS_TO",
"strength": 0.9
}'

The /associate endpoint also accepts a batch body that creates many relationships in a single request. Send an associations array instead of the single-pair fields; each item uses the same memory1_id / memory2_id / type / strength shape. A maximum of 500 associations is allowed per request.

Terminal window
curl -X POST https://your-automem-instance/associate \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"associations": [
{
"memory1_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"memory2_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"type": "LEADS_TO",
"strength": 0.9
},
{
"memory1_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"memory2_id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"type": "RELATES_TO",
"strength": 0.8
}
]
}'
FieldTypeDescription
statusstringsuccess when every item succeeds, partial_success when one or more fail
created_countintegerNumber of associations created
failed_countintegerNumber of associations that failed
succeededarrayThe associations that were created
failedarrayThe associations that failed, with their errors
summarystringHuman-readable count, e.g. "C/M associations created successfully"

Batch Response (201 Created — all items succeeded)

Section titled “Batch Response (201 Created — all items succeeded)”
{
"status": "success",
"created_count": 2,
"failed_count": 0,
"succeeded": [ /* ... */ ],
"failed": [],
"summary": "2/2 associations created successfully"
}

Batch Response (207 Multi-Status — some items failed)

Section titled “Batch Response (207 Multi-Status — some items failed)”
{
"status": "partial_success",
"created_count": 1,
"failed_count": 1,
"succeeded": [ /* ... */ ],
"failed": [ /* items that failed validation or referenced missing memories */ ],
"summary": "1/2 associations created successfully"
}

AutoMem lets you create 11 authorable semantic relationship types directly. The graph may also contain 3 system-generated types created by enrichment and consolidation.

graph TB
    subgraph "Causal & Temporal"
        M1["Memory: Problem"]
        M2["Memory: Solution"]
        M3["Memory: Planning"]
        M4["Memory: Execution"]

        M1 -->|LEADS_TO| M2
        M3 -->|OCCURRED_BEFORE| M4
    end

    subgraph "Preference & Pattern"
        M5["Memory: PostgreSQL Decision"]
        M6["Memory: MongoDB Evaluation"]
        M7["Memory: Code Review"]
        M8["Memory: Best Practice"]

        M5 -->|PREFERS_OVER| M6
        M7 -->|EXEMPLIFIES| M8
    end

    subgraph "Knowledge Evolution"
        M9["Memory: Initial Design"]
        M10["Memory: Final Design"]
        M11["Memory: Old Docs"]
        M12["Memory: New Docs"]

        M9 -->|EVOLVED_INTO| M10
        M11 -->|INVALIDATED_BY| M12
    end

    subgraph "Hierarchical"
        M13["Memory: User Story"]
        M14["Memory: Epic"]
        M15["Memory: Feature Spec"]
        M16["Memory: Implementation"]

        M13 -->|PART_OF| M14
        M16 -->|DERIVED_FROM| M15
    end
TypeDirectionUse CaseExample
RELATES_TOBidirectionalGeneral connection, default choiceBug report → Related issue
LEADS_TOA → B (Causal)Cause → Effect, A caused/resulted in BProblem analysis → Solution implemented
OCCURRED_BEFOREA → B (Temporal)Earlier → Later (without causation)Planning meeting → Execution sprint
PREFERS_OVERA → B (Preference)Chosen → RejectedPostgreSQL → MongoDB
EXEMPLIFIESB → A (Pattern)Concrete example → Abstract patternCode review feedback → Best practice rule
CONTRADICTSBidirectionalConflicting informationAlternative approach A ↔ Alternative approach B
REINFORCESB → A (Supporting)Additional evidence strengthening originalPerformance test → Design decision
INVALIDATED_BYA → B (Superseding)Outdated → CurrentOld documentation → Updated docs
EVOLVED_INTOA → B (Evolution)Initial → Final formDraft design → Shipping implementation
DERIVED_FROMB → A (Source)Implementation → SpecFeature code → Requirements document
PART_OFB → A (Hierarchical)Component → ContainerUser story → Epic
SIMILAR_TOBidirectionalSemantically similar memories (auto-created)Two related bug reports
PRECEDED_BYA → B (Temporal)Temporal predecessor (auto-created)Current sprint → Previous sprint
DISCOVEREDVariesSystem-internal heuristic edge created by consolidation (public_visible=False — excluded from recall results and the /graph/relations type list; may still appear in /graph/snapshot and /graph/neighbors responses; has kind property)Root cause → Observed symptom

The /associate endpoint enforces the following validation (the flow below shows the order the checks run in):

  1. Memory Existence: Both memory1_id and memory2_id must reference existing Memory nodes in FalkorDB
  2. Relationship Type: type must be one of the 11 authorable relation types in AUTHORABLE_RELATIONS
  3. Strength Bounds: If provided, strength must be between 0.0 and 1.0 (inclusive)
  4. Required Fields: memory1_id and memory2_id are mandatory; type is optional and defaults to RELATES_TO when omitted
  5. Authentication: Valid AUTOMEM_API_TOKEN required via Authorization header

Single-pair checks run in the order shown, and the first failure ends the request. strength is checked before the IDs, and FalkorDB availability is checked before the memories are looked up.

flowchart TD
    Request["POST /associate Request"]
    AuthCheck{"Valid API Token?"}
    BodyCheck{"JSON object body?"}
    StrengthCheck{"strength a number in 0.0-1.0?"}
    FieldCheck{"Required Fields Present?"}
    UuidCheck{"Both IDs valid UUIDs?"}
    SelfCheck{"memory1_id == memory2_id?"}
    TypeCheck{"type in AUTHORABLE_RELATIONS?"}
    GraphCheck{"FalkorDB available?"}
    CreateEdge["Run MATCH and MERGE query"]
    MemoryCheck{"Both memories matched?"}
    Success["Return 201 Success"]

    Err401["401 Unauthorized"]
    Err400Body["400 JSON body is required"]
    Err400Strength["400 Invalid Strength"]
    Err400Field["400 Missing Field"]
    Err400Uuid["400 Invalid UUID"]
    Err400Self["400 Cannot self-associate"]
    Err400Type["400 Invalid Type"]
    Err503["503 FalkorDB Unavailable"]
    Err500["500 Failed to create association"]
    Err404["404 One or both memories do not exist"]

    Request --> AuthCheck
    AuthCheck -->|No| Err401
    AuthCheck -->|Yes| BodyCheck
    BodyCheck -->|No| Err400Body
    BodyCheck -->|Yes| StrengthCheck
    StrengthCheck -->|No| Err400Strength
    StrengthCheck -->|Yes| FieldCheck
    FieldCheck -->|No| Err400Field
    FieldCheck -->|Yes| UuidCheck
    UuidCheck -->|No| Err400Uuid
    UuidCheck -->|Yes| SelfCheck
    SelfCheck -->|Yes| Err400Self
    SelfCheck -->|No| TypeCheck
    TypeCheck -->|No| Err400Type
    TypeCheck -->|Yes| GraphCheck
    GraphCheck -->|No| Err503
    GraphCheck -->|Yes| CreateEdge
    CreateEdge -->|Query error| Err500
    CreateEdge -->|Query ran| MemoryCheck
    MemoryCheck -->|No| Err404
    MemoryCheck -->|Yes| Success

{
"status": "success",
"message": "Association created between a1b2c3d4-e5f6-7890-abcd-ef1234567890 and b2c3d4e5-f6a7-8901-bcde-f12345678901",
"relation_type": "LEADS_TO",
"strength": 0.9
}

Errors from abort() use the shared envelope, {"status": "error", "code": <http status>, "message": <description>}.

404 Not Found — One or both memories don’t exist:

{
"status": "error",
"code": 404,
"message": "One or both memories do not exist"
}

400 Bad Request — Self-association attempt:

{
"status": "error",
"code": 400,
"message": "Cannot associate a memory with itself"
}

400 Bad Request — Invalid relationship type (the message lists the sorted authorable types):

{
"status": "error",
"code": 400,
"message": "Relation type must be one of ['CONTRADICTS', 'DERIVED_FROM', 'EVOLVED_INTO', ...]"
}

400 Bad Request — Missing required fields:

{
"status": "error",
"code": 400,
"message": "'memory1_id' and 'memory2_id' are required"
}

400 Bad Request — strength is not a number or is outside 0.0–1.0 (the route reuses the memory importance validator, so the message names importance):

{
"status": "error",
"code": 400,
"message": "'importance' must be between 0 and 1"
}

A non-numeric strength returns "'importance' must be a number" instead.

400 Bad Request — memory1_id or memory2_id is not a valid UUID:

{
"status": "error",
"code": 400,
"message": "memory_id must be a valid UUID"
}

503 Service Unavailable — FalkorDB unavailable:

{
"status": "error",
"code": 503,
"message": "FalkorDB is unavailable"
}

Relationships are stored as edges in FalkorDB, not as separate nodes. This enables efficient graph traversal queries.

The endpoint executes a Cypher MERGE query to create or update the relationship:

MATCH (m1:Memory {id: $id1})
MATCH (m2:Memory {id: $id2})
MERGE (m1)-[r:LEADS_TO]->(m2)
SET r.strength = $strength,
r.updated_at = $updated_at
graph LR
    M1["Memory Node<br/>id: mem-1<br/>content: 'PostgreSQL'<br/>type: Decision"]
    M2["Memory Node<br/>id: mem-2<br/>content: 'MongoDB'<br/>type: Decision"]

    M1 -->|"PREFERS_OVER<br/>strength: 0.9<br/>reason: 'ACID'<br/>updated_at: '2025-01-15'"| M2
AspectImplementation
Storage LayerFalkorDB only (not replicated to Qdrant)
Edge DirectionDirected (m1 → m2)
Edge LabelRelationship type (e.g., :PREFERS_OVER)
Edge Propertiesstrength, updated_at, plus the relation-specific properties for that type (for example reason on PREFERS_OVER), read from top-level request fields
IndexingFalkorDB automatically indexes edge types for traversal
Query CostO(1) for direct edge lookup, O(k) for k-hop traversal

Relationships created via /associate are utilized during recall operations when expand_relations=true is specified. See Recall Operations for the full parameter reference.

ParameterDefaultDescription
expand_relationsfalseEnable graph traversal from seed results
relation_limit5Max edges to follow per seed memory
expansion_limit25Total max expanded memories to return
expand_min_strength0.0Minimum edge strength to traverse
expand_min_importance0.0Minimum importance of expanded memories

Relationship strength contributes to the hybrid scoring formula:

relation_score = Σ (edge.strength × related_memory.importance)
final_score = vector_score × 0.35
+ keyword_score × 0.35
+ relation_score × 0.25 ← Relationship contribution
+ ... (6 other components)

Relationships enable AutoMem to answer queries requiring multiple traversal steps.

Example: “What is Sarah’s sister’s job?”

Section titled “Example: “What is Sarah’s sister’s job?””

Step 1: Store memories with relationships:

// Memory 1
{ "content": "Sarah's sister is Rachel", "tags": ["family", "sarah", "rachel"] }
// Memory 2
{ "content": "Rachel is a software engineer at Tech Corp", "tags": ["career", "rachel"] }

Create association:

{
"memory1_id": "<id of memory 1>",
"memory2_id": "<id of memory 2>",
"type": "RELATES_TO",
"strength": 0.95
}

Step 2: Query with entity expansion:

Terminal window
curl "https://your-automem-instance/recall?query=Sarah%27s+sister%27s+job&expand_entities=true" \
-H "Authorization: Bearer YOUR_TOKEN"

Step 3: AutoMem traverses the graph:

graph TB
    Query["Query: 'What is Sarah's sister's job?'"]
    Entity["Entity Extraction:<br/>sarah, sister, job"]

    Seed["Seed Memory:<br/>'Sarah's sister is Rachel'<br/>tags: family, sarah, rachel"]

    Related1["RELATES_TO (0.95)"]
    Bridge["Related Memory:<br/>'Rachel is a software engineer'<br/>tags: career, rachel"]

    Related2["RELATES_TO (0.8)"]
    Context["Context Memory:<br/>'Tech Corp projects'<br/>tags: work, tech-corp"]

    Result["Answer:<br/>'Rachel is a software engineer at Tech Corp'"]

    Query --> Entity
    Entity --> Seed
    Seed --> Related1
    Related1 --> Bridge
    Bridge --> Related2
    Related2 --> Context

    Bridge --> Result
    Context --> Result

The associate_memories MCP tool corresponds to POST /associate.

ParameterTypeRequiredConstraintsDescription
memory1_idstringYes—Source memory ID (from store_memory or recall_memory results)
memory2_idstringYes—Target memory ID to link to
typestring (enum)Yes11 authorable typesRelationship type
strengthnumberYes0.0–1.0Relationship strength. The MCP tool has no default
associationsarray of objectsBatch mode1–500 itemsBatch of associations. Each item takes the same fields as a single call. Do not combine with the top-level pair fields

Relationship type enum values (11 authorable):

  1. RELATES_TO — General relationship
  2. LEADS_TO — Causal relationship
  3. OCCURRED_BEFORE — Temporal ordering
  4. PREFERS_OVER — Chosen alternative
  5. EXEMPLIFIES — Concrete example of pattern
  6. CONTRADICTS — Conflicts with
  7. REINFORCES — Strengthens validity
  8. INVALIDATED_BY — Superseded by
  9. EVOLVED_INTO — Updated version
  10. DERIVED_FROM — Implementation of decision
  11. PART_OF — Component of larger effort

The following types are system-generated and cannot be created via associate_memories. SIMILAR_TO and PRECEDED_BY appear in recall results and can be filtered/expanded. DISCOVERED is system-internal (public_visible=False) — excluded from recall results and the /graph/relations type list, but may still appear in raw graph traversal responses:

  • SIMILAR_TO — Semantically similar (created by enrichment)
  • PRECEDED_BY — Temporal predecessor (created by enrichment)
  • DISCOVERED — System-internal heuristic edge with kind property (created by consolidation; excluded from recall but may appear in /graph/snapshot and /graph/neighbors)
FieldTypeRequiredDescription
successbooleanYesWhether every requested association was created; false for partial batch results
messagestringYesConfirmation message (batch mode: the service summary)
created_countintegerNoBatch mode: number of associations created
failed_countintegerNoBatch mode: number of associations that failed
succeededarrayNoBatch mode: successful association records
failedarrayNoBatch mode: failed association records with errors
summarystringNoBatch mode: service summary

The tool is annotated idempotentHint: true — creating the same relationship multiple times is safe (MERGE semantics).

sequenceDiagram
    participant Client
    participant Flask as automem/api/memory.py<br/>POST /associate
    participant Falkor as FalkorDB

    Client->>Flask: POST /associate<br/>{memory1_id, memory2_id, type}
    Flask->>Flask: validate relationship type
    Flask->>Falkor: MATCH (m1:Memory {id: $id1})<br/>MATCH (m2:Memory {id: $id2})
    Flask->>Falkor: MERGE (m1)-[r:TYPE]->(m2)
    Flask->>Falkor: SET r.strength = $strength
    Flask-->>Client: 201 Created

The strength parameter (0.0–1.0) quantifies the confidence or importance of the relationship. This value is used during graph expansion to filter weak connections via the expand_min_strength parameter in recall_memory.

RangeInterpretationExamples
0.9–1.0Direct causation, critical dependencyBug fix directly caused by root cause discovery; feature implementation directly derived from architecture decision
0.7–0.9Strong relationship, high confidencePattern strongly relates to multiple implementations; new decision reinforces established convention
0.5–0.7Moderate relationship, relevant connectionFeature relates to broader system architecture; alternative approach considered during decision
0.3–0.5Weak relationship, tangential connectionLoosely related topics; historical context with minimal current relevance
< 0.3Very weak — consider not creatingRisk of noise during graph expansion

Always create immediately:

  • User corrections (always link to what was corrected)
  • Bug fixes (link to root cause if stored separately)
  • Decisions (link to rejected alternatives)

Defer until meaningful:

  • Pattern discovery (wait to accumulate examples before linking)
  • Speculative relationships (verify the connection is meaningful first)

Target: 1–3 associations per stored memory. Avoid creating RELATES_TO for memories that are already semantically similar — vector search will find those automatically.

When a user corrects a previous statement or approach, link the correction to what was corrected:

// Store the correction
{ "content": "Do NOT use --force flag with git push to main", "importance": 0.95 }
// Associate: old approach INVALIDATED_BY new correction
{
"memory1_id": "<old approach memory id>",
"memory2_id": "<new correction memory id>",
"type": "INVALIDATED_BY",
"strength": 0.95
}

Rationale: Prevents the AI from repeating invalidated approaches. During recall, if the old memory surfaces, the INVALIDATED_BY relationship signals it has been superseded.

Link bug fixes to their root cause discoveries:

// Bug discovery → Bug fix
{
"memory1_id": "<bug discovery memory id>",
"memory2_id": "<bug fix memory id>",
"type": "LEADS_TO",
"strength": 0.9
}
// Bug fix ← Architecture decision (fix derived from)
{
"memory1_id": "<bug fix memory id>",
"memory2_id": "<architecture decision memory id>",
"type": "DERIVED_FROM",
"strength": 0.8
}

Rationale: Creates a causal chain from problem discovery through resolution. If a similar bug occurs, graph expansion can traverse from the new bug discovery to related fixes.

Architectural Decisions (PREFERS_OVER, PART_OF)

Section titled “Architectural Decisions (PREFERS_OVER, PART_OF)”
// Chosen solution → Rejected alternative
{
"memory1_id": "<PostgreSQL decision memory id>",
"memory2_id": "<MongoDB evaluation memory id>",
"type": "PREFERS_OVER",
"strength": 0.9
}

Rationale: Captures the decision context (why A was chosen over B) and system structure (how components relate to the whole).

Pattern Evolution (EVOLVED_INTO, EXEMPLIFIES)

Section titled “Pattern Evolution (EVOLVED_INTO, EXEMPLIFIES)”

Track how patterns change over time and connect abstract patterns to concrete examples:

// Old approach → New approach
{
"memory1_id": "<old pattern memory id>",
"memory2_id": "<new pattern memory id>",
"type": "EVOLVED_INTO",
"strength": 0.85
}
// Specific example → General pattern
{
"memory1_id": "<specific fix memory id>",
"memory2_id": "<general pattern memory id>",
"type": "EXEMPLIFIES",
"strength": 0.75
}

When deprecating old information, combine update_memory and associate_memories:

// 1. Update the old memory to flag it as outdated
PATCH /memory/<old-id>
{ "metadata": { "deprecated": true, "superseded_by": "<new-id>" } }
// 2. Associate: old EVOLVED_INTO new
{
"memory1_id": "<old memory id>",
"memory2_id": "<new memory id>",
"type": "EVOLVED_INTO",
"strength": 0.9
}

This pattern is preferred over deletion because it preserves history and provides context for why the approach changed.


Use PREFERS_OVER when:

  • Documenting user preferences (PostgreSQL over MongoDB)
  • Recording rejected alternatives
  • Establishing decision hierarchies

Use DERIVED_FROM when:

  • Linking implementation to specifications
  • Connecting code to requirements
  • Tracing decisions to principles

Use EVOLVED_INTO when:

  • Tracking design iterations
  • Recording refactoring history
  • Documenting process improvements

Use EXEMPLIFIES when:

  • Connecting instances to patterns
  • Building pattern libraries
  • Reinforcing best practices

Most relationships are directed (one-way):

  • LEADS_TO: Problem → Solution (not Solution → Problem)
  • INVALIDATED_BY: Old → New (not New → Old)
  • PART_OF: Component → Whole (not Whole → Component)

Use RELATES_TO for bidirectional general connections where direction doesn’t matter.

Over-association: Creating relationships for every possible connection adds noise to graph expansion and increases query latency.

Weak relationships: Associations with strength < 0.3 are rarely worth creating unless you have specific intent for that connection.

Wrong direction: Using LEADS_TO with (solution → problem) instead of (problem → solution) inverts the causal chain and confuses traversal.


To verify associations are working, use expand_relations with low thresholds during recall:

Terminal window
curl "https://your-automem-instance/recall?query=database+decisions&expand_relations=true&expand_min_strength=0.0&relation_limit=10" \
-H "Authorization: Bearer YOUR_TOKEN"

The relations field in recall results shows the relationship metadata for expanded memories, including type and strength.


Association storage cost (low):

  • Single edge creation in FalkorDB (O(1) write)
  • No vector embedding computation required
  • No impact on Qdrant

Recall expansion cost (moderate):

  • Each seed memory can trigger up to relation_limit edge traversals
  • Expanded memories are loaded from FalkorDB traversal results; Qdrant affects the initial semantic seed search, not relation expansion itself
  • Recommendation: Use expand_min_strength and expand_min_importance to limit expansion

Bi-directional traversal: FalkorDB supports efficient traversal in both directions — forward (m1 → m2 outgoing) and backward (m2 ← m1 incoming). The recall expansion follows edges in both directions by default, so a single LEADS_TO relationship enables discovery from either end.