Skip to content

Recall Operations

The recall system provides a single unified endpoint that supports multiple search strategies. It combines 10 scoring components into a hybrid ranking system and supports both basic retrieval and advanced graph expansion. Authentication is required via Authorization: Bearer <token> header or X-API-Key header.

GET /recall

ParameterTypeDefaultDescription
querystring—Natural language search query or wildcard *
embeddingarray[float]—Pre-computed embedding vector for semantic search
limitinteger5Maximum results to return (capped at RECALL_MAX_LIMIT=100)
sort / order_bystringscoreSort mode: score, time_desc, time_asc, updated_desc, updated_asc

The query parameter triggers keyword extraction and full-text search. When query="*" or is omitted, the system returns trending memories ordered by importance.

ParameterTypeDefaultDescription
tagsarray[string]—Tags to filter by (can specify multiple)
exclude_tagsarray[string]—Tags to exclude (supports prefix matching)
tag_modestringanyMatch mode: any or all
tag_matchstringprefixMatch type: prefix or exact

Tag filtering uses pre-computed tag_prefixes stored in both FalkorDB and Qdrant for efficient prefix matching. The exclude_tags parameter removes any memory containing ANY of the specified tags.

Prefix matching example:

  • Query: tags=slack
  • Matches: slack:channel:general, slack:user:U123, slack:reaction:thumbs-up
  • Matches against the precomputed tag_prefixes field, which stores each tag’s segment prefixes (slack, slack:channel, slack:channel:general), so a prefix filter is an exact membership check instead of a STARTS WITH scan

Mode behavior:

  • any (default): Match if ANY filter tag present
  • all: Match only if ALL filter tags present
ParameterTypeDefaultDescription
time_querystring—Natural language time expression (e.g., "last week")
startstring—ISO 8601 start timestamp
endstring—ISO 8601 end timestamp

Time queries are parsed by _parse_time_expression() from automem/utils/time.py, which supports expressions like "last 7 days", "yesterday", "last month". Explicit timestamps override natural language queries.

ParameterTypeDefaultDescription
contextstring—Contextual hint for ranking (e.g., "programming task")
languagestring—Programming language context (e.g., "python")
active_pathstring—File path being edited (auto-detects language)
context_tagsarray[string]—Tags to boost in scoring
context_typesarray[string]—Memory types to prioritize
priority_idsarray[string]—Memory IDs to boost during scoring and fetch directly when the search missed them

Context hints add an unweighted context bonus to the final score (see the formula under Hybrid Scoring System) but do not filter results. When active_path matches a coding language extension, the system prioritizes Style type memories for that language.

ParameterTypeDefaultDescription
expand_relationsbooleanfalseFollow graph edges from seed results
expand_entitiesbooleanfalseMulti-hop reasoning via entity tags
relation_limitinteger5Max relations per seed (from RECALL_RELATION_LIMIT)
expansion_limitinteger25Total max expanded memories (from RECALL_EXPANSION_LIMIT)
expand_min_importancefloat0.0Minimum importance threshold for expanded memories
expand_min_strengthfloat0.0Minimum relation strength to traverse
expand_respect_tagsbooleanfalseRestrict graph expansion to memories within the active tag scope

Graph expansion operates in two phases: seed results from hybrid search, then expansion via graph traversal. Expansion filtering parameters only apply to expanded memories, never to seed results.

State filtering is the largest behavior change in 0.16: recall now returns current state only by default. The current-state filter suppresses expired, not-yet-valid, archived, and invalidated/evolved memories, and state_mode=history turns that filter off. Supersession chains resolve to their head — if memory A was superseded by B and B by C (INVALIDATED_BY / EVOLVED_INTO), a recall that would surface A returns C instead. Resolution is bounded at 5 hops and is cycle-safe.

ParameterTypeDefaultDescription
state_modestringcurrentcurrent returns active memories only; history skips the current-state filter, so superseded, expired, and not-yet-valid memories can be returned. Archived memories stay excluded from the vector, keyword, trending, tag-only, and metadata candidate searches. Any other value returns 400
current_onlyboolean—Legacy flag. true == state_mode=current, false == state_mode=history. When both are set and resolve, current_only wins
state_debugbooleanfalseAdds suppression and replacement detail (which memories were hidden and what replaced them) under a state_filter object in the response. Only has an effect when the current-state filter runs; a history response has no state_filter

Text queries can admit a bounded set of additional candidates when the query strongly matches whitelisted metadata values (the metadata “sidecar”). This is enabled by default and controlled by the RECALL_METADATA_SEARCH_ENABLED environment variable — there is no request parameter.

The sidecar runs after vector and keyword candidates are merged. It reserves up to max(1, min(limit, 10)) slots and can upgrade memories already in the candidate pool (their IDs are passed as include_seen_ids) so a strong metadata match is not dropped when the vector pool is full. Sidecar matches still pass the same tag, time, and state filters as other candidates.

Vector search returns more candidates than the requested limit so the hybrid re-rank can surface high-importance or exact-match memories that rank slightly lower on raw cosine similarity alone. The fetch size is:

max(limit, min(limit × RECALL_VECTOR_OVERFETCH, RECALL_VECTOR_FETCH_CAP))

Tag-scoped queries with text or an explicit embedding may widen the pool further (still capped by RECALL_VECTOR_FETCH_CAP). The response is trimmed to limit after scoring — over-fetch does not increase payload size. Defaults: RECALL_VECTOR_OVERFETCH=4, RECALL_VECTOR_FETCH_CAP=200. Set RECALL_VECTOR_OVERFETCH=1 to restore legacy 1× vector fetch behavior.

User-facing /recall results exclude internal artifact memory types (for example consolidation MetaPattern cluster summaries). Filtering is controlled by RECALL_EXCLUDED_TYPES (default MetaPattern, comma-separated). At AutoMem 0.16.2 the same list also applies to the /health memory and vector counts, /admin/sync, and the background sync worker’s drift repair, so internal artifacts are not counted there either.

ParameterTypeDefaultDescription
recency_biasstringfrom RECALL_RECENCY_BIAS (ships off)off, on, or auto. Re-ranks score order toward more recent memories. auto activates only on temporal-intent queries

When the recency re-rank activates, the response echoes the applied recency_bias value.

ParameterTypeDefaultDescription
scope_fallbackbooleanfalseWhen tag-scoped results fall short of limit and a query is present, fill the remaining slots from an unscoped vector search

Fallback fills are appended after the scoped results, and each carries outside_tag_scope: true. The exclude_tags, time-window, min_score, and current-state filters still apply to fallback candidates.

ParameterTypeDefaultDescription
min_scorefloatfrom RECALL_MIN_SCORE (ships 0.0, disabled)Minimum final score for a result to be returned
adaptive_floorbooleanfrom RECALL_ADAPTIVE_FLOOR (ships true)Adjusts the minimum score floor based on overall result quality, dropping weak outliers

graph LR
    Request["GET /recall<br/>?query=X&tags=Y"]

    subgraph "Search Strategies"
        Vector["_vector_search<br/>automem/search/runtime_recall_helpers.py"]
        Keyword["_graph_keyword_search<br/>automem/search/runtime_recall_helpers.py"]
        Trending["_graph_trending_results<br/>automem/search/runtime_recall_helpers.py"]
    end

    subgraph "Scoring"
        Compute["_compute_metadata_score<br/>automem/utils/scoring.py"]
        Combine["Weighted combination<br/>vector+keyword+tag+<br/>importance+recency"]
    end

    subgraph "Filtering"
        Time["Time window filter<br/>_parse_time_expression"]
        Tags["Tag filter<br/>_build_graph_tag_predicate"]
        ResultFilter["_result_passes_filters"]
    end

    Request --> Vector
    Request --> Keyword
    Request --> Trending

    Vector --> Combine
    Keyword --> Combine
    Trending --> Combine

    Combine --> Time
    Time --> Tags
    Tags --> ResultFilter

    ResultFilter --> Response["Ranked results<br/>with relations"]

Search strategy:

  1. Vector Search (state.qdrant != None): Semantic similarity using the configured embedding provider (Voyage by default)
  2. Keyword Search (FalkorDB): Content and tag matching using Cypher CONTAINS, run only when vector search returned fewer candidates than the per-query limit, to fill the remaining slots
  3. Trending Results (no query): High-importance memories ordered by recency
  4. Deduplication: Track seen IDs across sources
  5. Filtering: Apply temporal and tag constraints
  6. Scoring: Combine weighted factors into final score
  7. Relations: Fetch connected memories (limited by RECALL_RELATION_LIMIT)

graph TB
    Query["Query Input"]

    subgraph VectorSearch["Vector Search (Qdrant)"]
        VectorComp["Vector Component<br/>Weight: SEARCH_WEIGHT_VECTOR<br/>Cosine similarity score"]
    end

    subgraph KeywordSearch["Keyword Search (FalkorDB)"]
        KeywordComp["Keyword Component<br/>Weight: SEARCH_WEIGHT_KEYWORD<br/>Keyword-search match score, or share<br/>of query keywords found in content"]
    end

    subgraph MetadataSidecar["Metadata Sidecar Search"]
        MetadataComp["Metadata Component<br/>Weight: SEARCH_WEIGHT_METADATA<br/>Metadata sidecar channel match"]
    end

    subgraph MetadataScoring["Metadata Scoring"]
        ImportanceComp["Importance Component<br/>Weight: SEARCH_WEIGHT_IMPORTANCE<br/>User-assigned priority"]
        ConfidenceComp["Confidence Component<br/>Weight: SEARCH_WEIGHT_CONFIDENCE<br/>Classification confidence"]
        RecencyComp["Recency Component<br/>Weight: SEARCH_WEIGHT_RECENCY<br/>Freshness boost"]
        TagComp["Tag Component<br/>Weight: SEARCH_WEIGHT_TAG<br/>Tag overlap score"]
        ExactComp["Exact Component<br/>Weight: SEARCH_WEIGHT_EXACT<br/>Whole-query exact match against metadata terms"]
    end

    subgraph ContextScoring["Context Scoring"]
        RelationComp["Relation Component<br/>Weight: SEARCH_WEIGHT_RELATION<br/>Computed from graph edges"]
        RelevanceComp["Relevance Component<br/>Weight: SEARCH_WEIGHT_RELEVANCE<br/>Consolidation decay score"]
    end

    FinalScore["Final Weighted Score<br/>Sum of all components"]

    Query --> VectorSearch
    Query --> KeywordSearch
    Query --> MetadataSidecar
    Query --> MetadataScoring
    Query --> ContextScoring

    VectorComp --> FinalScore
    KeywordComp --> FinalScore
    ExactComp --> FinalScore
    MetadataComp --> FinalScore
    ImportanceComp --> FinalScore
    ConfidenceComp --> FinalScore
    RecencyComp --> FinalScore
    TagComp --> FinalScore
    RelationComp --> FinalScore
    RelevanceComp --> FinalScore

The formula:

final_score = (vector_score × SEARCH_WEIGHT_VECTOR) +
(keyword_score × SEARCH_WEIGHT_KEYWORD) +
(metadata_score × SEARCH_WEIGHT_METADATA) +
(exact_match_score × SEARCH_WEIGHT_EXACT) +
(importance × SEARCH_WEIGHT_IMPORTANCE) +
(confidence × SEARCH_WEIGHT_CONFIDENCE) +
(recency_score × SEARCH_WEIGHT_RECENCY) +
(tag_score × SEARCH_WEIGHT_TAG) +
(relation_score × SEARCH_WEIGHT_RELATION) +
(relevance_score × SEARCH_WEIGHT_RELEVANCE) +
context_bonus

context_bonus is added without a weight and is 0 when no context profile applies (see Context Hint Parameters). The sum is not normalized, so final_score can exceed 1.0. When recency_bias activates, a separate re-rank then adds SEARCH_WEIGHT_TEMPORAL * relative_recency (default weight 0.1) to final_score; it is off by default.

Default weight configuration (all configurable via environment variables):

ComponentEnvironment VariableDefault Value
VectorSEARCH_WEIGHT_VECTOR0.35
KeywordSEARCH_WEIGHT_KEYWORD0.35
MetadataSEARCH_WEIGHT_METADATA0.35
ExactSEARCH_WEIGHT_EXACT0.20
ImportanceSEARCH_WEIGHT_IMPORTANCE0.10
ConfidenceSEARCH_WEIGHT_CONFIDENCE0.05
RecencySEARCH_WEIGHT_RECENCY0.10
TagSEARCH_WEIGHT_TAG0.20
RelationSEARCH_WEIGHT_RELATION0.25
RelevanceSEARCH_WEIGHT_RELEVANCE0.0

Vector Search Component:

Executes via _vector_search() using Qdrant when available. When Qdrant is unavailable, the system gracefully degrades to graph-only mode using keyword and metadata scoring.

Keyword Search Component:

Implemented in _graph_keyword_search() using Cypher queries:

  • Extracts keywords via _extract_keywords() from automem/utils/text.py
  • Matches against m.content and m.tags fields in FalkorDB
  • Scores based on keyword frequency and phrase containment
  • Returns memories ordered by score DESC, m.importance DESC, m.timestamp DESC
  • Content match: 2 points per keyword
  • Tag match: 1 point per keyword
  • Exact phrase bonus: +2 (content) or +1 (tag)

Metadata Sidecar Component:

The dedicated SEARCH_WEIGHT_METADATA component only applies to candidates admitted via the metadata sidecar search channel (see RECALL_METADATA_SEARCH_ENABLED) — it is separate from the importance/confidence/recency/tag sub-scores below.

Importance, Confidence, Recency & Tag Sub-Scores:

Computed by _compute_metadata_score() and _parse_metadata_field(), each with its own independent weight in the final formula above:

  • Importance: Direct multiplication by weight (0.0–1.0 range)
  • Confidence: Classification confidence from memory type detection
  • Recency: max(0, 1 - (age_days / SEARCH_RECENCY_WINDOW_DAYS)) — linear decay that reaches zero at the recency window (default 180 days), measured from the memory’s timestamp rather than last_accessed. Set SEARCH_RECENCY_CURVE=exp for exponential decay, 0.5 ** (age_days / SEARCH_RECENCY_WINDOW_DAYS), where the window is the half-life. A missing or unparseable timestamp scores 0.0
  • Tag: min(1, matched_tokens / total_query_tokens) — the share of query keywords that exactly match one of the memory’s tags or metadata terms

graph TB
    SeedResults["Seed Results<br/>from Hybrid Search"]

    CheckExpand{"expand_relations<br/>enabled?"}

    FetchRelations["Cypher MATCH per seed<br/>_expand_related_memories()<br/>api/recall.py"]

    CheckLimit{"Expanded count below<br/>expansion_limit?"}

    FilterStrength{"Relation strength >=<br/>expand_min_strength?"}

    FilterImportance{"Memory importance >=<br/>expand_min_importance?"}

    FilterScope{"Passes time window, exclude_tags,<br/>and tag scope if expand_respect_tags?"}

    AddToResults["Add to expanded results"]

    NextEdge["Next edge, then next seed"]

    FinalResults["Merged Results<br/>Seed + Expanded"]

    SeedResults --> CheckExpand
    CheckExpand -->|No| FinalResults
    CheckExpand -->|Yes| FetchRelations

    FetchRelations --> CheckLimit
    CheckLimit -->|"Limit reached"| FinalResults
    CheckLimit -->|"Within limit"| FilterStrength

    FilterStrength -->|"Fail, skip edge"| NextEdge
    FilterStrength -->|Pass| FilterImportance

    FilterImportance -->|"Fail, skip edge"| NextEdge
    FilterImportance -->|Pass| FilterScope

    FilterScope -->|"Fail, skip edge"| NextEdge
    FilterScope -->|Pass| AddToResults

    AddToResults --> NextEdge
    NextEdge -->|"More edges or seeds"| CheckLimit
    NextEdge -->|"None left"| FinalResults

The _expand_related_memories() function expands each seed memory by one hop, using a Cypher MATCH to follow typed relationship edges (e.g., RELATES_TO, PREFERS_OVER, LEADS_TO). Edges are ordered by update time (falling back to the related memory’s timestamp), newest first, and capped at relation_limit per seed.

Key configuration constants:

  • RECALL_RELATION_LIMIT (default: 5) — Max edges per seed memory
  • RECALL_EXPANSION_LIMIT (default: 25) — Total expanded memories cap
  • RECALL_MIN_SCORE (default: 0.0, disabled) — Minimum score threshold for results to be returned
  • RECALL_ADAPTIVE_FLOOR (default: true) — Adaptive floor that adjusts minimum score based on result quality

Edge types traversed: the relationship types flagged default_expand in the relationship registry: RELATES_TO, LEADS_TO, OCCURRED_BEFORE, PREFERS_OVER, EXEMPLIFIES, CONTRADICTS, REINFORCES, INVALIDATED_BY, EVOLVED_INTO, DERIVED_FROM, and PART_OF. The system-generated SIMILAR_TO, PRECEDED_BY, and DISCOVERED edges are not followed. See Relationship Operations for the full type reference.

When expand_entities=true, the system performs multi-hop reasoning via entity tags. It runs once, after the seed results and any relation expansion are assembled, and only when Qdrant is available:

  1. Collect entities from the seed memories and any relation-expanded memories: metadata.entities (people, places, organizations) and entity:people:<slug> tags
  2. For up to 5 of those entities, one after another, search for memories tagged entity:people:<slug> (up to 5 per entity, capped by expansion_limit)
  3. Return the new memories with match_type: "entity_expansion" and expanded_from_entity

Example: Query "What is Sarah's sister's job?" → Hybrid search finds the seed memory "Sarah's sister is Rachel" → Find memories tagged for rachel → Find Rachel’s job.

When auto_decompose: true, the backend automatically extracts entities and topics from the query to generate supplementary searches:

  • Query: "React OAuth authentication patterns"
  • Searched as: the original query plus up to three topic keywords, ["React OAuth authentication patterns", "react", "oauth", "authentication"] (no names are detected in this query)
  • When a query does contain capitalized names (at most two are used), each name is searched alone and combined with up to three topic keywords instead of the topic keywords alone
  • Runs the searches one after another, not in parallel
  • Merges and deduplicates results

The queries parameter (array of strings) allows multiple search queries in a single request. The backend executes them one after another and deduplicates results server-side.

Deduplication logic:

  1. Each query returns up to per_query_limit results (default limit)
  2. Server merges results by memory_id
  3. Keeps highest-scored version of each memory
  4. Returns dedup_removed count in response metadata

The MCP recall_memory tool (src/automem-client.ts) routes requests through three mutually exclusive branches — only one executes per call:

  1. ID fetch mode: If memory_id is provided, short-circuits to GET /memory/{id} and returns immediately without evaluating other parameters.
  2. Enumeration mode: If exhaustive: true is set (with tags), routes to GET /memory/by-tag. The following ranked-only parameters are rejected with a validation error in this mode: query, queries, embedding, time_query, start, end, exclude_tags, expand_relations, expand_entities, auto_decompose, expansion_limit, relation_limit, expand_min_importance, expand_min_strength, current_only, state_debug, state_mode, recency_bias, scope_fallback, expand_respect_tags, min_score, adaptive_floor, sort, context, language, active_path, context_tags, context_types, priority_ids, and per_query_limit. Only tags, limit, offset, tag_mode: "any", tag_match: "exact", and format are accepted.
  3. Ranked retrieval mode: All other cases call GET /recall with URL parameters built from the tool arguments. offset > 0 is rejected in this mode because GET /recall does not support pagination — raise limit instead.

sequenceDiagram
    participant Client
    participant API as "Flask API"
    participant Graph as "FalkorDB"
    participant Vector as "Qdrant"
    participant Scorer as "Hybrid Scorer"

    Client->>API: "GET /recall?query=...&expand_relations=true"

    API->>Vector: "search(embedding, limit)"
    Vector-->>API: "vector_candidates"
    API->>Graph: "keyword search Cypher (fills remaining slots)"
    Graph-->>API: "graph_candidates"

    API->>Scorer: "merge + rank candidates"
    Scorer-->>API: "ranked_results"

    opt Expand Relations
        API->>Graph: "MATCH relationships"
        Graph-->>API: "expanded_memories"
        API->>API: "filter by importance/strength"
    end

    API->>Graph: "SET m.last_accessed"
    API-->>Client: "final results + metadata"

Typical performance:

  • Sub-100ms for queries without expansion
  • 100–300ms with graph expansion enabled
  • Scales to 100k+ memories with proper indexing

{
"status": "success",
"query": "PostgreSQL database decisions",
"results": [
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"memory": {
"content": "Chose PostgreSQL over MongoDB for the primary database.",
"tags": ["project-alpha", "database"],
"importance": 0.9,
"confidence": 0.9,
"type": "Decision",
"timestamp": "2025-01-15T10:30:00+00:00"
},
"final_score": 0.792,
"match_type": "vector",
"score_components": {
"vector": 0.82,
"keyword": 0.6667,
"metadata": 0.0,
"relation": 0.0,
"tag": 0.3333,
"importance": 0.9,
"confidence": 0.9,
"recency": 0.7,
"exact": 0.0,
"relevance": 0.0,
"context": 0.0,
"evidence": 0.82,
"relevance_gated": false
},
"relations": []
}
],
"count": 1,
"dedup_removed": 0,
"sort": "score",
"state_mode": "current",
"state_filter": {
"current_only": true,
"suppressed_count": 0,
"replacement_count": 0
},
"keywords": ["postgresql", "database", "decisions"],
"vector_search": {
"enabled": true,
"matched": true
},
"tag_mode": "any",
"tag_match": "prefix",
"query_time_ms": 47.3
}

The sample’s numbers follow from the default weights. The query keywords are postgresql, database, and decisions; the content contains two of them (keyword 0.6667) and the tags match one (tag 0.3333). The vector hit scored 0.82, and the memory is 54 days old when queried, so recency is 0.7 on the default 180-day window. final_score is then 0.35 × 0.82 + 0.35 × 0.6667 + 0.2 × 0.3333 + 0.1 × 0.9 + 0.05 × 0.9 + 0.1 × 0.7 = 0.792, with no context bonus. Each result also carries bookkeeping fields (source, match_score, original_score, score, _query) that the sample leaves out. The memory object in the sample is abridged too: stored memories can also carry tag_prefixes, updated_at, last_accessed, t_valid, t_invalid, and metadata.

Result fields:

  • id: The memory’s ID
  • memory: The stored memory record (Qdrant payload or FalkorDB node properties) with fields such as content, tags, importance, timestamp, type, confidence, and metadata. The memory ID is the result-level id (vector hits carry no ID inside memory) and the creation time is stored as timestamp; the MCP tool presents them as memory_id and created_at
  • final_score: Combined relevance score
  • match_type: Type of match — vector, keyword, trending, metadata, tag, relation, entity_expansion, priority_id, or state_replacement
  • score_components: Breakdown of scoring factors: the ten weighted components plus context (the context bonus), evidence (the strongest of the vector, keyword, metadata, and exact scores), and relevance_gated. Two keys appear only conditionally: temporal (the result’s recency relative to the other candidates, 0.0 to 1.0) when the recency re-rank changes scores, and entity_expansion (0.15) on entity-expanded results
  • relations: Connected memories (if expansion enabled)
  • expanded_from_entity: Entity that triggered expansion (if applicable)
  • deduped_from: IDs of duplicate results removed

When expand_relations=true:

{
"expansion": {
"enabled": true,
"seed_count": 3,
"expanded_count": 5,
"relation_limit": 5,
"expansion_limit": 25,
"respect_tags": false
}
}

When expand_entities=true:

{
"entity_expansion": {
"enabled": true,
"expanded_count": 4,
"entities_found": ["rachel", "platform team"]
}
}

entities_found lists up to 10 entity names, lowercased, collected from the seed and relation-expanded results. Expansion searches at most 5 of them, so a listed name did not necessarily produce a result.


Terminal window
curl "https://your-automem-instance/recall?query=PostgreSQL+database+decisions&limit=5" \
-H "Authorization: Bearer YOUR_TOKEN"
Terminal window
curl "https://your-automem-instance/recall?embedding=0.1,0.2,0.3,...&limit=10" \
-H "Authorization: Bearer YOUR_TOKEN"
Terminal window
# Natural language time filter
curl "https://your-automem-instance/recall?time_query=last+week&query=bug+fixes" \
-H "Authorization: Bearer YOUR_TOKEN"
# Explicit time range
curl "https://your-automem-instance/recall?start=2025-01-01T00:00:00Z&end=2025-01-31T23:59:59Z" \
-H "Authorization: Bearer YOUR_TOKEN"
Terminal window
# Any tag match (default)
curl "https://your-automem-instance/recall?tags=project-alpha&tags=database" \
-H "Authorization: Bearer YOUR_TOKEN"
# All tags required
curl "https://your-automem-instance/recall?tags=project-alpha&tags=database&tag_mode=all" \
-H "Authorization: Bearer YOUR_TOKEN"
# Exclude certain tags
curl "https://your-automem-instance/recall?query=preferences&exclude_tags=conversation&exclude_tags=temp" \
-H "Authorization: Bearer YOUR_TOKEN"
Terminal window
curl "https://your-automem-instance/recall?query=error+handling&active_path=src/auth.ts&context_types=Style&context_types=Pattern" \
-H "Authorization: Bearer YOUR_TOKEN"

Auto-detects TypeScript context → boosts Style type memories → prioritizes language-specific patterns.

Terminal window
curl "https://your-automem-instance/recall?query=authentication&expand_relations=true&expand_min_strength=0.3&expand_min_importance=0.5" \
-H "Authorization: Bearer YOUR_TOKEN"

Performs hybrid search for seed results → follows graph edges with strength ≥ 0.3 → filters expanded memories with importance ≥ 0.5.

Terminal window
curl "https://your-automem-instance/recall?query=Sarah%27s+sister%27s+job&expand_entities=true" \
-H "Authorization: Bearer YOUR_TOKEN"
Terminal window
# Default: only current memories — superseded, expired, and not-yet-valid memories are suppressed
curl "https://your-automem-instance/recall?query=database+choice&state_debug=true" \
-H "Authorization: Bearer YOUR_TOKEN"
# Opt into history: skip the current-state filter so superseded and expired memories can be returned
curl "https://your-automem-instance/recall?query=database+choice&state_mode=history" \
-H "Authorization: Bearer YOUR_TOKEN"

The first call resolves supersession chains to their head and hides invalidated memories; with state_debug=true it reports what was suppressed and what replaced it under state_filter. The second skips that filter, so its response has no state_filter object.


When sort=score (or unspecified with query), results are ordered by the final hybrid score: the sum of all 10 weighted components plus the unweighted context bonus (plus the re-rank term when recency_bias is active).

Time-based sorting modes are designed for “what happened since X” queries:

  • time_desc / updated_desc: Newest first (ordered by coalesce(m.updated_at, m.timestamp))
  • time_asc / updated_asc: Oldest first

When a time window is specified without a query, the system defaults to time_desc to show recent activity.


The recall_memory MCP tool wraps GET /recall with additional client-side optimizations. It is annotated readOnlyHint: true and idempotentHint: true.

Full input schema:

ParameterTypeRequiredConstraintsDescription
memory_idstringNo—ID fetch mode: returns the single memory via GET /memory/{id} and ignores all other parameters
exhaustivebooleanNo—Tag enumeration mode: requires non-empty tags and routes to GET /memory/by-tag instead of ranked recall
querystringNo—Semantic search query
queriesarray[string]No—Multiple queries for broader recall
embeddingarray[number]No—Pre-computed embedding vector for direct similarity search
limitintegerNo1–200Max results to return. When omitted, the server defaults to 5 in ranked mode and 20 in enumeration mode; ranked recall is capped server-side (default 100 via RECALL_MAX_LIMIT) and enumeration at 200
tagsarray[string]No—Filter by tags
exclude_tagsarray[string]No—Ranked mode only: tags to exclude (any match excludes; independent of tag_match)
tag_modestringNo"any" or "all"Tag matching mode
tag_matchstringNo"exact" or "prefix"Tag matching strategy
time_querystringNo—Natural language time filter
startstringNoISO timestampTime range start
endstringNoISO timestampTime range end
expand_entitiesbooleanNo—Enable multi-hop entity expansion
expand_relationsbooleanNo—Follow graph relationships
expansion_limitintegerNo1–500, default 25Max expanded results
relation_limitintegerNo1–200, default 5Relations per seed memory
expand_min_importancenumberNo0–1Filter expanded results by importance
expand_min_strengthnumberNo0–1Min relation strength to traverse
expand_respect_tagsbooleanNo—Restrict graph expansion to memories within the active tag scope
contextstringNo—Context label for boosting
languagestringNo—Programming language hint
active_pathstringNo—Current file path for language detection
context_tagsarray[string]No—Priority tags to boost
context_typesarray[string]No—Priority memory types to boost
priority_idsarray[string]No—Memory IDs to boost during scoring and fetch directly when the search missed them (does not bypass filters)
auto_decomposebooleanNo—Auto-extract entities and topics from query to generate supplementary searches
per_query_limitintegerNo1–50, default limitPer-query result limit when using queries
state_modestringNo"current" or "history"Lifecycle filter; default current (current-state only)
current_onlybooleanNoschema default trueLegacy lifecycle flag; wins over state_mode when both resolve. The client forwards it only when the call sets it
state_debugbooleanNoschema default falseInclude suppression/replacement detail in state_filter
recency_biasstringNo"off", "on", or "auto"Relative-recency re-rank; default from RECALL_RECENCY_BIAS
scope_fallbackbooleanNo—Fill short tag-scoped results from an unscoped vector search
min_scorenumberNo0–1Minimum final score gate for returned results; default from RECALL_MIN_SCORE (ships 0.0, disabled)
adaptive_floorbooleanNo—Adaptive score-floor gating based on result quality; default from RECALL_ADAPTIVE_FLOOR (ships true)
sortstringNo"score", "time_desc", "time_asc", "updated_desc", or "updated_asc"Result ordering; the server default is score, or time_desc when a time window is given without a query (use time_* for chronological recaps)
formatstringNo"text", "items", "detailed", or "json"; default "text"Output format (see MCP Response Formatting)
offsetintegerNo0 or moreEnumeration mode only (exhaustive: true): records to skip when paging. Ranked recall rejects offset > 0, so raise limit instead

Output schema:

FieldTypeRequiredDescription
countintegerYesNumber of memories returned
resultsarrayYesArray of memory objects with scores
modestringNoMode that produced the result: ranked, enumeration, or id_fetch
has_morebooleanNoEnumeration mode only: true if more records exist past this page
limitintegerNoEnumeration mode only: page size of this response
offsetintegerNoEnumeration mode only: offset used for this response
next_offsetintegerNoEnumeration mode only, when has_more is true: the offset for the next page
truncationobjectNoPresent when the response budget cut anything
dedup_removedintegerNoDuplicates removed in multi-query

The schema also declares the ranked-recall diagnostics query, sort, exclude_tags, state_filter, state_mode, tag_scope, scope_fallback, recency_bias, score_filter, queries, vector_search, jit_enriched_count, query_time_ms, and entities. structuredContent can also carry fields the schema does not declare: expansion, entity_expansion, time_window, tags, keywords, tag_mode, tag_match, and context_priority.

Budgeted formats (text, items, detailed) trim each memory to stay under MCP client tool-response caps (~25k tokens in Claude Code). The formatter in src/recall-memory.ts:

  • Cuts content longer than 400 characters to a 400-character preview and flags it with content_truncated and content_chars (the full length)
  • Keeps a server-generated summary as an additional field; it stands in for the preview in the text channel only when content is empty
  • In detailed, collapses relations to compact stubs (max 3 per memory, 100-char summaries); stubs include id, from (the seed memory for expanded results or the suppressed memory for state replacements), type, strength, kind, and summary
  • In detailed, collapses metadata to its key list
  • Applies a global token budget (default 18,000 estimated tokens; override with AUTOMEM_RECALL_TOKEN_BUDGET); the budget bounds the whole serialized response (structuredContent plus every text block), not just the results array

format: "json" keeps raw per-field passthrough, including t_valid, t_invalid, and archived on each memory, but the global budget still applies. ID fetches via memory_id are never truncated and also include t_valid, t_invalid, and archived — use that mode to retrieve a full record.

Enumeration pages trimmed by the budget still report has_more: true and advance next_offset so pagers can continue normally. If the record at the current offset is too large to fit in the budget at all, the tool returns an error rather than an empty page.

Recall errors (API failures, validation errors, budget-too-small) are returned as isError: true tool results so the calling AI sees them as tool errors rather than empty responses.

Each memory in budgeted text output is a numbered list item:

1. [summary or preview] [tags] (importance: X) score=X.XXX [match_type] relations=X
ID: mem-abc123
Created: 2025-01-15T10:30:00Z

A summary line includes statistics when applicable:

(3 duplicates removed; 5 via entity expansion (rachel, platform team); 2 via relation expansion)

The MCP response also includes structuredContent with the (possibly budget-trimmed) RecallResult object for programmatic consumption.

Session start — load recent project context:

{
"queries": ["recent decisions", "user preferences"],
"tags": ["preference"],
"time_query": "last 30 days",
"limit": 10
}

Preference recall (no time filter needed):

{
"tags": ["preference"],
"limit": 10
}

Debug pattern search:

{
"query": "authentication timeout error",
"tags": ["bug-fix", "auth"],
"limit": 5
}

Multi-hop reasoning:

{
"query": "What does Amanda's sister do?",
"expand_entities": true,
"expansion_limit": 10
}

Filtered graph exploration:

{
"query": "database architecture",
"expand_relations": true,
"expand_min_strength": 0.5,
"expand_min_importance": 0.7,
"relation_limit": 3
}

ConditionBehaviorHTTP Status
FalkorDB unavailableGraph-backed steps (keyword, trending, metadata, relation expansion) are skipped; vector search still runs200
Qdrant unavailableGraph-only mode (keyword + metadata)200
Embedding generation failsFalls back to a deterministic placeholder vector, so vector hits are not semantically meaningful200
No query and no tagsReturns trending memories200

The system implements graceful degradation where vector search failures don’t block graph operations.

ErrorHTTP StatusCondition
Invalid embedding dimensions400Vector size doesn’t match VECTOR_SIZE
Invalid start or end400Not a valid ISO 8601 timestamp
Invalid state_mode400Not current or history
Limit exceeds maximum200Clamped to RECALL_MAX_LIMIT
Missing authentication401No valid token provided

An unrecognized time_query expression is not an error: it is ignored, and only start and end, if given, set the time window. Error responses use the body {"status": "error", "code": <HTTP status>, "message": "<description>"}.


  1. Tag Prefix Indexing: Pre-computed tag_prefixes turn prefix matching into an exact match on stored prefixes, which Qdrant serves from a keyword payload index
  2. Sequential Candidate Search: Vector search runs first, and graph keyword search only fills the slots it leaves open
  3. Deferred Relation Hydration: relations for vector hits are fetched only after scoring, filtering, and trimming, so over-fetched candidates that are dropped never trigger graph relation queries
  4. Access Tracking: last_accessed updates for the seed matches (not expanded memories) run synchronously, in one batched write before the response is returned