Quickstart

Add your first memory and ask a question in 60 seconds. Python, TypeScript, and curl code samples.

Quickstart

You'll have memory working in under a minute. This guide adds one fact, searches it back, gets a synthesized answer — and explains exactly what's happening at each step.

Get an API key

Sign in to the MemHQ dashboard and create a project. Open the project and go to its API Keys tab to generate a key and copy it.

export MEMHQ_API_KEY="mem_..."

Project keys are scoped to a single project and authorize the /v1/memhq/* endpoints. Keep them server-side — they grant write access to memory.

Install the SDK

pip install memhq

Add a memory

Memories belong to a user_id (per-user graph) or a group_id (shared graph). We'll use a per-user graph here.

from memhq import MemoryClient

client = MemoryClient()  # reads MEMHQ_API_KEY from the environment

result = client.add(
    user_id="user_42",
    messages=[
        {"role": "user", "content": "I prefer dark roast coffee, no sugar."},
    ],
)

print(result)

Response:

{
  "user_id": "user_42",
  "internal_user_id": "usr_9f3c1b7e",
  "thread_id": "thr_4d8a2e60",
  "messages_stored": 1,
  "memories_queued": 1
}

What just happened?

The call returns immediately (under 100 ms). messages_stored confirms the messages were persisted; memories_queued is how many of them were handed to extraction (user-role messages only). In the background, MemHQ's extraction pipeline:

  1. Chunks the conversation into atomic units
  2. Extracts typed entities and facts using an LLM — in this case, {type: "preference", content: "Prefers dark roast coffee, no sugar"}
  3. Writes the fact to user_42's knowledge graph and creates a vector embedding for it

By the time the next step below runs, the fact is indexed and ready to retrieve.

Search it back

results = client.search(
    user_id="user_42",
    query="what coffee does the user like?",
    limit=5,
)

for hit in results.memories:
    print(f"{hit.score:.2f}  {hit.content}")

Response:

{
  "memories": [
    {
      "id": "m_xyz123",
      "content": "Prefers dark roast coffee, no sugar",
      "score": 0.94,
      "type": "preference",
      "valid_from": "2026-06-19T00:00:00Z",
      "valid_until": null
    }
  ],
  "total": 1
}

What just happened?

search runs three retrieval lanes in parallel — BM25 keyword, vector similarity, and graph traversal — then merges the results with Reciprocal Rank Fusion. The score (0–1) reflects combined relevance across all three lanes. valid_until: null means the fact is currently true (no contradiction has overridden it yet).

Ask a synthesized question

For agent-style Q&A, skip search and go straight to ask. MemHQ retrieves, synthesizes, and returns an answer with citations in a single call — no LLM invocation needed on your side.

answer = client.ask(
    user_id="user_42",
    question="If I'm making them coffee, how should I prepare it?",
)

print(answer.answer)
# → "Brew a dark roast with no sugar."

for citation in answer.citations:
    print(f"  [{citation.score:.2f}] {citation.content}")
# →   [0.94] Prefers dark roast coffee, no sugar

Full response:

{
  "answer": "Brew a dark roast with no sugar.",
  "citations": [
    {
      "memory_id": "m_xyz123",
      "content": "Prefers dark roast coffee, no sugar",
      "score": 0.94,
      "valid_from": "2026-06-19T00:00:00Z"
    }
  ],
  "question_mode": "lookup"
}

What just happened?

ask is a convenience wrapper around the full retrieve → ground → synthesize pipeline:

  1. Retrieve — same hybrid search as search, returning the top-k memories
  2. Ground — memories are packed into a grounding prompt: "Answer ONLY using these facts. Cite each one."
  3. Synthesize — an LLM generates the answer, tagging each claim with the memory ID it came from

question_mode: "lookup" means MemHQ classified this as a factual retrieval question. Other modes are aggregate (counting questions) and temporal (questions about what was true at a specific time).

Complete working example

Here's the full script from this guide in one place:

import time
from memhq import MemoryClient

client = MemoryClient()  # MEMHQ_API_KEY from environment

# 1. Add a fact
client.add(
    user_id="user_42",
    messages=[
        {"role": "user", "content": "I prefer dark roast coffee, no sugar."},
    ],
)

# Give the async extraction pipeline a moment to finish
time.sleep(3)

# 2. Search it back
results = client.search(
    user_id="user_42",
    query="coffee preference",
    limit=5,
)
for hit in results.memories:
    print(f"search  {hit.score:.2f}  {hit.content}")

# 3. Ask a synthesized question
answer = client.ask(
    user_id="user_42",
    question="If I'm making them coffee, how should I prepare it?",
)
print(f"ask     {answer.answer}")
for c in answer.citations:
    print(f"        [{c.score:.2f}] {c.content}")

The time.sleep(3) / setTimeout(3000) is only needed in scripts where you add and immediately search in the same run. In production, facts are added during one conversation turn and searched in the next — the async extraction window is invisible.

Where to go next