Cookbook — 8 recipes, one afternoon.
Drop-in patterns for the most common memory-enabled builds. Each recipe is under 30 lines.
Every recipe. Copy, paste, ship.
Replace rem_... with your real key. Replace sk-... or other provider keys with yours. Everything else works as written.
Every user message gets remembered. Before each response, retrieve relevant context. Over time, the bot learns the user.
import openai, requests
OPENAI_KEY = "sk-..."
REM_KEY = "rem_..."
def chat(user_id, msg):
ctx = requests.post("https://remlabs.ai/v1/memory/ask",
headers={"Authorization": f"Bearer {REM_KEY}"},
json={"question": msg, "namespace": user_id}).json().get("answer", "")
resp = openai.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": f"Context: {ctx}"},
{"role": "user", "content": msg}
]).choices[0].message.content
requests.post("https://remlabs.ai/v1/memory-set",
headers={"Authorization": f"Bearer {REM_KEY}"},
json={"key": f"turn-{hash(msg)}", "value": f"{msg} → {resp}", "namespace": user_id})
return resp
namespace isolates memories; /memory/ask synthesizes with +15.33pp SWE-bench Lite lift (n=150, p<0.05); /memory-set stores the turn for next time.A research agent that remembers every finding. Stop re-researching.
import Anthropic from "@anthropic-ai/sdk";
import { REM } from "@remlabs/sdk";
const claude = new Anthropic({ apiKey: process.env.ANTHROPIC_KEY });
const rem = new REM({ apiKey: process.env.REM_KEY });
async function research(topic: string) {
const prior = await rem.memory.ask({ question: topic, namespace: "research" });
const msg = await claude.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user",
content: `Prior findings: ${prior.answer}\n\nDeeper dive on: ${topic}` }]
});
const finding = msg.content[0].type === "text" ? msg.content[0].text : "";
await rem.memory.set({
key: `research-${Date.now()}`,
value: finding,
namespace: "research",
metadata: { topic }
});
return finding;
}
Accept any text blob, store it structured. Full text search returns ranked hits.
# Write
curl -X POST https://remlabs.ai/v1/memory-set \
-H "Authorization: Bearer rem_..." \
-H "Content-Type: application/json" \
-d '{"key":"note-'"$(date +%s)"'","value":"Your note body","namespace":"user-42"}'
# Search
curl -X POST https://remlabs.ai/v1/memory-search \
-H "Authorization: Bearer rem_..." \
-H "Content-Type: application/json" \
-d '{"query":"what did I write about Q2 OKRs","namespace":"user-42","limit":5}'
Get pinged the moment a memory enters or changes.
from flask import Flask, request
app = Flask(__name__)
@app.post("/rem-webhook")
def on_event():
ev = request.json
if ev["event"] == "memory.contradiction":
notify_slack(f"Contradiction: {ev['memory_a']} vs {ev['memory_b']}")
elif ev["event"] == "dream.complete":
email_briefing(ev["summary"])
return {"ok": True}
curl -X POST https://remlabs.ai/v1/webhooks \
-H "Authorization: Bearer rem_..." \
-d '{"url":"https://yourapp.com/rem-webhook","events":["memory.set","dream.complete","memory.contradiction"]}'
Three agents share one namespace. All can read, each can write to its own scope.
import requests
def agent_write(agent_id, key, value):
requests.post("https://remlabs.ai/v1/memory-set",
headers={"Authorization": f"Bearer {REM_KEY}"},
json={"key": key, "value": value,
"namespace": "team-alpha",
"metadata": {"agent": agent_id}})
agent_write("researcher", "f-1", "Found: Q3 churn up 8%.")
agent_write("analyst", "a-1", "Cause: onboarding dropoff at step 3.")
agent_write("writer", "w-1", "Draft post: 'Why step 3 broke.'")
# Any agent asks:
ans = requests.post("https://remlabs.ai/v1/memory/ask",
headers={"Authorization": f"Bearer {REM_KEY}"},
json={"question": "What's our Q3 churn story?", "namespace": "team-alpha"}).json()
metadata = team hive without custom plumbing.Pull the sub-graph around a concept.
curl -X POST https://remlabs.ai/v1/memory/graph-query \
-H "Authorization: Bearer rem_..." \
-H "Content-Type: application/json" \
-d '{"seed":"Q3 churn","depth":2,"namespace":"team-alpha"}'
Returns nodes + edges. Render with your favorite graph viz (Cytoscape, D3, VisNetwork).
Trigger consolidation manually. Pick specific strategies.
const job = await rem.dream.start({
namespace: "team-alpha",
strategies: ["contradict", "synthesize", "forecast"],
task: "Why is our Q3 churn up?"
});
// Poll
let status = job;
while (status.status !== "complete") {
await new Promise(r => setTimeout(r, 2000));
status = await rem.dream.status(job.id);
}
console.log(`${status.insights} insights emitted.`);
Ingest a folder of markdown into REM in one call.
import os, glob, requests
notes = []
for path in glob.glob("./vault/**/*.md", recursive=True):
with open(path) as f:
notes.append({
"value": f.read(),
"namespace": "obsidian",
"metadata": {"source": "obsidian", "path": path}
})
# Batch-send
for chunk in [notes[i:i+100] for i in range(0, len(notes), 100)]:
r = requests.post("https://remlabs.ai/v1/memory/store-batch",
headers={"Authorization": f"Bearer {REM_KEY}"},
json={"items": chunk}).json()
print(f"stored: {r.get('count', 0)}")
store-batch accepts up to 1000 items per call. Nightly Dream Engine consolidates the entire vault. Critical: items MUST use field value, not content or text. Other fields are silently dropped by the backend.Three traps everyone hits on day one.
All three are silent failures that return HTTP 200. Watch for them.
content instead of value in store-batch — items silently drop.value. The server accepts content and text as payload keys with a 200 response, but the items never hit the store. Swap the key name and retry./memory-set before email confirm — returns 403 scope_denied./auth/signup (password) which returns a full-scope key immediately. Programmatic bot-signup keys stay provisional until the confirmation link is clicked./v1/memory/dream/status/{id} instead of blocking. Cycle duration scales with graph size and strategy count. Typical namespace + 3 strategies = 20–45s.