Skip to main content

System Architecture

Nectr is a full-stack AI agent with a FastAPI backend, Next.js frontend, and three key data stores:
The entire flow from webhook receipt to review comment takes 15-45 seconds depending on PR size and context complexity.

Core Components

FastAPI Backend

Python 3.14 + FastAPI + Uvicorn
  • Hosted on: Railway (auto-deploys from main branch)
  • Database: PostgreSQL (asyncpg + SQLAlchemy async)
  • Auth: GitHub OAuth → JWT httpOnly cookie
  • Workers: Background tasks via BackgroundTasks

Next.js Frontend

Next.js 15 + React 19 + TypeScript
  • Hosted on: Vercel
  • Styling: TailwindCSS 4 + custom dark theme
  • State: React Query (TanStack Query v5)
  • Auth: JWT cookie + withCredentials: true

Neo4j Graph

Stores structural code relationships
  • Nodes: Repository, File, PullRequest, Developer, Issue
  • Edges: CONTAINS, TOUCHES, AUTHORED_BY, CLOSES, CONTRIBUTED_TO
  • Queries: File experts, related PRs, code ownership

Mem0 Memory

Long-term semantic memory
  • Scoped by: project_id=repo, user_id=developer
  • Types: project_pattern, decision, developer_strength, risk_module, contributor_profile
  • API: v2 search with filters

Step-by-Step: PR Review Flow

1. GitHub Webhook Event

When a developer opens or updates a PR, GitHub sends a webhook:
POST https://your-backend.up.railway.app/api/v1/webhooks/github
X-GitHub-Event: pull_request
X-Hub-Signature-256: sha256=...

{
  "action": "opened" | "synchronize" | "reopened",
  "pull_request": {
    "number": 42,
    "title": "Add user authentication",
    "body": "Fixes #123\n\nImplements JWT-based auth...",
    "user": { "login": "alice" },
    "head": { "sha": "a1b2c3d4e5f6" }
  },
  "repository": { "full_name": "org/repo" }
}
Backend validation (app/api/v1/webhooks.py:30-80):
# 1. Verify HMAC-SHA256 signature
expected_sig = hmac.new(webhook_secret.encode(), body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(f"sha256={expected_sig}", signature):
    raise HTTPException(status_code=401, detail="Invalid signature")

# 2. Deduplicate (ignore duplicate events within 1 hour)
existing_event = await db.execute(
    select(Event).where(
        Event.repo_full_name == repo_full_name,
        Event.pr_number == pr_number,
        Event.event_type == action,
        Event.created_at > datetime.now() - timedelta(hours=1)
    )
)
if existing_event.scalar_one_or_none():
    return {"status": "duplicate", "message": "Event already processed"}

# 3. Create Event record
event = Event(
    repo_full_name=repo_full_name,
    pr_number=pr_number,
    event_type=action,
    payload=payload_dict,
    status="pending"
)
db.add(event)
await db.commit()

# 4. Spawn background task (non-blocking)
background_tasks.add_task(pr_review_service.process_pr_review, payload, event, db)

return {"status": "accepted"}
Nectr returns HTTP 200 immediately, then processes the PR review in the background. This prevents GitHub webhook timeouts (10s limit).

2. Fetch PR Data

The background task starts by fetching PR details from GitHub:
# app/services/pr_review_service.py:496-499
diff = await github_client.get_pr_diff(owner, repo, pr_number)
files = await github_client.get_pr_files(owner, repo, pr_number)
Example diff (unified format):
--- a/src/auth/middleware.py
+++ b/src/auth/middleware.py
@@ -10,6 +10,7 @@
 def authenticate(request: Request):
     token = request.cookies.get("access_token")
     if not token:
-        return None
+        raise HTTPException(status_code=401, detail="Not authenticated")
     return verify_jwt(token)
Example files array:
[
  {
    "filename": "src/auth/middleware.py",
    "status": "modified",
    "additions": 12,
    "deletions": 3,
    "patch": "@@ -10,6 +10,7 @@\n def authenticate...\n+        raise HTTPException..."
  },
  {
    "filename": "tests/test_auth.py",
    "status": "added",
    "additions": 45,
    "deletions": 0,
    "patch": "@@ -0,0 +1,45 @@\n+import pytest..."
  }
]

3. Build Review Context

Nectr gathers contextual intelligence from multiple sources in parallel:
# app/services/pr_review_service.py:512-523
issue_details, open_pr_conflicts, candidate_issues, related_prs = await asyncio.gather(
    _fetch_issue_details(owner, repo, issue_refs),
    _get_open_pr_conflicts(owner, repo, pr_number, file_paths),
    _find_candidate_issues(owner, repo, pr_title, pr_body, file_paths, set(issue_refs)),
    graph_builder.get_related_prs(repo_full_name, file_paths[:10], top_k=5),
    return_exceptions=True,
)
# app/services/context_service.py:76-84
project_memories = await memory_adapter.search_relevant(
    repo=repo_full_name,
    query=f"{pr_title} {pr_description} {file_paths}",
    developer=None,
    top_k=12
)
developer_memories = await memory_adapter.search_relevant(
    repo=repo_full_name,
    query="Developer patterns, strengths, recurring issues",
    developer=author,
    top_k=5
)
Example memories returned:
[
  {
    "memory": "Always use async/await for database queries",
    "metadata": {"memory_type": "project_pattern", "user_id": "project"}
  },
  {
    "memory": "@alice tends to miss error handling in edge cases",
    "metadata": {"memory_type": "developer_pattern", "user_id": "alice"}
  }
]

4. AI Analysis (Agentic Loop)

Nectr runs an agentic review loop powered by Claude Sonnet 4.6. The AI has access to 8 tools and fetches context on-demand:
# app/services/pr_review_service.py:566-569
review_result = await ai_service.analyze_pull_request_agentic(
    pr, diff, files, tool_executor, issue_refs=issue_refs
)

Available Tools

During the review, Claude can call any of these tools:
Fetches the full content of a file at HEAD commit.
# Example usage by AI:
content = await read_file("src/auth/middleware.py")
# Returns: "def authenticate(request: Request):\n    token = ..."
Truncation: Files larger than 8,000 chars are truncated with "... (truncated at 8,000 chars)".
Searches Mem0 for project-wide patterns, rules, and decisions.
results = await search_project_memory("authentication patterns")
# Returns:
# - Always use bcrypt for password hashing
# - JWT tokens expire after 24 hours
# - Refresh tokens stored in httpOnly cookies
Searches Mem0 for developer-specific patterns and strengths.
results = await search_developer_memory("alice", "common issues")
# Returns:
# - @alice often forgets to add error handling
# - @alice excels at SQL query optimization
Queries Neo4j for file experts and related past PRs.
history = await get_file_history(["src/auth/middleware.py"])
# Returns:
# File experts:
#   @alice — 23 PRs
#   @bob — 12 PRs
# Related past PRs:
#   PR #120 [APPROVE] by @alice: Add JWT refresh token logic
Fetches full GitHub issue details by number.
issues = await get_issue_details([123, 456])
# Returns:
# Issue #123 [open]: Users getting 401 errors after token expires
#   Body: "When JWT expires, users are not redirected to login..."
Searches candidate open issues by keyword overlap.
matches = await search_open_issues("authentication token")
# Returns:
# Issue #123: Users getting 401 errors after token expires
# Issue #145: Refresh token endpoint returns 500
Fetches issues from Linear or GitHub via MCP integration.
issues = await get_linked_issues("authentication", source="linear")
# Returns:
# Linked linear issues for 'authentication':
#   #ENG-456 [in progress]: Implement OAuth refresh flow

Example Agentic Loop

# Simplified illustration of the agentic loop

messages = [
    {"role": "user", "content": f"Review this PR:\n{diff}\n{context}"}
]

while True:
    response = await anthropic_client.messages.create(
        model="claude-sonnet-4.6",
        messages=messages,
        tools=REVIEW_TOOLS
    )
    
    if response.stop_reason == "end_turn":
        # AI finished, extract summary + verdict
        summary = response.content[0].text
        break
    
    if response.stop_reason == "tool_use":
        # AI wants to call a tool
        for tool_call in response.content:
            if tool_call.type == "tool_use":
                tool_name = tool_call.name
                tool_input = tool_call.input
                tool_result = await tool_executor.execute(tool_name, tool_input)
                
                messages.append({
                    "role": "assistant",
                    "content": response.content
                })
                messages.append({
                    "role": "user",
                    "content": [
                        {
                            "type": "tool_result",
                            "tool_use_id": tool_call.id,
                            "content": tool_result
                        }
                    ]
                })
The agentic loop typically runs 2-5 turns. Claude rarely needs more than 3 tool calls to produce a comprehensive review.

5. Post Review Comment

After AI analysis completes, Nectr posts the review to GitHub:
# app/services/pr_review_service.py:717-723
await github_client.post_pr_review(
    owner, repo, pr_number,
    commit_id=head_sha,
    body=comment_body,
    event="APPROVE" | "REQUEST_CHANGES" | "COMMENT",
    comments=inline_comments
)
Review comment structure:
Hi I am Nectr - AI code review agent built by [Dhanush Chalicheemala](https://x.com/dhanush_chali)

## Verdict: **APPROVE**

This PR implements JWT authentication middleware correctly. Key changes:
- ✅ Raises HTTPException(401) for missing tokens
- ✅ Uses `verify_jwt()` for token validation
- ✅ Adds comprehensive test coverage

### Minor Suggestions:
- Consider adding rate limiting to prevent brute-force attacks
- Add refresh token rotation for enhanced security

## Resolved Issues
- 🟢 Closes [#123: Users getting 401 errors after token expires](https://github.com/org/repo/issues/123)

## 🔍 Potentially Resolves
_These open issues appear to be resolved by this PR's changes, even though they weren't explicitly mentioned:_
- 🟡 [#145: Refresh token endpoint returns 500](https://github.com/org/repo/issues/145) — This PR fixes the root cause by validating tokens before refresh

---
*If you have any concerns, connect with [Dhanush Chalicheemala](https://x.com/dhanush_chali)*
Inline suggestions (GitHub review comments):
# Example inline comment with suggestion block
{
    "path": "src/auth/middleware.py",
    "line": 15,
    "side": "RIGHT",
    "body": "Consider adding a try-except block to handle JWT decode errors gracefully.\n\n```suggestion\ntry:\n    return verify_jwt(token)\nexcept JWTDecodeError as e:\n    logger.error('JWT decode failed: %s', e)\n    raise HTTPException(status_code=401, detail='Invalid token')\n```"
}
Users can click Commit suggestion on GitHub to apply the fix directly.

6. Index PR in Neo4j

After posting the review, Nectr updates the knowledge graph:
-- Create PullRequest node
MERGE (pr:PullRequest {repo: "org/repo", number: 42})
SET pr.title = "Add user authentication",
    pr.author = "alice",
    pr.verdict = "APPROVE",
    pr.reviewed_at = "2024-03-10T14:32:11Z"

-- Link to Developer
MERGE (d:Developer {login: "alice"})
MERGE (pr)-[:AUTHORED_BY]->(d)
MERGE (d)-[:CONTRIBUTED_TO]->(r:Repository {full_name: "org/repo"})

-- Link to changed Files
UNWIND ["src/auth/middleware.py", "tests/test_auth.py"] AS path
MERGE (file:File {repo: "org/repo", path: path})
ON CREATE SET file.language = "Python"
MERGE (pr)-[:TOUCHES]->(file)

-- Link to closed Issues
UNWIND [123] AS issue_num
MERGE (i:Issue {repo: "org/repo", number: issue_num})
MERGE (pr)-[:CLOSES]->(i)
Graph after indexing:
(Repository:"org/repo")-[:CONTAINS]->(File:"src/auth/middleware.py")
(PullRequest:42)-[:TOUCHES]->(File:"src/auth/middleware.py")
(PullRequest:42)-[:AUTHORED_BY]->(Developer:"alice")
(PullRequest:42)-[:CLOSES]->(Issue:123)
(Developer:"alice")-[:CONTRIBUTED_TO]->(Repository:"org/repo")

7. Extract Learned Memories

Finally, Claude extracts new memories from the PR:
# app/services/memory_extractor.py:50-80
extracted = await ai_service.extract_memories(
    pr_title="Add user authentication",
    files=files,
    review_summary=summary
)
Example extracted memories:
[
  {
    "type": "project_pattern",
    "content": "JWT authentication uses httpOnly cookies for security"
  },
  {
    "type": "decision",
    "content": "Switched from session-based auth to JWT tokens (2024-03-10)"
  },
  {
    "type": "developer_strength",
    "content": "@alice writes comprehensive test coverage for auth flows"
  },
  {
    "type": "risk_module",
    "content": "src/auth/middleware.py requires careful review for security vulnerabilities"
  }
]
These memories are stored in Mem0 and used to inform future reviews.

Parallel Review Mode (Experimental)

Set PARALLEL_REVIEW_AGENTS=true to run 3 specialized agents concurrently:
# app/services/ai_service.py:200-220
if settings.PARALLEL_REVIEW_AGENTS:
    security_review, performance_review, style_review = await asyncio.gather(
        analyze_security(pr, diff, files),
        analyze_performance(pr, diff, files),
        analyze_style(pr, diff, files)
    )
    
    # Synthesis agent combines all three
    final_review = await synthesize_reviews(
        security_review, performance_review, style_review
    )
Parallel mode uses 4× the Claude API quota (3 agent reviews + 1 synthesis) but reduces latency by ~30% due to concurrent execution.

Data Flow Summary

1

Webhook Received

GitHub → FastAPI /api/v1/webhooks/github (validates HMAC, deduplicates, spawns background task)
2

PR Data Fetched

Background task → GitHub REST API (fetch diff + files)
3

Context Gathered

Parallel queries → Neo4j + Mem0 + GitHub + MCP integrations
4

AI Analysis

Agentic loop → Claude Sonnet 4.6 with 8 on-demand tools
5

Review Posted

GitHub REST API → Post review comment with inline suggestions
6

Graph Updated

Neo4j write → Index PR + edges (AUTHORED_BY, TOUCHES, CLOSES)
7

Memories Extracted

Claude extraction → Mem0 storage (project patterns + developer profiles)

Next Steps

PR Reviews Deep Dive

Explore the review workflow in detail

Knowledge Graph

Learn how Neo4j tracks code relationships

Semantic Memory

Understand how Mem0 stores long-term context

AI Analysis

Deep dive into the agentic review process

Build docs developers (and LLMs) love