Evolving Your Stack
Your architecture isn't set in stone. Build for today's requirements, adapt as they change, refactor when needed.
Most project starters lock you into decisions made during init. Aegis Stack gives you the freedom to evolve your stack as your product evolves.
The Friday Afternoon Dilemma
It's Friday afternoon. Product wants the MVP Monday.
Do you:
A) Over-engineer for "future scale" (workers, queues, caching, message buses...)
B) Ship the minimal viable stack (FastAPI + Flet)
With Aegis Stack, you can confidently choose B - because adding components later is trivial.
# Friday 4 PM: Bootstrap it
uvx aegis-stack init mvp-api
cd mvp-api && uv sync && make serve
# Monday 9 AM: It's live
Week 3: Your First Background Job
Product loves the MVP. Now they want automated daily reports.
Before Aegis Stack:
- Manually add APScheduler dependency
- Create scheduler infrastructure
- Configure Docker services
- Update docker-compose.yml
- Fight merge conflicts
- Cross fingers
With Aegis Stack:
Added:
app/entrypoints/scheduler.py- Scheduler entrypointapp/components/scheduler.py- Job registrationtests/components/test_scheduler.py- Component tests- Docker service configuration
- Health check integration
Updated:
docker-compose.yml- New scheduler servicepyproject.toml- APScheduler dependency.copier-answers.yml- Component state
Ran automatically:
uv sync- Installed dependenciesmake fix- Formatted code
Your First Scheduled Job
Now add your daily report:
# app/components/scheduler.py
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.cron import CronTrigger
def register_jobs(scheduler: AsyncIOScheduler) -> None:
"""Register all scheduled jobs."""
# Daily report at 9 AM
scheduler.add_job(
generate_daily_report,
trigger=CronTrigger(hour=9, minute=0),
id="daily_report",
name="Generate Daily Report",
replace_existing=True,
)
async def generate_daily_report() -> None:
"""Generate and send daily report."""
# Your logic here
pass
Ship it:
Month 2: Scaling Up
Traffic is growing. Some API endpoints are slow. Time for background processing.
Added:
app/components/worker/- Worker queues (system, load-test)app/services/load_test.py- Load testing service- Redis service (auto-included dependency)
- Worker health monitoring
Updated:
docker-compose.yml- Redis + worker servicespyproject.toml- arq + redis dependencies
Move Work to Background
# app/components/backend/api/reports.py
from fastapi import APIRouter
from arq import create_pool
from arq.connections import RedisSettings
router = APIRouter()
@router.post("/reports/generate")
async def generate_report_async(report_data: dict):
"""Queue report generation."""
redis = await create_pool(RedisSettings())
job = await redis.enqueue_job("generate_report", report_data)
return {"job_id": str(job.job_id), "status": "queued"}
# app/components/worker/queues/system.py
async def generate_report(ctx: dict, report_data: dict) -> dict:
"""Process report generation in background."""
# Heavy lifting happens here
report = await process_report(report_data)
await send_report_email(report)
return {"status": "complete", "report_id": report.id}
Month 4: Adding User Authentication
Product wants to add user accounts. You need authentication.
Before Aegis Stack:
- Research auth libraries
- Set up database models
- Create migration system
- Build JWT token handling
- Write auth endpoints
- Add password hashing
- Configure environment variables
- Test everything manually
With Aegis Stack:
Added:
app/components/backend/api/auth/- Auth API endpoints (login, register, etc.)app/models/user.py- User model with password hashingapp/services/auth/- Authentication service layerapp/core/security.py- JWT token handlingapp/cli/auth.py- User management CLI commandsalembic/- Database migration infrastructuretests/- Comprehensive auth test suite (52 tests)- Database component (auto-added as required dependency)
Updated:
pyproject.toml- Auth dependencies (python-jose, passlib, python-multipart).env.example- Auth configuration (JWT_SECRET, etc.)docker-compose.yml- No changes (database already there from worker)
Post-Addition Setup
# Apply auth migrations
make migrate
# Create test users
mvp-api auth create-test-users --count 5
# Test authentication
curl -X POST http://localhost:8000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "admin@example.com", "password": "admin123"}'
Using Auth in Your API
# app/components/backend/api/reports.py
from fastapi import APIRouter, Depends
from app.components.backend.api.deps import get_current_active_user
from app.models.user import User
router = APIRouter()
@router.post("/reports/generate")
async def generate_report_async(
report_data: dict,
current_user: User = Depends(get_current_active_user) # Protected!
):
"""Queue report generation (requires authentication)."""
redis = await create_pool(RedisSettings())
job = await redis.enqueue_job(
"generate_report",
report_data,
user_id=current_user.id # Track who requested it
)
return {"job_id": str(job.job_id), "status": "queued"}
Month 5: Adding AI Chat
Product wants an AI chatbot for customer support.
Added:
app/services/ai/- PydanticAI integration with multiple providersapp/components/backend/api/ai/- AI API endpoints (chat, streaming)app/cli/ai.py- Interactive CLI chat interface with markdown rendering- AI provider support (OpenAI, Anthropic, Google Gemini, Groq)
- Conversation persistence and memory management
Updated:
pyproject.toml- PydanticAI dependencies.env.example- AI provider configuration
Post-Addition Setup
# Configure AI provider
echo "AI_PROVIDER=openai" >> .env
echo "OPENAI_API_KEY=your-key-here" >> .env
# Test via CLI
mvp-api ai chat
# > How can I help you today?
# Hello! Can you explain webhooks?
# Test via API
curl -X POST http://localhost:8000/api/ai/chat \
-H "Content-Type: application/json" \
-d '{"message": "Explain webhooks", "stream": false}'
Combining Services: AI + Auth
# app/components/backend/api/ai/router.py
from fastapi import APIRouter, Depends
from app.components.backend.api.deps import get_current_active_user
from app.models.user import User
from app.services.ai import get_ai_response
router = APIRouter()
@router.post("/ai/chat")
async def chat(
message: str,
current_user: User = Depends(get_current_active_user) # Require auth
):
"""User-specific AI chat with conversation history."""
response = await get_ai_response(
message=message,
user_id=current_user.id # Personalized context
)
return {"response": response, "user": current_user.email}
Month 6: Refactoring
You've learned your system. Turns out scheduled jobs work better as worker tasks. Time to clean up.
Removed:
- All scheduler component files
- Scheduler Docker service
- APScheduler dependency
Updated:
docker-compose.yml- Scheduler service gonepyproject.toml- Dependency cleaned up
Preserved:
- All your business logic (you migrated it to workers)
- Database data (if scheduler was using SQLite)
- Git history (full rollback support)
Migration Pattern
Before removal, move jobs to workers:
# app/components/worker/queues/system.py
from arq import cron
# Daily report - now a worker cron job
async def generate_daily_report(ctx):
"""Generate and send daily report."""
# Your logic here
pass
class WorkerSettings:
cron_jobs = [
cron(generate_daily_report, hour=9, minute=0) # Daily at 9 AM
]
Then remove scheduler:
git add . && git commit -m "Migrate to worker-based scheduling"
aegis remove scheduler --project-path ./mvp-api
git add . && git commit -m "Remove scheduler component"
Template Updates: Your Safety Net
Most template tools abandon you at init. Aegis stays with you.
Six months from now, we'll fix a security bug in our FastAPI template. Or add a better health check pattern. Or improve Docker configurations.
With most scaffolding tools? You're on your own. Manually diff templates. Copy-paste fixes. Hope nothing breaks.
With Aegis Stack:
What aegis update Actually Does
Powered by Copier's git-based 3-way merge engine, your project stays connected to its template. When you run aegis update:
- Fetches latest template from your configured source
- Compares three versions: Original template → Your customizations → New template
- Merges intelligently - template improvements flow in, your code stays intact
- Shows conflicts clearly - when manual review is needed, you see exactly where
# Preview what would change
aegis update --dry-run
# Apply template updates
aegis update
# What gets updated:
# ✓ Security fixes in generated code
# ✓ Improved Docker configurations
# ✓ Better testing patterns
# ✓ Enhanced tooling setup
# What stays yours:
# ✓ Your business logic
# ✓ Custom modifications
# ✓ Database data
# ✓ Git history
The Safety Guarantee
- Non-destructive: Your custom code is preserved through Copier's 3-way merge
- Transparent:
--dry-runshows exactly what changes before applying - Reversible: Full git history for rollback if needed
- Incremental: Small, frequent updates over painful migrations
Next Steps
- CLI Reference - Complete
aegis addandaegis removedocumentation - Component Overview - Deep dive into available components
Build for today. Adapt for tomorrow. That's Aegis Stack.
