12

12-Factor Agents

1,644Markdown/PythonMethodology

Principles for building production-ready LLM-powered software that customers trust.

MethodologyBest PracticesProductionEngineering

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 repository

Pros

  • +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 CaseWhy 12-Factor Agents
Production AI ServicesBridge prototype-to-production gap with proven principles
Team ProjectsStandardize AI development across multiple developers
Enterprise ApplicationsEnsure reliability and maintainability for business-critical AI
Long-Term ProjectsReduce technical debt as codebase evolves

Comparison with Alternatives

Feature12-Factor AgentsLangChainCrewAICustom
ParadigmMethodologyFrameworkFrameworkVaries
Production Ready✅ Guidelines⚠️ Manual⚠️ Manual⚠️ Manual
Team Adoption✅ Easy⚠️ Learning⚠️ Learning⚠️ Hard
Implementation❌ Principles only✅ Code✅ Code✅ Code
Flexibility✅ High⚠️ Opinionated⚠️ Opinionated✅ High
Learning CurveLowHighLowVaries
Best forBest practicesImplementationMulti-agentCustom needs

Best Practices

  1. Store config in environment — Never hardcode API keys or secrets
  2. Treat backing services as resources — Connect via config, not code
  3. Execute as stateless processes — Load/save context externally
  4. Separate build, release, run — Use CI/CD pipelines for deployments
  5. Treat logs as event streams — Stream to stdout, don't manage log files

Troubleshooting

IssueSolution
Config leaks to codeMove all secrets to environment variables
State persists between callsExternalize context storage (Redis, database)
Hard to deployContainerize with Docker and use port binding
Logs not accessibleStream to stdout and use log aggregation

Resources