Development Standards
This document outlines the coding standards and patterns used in the ATOM SaaS platform. Following these standards ensures consistency, maintainability, and scalability.
Table of Contents
- Tenant Context Extraction
- Error Handling
- Database Access
- API Response Formats
- Brain System Usage
- Naming Conventions
- Testing Standards
---
Tenant Context Extraction
**CRITICAL**: All multi-tenant API routes MUST extract tenant context to ensure data isolation.
Frontend Pattern (Next.js)
import { getTenantFromRequest } from '@/lib/tenant/tenant-extractor'
import { NextRequest, NextResponse } from 'next/server'
export async function POST(request: NextRequest) {
// ALWAYS extract tenant first
const tenant = await getTenantFromRequest(request)
if (!tenant) {
return NextResponse.json({ error: 'Tenant not found' }, { status: 404 })
}
// Use tenant.id for all database operations
const result = await db.query(
'SELECT * FROM agents WHERE tenant_id = $1',
[tenant.id]
)
return NextResponse.json(result)
}Backend Pattern (FastAPI)
from core.tenant_utils import get_current_tenant
from fastapi import Depends
@router.post("/agents")
async def create_agent(
agent_data: AgentCreate,
tenant_id: str = Depends(get_current_tenant)
):
# FastAPI dependency injection handles tenant extraction
return await create_agent_for_tenant(tenant_id, agent_data)When NOT to Extract Tenant
These routes do NOT need tenant extraction:
- Authentication endpoints (
/api/auth/*) - Public health endpoints (
/api/health) - Tenant resolution endpoints (
/api/tenant/current) - NextAuth callbacks (
/api/auth/[...nextauth])
---
Error Handling
Frontend Error Handling
Use the standardized error classes from @/lib/errors/api-error.ts:
import { ValidationError, NotFoundError, handleApiError } from '@/lib/errors/api-error'
export async function POST(request: NextRequest) {
try {
const body = await request.json()
// Validate input
if (!body.name) {
throw new ValidationError('Name is required')
}
// ... your logic
} catch (error) {
return handleApiError(error) // Automatic NextResponse with correct status
}
}Backend Error Handling
Use the standardized error classes from core.error_handler:
from core.error_handler import (
ValidationError,
NotFoundError,
AuthenticationError,
RateLimitError
)
@router.post("/agents/{agent_id}")
async def update_agent(agent_id: str, agent_data: dict):
agent = await get_agent(agent_id)
if not agent:
raise NotFoundError("Agent", agent_id)
if agent_data["name"] == "forbidden":
raise ValidationError("Invalid agent name")
return agentStandard Error Response Format
Both frontend and backend use this structure:
{
"success": false,
"error": {
"message": "User-friendly error message",
"code": "VALIDATION_ERROR",
"details": {}
}
}---
Database Access
❌ NEVER access database directly in route handlers
**Frontend:**
// ❌ BAD
import { getDatabase } from '@/lib/database'
const db = getDatabase()
const result = await db.query('SELECT * FROM agents')
// ✅ GOOD
import { AgentService } from '@/lib/services/agent-service'
const agents = await AgentService.getByTenant(tenant.id)**Backend:**
# ❌ BAD
@router.get("/agents")
async def get_agents():
db = SessionLocal()
return db.query(Agent).all()
# ✅ GOOD
@router.get("/agents")
async def get_agents(
tenant_id: str = Depends(get_current_tenant),
agent_service: AgentService = Depends()
):
return await agent_service.list_agents(tenant_id)Use Service Layer for Business Logic
Encapsulate database logic in service classes:
// services/agent-service.ts
export class AgentService {
static async getByTenant(tenantId: string) {
const db = getDatabase()
const result = await db.query(
'SELECT * FROM agents WHERE tenant_id = $1',
[tenantId]
)
return result.rows
}
}---
API Response Formats
Success Response
{
"success": true,
"data": { ... }
}Error Response
{
"success": false,
"error": {
"message": "User-friendly error",
"code": "ERROR_CODE",
"details": {}
}
}Pagination
{
"success": true,
"data": [...],
"pagination": {
"page": 1,
"limit": 20,
"total": 100,
"totalPages": 5
}
}---
Brain System Usage
The ATOM platform has 6 core brain systems. Use them appropriately:
When to Use Each Brain System
| System | Purpose | Location |
|---|---|---|
| **Cognitive Architecture** | Human-like reasoning, decision making | src/lib/ai/cognitive-architecture.ts |
| **Learning Engine** | Learn from experience, adapt behavior | src/lib/ai/learning-adaptation-engine.ts |
| **World Model** | Long-term memory, recall experiences | src/lib/ai/world-model.ts |
| **Reasoning Engine** | Proactive intelligence, interventions | src/lib/ai/reasoning-engine.ts |
| **Cross-System Reasoning** | Correlate data across integrations | src/lib/ai/cross-system-reasoning.ts |
| **Agent Governance** | Permission checking, maturity levels | src/lib/ai/agent-governance.ts |
Brain System Usage Pattern
import { CognitiveArchitecture } from '@/lib/ai/cognitive-architecture'
import { WorldModelService } from '@/lib/ai/world-model'
import { AgentGovernanceService } from '@/lib/ai/agent-governance'
// 1. Recall past experiences
const worldModel = new WorldModelService(db)
const memories = await worldModel.recallExperiences(
tenantId,
agentRole,
taskDescription,
limit: 5
)
// 2. Check permissions
const governance = new AgentGovernanceService(db)
const decision = await governance.canPerformAction(tenantId, agentId, actionType)
if (!decision.allowed) {
throw new Error(`Action not allowed: ${decision.reason}`)
}
// 3. Execute with cognitive support
const cognitive = new CognitiveArchitecture(db)
const reasoning = await cognitive.reason(tenantId, agentId, task, context)---
Naming Conventions
Files
- **Components**: PascalCase with
.tsxextension →AgentCard.tsx - **Utilities**: camelCase with
.tsextension →tenantService.ts - **Types**: PascalCase with
.tsextension →AgentConfig.ts - **Constants**: UPPER_SNAKE_CASE →
MAX_AGENTS_FREE
Directories
- **Components**: kebab-case →
agent-dashboard/,training-config-panel/ - **Utilities**: kebab-case →
tenant/,ai/,integrations/
Variables
- **Variables**: camelCase →
agentId,tenantService - **Constants**: UPPER_SNAKE_CASE →
MAX_RETRIES,DEFAULT_TIMEOUT - **Private variables**: camelCase with underscore prefix →
_internalState
Functions
- **Regular functions**: camelCase →
getTenant(),validateInput() - **Async functions**: camelCase →
fetchTenantData(),processPayment() - **Event handlers**: camelCase with
handleprefix →handleSubmit(),handleError()
Classes
- **Classes**: PascalCase →
AgentService,TenantExtractor - **Interfaces**: PascalCase with
Iprefix →IConfig,IRepository - **Types**: PascalCase →
AgentConfig,TenantContext
Database
- **Tables**: snake_case →
agent_registry,tenant_settings - **Columns**: snake_case →
tenant_id,created_at - **Indexes**: snake_case →
idx_tenant_id,uq_user_email
---
Testing Standards
Unit Tests
// services/__tests__/agent-service.test.ts
describe('AgentService', () => {
it('should return agents for tenant', async () => {
const agents = await AgentService.getByTenant('tenant-1')
expect(agents).toHaveLength(3)
})
it('should throw not found for invalid agent', async () => {
await expect(
AgentService.getById('tenant-1', 'invalid-id')
).rejects.toThrow(NotFoundError)
})
})Integration Tests
describe('Agent API', () => {
it('should create agent via POST /api/agents', async () => {
const response = await fetch('/api/agents', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Test Agent' })
})
expect(response.status).toBe(201)
})
})E2E Tests
Located in tests/ directory. Run with:
npm run test:e2e # 212 tests covering critical user flows---
Code Organization
Frontend (Next.js)
src/
├── app/
│ └── api/ # API routes (Next.js 14 App Router)
├── components/ # React components
│ ├── agents/ # Domain-specific components
│ ├── ui/ # Reusable UI components
│ └── canvas/ # Canvas editors
├── lib/
│ ├── ai/ # Brain systems (CRITICAL)
│ ├── integrations/ # OAuth clients
│ ├── tenant/ # Multi-tenant logic
│ └── services/ # Business logic layer
└── styles/ # Global stylesBackend (FastAPI)
backend-saas/
├── core/ # Business logic
│ ├── models.py # SQLAlchemy models
│ ├── services/ # Service classes
│ └── agent_*/ # Agent-related logic
├── api/ # FastAPI routes
└── alembic/ # Database migrations---
Common Patterns
1. Dependency Injection Pattern
Use dependency injection for database connections and services:
**Frontend:**
import { getDatabase } from '@/lib/database'
export class AgentService {
constructor(private db = getDatabase()) {}
}**Backend:**
from fastapi import Depends
from core.database import get_db
@router.get("/agents")
async def get_agents(
db: Session = Depends(get_db)
):
return db.query(Agent).all()2. Tenant Isolation Pattern
**CRITICAL**: Every database query MUST filter by tenant_id:
// ❌ BAD - returns all tenants' data
const agents = await db.query('SELECT * FROM agents')
// ✅ GOOD - filters by tenant
const agents = await db.query(
'SELECT * FROM agents WHERE tenant_id = $1',
[tenant.id]
)3. Error Handling Pattern
Wrap business logic in try-catch and convert to standardized errors:
try {
const agent = await createAgent(data)
return NextResponse.json(agent, { status: 201 })
} catch (error) {
if (error instanceof ValidationError) {
return handleApiError(error)
}
return handleApiError(new InternalServerError('Failed to create agent'))
}4. Async Route Pattern
All route handlers should be async:
export async function GET(request: NextRequest) {
// Always await async operations
const data = await fetchData()
return NextResponse.json(data)
}---
Performance Considerations
1. Use Server Components for Data Fetching
// ✅ GOOD - Server Component
export default async function AgentsPage() {
const agents = await AgentService.getByTenant(tenantId)
return <AgentList agents={agents} />
}
// ❌ BAD - Client Component fetching
'use client'
export function AgentsPage() {
const [agents, setAgents] = useState([])
useEffect(() => {
fetchAgents().then(setAgents)
}, [])
}2. Implement Rate Limiting
import { AbuseProtectionService } from '@/lib/safety/abuse-protection'
const canProceed = await abuseProtection.checkRateLimit(tenantId)
if (!canProceed) {
return NextResponse.json(
{ error: 'Rate limit exceeded' },
{ status: 429 }
)
}3. Use Database Indexes
CREATE INDEX idx_agents_tenant_id ON agents(tenant_id);
CREATE INDEX idx_agents_maturity ON agents(maturity_level) WHERE tenant_id = $1;---
Security Best Practices
1. Always Validate Input
import { z } from 'zod'
const AgentSchema = z.object({
name: z.string().min(1).max(100),
maturity_level: z.enum(['student', 'intern', 'supervised', 'autonomous'])
})
const validated = AgentSchema.parse(body)2. Never Expose Internal Errors
catch (error) {
// ❌ BAD - exposes stack trace
return NextResponse.json({ error: error.stack })
// ✅ GOOD - logs but doesn't expose
logger.error('Error details:', error)
return NextResponse.json(
{ error: 'An error occurred' },
{ status: 500 }
)
}3. Use Governance Checks
import { AgentGovernanceService } from '@/lib/ai/agent-governance'
const governance = new AgentGovernanceService(db)
const decision = await governance.canPerformAction(tenantId, agentId, 'DELETE')
if (!decision.allowed) {
return NextResponse.json(
{ error: `Action not allowed: ${decision.reason}` },
{ status: 403 }
)
}---
Documentation Standards
Code Comments
- **Public APIs**: Always document with JSDoc/Docstrings
- **Complex logic**: Add explanatory comments
- **TODOs**: Add context and expected behavior
- **FIXMEs**: Add urgency and issue reference
Component Documentation
Add JSDoc for all exported components:
/**
* Agent Card Component
*
* Displays agent information with actions for run, schedule, plan, and view history.
* Shows performance score, maturity level, and last run status.
*
* @param agent - Agent data to display
* @param onRun - Callback when user clicks run button
* @param onSchedule - Callback when user clicks schedule button
*/
export function AgentCard({ agent, onRun, onSchedule }: AgentCardProps) {
// ...
}API Endpoint Documentation
Add JSDoc to all API route functions:
/**
* GET /api/agents
*
* Get all agents for the current tenant.
* Returns paginated list of agents sorted by name.
*
* @query page - Page number (default: 1)
* @query limit - Items per page (default: 20)
* @returns {Promise<NextResponse>} JSON response with agents array
*/
export async function GET(request: NextRequest) {
// ...
}---
Review Checklist
Before submitting code, verify:
- [ ] All API routes extract tenant context (if needed)
- [ ] Error responses use standardized format
- [ ] Database queries filter by tenant_id
- [ ] Input validation with Zod schemas
- [ ] Sensitive data is not logged
- [ ] Governance checks for agent actions
- [ ] TypeScript compiles without errors
- [ ] Tests added for new functionality
- [ ] JSDoc comments added for public APIs
- [ ] Code follows naming conventions
---
Getting Help
For Code Reviews
- Ensure all patterns in this document are followed
- Check for tenant isolation in all database operations
- Verify error handling is consistent
- Confirm brain systems are used appropriately
For Architecture Decisions
- Document the decision in this file
- Update relevant architecture docs
- Team discussion before major changes
---
Related Documents
- CLAUDE.md - Platform overview
- BRAIN_SYSTEMS.md - Brain system usage
- MULTI_TENANCY.md - Tenant architecture
- API_STANDARDS.md - API design patterns
Last updated: 2025-02-03