Backend MVP: FastAPI + Claude integration

Core components:
- FastAPI server with health endpoints
- AI client (Anthropic Claude integration)
- Context manager (loads company knowledge)
- Chat API (non-streaming and streaming)
- Requirements and environment setup

Ready to run: python backend/main.py
This commit is contained in:
JA
2026-02-12 21:16:38 +00:00
parent ee273be9c5
commit d9a7c016b1
7 changed files with 883 additions and 0 deletions

21
backend/.env.example Normal file
View File

@@ -0,0 +1,21 @@
# Grimlock Backend Configuration
# API Keys
ANTHROPIC_API_KEY=your-anthropic-api-key-here
# Server Configuration
HOST=0.0.0.0
PORT=8000
DEBUG=true
# Context Configuration
CONTEXT_PATH=/app/context
REPOS_PATH=/app/repos
# AI Configuration
AI_MODEL=claude-sonnet-4-5-20250514
AI_MAX_TOKENS=4096
AI_TEMPERATURE=0.7
# Logging
LOG_LEVEL=INFO

137
backend/api/chat.py Normal file
View File

@@ -0,0 +1,137 @@
"""
Chat API - Main endpoint for interacting with Grimlock
"""
from fastapi import APIRouter, Depends, HTTPException
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from typing import List, Optional
import logging
from core.context_manager import ContextManager
from core.ai_client import AIClient
import main
logger = logging.getLogger(__name__)
router = APIRouter()
class Message(BaseModel):
role: str
content: str
class ChatRequest(BaseModel):
messages: List[Message]
role: Optional[str] = None
stream: bool = False
class ChatResponse(BaseModel):
response: str
context_used: bool
@router.post("/", response_model=ChatResponse)
async def chat(
request: ChatRequest,
context_manager: ContextManager = Depends(main.get_context_manager),
ai_client: AIClient = Depends(main.get_ai_client)
):
"""
Chat with Grimlock
Args:
request: Chat request with messages and optional role
Returns:
ChatResponse with AI response
"""
try:
# Get the last user message for context
user_message = None
for msg in reversed(request.messages):
if msg.role == "user":
user_message = msg.content
break
# Get relevant context
context = ""
if user_message:
context = context_manager.get_context_for_query(
user_message,
role=request.role
)
# Build system prompt
system_prompt = context_manager.get_system_prompt(role=request.role)
# Add context to system prompt if available
if context:
system_prompt += f"\n\n# Company Context\n{context}"
# Convert messages to API format
api_messages = [
{"role": msg.role, "content": msg.content}
for msg in request.messages
]
# Get response from AI
response = await ai_client.chat(
messages=api_messages,
system_prompt=system_prompt
)
return ChatResponse(
response=response,
context_used=bool(context)
)
except Exception as e:
logger.error(f"Error in chat endpoint: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/stream")
async def chat_stream(
request: ChatRequest,
context_manager: ContextManager = Depends(main.get_context_manager),
ai_client: AIClient = Depends(main.get_ai_client)
):
"""Stream chat response from Grimlock"""
try:
# Get context
user_message = None
for msg in reversed(request.messages):
if msg.role == "user":
user_message = msg.content
break
context = ""
if user_message:
context = context_manager.get_context_for_query(
user_message,
role=request.role
)
# Build system prompt
system_prompt = context_manager.get_system_prompt(role=request.role)
if context:
system_prompt += f"\n\n# Company Context\n{context}"
# Convert messages
api_messages = [
{"role": msg.role, "content": msg.content}
for msg in request.messages
]
# Stream response
async def generate():
async for chunk in ai_client.chat_stream(
messages=api_messages,
system_prompt=system_prompt
):
yield chunk
return StreamingResponse(generate(), media_type="text/plain")
except Exception as e:
logger.error(f"Error in chat stream: {e}")
raise HTTPException(status_code=500, detail=str(e))

View File

@@ -0,0 +1,339 @@
# Grimlock
> **AI-Native Company Operating System**
> *Your team's AI companion. One interface. All your context. Infinite intelligence.*
---
## What is Grimlock?
Grimlock is an AI-powered operating system for modern companies. It's like having Jarvis (from Iron Man) for your entire organization - an intelligent assistant that knows everything about your company, can access all your internal systems, and generates any artifact on-demand.
**The Problem:**
- Knowledge workers waste 60-80% of their time finding files, asking for updates, manual data entry, scheduling, and formatting documents
- Current tools (Slack, Notion, ChatGPT) are disconnected and lack company context
- AI assistants are generic and can't access internal systems
- Companies are locked into Microsoft/Google ecosystems
**The Solution:**
One AI interface that:
- ✅ Knows everything about your company
- ✅ Connects to all your internal systems
- ✅ Generates documents, spreadsheets, code, presentations on-demand
- ✅ Works for everyone (BD, engineering, ops, finance, assistants)
- ✅ Self-hosted for data sovereignty
- ✅ Platform-agnostic (not locked to MS/Google)
---
## Key Features
### 🤖 AI-Native Interface
- Natural language interaction with company-aware AI
- Context preserved across conversations
- Role-based responses (BD gets BD-focused, engineers get technical)
### 🔌 Universal Connectors
- Git repositories (GitHub, GitLab, Gitea)
- Databases (PostgreSQL, MySQL, MongoDB)
- File storage (MinIO, S3, local filesystems)
- Calendar systems
- Project management tools
- Custom internal tools via plugin architecture
### 📄 Artifact Generation
- Documents (Markdown, PDF)
- Spreadsheets (CSV, JSON - no Excel dependency)
- Code (any language)
- Presentations
- Reports and dashboards
### 🏢 Multi-Tenant Architecture
- Self-hosted on customer infrastructure
- Complete data sovereignty
- Role-based access control
- SSO integration
### 🔒 Security First
- All data stays on customer infrastructure
- Encrypted at rest and in transit
- Audit logs for compliance
- SOC2, HIPAA-ready architecture
---
## Use Cases
### For Engineers
```
Engineer: @grimlock I need to build a payment processing system for construction invoices
Grimlock: Based on Vector Zulu patterns, this maps to the UTILEN + Finance Infrastructure patterns:
Core Stack: FastAPI + PostgreSQL + Redis + Celery
Key Patterns: Multi-tenant isolation, background job processing, audit trails
Cost Estimate: ~$3-5/transaction for AI processing
Timeline: 30 days to working prototype
Should I generate the project scaffold?
```
### For Business Development
```
BD: @grimlock create a capabilities deck for a prospective cyber range client
Grimlock: [Generates PDF]
Created: VectorZulu-CyberRange-Capabilities-2026-02.pdf
Download: [link]
```
### For Executives
```
Exec: @grimlock summarize all active projects and their status
Grimlock: Active Projects Summary:
UTILEN (Document Management) - Production, 499 docs processed
Vector Zulu Platform (Cyber Range) - K3s deployment in progress
Blockchain Platform - Architecture phase
Full report: [link to generated document]
```
### For Operations
```
Ops: @grimlock generate January infrastructure cost report
Grimlock: [Generates spreadsheet with cost breakdown]
Total: $2,847
Breakdown: AI APIs ($1,200), Hosting ($897), Network ($750)
Recommendations: Consider tier upgrade for AI API (ROI positive)
```
---
## Architecture
```
┌─────────────────────────────────────────────────┐
│ Grimlock Web Interface │
│ (React/Next.js - ai.yourcompany.com) │
└─────────────────┬───────────────────────────────┘
┌─────────────────▼───────────────────────────────┐
│ Grimlock Core │
│ (FastAPI Backend) │
│ │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ AI Layer │ │ Context │ │
│ │ (Claude) │ │ Manager │ │
│ └─────────────┘ └──────────────┘ │
│ │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ Artifact │ │ Connector │ │
│ │ Generator │ │ Engine │ │
│ └─────────────┘ └──────────────┘ │
└─────────────────┬───────────────────────────────┘
┌─────────────────▼───────────────────────────────┐
│ Company Data Sources │
│ (via Connectors) │
│ │
│ • Git Repos • Databases • File Storage │
│ • Calendars • Project Mgmt • Custom APIs │
└─────────────────────────────────────────────────┘
```
---
## Tech Stack
**Backend:**
- FastAPI (Python) - API server
- PostgreSQL - Metadata and user data
- Redis - Caching and task queue
- Celery - Background job processing
- Anthropic Claude API - AI reasoning
**Frontend:**
- React/Next.js - Web interface
- TailwindCSS - Styling
- WebSocket - Real-time updates
**Deployment:**
- Docker/Docker Compose - Containerization
- Kubernetes - Production orchestration (optional)
- Self-hosted on customer infrastructure
**Connectors:**
- Plugin architecture for extensibility
- Pre-built connectors for common systems
- Custom connector SDK
---
## Getting Started
### Quick Start (Docker Compose)
```bash
# Clone the repository
git clone https://gittea.979labs.com/amitis55/grimlock.git
cd grimlock
# Set up environment variables
cp .env.example .env
# Edit .env with your configuration
# Start Grimlock
docker-compose up -d
# Access at http://localhost:3000
```
### Manual Installation
```bash
# Backend
cd backend
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
python main.py
# Frontend
cd frontend
npm install
npm run dev
```
See [Installation Guide](docs/installation.md) for detailed instructions.
---
## Project Structure
```
grimlock/
├── backend/ # FastAPI backend
│ ├── api/ # API endpoints
│ ├── core/ # Core business logic
│ ├── connectors/ # Data source connectors
│ ├── ai/ # AI integration layer
│ └── artifacts/ # Artifact generation
├── frontend/ # React frontend
│ ├── components/ # UI components
│ ├── pages/ # Next.js pages
│ └── lib/ # Utilities
├── connectors/ # Connector plugins
│ ├── git/ # Git connector
│ ├── database/ # Database connector
│ └── ...
├── docs/ # Documentation
├── docker/ # Docker configurations
└── scripts/ # Deployment scripts
```
---
## Roadmap
### Phase 1: MVP (Current - Month 3)
- [x] Repository setup
- [ ] Core backend (FastAPI + Claude integration)
- [ ] Basic web interface
- [ ] Git connector (read-only)
- [ ] Document generation (Markdown, PDF)
- [ ] Spreadsheet generation (CSV, JSON)
- [ ] Deploy internally at Vector Zulu
### Phase 2: Beta (Month 4-6)
- [ ] Mobile apps (iOS, Android)
- [ ] Desktop app (Electron)
- [ ] Connector architecture framework
- [ ] 10+ pre-built connectors
- [ ] Admin dashboard
- [ ] Multi-tenant support
- [ ] 5-10 beta customers
### Phase 3: Production (Month 7-12)
- [ ] Advanced artifact generation
- [ ] Workflow automation
- [ ] SSO integrations
- [ ] Compliance features (SOC2, HIPAA)
- [ ] Self-service onboarding
- [ ] Public launch
### Phase 4: Scale (Year 2+)
- [ ] White-label options
- [ ] Enterprise features
- [ ] API marketplace
- [ ] AI model flexibility
- [ ] Advanced analytics
---
## Business Model
**Self-Hosted License:**
- $50-150/user/month
- Deploy on customer infrastructure
- Include support and updates
**Managed Hosting:**
- $100-200/user/month
- We host on Vector Zulu infrastructure
- Full management and support
**Professional Services:**
- Custom connector development
- Implementation and training
- Enterprise support
---
## Why Grimlock?
**The name:** Grimlock is a powerful Transformer - representing transformation of how companies operate. Just as Grimlock is a leader among Transformers, this platform transforms company operations.
**The mission:** Eliminate bullshit work. Empower knowledge workers. Make AI-native operations accessible to every company.
**The vision:** Every company runs on AI-native infrastructure where the AI is the interface to everything.
---
## Contributing
We welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
---
## License
[License TBD - Likely dual license: OSS for self-hosted, commercial for managed hosting]
---
## Contact
**Project Owner:** JA @ Vector Zulu LLC
**Repository:** https://gittea.979labs.com/amitis55/grimlock
**Company:** Vector Zulu LLC
---
## Case Study: Vector Zulu
Grimlock was born from Vector Zulu's need to eliminate operational overhead. We built UTILEN (an enterprise document management system) in 30 days using AI-assisted development - a project others quoted at 18 months. We realized the AI workflows we developed could transform how any company operates.
Vector Zulu serves as Grimlock's pilot customer, validating every feature in a real production environment serving K-12 cybersecurity education and commercial cloud services.
**Results:**
- 10x faster project delivery
- 80% reduction in "where's that file?" questions
- Zero Microsoft Office dependencies
- Engineers spend time engineering, not finding documents
- BD team self-serves on technical content
---
**Built with ❤️ by Vector Zulu**
*Transforming companies through AI-native operations*
Grimlock - AI-native company operating system. The Jarvis for modern businesses.

96
backend/core/ai_client.py Normal file
View File

@@ -0,0 +1,96 @@
"""
AI Client - Anthropic Claude Integration
"""
import anthropic
import os
import logging
from typing import List, Dict, Optional
logger = logging.getLogger(__name__)
class AIClient:
"""Client for interacting with Anthropic Claude API"""
def __init__(self, api_key: str):
self.client = anthropic.Anthropic(api_key=api_key)
self.model = os.getenv("AI_MODEL", "claude-sonnet-4-5-20250514")
self.max_tokens = int(os.getenv("AI_MAX_TOKENS", "4096"))
self.temperature = float(os.getenv("AI_TEMPERATURE", "0.7"))
logger.info(f"AIClient initialized with model: {self.model}")
async def chat(
self,
messages: List[Dict[str, str]],
system_prompt: Optional[str] = None,
max_tokens: Optional[int] = None
) -> str:
"""
Send chat messages to Claude and get response
Args:
messages: List of message dicts with 'role' and 'content'
system_prompt: Optional system prompt
max_tokens: Optional max tokens override
Returns:
Response text from Claude
"""
try:
kwargs = {
"model": self.model,
"max_tokens": max_tokens or self.max_tokens,
"temperature": self.temperature,
"messages": messages
}
if system_prompt:
kwargs["system"] = system_prompt
response = self.client.messages.create(**kwargs)
# Extract text from response
if response.content and len(response.content) > 0:
return response.content[0].text
return "No response generated"
except Exception as e:
logger.error(f"Error calling Claude API: {e}")
raise
async def chat_stream(
self,
messages: List[Dict[str, str]],
system_prompt: Optional[str] = None,
max_tokens: Optional[int] = None
):
"""
Stream chat response from Claude
Args:
messages: List of message dicts
system_prompt: Optional system prompt
max_tokens: Optional max tokens override
Yields:
Text chunks from Claude
"""
try:
kwargs = {
"model": self.model,
"max_tokens": max_tokens or self.max_tokens,
"temperature": self.temperature,
"messages": messages
}
if system_prompt:
kwargs["system"] = system_prompt
with self.client.messages.stream(**kwargs) as stream:
for text in stream.text_stream:
yield text
except Exception as e:
logger.error(f"Error streaming from Claude API: {e}")
raise

View File

@@ -0,0 +1,171 @@
"""
Context Manager - Loads and manages company context
"""
import os
import logging
from pathlib import Path
from typing import Dict, List, Optional
import yaml
logger = logging.getLogger(__name__)
class ContextManager:
"""Manages loading and accessing company context"""
def __init__(self, context_path: str):
self.context_path = Path(context_path)
self.context = {
"projects": {},
"patterns": {},
"anti_patterns": {},
"cost_models": {},
"repos": {}
}
self._loaded = False
def load_all_context(self):
"""Load all context from disk"""
logger.info(f"Loading context from {self.context_path}")
# Create context directories if they don't exist
self.context_path.mkdir(parents=True, exist_ok=True)
for subdir in ["projects", "patterns", "anti_patterns", "cost_models", "repos"]:
(self.context_path / subdir).mkdir(exist_ok=True)
# Load each context type
self._load_directory("projects")
self._load_directory("patterns")
self._load_directory("anti_patterns")
self._load_directory("cost_models")
self._loaded = True
logger.info(f"Context loaded: {self.get_summary()}")
def _load_directory(self, directory: str):
"""Load all markdown files from a directory"""
dir_path = self.context_path / directory
if not dir_path.exists():
logger.warning(f"Context directory not found: {dir_path}")
return
for file_path in dir_path.glob("*.md"):
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
self.context[directory][file_path.stem] = content
logger.debug(f"Loaded {directory}/{file_path.name}")
except Exception as e:
logger.error(f"Error loading {file_path}: {e}")
def get_context_for_query(self, query: str, role: Optional[str] = None) -> str:
"""
Get relevant context for a query
Args:
query: User query
role: User role (engineer, bd, admin, exec)
Returns:
Formatted context string
"""
# Simple implementation: include all context
# TODO: Implement smarter context selection based on query relevance
context_parts = []
# Add projects
if self.context["projects"]:
context_parts.append("# Vector Zulu Projects\n")
for name, content in self.context["projects"].items():
context_parts.append(f"## {name}\n{content}\n")
# Add patterns (most relevant for engineers)
if self.context["patterns"] and (role == "engineer" or role is None):
context_parts.append("\n# Reference Architectures & Patterns\n")
for name, content in self.context["patterns"].items():
context_parts.append(f"## {name}\n{content}\n")
# Add anti-patterns
if self.context["anti_patterns"]:
context_parts.append("\n# Anti-Patterns (Things to Avoid)\n")
for name, content in self.context["anti_patterns"].items():
context_parts.append(f"## {name}\n{content}\n")
# Add cost models (relevant for estimates)
if "cost" in query.lower() or "price" in query.lower():
if self.context["cost_models"]:
context_parts.append("\n# Cost Models\n")
for name, content in self.context["cost_models"].items():
context_parts.append(f"## {name}\n{content}\n")
return "\n".join(context_parts)
def get_system_prompt(self, role: Optional[str] = None) -> str:
"""
Generate system prompt based on role
Args:
role: User role
Returns:
System prompt for Claude
"""
base_prompt = """You are Grimlock, Vector Zulu's AI assistant. You help team members by:
- Answering questions about projects, patterns, and internal systems
- Generating documents, spreadsheets, code, and other artifacts
- Providing technical guidance based on Vector Zulu's proven patterns
- Keeping responses concise and actionable
You have access to Vector Zulu's internal context including:
- Project summaries (UTILEN, Vector Zulu platform, blockchain)
- Reference architectures and patterns
- Anti-patterns to avoid
- Cost models and estimates
"""
role_prompts = {
"engineer": "\nYou're speaking with an engineer. Provide technical depth, code examples, and architecture details.",
"bd": "\nYou're speaking with business development. Focus on capabilities, timelines, costs, and client-facing information.",
"admin": "\nYou're speaking with admin/operations. Focus on processes, reports, schedules, and organizational information.",
"exec": "\nYou're speaking with an executive. Provide high-level summaries, key metrics, and strategic insights."
}
if role and role in role_prompts:
base_prompt += role_prompts[role]
return base_prompt
def is_loaded(self) -> bool:
"""Check if context is loaded"""
return self._loaded
def get_summary(self) -> Dict:
"""Get summary of loaded context"""
return {
"projects": len(self.context["projects"]),
"patterns": len(self.context["patterns"]),
"anti_patterns": len(self.context["anti_patterns"]),
"cost_models": len(self.context["cost_models"]),
"repos": len(self.context["repos"])
}
def add_context(self, category: str, name: str, content: str):
"""Add or update context"""
if category in self.context:
self.context[category][name] = content
logger.info(f"Added/updated {category}/{name}")
def save_context(self, category: str, name: str):
"""Save context to disk"""
if category not in self.context or name not in self.context[category]:
logger.error(f"Context not found: {category}/{name}")
return
file_path = self.context_path / category / f"{name}.md"
try:
with open(file_path, 'w', encoding='utf-8') as f:
f.write(self.context[category][name])
logger.info(f"Saved {category}/{name} to disk")
except Exception as e:
logger.error(f"Error saving {file_path}: {e}")

106
backend/main.py Normal file
View File

@@ -0,0 +1,106 @@
"""
Grimlock - AI-Native Company Operating System
Main FastAPI Application
"""
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from contextlib import asynccontextmanager
import logging
from dotenv import load_dotenv
import os
from api.chat import router as chat_router
from core.context_manager import ContextManager
from core.ai_client import AIClient
# Load environment variables
load_dotenv()
# Configure logging
logging.basicConfig(
level=os.getenv("LOG_LEVEL", "INFO"),
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# Global state
context_manager = None
ai_client = None
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Startup and shutdown events"""
global context_manager, ai_client
logger.info("Starting Grimlock backend...")
# Initialize context manager
context_path = os.getenv("CONTEXT_PATH", "./context")
context_manager = ContextManager(context_path)
context_manager.load_all_context()
logger.info(f"Loaded context from {context_path}")
# Initialize AI client
api_key = os.getenv("ANTHROPIC_API_KEY")
if not api_key:
logger.error("ANTHROPIC_API_KEY not set!")
raise ValueError("ANTHROPIC_API_KEY environment variable is required")
ai_client = AIClient(api_key=api_key)
logger.info("AI client initialized")
yield
# Cleanup
logger.info("Shutting down Grimlock backend...")
# Create FastAPI app
app = FastAPI(
title="Grimlock",
description="AI-Native Company Operating System",
version="0.1.0",
lifespan=lifespan
)
# CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Configure appropriately for production
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Include routers
app.include_router(chat_router, prefix="/api/chat", tags=["chat"])
@app.get("/")
async def root():
"""Health check endpoint"""
return {
"status": "online",
"service": "Grimlock",
"version": "0.1.0"
}
@app.get("/api/health")
async def health():
"""Detailed health check"""
return {
"status": "healthy",
"context_loaded": context_manager is not None and context_manager.is_loaded(),
"ai_client_ready": ai_client is not None
}
def get_context_manager() -> ContextManager:
"""Dependency to get context manager"""
if context_manager is None:
raise HTTPException(status_code=500, detail="Context manager not initialized")
return context_manager
def get_ai_client() -> AIClient:
"""Dependency to get AI client"""
if ai_client is None:
raise HTTPException(status_code=500, detail="AI client not initialized")
return ai_client

13
backend/requirements.txt Normal file
View File

@@ -0,0 +1,13 @@
fastapi==0.109.0
uvicorn[standard]==0.27.0
anthropic==0.18.1
python-dotenv==1.0.0
pydantic==2.5.3
pydantic-settings==2.1.0
python-multipart==0.0.6
aiofiles==23.2.1
GitPython==3.1.41
PyYAML==6.0.1
markdown==3.5.2
weasyprint==60.2
pandas==2.2.0