Overview
12-Factor Agents is a methodology and framework for building LLM-powered software that follows proven software engineering principles adapted for the AI era. Inspired by the classic 12-Factor App methodology, it provides guidelines for building agents that are reliable, maintainable, and production-ready. With over 1,600 GitHub stars, it's gaining traction among teams building enterprise AI applications.
Features
- ✓Codebase version control for agent logic
- ✓Dependency management for AI models
- ✓Configuration for different environments
- ✓Backing services integration patterns
- ✓Build, release, run separation
- ✓Process isolation and statelessness
- ✓Port binding for deployment
- ✓Concurrency through scaling
- ✓Disposability for fast startup
- ✓Dev/prod parity in environments
- ✓Logs as event streams
- ✓Admin processes for one-off tasks
Installation
Read methodology from GitHub repositoryPros
- +Proven principles adapted for AI era
- +Enterprise-ready guidelines
- +Clear separation of concerns
- +Production-tested patterns
- +Good for team adoption
Cons
- −Methodology only, no code implementation
- −May need adaptation for specific use cases
- −New concept still being refined
- −Requires team buy-in
Alternatives
Documentation
12-Factor Agents
Overview
12-Factor Agents is a methodology and framework for building LLM-powered software that follows proven software engineering principles adapted for the AI era. Inspired by the classic 12-Factor App methodology, it provides guidelines for building agents that are reliable, maintainable, and production-ready.
The methodology addresses a critical gap: many AI agent projects start as prototypes but struggle to become production systems. 12-Factor Agents provides a structured approach to bridge this gap.
The 12 Factors
1. Codebase
One codebase tracked in version control, many deploys.
# Agent code should be version-controlled
git init
git add agents/
git commit -m "Initial agent implementation"
2. Dependencies
Explicitly declare and isolate dependencies.
# requirements.txt
anthropic>=0.25.0
openai>=1.0.0
pydantic>=2.0.0
3. Config
Store config in the environment.
# config.py
import os
class AgentConfig:
ANTHROPIC_API_KEY = os.environ["ANTHROPIC_API_KEY"]
MODEL = os.environ.get("MODEL", "claude-3-5-sonnet")
MAX_TOKENS = int(os.environ.get("MAX_TOKENS", "4096"))
4. Backing Services
Treat backing services as attached resources.
# services.py
from anthropic import Anthropic
from redis import Redis
# Connect to services via config
anthropic = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
redis = Redis.from_url(os.environ["REDIS_URL"])
5. Build, Release, Run
Strictly separate build, release, and run stages.
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Build │───▶│ Release│───▶│ Run │
│ (compile│ │ (combine│ │(execute │
│ code) │ │ config)│ │ agent) │
└─────────┘ └─────────┘ └─────────┘
6. Processes
Execute the app as one or more stateless processes.
# Each agent invocation is stateless
def handle_request(request: Request) -> Response:
# No persistent state between calls
context = load_context(request.session_id)
result = agent.process(request, context)
save_context(request.session_id, context)
return result
7. Port Binding
Export services via port binding.
# FastAPI agent server
from fastapi import FastAPI
app = FastAPI()
@app.post("/agents/{agent_id}/invoke")
async def invoke_agent(agent_id: str, request: AgentRequest):
result = get_agent(agent_id).run(request)
return result
8. Concurrency
Scale out via the process model.
# Use async for concurrent requests
import asyncio
async def handle_concurrent_requests(requests: list[Request]):
tasks = [handle_request(req) for req in requests]
return await asyncio.gather(*tasks)
9. Disposability
Maximize robustness with fast startup and graceful shutdown.
class Agent:
def __init__(self):
self._shutdown = False
async def shutdown(self):
self._shutdown = True
await self.cleanup()
10. Dev/Prod Parity
Keep development, staging, and production as similar as possible.
# docker-compose.yml
services:
agent:
image: my-agent:latest
environment:
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
11. Logs
Treat logs as event streams.
import logging
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger('agent')
def process(request):
logger.info(f"Processing request: {request.id}")
# ...
logger.info(f"Completed request: {request.id}")
12. Admin Processes
Run admin/management tasks as one-off processes.
# admin.py
def migrate_database():
"""Run database migrations"""
pass
def backup_context():
"""Backup agent context"""
pass
Example: Production-Ready Agent
"""
A 12-factor compliant AI agent service.
"""
import os
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from anthropic import Anthropic
import redis
import logging
# 1. Codebase: Version controlled
# 2. Dependencies: requirements.txt
# 3. Config: Environment variables
ANTHROPIC_API_KEY = os.environ["ANTHROPIC_API_KEY"]
REDIS_URL = os.environ.get("REDIS_URL", "redis://localhost:6379")
# 11. Logs: Event streams
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = FastAPI(title="Agent Service")
anthropic = Anthropic(api_key=ANTHROPIC_API_KEY)
redis_client = redis.from_url(REDIS_URL)
class AgentRequest(BaseModel):
prompt: str
session_id: str | None = None
class AgentResponse(BaseModel):
result: str
session_id: str
# 7. Port binding: HTTP service
@app.post("/agents/chat", response_model=AgentResponse)
async def chat(request: AgentRequest):
logger.info(f"Chat request: session={request.session_id}")
# 6. Processes: Stateless
context = _load_context(request.session_id)
result = await _process_prompt(anthropic, request.prompt, context)
_save_context(request.session_id, context)
return AgentResponse(result=result, session_id=request.session_id)
# 9. Disposability: Graceful shutdown
@app.on_event("shutdown")
async def shutdown():
logger.info("Shutting down agent service")
redis_client.close()
# 12. Admin processes
@app.post("/admin/backup")
async def backup_context():
"""Admin: Backup all agent contexts"""
contexts = redis_client.keys("context:*")
# Backup logic here
return {"status": "backup complete", "count": len(contexts)}
def _load_context(session_id: str) -> dict:
if not session_id:
return {}
data = redis_client.get(f"context:{session_id}")
return json.loads(data) if data else {}
def _save_context(session_id: str, context: dict):
if session_id:
redis_client.set(f"context:{session_id}", json.dumps(context))
async def _process_prompt(client, prompt: str, context: dict) -> str:
response = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
messages=[
{"role": "user", "content": prompt}
]
)
return response.content[0].text
Pros
- ✅ Proven principles adapted for AI era
- ✅ Enterprise-ready guidelines
- ✅ Clear separation of concerns
- ✅ Production-tested patterns
- ✅ Good for team adoption
- ✅ Reduces technical debt
Cons
- ❌ Methodology only, no code implementation
- ❌ May need adaptation for specific use cases
- ❌ New concept still being refined
- ❌ Requires team buy-in
When to Use
- Production AI services: When building AI features for production
- Team projects: When multiple developers work on AI code
- Enterprise applications: When reliability and maintainability matter
- Long-term projects: When the codebase will evolve over time
- Scaling AI features: When moving from prototype to production
Use Cases
| Use Case | Why 12-Factor Agents |
|---|---|
| Production AI Services | Bridge prototype-to-production gap with proven principles |
| Team Projects | Standardize AI development across multiple developers |
| Enterprise Applications | Ensure reliability and maintainability for business-critical AI |
| Long-Term Projects | Reduce technical debt as codebase evolves |
Comparison with Alternatives
| Feature | 12-Factor Agents | LangChain | CrewAI | Custom |
|---|---|---|---|---|
| Paradigm | Methodology | Framework | Framework | Varies |
| Production Ready | ✅ Guidelines | ⚠️ Manual | ⚠️ Manual | ⚠️ Manual |
| Team Adoption | ✅ Easy | ⚠️ Learning | ⚠️ Learning | ⚠️ Hard |
| Implementation | ❌ Principles only | ✅ Code | ✅ Code | ✅ Code |
| Flexibility | ✅ High | ⚠️ Opinionated | ⚠️ Opinionated | ✅ High |
| Learning Curve | Low | High | Low | Varies |
| Best for | Best practices | Implementation | Multi-agent | Custom needs |
Best Practices
- Store config in environment — Never hardcode API keys or secrets
- Treat backing services as resources — Connect via config, not code
- Execute as stateless processes — Load/save context externally
- Separate build, release, run — Use CI/CD pipelines for deployments
- Treat logs as event streams — Stream to stdout, don't manage log files
Troubleshooting
| Issue | Solution |
|---|---|
| Config leaks to code | Move all secrets to environment variables |
| State persists between calls | Externalize context storage (Redis, database) |
| Hard to deploy | Containerize with Docker and use port binding |
| Logs not accessible | Stream to stdout and use log aggregation |
