Contextify - Implementasyon Planı
Defines a multi-phase plan for a local AI agent memory system with Go MCP server, PostgreSQL+pgvector, Ollama embeddings, and a web UI.
What this file does
Defines a multi-phase plan for a local AI agent memory system with Go MCP server, PostgreSQL+pgvector, Ollama embeddings, and a web UI.
When to use it
- Building a shared memory layer for multiple local AI agents like Claude Code, Cursor, Gemini
- Implementing a Docker-based vector search backend with TTL-based memory management
- Creating an MCP server with Streamable HTTP transport for agent tool integration
- Migrating from a DuckDB-based memory system to PostgreSQL+pgvector
Assumes this stack
Contextify - Implementasyon Planı
Özet
Tüm local AI agent'lar (Claude Code, Cursor, Gemini, Antigravity, vb.) için merkezi bir memory sistemi. Docker üzerinde çalışan Go MCP Server + REST API, PostgreSQL + pgvector DB, Ollama embedding, ve Web UI.
Faz 1: Proje İskeleti ve Docker Altyapısı
1.1 Go Proje Yapısı
contextify/
├── cmd/
│ └── server/
│ └── main.go # Entry point
├── internal/
│ ├── config/
│ │ └── config.go # Env/YAML config loader
│ ├── db/
│ │ ├── postgres.go # PostgreSQL connection + migrations
│ │ └── migrations/
│ │ └── 001_init.sql # Initial schema (pgvector, tables, indexes)
│ ├── embedding/
│ │ └── ollama.go # Ollama embedding client
│ ├── memory/
│ │ ├── model.go # Memory struct, types, enums
│ │ ├── repository.go # DB CRUD operations
│ │ ├── service.go # Business logic (TTL, promotion, search)
│ │ └── service_test.go
│ ├── mcp/
│ │ ├── server.go # MCP server setup (tools registration)
│ │ ├── tools.go # MCP tool handlers
│ │ └── transport.go # Streamable HTTP + stdio transport
│ ├── api/
│ │ ├── router.go # REST API router (chi/echo)
│ │ ├── handlers.go # REST endpoint handlers
│ │ └── middleware.go # Logging, CORS, request ID
│ └── scheduler/
│ └── cleanup.go # TTL expiry background job
├── web/ # Web UI (ayrı SPA)
│ ├── package.json
│ ├── src/
│ └── ...
├── docker-compose.yml
├── Dockerfile # Multi-stage Go build
├── Dockerfile.web # Web UI build
├── go.mod
├── go.sum
├── config.yaml # Default config
└── README.md
1.2 Docker Compose
services:
postgres:
image: pgvector/pgvector:pg16
ports: ["5432:5432"]
volumes: [pgdata:/var/lib/postgresql/data]
environment:
POSTGRES_DB: contextify
POSTGRES_USER: contextify
POSTGRES_PASSWORD: contextify_local
healthcheck: pg_isready
ollama:
image: ollama/ollama:latest
ports: ["11434:11434"]
volumes: [ollama_models:/root/.ollama]
deploy:
resources:
reservations:
devices:
- capabilities: [gpu] # GPU varsa kullan
server:
build: .
ports: ["8420:8420"]
depends_on: [postgres, ollama]
environment:
DATABASE_URL: postgres://contextify:contextify_local@postgres:5432/contextify
OLLAMA_URL: http://ollama:11434
EMBEDDING_MODEL: nomic-embed-text
web:
build:
dockerfile: Dockerfile.web
ports: ["3000:3000"]
depends_on: [server]
1.3 Veritabanı Schema (001_init.sql)
CREATE EXTENSION IF NOT EXISTS vector;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE TYPE memory_type AS ENUM (
'solution', 'problem', 'code_pattern', 'fix',
'error', 'workflow', 'decision', 'general'
);
CREATE TYPE memory_scope AS ENUM ('global', 'project');
CREATE TABLE memories (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
content TEXT NOT NULL,
summary TEXT,
embedding vector(768),
type memory_type NOT NULL DEFAULT 'general',
scope memory_scope NOT NULL DEFAULT 'project',
project_id TEXT,
agent_source TEXT, -- claude-code, cursor, gemini, etc.
title TEXT NOT NULL,
tags TEXT[] DEFAULT '{}',
importance REAL NOT NULL DEFAULT 0.5 CHECK (importance >= 0 AND importance <= 1),
ttl_seconds INTEGER, -- NULL = permanent (long-term)
access_count INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
expires_at TIMESTAMPTZ -- computed from created_at + ttl
);
-- Relationships table
CREATE TABLE memory_relationships (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
from_memory_id UUID NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
to_memory_id UUID NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
relationship TEXT NOT NULL, -- SOLVES, CAUSES, RELATED_TO, etc.
strength REAL DEFAULT 0.5,
context TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(from_memory_id, to_memory_id, relationship)
);
-- HNSW index for vector similarity search (better for dynamic data)
CREATE INDEX idx_memories_embedding ON memories
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- B-tree indexes for filtering
CREATE INDEX idx_memories_type ON memories(type);
CREATE INDEX idx_memories_scope ON memories(scope);
CREATE INDEX idx_memories_project ON memories(project_id);
CREATE INDEX idx_memories_importance ON memories(importance);
CREATE INDEX idx_memories_expires ON memories(expires_at) WHERE expires_at IS NOT NULL;
CREATE INDEX idx_memories_tags ON memories USING gin(tags);
CREATE INDEX idx_memories_created ON memories(created_at DESC);
Neden HNSW (IVFFlat değil):
- Dinamik data: Memory sürekli ekleniyor/siliniyor, HNSW incremental index destekliyor
- IVFFlat centroid'leri statik, data değişince rebuild gerekiyor
- HNSW recall'u daha yüksek (~0.998 vs IVFFlat ~0.95)
- Build süresi uzun ama memory sistemi için kabul edilebilir
Faz 2: Go Server Core
2.1 Memory Model (internal/memory/model.go)
Memorystruct: DB tablosuyla 1:1 mapMemoryRelationshipstructStoreRequest,SearchRequest,UpdateRequestDTO'larSearchResultstruct (memory + similarity score)
2.2 Embedding Client (internal/embedding/ollama.go)
- Ollama
/api/embedendpoint'ine HTTP call - Model:
nomic-embed-text(768 dim) - Batch embedding desteği (birden fazla text'i tek request'te)
- Connection pooling + retry logic
- Startup'ta model pull kontrolü (
ollama pull nomic-embed-text)
2.3 Repository Layer (internal/memory/repository.go)
Store(ctx, memory)→ INSERT + embedding oluşturGet(ctx, id)→ tek memory getirUpdate(ctx, id, updates)→ UPDATE + re-embed if content changedDelete(ctx, id)→ CASCADE delete (relationships dahil)Search(ctx, query, filters)→ Hybrid search:- Vector similarity:
embedding <=> query_embedding(cosine distance) - Keyword match:
to_tsvector(content) @@ plainto_tsquery(query) - Tag filter:
tags @> ARRAY[...] - Combined score:
0.7 * vector_score + 0.3 * keyword_score
- Vector similarity:
IncrementAccess(ctx, id)→ access_count++ ve expires_at uzatFindExpired(ctx)→ TTL'i dolmuş memory'lerPromoteToLongTerm(ctx, id)→ ttl_seconds = NULL, expires_at = NULL
2.4 Service Layer (internal/memory/service.go)
- Store: embedding oluştur → DB'ye kaydet → TTL hesapla
- Search: query embed → hybrid search → access_count++ → sonuçları döndür
- Auto-promote logic:
importance >= 0.8→ direkt long-termaccess_count >= 5→ auto-promote to long-term- Her erişimde
expires_at += original_ttl * 0.5(decay extend)
- Recall: semantic search + tag filter + type filter
2.5 Scheduler (internal/scheduler/cleanup.go)
- Her 5 dakikada bir çalışan goroutine
DELETE FROM memories WHERE expires_at IS NOT NULL AND expires_at < NOW()- Log: kaç memory temizlendi
Faz 3: MCP Server + REST API
3.1 MCP Server (internal/mcp/)
SDK: github.com/modelcontextprotocol/go-sdk (resmi SDK)
Transport:
- Streamable HTTP (yeni standart, SSE deprecated):
/mcpendpoint'i - Stdio da desteklenecek (Claude Code stdio transport kullanabilir)
MCP Tools (memorygraph-uyumlu API + ek özellikler):
| Tool | Açıklama |
|---|---|
store_memory | Yeni memory kaydet (auto-embed) |
recall_memories | Semantic search + filters |
search_memories | Advanced search (tags, type, importance filter) |
get_memory | ID ile memory getir |
update_memory | Memory güncelle |
delete_memory | Memory sil |
create_relationship | İki memory arasında ilişki kur |
get_related_memories | İlişkili memory'leri bul |
get_context | Proje bazlı tüm önemli memory'leri getir (session başlangıcı için) |
promote_memory | Manual olarak long-term'e promote et |
3.2 REST API (internal/api/)
MCP desteklemeyen agent'lar için (Gemini, Antigravity, vb.):
POST /api/v1/memories → Store
GET /api/v1/memories/:id → Get
PUT /api/v1/memories/:id → Update
DELETE /api/v1/memories/:id → Delete
POST /api/v1/memories/search → Search (body: query, filters)
POST /api/v1/memories/recall → Semantic recall
POST /api/v1/relationships → Create relationship
GET /api/v1/memories/:id/related → Get related
GET /api/v1/stats → Memory stats (count by type, scope, etc.)
POST /api/v1/context/:project → Get project context
3.3 Dual Server Setup
// main.go
func main() {
// Config, DB, Ollama bağlantıları
// ...
// MCP Server (Streamable HTTP on /mcp)
mcpHandler := mcp.NewStreamableHTTPHandler(...)
// REST API (/api/v1/...)
apiRouter := api.NewRouter(memoryService)
// Combined HTTP server
mux := http.NewServeMux()
mux.Handle("/mcp", mcpHandler)
mux.Handle("/api/", apiRouter)
// Tek port: 8420
http.ListenAndServe(":8420", mux)
}
Faz 4: Web UI
4.1 Teknoloji
- Svelte (veya React) + Tailwind CSS
- REST API'yi kullanır (
http://localhost:8420/api/v1/) - Single-page app, Docker'da nginx ile serve edilir
4.2 Sayfalar
- Dashboard: Memory stats, son eklenenler, scope/type breakdown chart
- Memory Browser: Liste + filtreleme (type, scope, project, tags, importance)
- Memory Detail: İçerik, ilişkiler, erişim geçmişi
- Search: Semantic search arayüzü
- Graph View: Memory ilişkilerini görselleştir (d3.js force graph)
- Settings: Cleanup politikası, embedding model seçimi
Faz 5: Agent Entegrasyonu
5.1 Claude Code
// ~/.claude/settings.json → mcpServers
{
"contextify": {
"type": "streamableHttp",
"url": "http://localhost:8420/mcp"
}
}
Alternatif: stdio transport ile doğrudan binary çalıştırma.
5.2 Cursor
// .cursor/mcp.json
{
"mcpServers": {
"contextify": {
"url": "http://localhost:8420/mcp",
"transport": "streamable-http"
}
}
}
5.3 Gemini / Antigravity / Diğerleri
REST API kullanımı. Sistem prompt'a eklenir:
Memory API: http://localhost:8420/api/v1/
- Her session başında GET /api/v1/context/{project} çağır
- Önemli bilgileri POST /api/v1/memories ile kaydet
5.4 Memorygraph Migration
Mevcut memorygraph tablosu boş (Table Memory does not exist hatası alındı), dolayısıyla veri migration'a gerek yok. Ancak migration tool'u yine de yazılacak (gelecekte lazım olabilir):
- DuckDB dosyasını okuyup PostgreSQL'e aktaran CLI komutu
contextify migrate --from duckdb --file /path/to/memory.duckdb
Implementasyon Sırası
| Sıra | Faz | Tahmini Süre | Detay |
|---|---|---|---|
| 1 | Docker Compose + DB Schema | İlk | PostgreSQL, Ollama, migration |
| 2 | Go Project Skeleton | İlk | go mod, config, DB connection |
| 3 | Embedding Client | İlk | Ollama integration |
| 4 | Memory Repository + Service | Core | CRUD, hybrid search, TTL logic |
| 5 | MCP Server | Core | Tools, Streamable HTTP transport |
| 6 | REST API | Core | Chi router, handlers |
| 7 | Scheduler | Core | TTL cleanup goroutine |
| 8 | Web UI | Son | Svelte/React dashboard |
| 9 | Agent Config + Docs | Son | Entegrasyon kılavuzları |
Teknik Kararlar Özeti
| Karar | Seçim | Gerekçe |
|---|---|---|
| Dil | Go | Düşük resource, hızlı binary, resmi MCP SDK |
| DB | PostgreSQL 16 + pgvector | Production-grade, native vector search, TTL desteği |
| Vector Index | HNSW | Dinamik data için ideal, yüksek recall |
| Embedding | Ollama + nomic-embed-text (768 dim) | Local, ücretsiz, gizlilik dostu |
| MCP Transport | Streamable HTTP | Yeni standart (SSE deprecated March 2025) |
| MCP SDK | modelcontextprotocol/go-sdk (resmi) | Official, Google co-maintained |
| REST Framework | chi veya stdlib | Hafif, Go idiomatik |
| Web UI | Svelte + Tailwind | Hafif, hızlı, tek dosya build |
| Auth | Yok (localhost only) | Local development, network dışına kapalı |
| Port | 8420 | MCP + REST aynı port |
What's inside
5 phases, 10 MCP tools, 12 REST endpoints, 3 Docker services, 1 database schema with 2 tables and 7 indexes
Change this for your project
- Replace
postgres://contextify:contextify_local@postgres:5432/contextifywith your own database URL - Replace
http://ollama:11434with your Ollama endpoint - Replace
nomic-embed-textwith your preferred embedding model - Replace
8420with your desired port number
Where it goes
Keep it in your repository where the agent or team that needs it will read it.
Worth borrowing
- Hybrid search combining vector similarity and keyword match with configurable weights
- Auto-promote logic that promotes memories to long-term based on importance or access count
- Dual server setup serving both MCP and REST API on a single port
Related Documents
Building SupportX AI Assist: A Multi-Agent IT Support System
Describes building a multi-agent IT support system with AutoGen, Azure AI Search, and Gemini embeddings for instant issue resolution and automatic escalation.
Intelligent Document Query Platform — GitHub-ready Low-Level Design (LLD)
Provides a copy-ready low-level design for a serverless document query platform with vector search and LLM integration.
Graph Matching with Topological Features
Teaches enhanced graph matching by combining spatial distances with node2vec and commute times embeddings, then applying the Hungarian algorithm.
Pulse — Life Cofounder | Build Log
Documents a full-stack monorepo that ingests LinkedIn and GitHub data, generates embeddings in-browser, and provides a RAG chat with an AI cofounder.