Nectr uses Mem0 to remember project patterns, architectural decisions, and developer-specific habits across PRs. Unlike the Neo4j knowledge graph (which tracks structural relationships), Mem0 stores semantic memories — natural language insights that Claude extracts after each review.Every time Nectr reviews a PR, it:
Searches Mem0 for relevant memories to include as context
Extracts new learnings from the completed review and stores them for future PRs
Mem0 memories are scoped by project_id=repo and user_id=developer. This means each repo has its own context, and each developer has a personal profile within that repo.
Nectr extracts six types of memories (defined in memory_extractor.py:17-24):
project_pattern
Architectural patterns confirmed by this PRExample: “This repo uses dependency injection via FastAPI Depends for database sessions”
decision
Approaches approved or rejectedExample: “PR #42: Rejected eager-loading users with all posts due to N+1 risk; approved lazy loading”
developer_pattern
Recurring issues for this developerExample: “@alice often forgets to close database connections in exception handlers”
developer_strength
Things this developer does wellExample: “@bob consistently writes comprehensive test coverage for edge cases”
risk_module
Fragile or security-critical filesExample: “app/auth/token_service.py is security-critical; changes here need extra scrutiny”
contributor_profile
Aggregated profile for this developerExample: “@charlie works on backend services (auth, payments). Strengths: API design, testing. Feedback: occasionally misses edge cases in error handling. Languages: Python, TypeScript.”
When building context for a new PR, Nectr queries Mem0 for relevant memories.
# app/services/context_service.py:72-84mem0_results = await asyncio.gather( memory_adapter.search_relevant( repo=repo_full_name, query=query, # built from PR title + description + file paths developer=None, top_k=12 ), memory_adapter.search_relevant( repo=repo_full_name, query="Developer patterns, strengths, recurring issues", developer=author, top_k=5, ) if author else _noop(), return_exceptions=True,)
Query construction (from context_service.py:74):
query_parts = [pr_title or "", (pr_description or "")[:300], ", ".join(file_paths[:10])]query = " ".join(q for q in query_parts if q).strip() or "Project context, rules, patterns"
Nectr runs two parallel searches:
Project memories: Patterns/decisions relevant to this PR’s topic and files
Developer memories: Patterns/strengths specific to the PR author
The retrieved memories are serialized and injected into Claude’s prompt.
# app/services/context_service.py:95-126lines: list[str] = []if project_memories: lines.append("PROJECT INTELLIGENCE:") for m in project_memories[:10]: content = m.get("memory", m.get("content", "")) if content: lines.append(f"- {content}")if developer_memories: lines.append("") lines.append(f"DEVELOPER CONTEXT ({author}):") for m in developer_memories[:5]: content = m.get("memory", m.get("content", "")) if content: lines.append(f"- {content}")serialized = "\n".join(lines) if lines else ""
Example serialized context:
PROJECT INTELLIGENCE:- This repo uses FastAPI with async SQLAlchemy for database operations- Authentication uses JWT tokens stored in httpOnly cookies- All API routes require dependency injection for db sessionsDEVELOPER CONTEXT (alice):- @alice works on auth services and payment integrations- Strengths: API design, comprehensive test coverage- Recurring issue: sometimes forgets to handle 401 responses in frontend
# app/services/ai_service.py:38-51{ "name": "search_project_memory", "description": ( "Search the project's accumulated knowledge for patterns, past decisions, " "and known risks relevant to your query. Use this when the diff touches " "an area you want to cross-check against historical context." ), "input_schema": { "type": "object", "properties": { "query": {"type": "string", "description": "What to look up, e.g. 'rate limiting strategy' or 'auth token handling'"}, }, "required": ["query"], },}
# app/services/ai_service.py:53-67{ "name": "search_developer_memory", "description": ( "Search what Nectr has learned about a specific developer — " "their recurring patterns, known strengths, and past issues. " "Use this when the PR author is known and you want to tailor feedback." ), "input_schema": { "type": "object", "properties": { "developer": {"type": "string", "description": "GitHub username"}, "query": {"type": "string", "description": "What aspect to retrieve, e.g. 'error handling habits'"}, }, "required": ["developer", "query"], },}
EXTRACTION_PROMPT = """Given this completed PR review, extract any learnings as structured memories.Output each memory as a JSON object on its own line. Only output valid JSON lines, no other text.Format per line: {{"memory_type": "...", "title": "...", "content": "...", "developer": "..."}}memory_type must be one of:- project_pattern: Architectural patterns confirmed by this PR- decision: Approaches approved or rejected (include PR number in content)- developer_pattern: Recurring issues for this developer- developer_strength: Things this developer does well- risk_module: Files that appear fragile or security-critical- contributor_profile: Updated aggregated profile for this developerFor contributor_profile: - content must be a single comprehensive summary covering: PR topics worked on, files commonly touched, types of feedback received, strengths, languages used - Always emit exactly one contributor_profile entry per review - developer field must be "{author}" - Absorb the existing profile info below into your updated summary (don't lose old data)Only extract 2-5 memories total (always include exactly one contributor_profile).Be concise. Each content: 1-2 sentences max.PR #{pr_number} by {author} on {repo}Title: {title}Files changed: {files}Languages detected: {languages}Existing contributor profile for {author}:{existing_profile}Review:{review}"""
Example Claude output:
{"memory_type": "project_pattern", "title": "Async SQLAlchemy", "content": "This repo uses async SQLAlchemy with dependency injection for database sessions", "developer": ""}{"memory_type": "decision", "title": "Rejected eager loading", "content": "PR #42: Rejected eager-loading users with all posts due to N+1 risk; approved lazy loading with explicit joins", "developer": ""}{"memory_type": "developer_strength", "title": "Test coverage", "content": "@alice consistently writes comprehensive test coverage for edge cases", "developer": "alice"}{"memory_type": "contributor_profile", "title": "alice profile", "content": "@alice works on backend services (auth, payments). Strengths: API design, comprehensive testing. Feedback: occasionally misses edge cases in error handling. Languages: Python, TypeScript. Files: app/auth/*, app/payments/*.", "developer": "alice"}
If Mem0 is not configured (no MEM0_API_KEY), all operations no-op silently.
# app/services/memory_adapter.py:20-33def _get_client(): """Get or create Mem0 client. Returns None if not configured.""" global _mem0_client if not settings.MEM0_API_KEY: return None if _mem0_client is None: try: from mem0 import MemoryClient _mem0_client = MemoryClient(api_key=settings.MEM0_API_KEY.strip()) logger.info("Mem0 client initialized") except Exception as e: logger.warning(f"Mem0 client init failed: {e}") return None return _mem0_client
POST /api/v1/memoryContent-Type: application/json{ "repo": "owner/repo", "content": "Custom rule: All mutations must be wrapped in database transactions", "memory_type": "project_pattern", "developer": null}
Special behavior: contributor_profile memories are additive — each new PR absorbs the existing profile and updates it.
# app/services/memory_extractor.py:86-102existing_profile = "No profile yet."if author: try: profile_hits = await memory_adapter.search_relevant( repo=repo_full_name, query="Contributor profile", developer=author, top_k=3, ) for hit in profile_hits: meta = hit.get("metadata") or {} if meta.get("memory_type") == "contributor_profile": existing_profile = hit.get("memory", hit.get("content", ""))[:400] break except Exception as e: logger.debug(f"Could not fetch existing profile for {author}: {e}")
The extraction prompt instructs Claude to absorb the old profile into the new one, so historical context isn’t lost.
{"memory_type": "project_pattern", "content": "Uses JWT tokens stored in httpOnly cookies for auth"}{"memory_type": "contributor_profile", "content": "@alice works on auth services. Strengths: API design. Languages: Python."}
Project memory: “Uses JWT tokens stored in httpOnly cookies for auth”
Developer memory: “@alice works on auth services. Strengths: API design.”
9
Extracted memories:
10
{"memory_type": "project_pattern", "content": "Payment webhooks use HMAC-SHA256 signature verification"}{"memory_type": "contributor_profile", "content": "@alice works on auth and payment services. Strengths: API design, webhook security. Languages: Python. Files: app/auth/*, app/payments/*."}
11
PR #3: Auth Bug Fix
12
Files changed: app/auth/token_service.py
13
Context fetched:
14
Project memory: “Uses JWT tokens stored in httpOnly cookies for auth”
Project memory: “Payment webhooks use HMAC-SHA256 signature verification”
Developer memory: “@alice works on auth and payment services. Strengths: API design, webhook security.”
15
Extracted memories:
16
{"memory_type": "developer_pattern", "content": "@alice occasionally forgets to handle token expiry in edge cases"}{"memory_type": "risk_module", "content": "app/auth/token_service.py is security-critical; changes need extra scrutiny"}{"memory_type": "contributor_profile", "content": "@alice works on auth and payment services. Strengths: API design, webhook security. Feedback: occasionally misses token expiry edge cases. Languages: Python. Files: app/auth/*, app/payments/*."}
Result: By PR #3, Nectr has built a rich context about the project’s auth patterns and Alice’s strengths/weaknesses. Future reviews will be more tailored.