> your AI agent picks dependencies from memory; give it dated facts — try starlog.dev ↗ vet your agent's deps ↗ vibe-coding is fine. vibe-importing isn’t. — try starlog.dev ↗ vibe-importing isn’t fine ↗ your agent has never seen your private packages — try starlog.dev ↗ facts for private packages ↗ a linter for the dependencies your AI agent picks — try starlog.dev ↗ a linter for agent deps ↗

Back to Articles

Cognee: Building Stateful AI Agents with Postgres-Backed Knowledge Graphs

[ View on GitHub ]

Cognee: Building Stateful AI Agents with Postgres-Backed Knowledge Graphs

Hook

Every RAG developer eventually faces the same infrastructure nightmare: Neo4j for graphs, Pinecone for vectors, Redis for sessions, and Postgres for metadata. Cognee runs all four on a single Postgres instance—and benchmarks show it's actually faster.

Context

Conversational AI agents have a memory problem that vector databases alone can't solve. Traditional RAG systems store document embeddings in Pinecone or Weaviate, retrieve similar chunks, and inject them into prompts. This works for single-turn questions but falls apart when agents need to remember what happened three conversations ago, reason about relationships between entities, or maintain context across browser refreshes.

The standard solution is a Frankenstein stack: Neo4j for entity relationships, a vector database for semantic search, Redis for session state, and Postgres for everything else. This works but creates operational hell—four databases to monitor, four failure modes, and complex synchronization logic when a user's conversation updates both the session cache and the knowledge graph. Cognee emerged from this pain point: what if you could give agents persistent, graph-structured memory without running a separate graph database? The key insight is that Postgres with pgvector can handle both vector similarity search and graph traversals using recursive CTEs, eliminating three databases from your stack while preserving the semantic and structural reasoning capabilities agents need.

Technical Insight

Postgres Storage

Processing

Ingest

Chunk & Embed

Extract Entities

Graph Construction

Write

Write

Write

recall API

1. Fast Cache

2. Similarity

3. Multi-hop

Sync on End

Results

Documents

remember API

Async Pipeline

LLM Provider

Knowledge Graph

Session Cache

Vector Embeddings

User Query

Auto Router

Agent Response

System architecture — auto-generated

Cognee's architecture revolves around three memory tiers that cascade during retrieval: session memory (write-through cache for active conversations), vector memory (semantic similarity search), and graph memory (entity relationships and multi-hop reasoning). The brilliance is in the implementation—all three tiers run on Postgres with pgvector, using custom SQL tables instead of dedicated graph and vector databases.

Here's how you build memory into an agent:

import cognee

# Configure to use local Postgres with pgvector
await cognee.config.set_llm_provider("openai")
await cognee.config.set_vector_db_provider("postgres")
await cognee.config.set_graph_db_provider("postgres")

# Ingest documents into persistent memory
await cognee.add(
    "product_docs.pdf",
    dataset_id="customer_support"
)

# Build the knowledge graph
await cognee.cognify()

# Later, in a conversation loop:
session_id = "user_12345_conversation_7"

# Query cascades: session cache → vectors → graph
results = await cognee.recall(
    "What features did we discuss last week?",
    session_id=session_id
)

# Update session memory (writes immediately, syncs to graph async)
await cognee.remember(
    "User prefers API-first integrations",
    session_id=session_id
)

The cognify() call triggers a multi-stage pipeline that's more sophisticated than typical RAG indexing. First, it chunks documents using semantic boundaries (not just fixed 512-token windows). Then it generates embeddings via your chosen LLM provider and stores them in Postgres tables with pgvector indexes. The critical third stage is entity extraction: Cognee prompts the LLM to identify entities and relationships based on a predefined ontology, then materializes these as graph nodes and edges in SQL tables. This grounds the knowledge graph in your domain model—entities aren't free-form LLM hallucinations but constrained to your schema.

The auto-routing retrieval in recall() is where the architecture shines. When you query, Cognee first checks the session cache—a fast key-value store scoped to the current conversation. If it misses, it falls through to vector similarity search using pgvector's cosine distance operators. If that returns low-confidence results, it pivots to graph traversal: starting from entities mentioned in the query, it walks relationships using recursive SQL CTEs to find semantically distant but structurally connected information. This happens automatically—no manual decision about whether to use semantic or graph search.

The session memory abstraction solves a real cold-start problem. When an agent conversation begins, there's no history to inject into the prompt. Traditional systems either start from zero or try to preload "relevant" context, which often misses what actually matters. Cognee's remember() writes to session cache immediately (sub-millisecond latency), then asynchronously syncs to the permanent graph when the session ends. Early conversation turns get instant context from the cache, while long-term memory accumulates in the graph for future sessions.

One underappreciated feature is the improve() method, which treats the knowledge graph as a materialized view over source documents. When your ontology evolves—say you add a new entity type or relationship—you can call cognee.improve() to reprocess existing data without manual migration scripts. It reruns the extraction pipeline with the updated schema, rebuilding graph relationships while preserving vector embeddings and raw documents. This is powerful for iterative development but comes with a cost we'll discuss in the gotcha section.

For Claude Code integration, Cognee provides a Model Context Protocol server that hooks into Claude's lifecycle events:

# MCP server captures tool execution traces
@mcp.event("PostToolUse")
async def capture_tool_result(tool_name, result, session_id):
    await cognee.remember(
        f"Tool {tool_name} returned: {result}",
        session_id=session_id,
        metadata={"tool": tool_name, "timestamp": datetime.now()}
    )

@mcp.event("SessionStart")
async def load_context(session_id):
    history = await cognee.recall(
        "What did we work on recently?",
        session_id=session_id,
        limit=10
    )
    return {"context": history}

This captures what tools Claude executed, what code it wrote, and what you discussed across context window resets—addressing the stateless nature of Claude's execution model. The memory persists even when you close and reopen the editor, giving Claude continuity that feels remarkably close to working with a human who actually remembers previous sessions.

Gotcha

The elephant in the room is cost and performance at scale. Cognee's entity extraction calls your LLM provider multiple times per document chunk—once to identify entities, again for relationships, and potentially more for classification. For a 100-page PDF, you might burn through 50,000+ tokens just for ingestion, which translates to $0.50-$2.00 per document with GPT-4. There's no fallback to cheaper rule-based NER or smaller models like spaCy for common entity types (dates, people, organizations). If you're ingesting thousands of documents, the API bills add up fast, and there's no batching or caching to amortize costs.

The Postgres-backed graph layer is clever for simple use cases but hits limits quickly. You can't write Cypher queries—graph traversals are manual recursive CTEs that become unwieldy for complex patterns like "find all entities three hops away that share at least two relationship types with the query entity." There's no graph algorithms library, so forget PageRank, community detection, or shortest-path optimizations. For knowledge graphs with millions of entities and complex reasoning requirements, you'll feel the absence of Neo4j's mature query planner and algorithm ecosystem.

The improve() method is all-or-nothing: changing your ontology triggers a full reprocessing of every document in the dataset. There's no incremental update mechanism or delta computation. If you have a 10GB knowledge base and realize you need to add a "department" entity type, you're reprocessing everything. This makes iterative schema development painful in production and creates downtime windows that scale linearly with data size.

Verdict

Use if: You're building conversational agents or AI assistants that need persistent memory across sessions, you want to avoid the operational complexity of running Neo4j+Pinecone+Redis+Postgres, your knowledge base is under 1 million entities, and you prioritize deployment simplicity over raw query performance. Cognee is genuinely excellent for prototypes, internal tools, and products where the cost of LLM-based ingestion is acceptable relative to the value of structured memory. The session memory abstraction alone justifies adoption if you're building anything conversational.

Skip if: You need sub-200ms retrieval on datasets with 10M+ documents (dedicated vector databases win on performance), you require complex graph queries like pattern matching or graph algorithms (Neo4j is irreplaceable here), you're ingesting thousands of documents daily and can't stomach the LLM API costs, or you're using agent frameworks other than Claude Code (the MCP integration doesn't generalize to LangChain or AutoGPT without custom work). Also skip if you need incremental schema updates—the full-reprocessing requirement makes production ontology evolution impractical at scale.