# Feather DB + Gemini: Give Google's AI Agents Persistent Memory > Gemini's API is stateless — every call starts cold. Feather DB fixes that. Here's how to build a Gemini chatbot that actually remembers across sessions, with typed memory graphs, adaptive decay, and fast cold load on Cloud Run. - **Category**: Deploy - **Read time**: 8 min read - **Date**: June 16, 2026 - **Author**: Ashwath (Founder, Feather DB) - **URL**: https://getfeather.store/theory/feather-db-gemini-agent-memory --- list[float]: """Embed text using Gemini Embedding 2 (768-dim, multimodal-capable).""" result = genai.embed_content( model=EMBED_MODEL, content=text, task_type=task, ) return result["embedding"] def numeric_id() -> int: """Generate a unique positive integer ID from a UUID.""" return uuid.uuid4().int >> 64 # top 64 bits → fits in int64 def store_turn( db: feather_db.DB, user_text: str, assistant_text: str, session_id: str, is_preference: bool = False, ) -> tuple[int, int]: """ Store one conversation turn as two linked nodes. - user message → half_life determined by is_preference - assistant text → linked via same_session edge Returns (user_id, assistant_id). """ timestamp = int(time.time()) # Embed both sides user_vec = embed(user_text, task="RETRIEVAL_DOCUMENT") asst_vec = embed(assistant_text, task="RETRIEVAL_DOCUMENT") # Node IDs user_id = numeric_id() asst_id = numeric_id() # Importance: preference memories are more important importance = 0.9 if is_preference else 0.6 # User message node user_meta = feather_db.Metadata() user_meta.importance = importance user_meta.set_attribute("role", "user") user_meta.set_attribute("text", user_text[:512]) # store preview user_meta.set_attribute("session_id", session_id) user_meta.set_attribute("timestamp", str(timestamp)) user_meta.set_attribute("memory_type", "preference" if is_preference else "fact") db.add(id=user_id, vec=user_vec, meta=user_meta) # Assistant response node asst_meta = feather_db.Metadata() asst_meta.importance = importance * 0.85 # slightly lower — it's the answer, not the fact asst_meta.set_attribute("role", "assistant") asst_meta.set_attribute("text", assistant_text[:512]) asst_meta.set_attribute("session_id", session_id) asst_meta.set_attribute("timestamp", str(timestamp)) asst_meta.set_attribute("memory_type", "preference" if is_preference else "fact") db.add(id=asst_id, vec=asst_vec, meta=asst_meta) # Link user ↔ assistant from same turn db.link(from_id=user_id, to_id=asst_id, rel_type="same_session", weight=1.0) db.link(from_id=asst_id, to_id=user_id, rel_type="same_session", weight=1.0) return user_id, asst_id def retrieve_context( db: feather_db.DB, query: str, current_session_id: str, k: int = RETRIEVAL_K, ) -> str: """ Retrieve the most relevant past memories for a query. Uses two searches with different half_life values: - half_life=90 to surface long-term preferences - half_life=7 to surface recent facts Then deduplicates and formats as a context block. """ if db.size() == 0: return "" query_vec = embed(query, task="RETRIEVAL_QUERY") # Search 1: long-term preferences (slow decay) pref_results = db.search( vec=query_vec, k=k, half_life=90, time_weight=0.2, ) # Search 2: recent facts (fast decay) fact_results = db.search( vec=query_vec, k=k, half_life=7, time_weight=0.4, ) # Deduplicate by node ID and collect text seen_ids = set() memories = [] for result in pref_results + fact_results: node_id = result.id if node_id in seen_ids: continue seen_ids.add(node_id) role = result.meta.get_attribute("role") or "unknown" text = result.meta.get_attribute("text") or "" mem_type = result.meta.get_attribute("memory_type") or "fact" sess = result.meta.get_attribute("session_id") or "" if not text: continue # Skip assistant nodes from current session — they're already in context if role == "assistant" and sess == current_session_id: continue label = f"[past {mem_type} — {role}]" memories.append(f"{label} {text}") if not memories: return "" block = "\n".join(memories[:k]) return f"\n{block}\n" def is_preference_statement(text: str) -> bool: """ Heuristic: mark a turn as a preference if the user expresses a persistent preference or constraint. In production, ask Gemini to classify this. """ keywords = [ "prefer", "always", "never", "don't like", "hate", "love", "please don't", "instead of", "i want you to", "from now on", "remember that", "my name is", "i am a", "i work", ] lower = text.lower() return any(kw in lower for kw in keywords) def link_to_topic( db: feather_db.DB, new_user_id: int, query_vec: list[float], current_session_id: str, ) -> None: """ Find the most semantically similar past user message and link it via a same_topic edge. """ if db.size() list[float]: """Embed an image (+ optional caption) into the shared 768-dim space.""" content = [{"mime_type": "image/jpeg", "data": base64.b64encode(image_bytes).decode()}] if caption: content.append(caption) result = genai.embed_content( model=EMBED_MODEL, content=content, task_type="RETRIEVAL_DOCUMENT", ) return result["embedding"] # Store image memory alongside text memories — same index, same dim img_vec = embed_image(image_bytes, caption="Screenshot of user's dashboard error") img_id = numeric_id() img_meta = feather_db.Metadata() img_meta.importance = 0.75 img_meta.set_attribute("role", "user") img_meta.set_attribute("text", "Screenshot of dashboard error") img_meta.set_attribute("modality", "image") img_meta.set_attribute("session_id", session_id) db.add(id=img_id, vec=img_vec, meta=img_meta) db.save() ``` A text query for "the error I showed you last week" will surface this image node in the search results — because text and image vectors are in the same semantic space. --- ## The numbers that matter - **40× cheaper per query** vs sending full conversation history every call - **0.19ms p50 ANN latency** on 500K vectors — retrieval is not the bottleneck - **97.2% recall@10** — you're not losing relevant memories to index inaccuracy - **768 dimensions** — exact match between `gemini-embedding-exp-03-07` and Feather DB's default index, zero config - **One `.feather` file** — deploy anywhere, no vector database server to run --- ## What's next The pattern above is the foundation. From here you can: - Add a Gemini classification step to detect preference statements more reliably than the keyword heuristic - Use `context_chain()` to traverse `same_topic` edges for richer multi-hop context retrieval - Store tool call results as memories so the agent doesn't repeat API calls it already made - Run multiple agents sharing one `.feather` file as a shared memory layer (read-only for all but one writer) Install Feather DB: `pip install feather-db`. The `memory.feather` file is yours — no server, no cloud dependency, no vendor lock-in. ]]> --- *This is the machine-readable mirror of the theory post at [getfeather.store/theory/feather-db-gemini-agent-memory](https://getfeather.store/theory/feather-db-gemini-agent-memory). For the full Feather DB documentation, see [getfeather.store/llms-full.txt](https://getfeather.store/llms-full.txt).*