You have traveled a long road. In Part I, you learned what vibe coding is and set up your development environment. In Part II, you mastered prompt engineering -- from basic prompts to specification-driven development and iterative refinement. In...
In This Chapter
- Learning Objectives
- Introduction
- 41.1 Project 1: Full-Stack SaaS Application
- 41.2 Project 1: Requirements and Architecture
- 41.3 Project 1: Implementation Walkthrough
- 41.4 Project 1: Testing and Deployment
- 41.5 Project 2: Data Pipeline and Analytics Platform
- 41.6 Project 2: Requirements and Architecture
- 41.7 Project 2: Implementation Walkthrough
- 41.8 Project 2: Testing and Deployment
- 41.9 Project 3: Multi-Agent Development Tool
- 41.10 Project 3: Requirements, Implementation, and Reflection
- Chapter Summary
- Looking Ahead
Chapter 41: Capstone Projects
Learning Objectives
After completing this chapter, you will be able to:
- Identify the architectural components and integration points required for a full-stack SaaS application, a data pipeline platform, and a multi-agent development tool (Remember)
- Explain how vibe coding techniques from Parts I through VI combine to enable building substantial, production-quality systems (Understand)
- Compare the architectural trade-offs between monolithic and microservice approaches for each capstone project (Analyze)
- Design a complete system architecture for a SaaS application including authentication, subscription management, and dashboard components (Apply)
- Implement a data pipeline that ingests, transforms, stores, and visualizes data using AI-assisted development workflows (Apply)
- Construct a multi-agent orchestration system that coordinates specialized AI agents for automated coding tasks (Apply)
- Evaluate testing strategies appropriate for each project type, including unit tests, integration tests, and end-to-end validation (Evaluate)
- Develop deployment plans that address environment configuration, scaling, monitoring, and security for production systems (Evaluate)
- Create comprehensive capstone projects that demonstrate mastery of vibe coding across the full software development lifecycle (Create)
- Synthesize skills from prompt engineering, architecture design, testing, security, and deployment into cohesive, working applications (Create)
Introduction
You have traveled a long road. In Part I, you learned what vibe coding is and set up your development environment. In Part II, you mastered prompt engineering -- from basic prompts to specification-driven development and iterative refinement. In Part III, you built real software: CLI tools, web frontends, REST APIs, databases, and full-stack applications. In Part IV, you learned to think architecturally about design patterns, security, performance, and deployment. In Part V, you explored professional workflows including version control, team collaboration, and managing technical debt. In Part VI, you pushed into advanced territory with AI coding agents, custom MCP servers, and multi-agent systems.
Now it is time to put it all together.
This chapter presents three comprehensive capstone projects, each designed to integrate skills from multiple parts of this book. These are not toy examples or isolated exercises. They are substantial systems -- the kind of projects that demonstrate genuine competence and could serve as the foundation for real products.
Project 1: Full-Stack SaaS Application integrates skills from Parts I through IV. You will build a subscription-based task management platform with user authentication, role-based access control, Stripe integration for payments, a React dashboard, a FastAPI backend, and a PostgreSQL database. This project demonstrates that vibe coding can produce production-quality, commercially viable software.
Project 2: Data Pipeline and Analytics Platform integrates skills from Parts III and IV. You will build a system that ingests data from multiple sources, transforms and cleanses it through a configurable pipeline, stores it in an optimized schema, and presents interactive visualizations. This project shows how vibe coding handles data-intensive applications with complex business logic.
Project 3: Multi-Agent Development Tool integrates skills from Part VI and draws on every preceding part. You will build an orchestration system that coordinates multiple specialized AI agents -- a planner, a coder, a reviewer, and a tester -- to collaboratively develop software from a natural-language specification. This project represents the frontier of AI-assisted development.
For each project, we provide requirements, architecture decisions, an implementation walkthrough with the exact prompts you would use, a testing strategy, and a deployment plan. The goal is not just to show you what to build, but to demonstrate how the vibe coding workflow scales from small scripts to substantial systems.
Note
These projects are designed to be attempted after completing the rest of the book. Each one draws on techniques from multiple chapters. If a section references a concept that feels unfamiliar, revisit the relevant chapter before continuing. Cross-references are provided throughout.
41.1 Project 1: Full-Stack SaaS Application
The first capstone project is a full-stack Software-as-a-Service application called TaskFlow -- a subscription-based task management platform designed for small teams. This project integrates virtually every skill from Parts I through IV of this book: frontend development (Chapter 16), backend API design (Chapter 17), database modeling (Chapter 18), full-stack integration (Chapter 19), authentication and security (Chapter 27), and deployment (Chapter 29).
Why a SaaS Application?
SaaS applications represent the dominant business model for modern software. They combine technical depth -- authentication, authorization, payment processing, real-time updates, multi-tenant data isolation -- with commercial reality. Building one demonstrates that you can ship software people would actually pay for.
The SaaS model also exercises every layer of the stack simultaneously. A user signs up (frontend form, API endpoint, database write, email verification). They subscribe to a plan (payment processing, subscription state management, feature gating). They use the product daily (real-time UI updates, API calls, database queries, background jobs). When something goes wrong, they contact support (logging, error tracking, audit trails).
Intuition: Think of a SaaS application as a small city. It needs infrastructure (database, hosting), utilities (authentication, payments), roads (APIs), buildings (features), and services (monitoring, support). Vibe coding is like having an expert urban planner who can help you design and build each component while keeping the whole city coherent.
Feature Overview
TaskFlow includes the following features:
- User authentication: Registration, login, password reset, email verification, and session management using JWT tokens
- Team management: Create teams, invite members via email, assign roles (owner, admin, member)
- Task management: Create, update, delete, and organize tasks with priorities, due dates, labels, and assignees
- Project boards: Kanban-style boards with customizable columns and drag-and-drop task movement
- Subscription management: Free tier (1 project, 3 team members), Pro tier ($12/month, unlimited projects and members), and Enterprise tier (custom pricing)
- Dashboard: Overview of team activity, task completion rates, overdue items, and productivity trends
- REST API: Full API for third-party integrations with API key authentication
- Notifications: In-app and email notifications for task assignments, due dates, and team activity
This is an ambitious scope, and that is intentional. The purpose of a capstone project is to push beyond your comfort zone. With vibe coding, you will find that managing this complexity is far more tractable than it would be writing every line by hand.
41.2 Project 1: Requirements and Architecture
Functional Requirements
Let us formalize the requirements using the specification-driven approach from Chapter 10.
User Stories:
- As a new user, I can register with my email and password so that I can access the platform.
- As a registered user, I can log in and see my dashboard so that I can manage my tasks.
- As a team owner, I can invite members by email so that we can collaborate.
- As a team member, I can create tasks with titles, descriptions, priorities, and due dates so that work is tracked.
- As a subscriber, I can upgrade or downgrade my plan so that I pay only for what I need.
- As an admin, I can view team activity and productivity metrics on the dashboard.
Non-Functional Requirements:
- Response time under 200ms for API calls
- Support for 1,000 concurrent users
- 99.9% uptime target
- GDPR-compliant data handling
- Automated backups every 6 hours
Architecture Decisions
The architecture follows a standard three-tier pattern with some modern refinements:
┌─────────────────────────────────────────────┐
│ Frontend (React + TypeScript) │
│ - Single-page application │
│ - State management with React Query │
│ - Tailwind CSS for styling │
└──────────────────┬──────────────────────────┘
│ HTTPS / REST
┌──────────────────▼──────────────────────────┐
│ Backend (FastAPI + Python) │
│ - JWT authentication middleware │
│ - Pydantic request/response validation │
│ - Stripe webhook handler │
│ - Background task queue (Celery) │
└──────────────────┬──────────────────────────┘
│ SQLAlchemy ORM
┌──────────────────▼──────────────────────────┐
│ Database (PostgreSQL) │
│ - Users, teams, projects, tasks tables │
│ - Subscription and payment records │
│ - Audit log for compliance │
└─────────────────────────────────────────────┘
Why these choices?
- React with TypeScript for the frontend because AI assistants generate excellent TypeScript and React code (Chapter 16), and the type safety catches integration errors early.
- FastAPI for the backend because it provides automatic API documentation, built-in validation through Pydantic, and async support for handling concurrent requests (Chapter 17).
- PostgreSQL for the database because it handles complex queries, supports JSON columns for flexible data, and has excellent tooling for migrations (Chapter 18).
- Stripe for payments because it handles PCI compliance, subscription lifecycle management, and provides well-documented webhooks.
Architecture Tip: When building a SaaS application with AI assistance, resist the temptation to use microservices from the start. A well-structured monolith is simpler to build, deploy, and debug. You can extract services later if needed. This aligns with the YAGNI principle discussed in Chapter 25.
Database Schema
The core schema includes the following tables:
-- Users table
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
full_name VARCHAR(255) NOT NULL,
is_verified BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- Teams table
CREATE TABLE teams (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
owner_id UUID REFERENCES users(id),
subscription_tier VARCHAR(50) DEFAULT 'free',
stripe_customer_id VARCHAR(255),
created_at TIMESTAMP DEFAULT NOW()
);
-- Team memberships (junction table)
CREATE TABLE team_members (
user_id UUID REFERENCES users(id),
team_id UUID REFERENCES teams(id),
role VARCHAR(50) DEFAULT 'member',
joined_at TIMESTAMP DEFAULT NOW(),
PRIMARY KEY (user_id, team_id)
);
-- Projects table
CREATE TABLE projects (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
team_id UUID REFERENCES teams(id),
name VARCHAR(255) NOT NULL,
description TEXT,
created_at TIMESTAMP DEFAULT NOW()
);
-- Tasks table
CREATE TABLE tasks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID REFERENCES projects(id),
title VARCHAR(255) NOT NULL,
description TEXT,
status VARCHAR(50) DEFAULT 'todo',
priority VARCHAR(50) DEFAULT 'medium',
assignee_id UUID REFERENCES users(id),
due_date DATE,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
This schema was generated using the prompt techniques from Chapter 18. Notice how the relationships model real-world concepts cleanly: users belong to teams through memberships, teams own projects, and projects contain tasks.
41.3 Project 1: Implementation Walkthrough
Building TaskFlow with vibe coding follows a structured sequence. Each phase builds on the previous one, and the prompts grow more specific as the codebase evolves.
Phase 1: Project Scaffolding
The first prompt sets up the entire project structure:
Prompt: "Create a full-stack project structure for a SaaS task management
application called TaskFlow. Backend: FastAPI with SQLAlchemy, Alembic for
migrations, and pytest for testing. Frontend: React with TypeScript, Tailwind CSS,
and React Query. Include Docker and docker-compose files for local development
with PostgreSQL and Redis. Set up a proper .gitignore, requirements.txt,
package.json, and environment variable templates."
This prompt leverages the scaffolding techniques from Chapter 15. The AI generates a complete directory structure with configuration files, which gives you a working development environment in minutes rather than hours.
Phase 2: Authentication System
Authentication is the foundation for everything else. The prompt for this phase is detailed because security mistakes are costly (Chapter 27):
Prompt: "Implement a complete authentication system for the FastAPI backend.
Include: (1) User registration with email and password, storing bcrypt-hashed
passwords. (2) Login endpoint returning JWT access and refresh tokens.
(3) Token refresh endpoint. (4) Password reset flow with email verification
tokens. (5) Middleware that extracts and validates JWT tokens on protected
routes. (6) Rate limiting on auth endpoints (5 attempts per minute).
Use Pydantic models for all request/response schemas. Write comprehensive
pytest tests for every endpoint, including edge cases like duplicate emails,
weak passwords, expired tokens, and rate limit violations."
Prompt Strategy: Notice how this prompt specifies the security requirements explicitly -- bcrypt hashing, rate limiting, token expiration. AI assistants generate more secure code when you ask for specific security measures rather than leaving security as an afterthought. This technique is covered in depth in Chapter 27.
Phase 3: Core Business Logic
With authentication in place, you build the core domain model:
Prompt: "Implement the team and task management features for TaskFlow.
Include SQLAlchemy models for teams, team_members, projects, and tasks.
Create FastAPI endpoints for: creating and managing teams, inviting users
to teams via email, creating and updating tasks with title, description,
priority (low/medium/high/critical), status (todo/in_progress/review/done),
assignee, and due date. Implement role-based access control: team owners
can manage settings and billing, admins can manage members and projects,
members can create and update tasks. Include proper error handling,
pagination for list endpoints, and filtering/sorting capabilities."
This prompt uses the specification-driven approach from Chapter 10 -- it defines the domain model, the access control rules, and the API behavior in a single, comprehensive prompt. The AI generates coordinated code across models, schemas, routes, and permissions.
Phase 4: Subscription and Payment Integration
Integrating Stripe requires careful handling of webhooks and state synchronization (Chapter 20):
Prompt: "Integrate Stripe subscription billing into TaskFlow. Define three
tiers: Free (1 project, 3 members), Pro ($12/month, unlimited), Enterprise
(custom). Implement: (1) Stripe Checkout session creation for upgrades.
(2) Webhook handler for subscription events (created, updated, cancelled,
payment_failed). (3) Subscription status stored in the teams table.
(4) Middleware that checks subscription limits before allowing project
creation or member invitations. (5) Billing portal redirect for customers
to manage their own subscriptions. Use Stripe test mode for development."
Phase 5: Frontend Dashboard
The frontend ties everything together visually (Chapter 16):
Prompt: "Build a React dashboard for TaskFlow. Include: (1) Login and
registration pages with form validation. (2) Main dashboard showing task
summary statistics -- tasks due today, overdue tasks, completion rate this
week. (3) Kanban board view with draggable task cards across columns
(To Do, In Progress, Review, Done). (4) Team management page showing
members, their roles, and an invitation form. (5) Settings page with
subscription management and billing history. Use Tailwind CSS for styling,
React Query for API state management, and react-beautiful-dnd for
drag-and-drop. Implement proper loading states, error boundaries,
and empty states for every view."
Callout: The Iterative Refinement Loop -- You will not get every component right on the first prompt. The dashboard, in particular, requires multiple rounds of refinement (Chapter 11). Start with the layout and navigation, then refine individual components. A common sequence is: basic layout first, then data fetching, then interactivity, then visual polish. Each refinement prompt references the existing code and asks for specific improvements.
Key Implementation Patterns
Several patterns emerge during the implementation that are worth highlighting:
1. The Repository Pattern for Data Access
Rather than querying the database directly in route handlers, TaskFlow uses a repository pattern (Chapter 25):
class TaskRepository:
def __init__(self, session: AsyncSession):
self.session = session
async def get_by_id(self, task_id: UUID) -> Task | None:
result = await self.session.execute(
select(Task).where(Task.id == task_id)
)
return result.scalar_one_or_none()
async def list_by_project(
self, project_id: UUID, status: str | None = None
) -> list[Task]:
query = select(Task).where(Task.project_id == project_id)
if status:
query = query.where(Task.status == status)
result = await self.session.execute(query)
return list(result.scalars().all())
This separation makes the code testable -- you can mock the repository in unit tests without needing a real database.
2. Dependency Injection for Services
FastAPI's dependency injection system keeps the route handlers clean:
async def get_current_user(
token: str = Depends(oauth2_scheme),
session: AsyncSession = Depends(get_session),
) -> User:
payload = decode_jwt_token(token)
user = await UserRepository(session).get_by_id(payload["user_id"])
if not user:
raise HTTPException(status_code=401, detail="User not found")
return user
@router.get("/tasks")
async def list_tasks(
project_id: UUID,
current_user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_session),
) -> list[TaskResponse]:
await verify_project_access(current_user, project_id, session)
repo = TaskRepository(session)
tasks = await repo.list_by_project(project_id)
return [TaskResponse.model_validate(t) for t in tasks]
3. Event-Driven Subscription Management
Stripe webhooks trigger state changes asynchronously, which requires careful handling:
@router.post("/webhooks/stripe")
async def handle_stripe_webhook(request: Request):
payload = await request.body()
sig_header = request.headers.get("stripe-signature")
event = stripe.Webhook.construct_event(
payload, sig_header, settings.stripe_webhook_secret
)
if event["type"] == "customer.subscription.updated":
subscription = event["data"]["object"]
await update_team_subscription(
stripe_customer_id=subscription["customer"],
tier=map_price_to_tier(subscription["items"]["data"][0]["price"]["id"]),
status=subscription["status"],
)
elif event["type"] == "invoice.payment_failed":
await handle_payment_failure(event["data"]["object"])
return {"status": "ok"}
41.4 Project 1: Testing and Deployment
Testing Strategy
TaskFlow uses a three-tier testing strategy aligned with the testing pyramid from Chapter 21:
Unit Tests cover individual functions and classes in isolation. The repository classes, utility functions, and Pydantic validators each have dedicated test files. Mocking is used extensively to isolate units from their dependencies:
@pytest.mark.asyncio
async def test_create_task_with_valid_data():
mock_session = AsyncMock()
repo = TaskRepository(mock_session)
task_data = TaskCreate(
title="Write tests",
project_id=uuid4(),
priority="high",
)
result = await repo.create(task_data)
mock_session.add.assert_called_once()
mock_session.commit.assert_called_once()
Integration Tests verify that the API endpoints work correctly with a real test database. Each test runs inside a database transaction that is rolled back after the test, ensuring test isolation:
@pytest.mark.asyncio
async def test_create_and_retrieve_task(client, auth_headers, test_project):
create_response = await client.post(
f"/api/projects/{test_project.id}/tasks",
json={"title": "Integration test task", "priority": "medium"},
headers=auth_headers,
)
assert create_response.status_code == 201
task_id = create_response.json()["id"]
get_response = await client.get(
f"/api/tasks/{task_id}",
headers=auth_headers,
)
assert get_response.status_code == 200
assert get_response.json()["title"] == "Integration test task"
End-to-End Tests use Playwright to verify critical user flows through the actual browser interface. These cover registration, login, creating a project, adding tasks, and managing team members.
Testing Tip: When using AI to generate tests, always specify the edge cases you want covered. The prompt "write tests for the login endpoint" produces generic happy-path tests. The prompt "write tests for the login endpoint including wrong password, unverified email, locked account, expired session, SQL injection attempt, and rate limit exceeded" produces a comprehensive test suite.
Deployment Plan
TaskFlow deploys to a cloud platform using containers (Chapter 29):
Infrastructure: - Application: Docker containers running on AWS ECS or Google Cloud Run - Database: Managed PostgreSQL (AWS RDS or Google Cloud SQL) - Cache: Managed Redis (AWS ElastiCache) - CDN: CloudFront or Cloud CDN for static frontend assets - DNS: Route 53 or Cloud DNS with SSL certificates
CI/CD Pipeline: 1. Push to main branch triggers GitHub Actions 2. Run linting (ruff) and type checking (mypy) 3. Run unit and integration tests 4. Build Docker image and push to container registry 5. Deploy to staging environment 6. Run end-to-end tests against staging 7. Promote to production with blue-green deployment
Environment Configuration:
# .env.production (stored in secrets manager, not in git)
DATABASE_URL=postgresql+asyncpg://user:pass@db-host:5432/taskflow
REDIS_URL=redis://cache-host:6379
JWT_SECRET_KEY=<generated-256-bit-key>
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
FRONTEND_URL=https://app.taskflow.example.com
Security Reminder: Never commit secrets to version control. Use a secrets manager (AWS Secrets Manager, Google Secret Manager, or HashiCorp Vault) and inject them as environment variables at deployment time. This was covered in detail in Chapter 27.
41.5 Project 2: Data Pipeline and Analytics Platform
The second capstone project is DataLens -- a data pipeline and analytics platform that ingests data from multiple sources, transforms it through configurable processing stages, stores it in an optimized schema, and presents interactive visualizations. This project integrates database design (Chapter 18), backend development (Chapter 17), external API integration (Chapter 20), and architecture patterns (Chapter 24).
Why a Data Pipeline?
Data pipelines are among the most common software systems built in modern organizations. Every company needs to collect data from various sources, clean and transform it, and make it available for analysis. The domain exercises skills that are distinct from web application development: batch processing, schema evolution, data quality validation, and analytical query optimization.
Data pipelines also showcase a different dimension of vibe coding. While the SaaS project emphasized interactive user interfaces and real-time responses, the data pipeline emphasizes correctness, reliability, and throughput. The prompting strategies shift accordingly -- you focus more on data validation rules, error recovery, and processing guarantees.
Feature Overview
DataLens includes:
- Data Ingestion: Pull data from CSV files, REST APIs, databases, and webhook receivers
- Transformation Pipeline: Configurable chain of transformations including cleaning, normalization, enrichment, and aggregation
- Data Quality Checks: Automated validation rules that flag or reject invalid records
- Storage Layer: Optimized PostgreSQL schema with materialized views for common queries
- Scheduling: Cron-based pipeline execution with monitoring and alerting
- Analytics Dashboard: Interactive charts showing trends, distributions, and anomalies
- Pipeline Configuration: YAML-based pipeline definitions that non-engineers can modify
- Audit Trail: Complete history of every pipeline run including records processed, errors encountered, and transformations applied
41.6 Project 2: Requirements and Architecture
Functional Requirements
DataLens is designed around a concrete use case: an e-commerce company that needs to consolidate sales data from multiple channels (website, mobile app, marketplace integrations) and present unified analytics.
User Stories:
- As a data analyst, I can configure a new data source by specifying its type, connection details, and schema mapping.
- As a data engineer, I can define transformation pipelines using YAML configuration files.
- As a business user, I can view sales dashboards showing revenue trends, product performance, and channel comparison.
- As an operations manager, I can monitor pipeline health and receive alerts when pipelines fail or data quality degrades.
- As an administrator, I can schedule pipelines to run at specific intervals and review execution history.
Architecture Decisions
DataLens uses a layered pipeline architecture:
┌─────────────────────────────────────────────┐
│ Data Sources │
│ CSV Files │ REST APIs │ Databases │ Webhooks│
└──────┬──────┬──────────┬───────────┬────────┘
│ │ │ │
┌──────▼──────▼──────────▼───────────▼────────┐
│ Ingestion Layer │
│ - Source connectors (pluggable) │
│ - Raw data staging │
│ - Schema detection and mapping │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ Transformation Layer │
│ - Data cleaning (nulls, duplicates, types) │
│ - Normalization (dates, currencies, units) │
│ - Enrichment (computed fields, lookups) │
│ - Aggregation (rollups, summaries) │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ Storage Layer │
│ - Fact and dimension tables │
│ - Materialized views for dashboards │
│ - Partitioned tables for large datasets │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ Presentation Layer │
│ - FastAPI endpoints for dashboard data │
│ - Chart rendering with Plotly │
│ - Export to CSV/Excel/PDF │
└─────────────────────────────────────────────┘
Key Design Decisions:
- Pluggable connectors for data sources, using an abstract base class that each source type implements. This makes adding new source types straightforward (Chapter 25 -- Strategy pattern).
- YAML-based pipeline configuration rather than hard-coded pipelines. This allows data engineers to create new pipelines without modifying application code.
- Staging tables for raw data before transformation. This preserves the original data for debugging and reprocessing.
- Materialized views for dashboard queries. These pre-compute expensive aggregations and can be refreshed on a schedule.
Pipeline Configuration Format
Pipelines are defined in YAML, making them readable and version-controllable:
pipeline:
name: daily_sales_etl
schedule: "0 2 * * *" # 2 AM daily
sources:
- name: website_orders
type: postgres
connection: ${WEBSITE_DB_URL}
query: "SELECT * FROM orders WHERE date >= :last_run"
- name: marketplace_sales
type: rest_api
url: "https://api.marketplace.com/v2/sales"
auth: bearer ${MARKETPLACE_API_KEY}
pagination: cursor
transformations:
- type: clean
rules:
- drop_nulls: [order_id, amount]
- deduplicate: [order_id]
- type: normalize
rules:
- dates: {format: "ISO8601", timezone: "UTC"}
- currency: {target: "USD", rate_source: "ecb"}
- type: enrich
rules:
- compute: {field: "margin", expression: "revenue - cost"}
- lookup: {field: "region", source: "dim_geography", key: "zip_code"}
- type: aggregate
group_by: [date, channel, region]
metrics:
- {name: total_revenue, function: sum, field: revenue}
- {name: order_count, function: count, field: order_id}
- {name: avg_order_value, function: avg, field: revenue}
output:
table: fact_daily_sales
mode: upsert
key: [date, channel, region]
quality_checks:
- {check: row_count, min: 100, alert: warning}
- {check: null_rate, field: amount, max: 0.01, alert: error}
- {check: value_range, field: amount, min: 0, max: 100000, alert: error}
Design Pattern: The YAML pipeline configuration is an example of the Interpreter pattern (Chapter 25). The application reads the configuration, interprets each directive, and executes the corresponding operations. This separates the "what" (pipeline definition) from the "how" (execution engine), making pipelines easy to create, modify, and version-control.
41.7 Project 2: Implementation Walkthrough
Phase 1: Ingestion Framework
The ingestion layer uses a pluggable connector architecture:
Prompt: "Create a data ingestion framework in Python. Define an abstract
base class DataConnector with methods: connect(), fetch_schema(),
fetch_data(since: datetime) -> Iterator[dict], and close(). Implement
concrete connectors for: (1) CSV files from a directory, (2) PostgreSQL
database queries, (3) REST API with pagination support (cursor and offset),
(4) Webhook receiver using FastAPI. Each connector should handle errors
gracefully, log its operations, and support incremental fetching (only
new records since last run). Include type hints and comprehensive docstrings."
The abstract base class pattern ensures all connectors have a consistent interface:
from abc import ABC, abstractmethod
from datetime import datetime
from typing import Any, Iterator
class DataConnector(ABC):
"""Base class for all data source connectors."""
@abstractmethod
def connect(self) -> None:
"""Establish connection to the data source."""
...
@abstractmethod
def fetch_schema(self) -> dict[str, str]:
"""Return a mapping of field names to their data types."""
...
@abstractmethod
def fetch_data(self, since: datetime | None = None) -> Iterator[dict[str, Any]]:
"""Yield records from the data source, optionally since a timestamp."""
...
@abstractmethod
def close(self) -> None:
"""Clean up resources and close connections."""
...
Phase 2: Transformation Engine
The transformation engine processes records through a chain of configurable steps:
Prompt: "Build a data transformation engine that reads pipeline definitions
from YAML and applies them to data records. Implement transformation types:
(1) clean -- drop nulls, remove duplicates, validate data types.
(2) normalize -- standardize date formats, convert currencies, unify units.
(3) enrich -- compute new fields from expressions, look up values from
reference tables. (4) aggregate -- group by specified fields and compute
metrics (sum, count, avg, min, max). Each transformer should be a class
implementing a Transformer interface with a transform(records) method.
The engine should chain transformers based on the YAML configuration.
Include detailed logging of how many records each step processes,
modifies, and rejects."
The chain-of-responsibility pattern (Chapter 25) is natural here:
class TransformationPipeline:
def __init__(self, steps: list[Transformer]):
self.steps = steps
def execute(self, records: list[dict]) -> list[dict]:
current = records
for step in self.steps:
logger.info(f"Running {step.name}: {len(current)} records input")
current = step.transform(current)
logger.info(f" -> {len(current)} records output")
return current
Phase 3: Storage and Analytics
The storage layer uses a star schema optimized for analytical queries:
Prompt: "Design a star schema for an e-commerce analytics database using
SQLAlchemy. Create dimension tables for: dim_date (with fiscal calendar
fields), dim_product (with category hierarchy), dim_geography (with
region/country/city), dim_channel (website, mobile, marketplace). Create
a fact table fact_daily_sales with foreign keys to all dimensions and
measures: revenue, cost, margin, order_count, unit_count, avg_order_value.
Include a materialized view refresh mechanism. Add utility functions for
common analytical queries: revenue by channel over time, top products by
margin, geographic distribution. Use proper indexes for query performance."
Phase 4: Dashboard and Visualization
The analytics dashboard provides interactive visualizations:
Prompt: "Create a FastAPI-based analytics dashboard for the DataLens
platform. Build endpoints that return chart-ready JSON data for:
(1) Revenue trend line chart (daily/weekly/monthly granularity).
(2) Channel comparison bar chart. (3) Product category breakdown pie chart.
(4) Geographic heat map data. (5) Data quality scorecard. Create a simple
HTML dashboard using Plotly.js that calls these endpoints and renders
interactive charts. Include date range filters, channel selectors, and
a refresh button. The dashboard should load quickly by querying
materialized views rather than raw data."
Performance Insight: Materialized views are essential for dashboard performance (Chapter 28). Without them, every dashboard load triggers expensive aggregation queries across millions of rows. With materialized views, the aggregation happens once (during refresh) and the dashboard reads pre-computed results. The trade-off is data freshness -- views show data as of their last refresh, not real-time data. For most analytics use cases, this is acceptable.
41.8 Project 2: Testing and Deployment
Testing Strategy
Data pipelines require a testing approach that is different from web applications. The focus is on data correctness rather than user interactions.
Data Quality Tests verify that transformations produce correct output for known inputs:
def test_currency_normalization():
records = [
{"amount": 100, "currency": "EUR", "date": "2025-01-15"},
{"amount": 200, "currency": "GBP", "date": "2025-01-15"},
{"amount": 300, "currency": "USD", "date": "2025-01-15"},
]
normalizer = CurrencyNormalizer(target="USD")
result = normalizer.transform(records)
# EUR and GBP should be converted; USD should remain unchanged
assert all(r["currency"] == "USD" for r in result)
assert result[2]["amount"] == 300 # USD unchanged
assert result[0]["amount"] != 100 # EUR converted
Pipeline Integration Tests run complete pipelines against test datasets and verify the output:
def test_full_pipeline_execution(test_db, sample_csv):
config = load_pipeline_config("test_pipeline.yaml")
pipeline = PipelineEngine(config, db=test_db)
result = pipeline.run()
assert result.status == "success"
assert result.records_processed > 0
assert result.records_rejected == 0
# Verify data landed correctly
rows = test_db.execute("SELECT COUNT(*) FROM fact_daily_sales").scalar()
assert rows == result.records_processed
Regression Tests ensure that pipeline changes do not alter output for fixed input datasets. These tests snapshot the expected output and compare against it.
Deployment Architecture
DataLens deploys differently from a web application because it includes scheduled batch processing:
- Pipeline Workers: Run on scheduled containers (AWS ECS Scheduled Tasks or Kubernetes CronJobs) that spin up, execute the pipeline, and shut down
- API Server: Runs continuously on a container service to serve dashboard requests
- Database: Managed PostgreSQL with read replicas for dashboard queries
- Monitoring: CloudWatch or Prometheus metrics for pipeline execution time, record counts, error rates, and data quality scores
- Alerting: PagerDuty integration for pipeline failures and data quality violations
Prompt: "Create a Docker-based deployment configuration for DataLens.
Include: (1) Dockerfile for the pipeline worker and API server.
(2) docker-compose.yaml for local development with PostgreSQL.
(3) Kubernetes CronJob manifest for scheduled pipeline execution.
(4) Prometheus metrics endpoint for monitoring pipeline health.
(5) Health check endpoint that verifies database connectivity and
reports the last successful pipeline run time."
41.9 Project 3: Multi-Agent Development Tool
The third and most ambitious capstone project is CodeForge -- a multi-agent development tool that orchestrates multiple specialized AI agents to collaboratively develop software from natural-language specifications. This project integrates the advanced concepts from Part VI: AI coding agents (Chapter 36), custom tools and MCP servers (Chapter 37), and multi-agent systems (Chapter 38).
Why a Multi-Agent Tool?
Multi-agent systems represent the frontier of AI-assisted development. While a single AI assistant is powerful, a team of specialized agents can tackle problems that exceed any single agent's capability. Just as human software teams have specialists -- architects, developers, testers, reviewers -- a multi-agent system can assign different roles to different agents, each optimized for its task.
Building this tool is also deeply meta: you are using vibe coding to build a system that automates vibe coding itself. This recursive quality makes it an ideal capstone project because it forces you to think critically about the entire AI-assisted development workflow.
Perspective: Building CodeForge is like building a factory that builds other factories. The complexity is substantial, but the leverage is extraordinary. A well-designed multi-agent coding tool can generate, test, and refine software faster than a solo developer or even a solo developer with AI assistance. The key challenge is coordination -- making sure the agents work together effectively rather than at cross-purposes.
Feature Overview
CodeForge includes:
- Specification Agent: Converts natural-language project descriptions into structured requirements documents with user stories, acceptance criteria, and technical constraints
- Architect Agent: Takes requirements and produces architecture decisions, component diagrams, API contracts, and database schemas
- Coder Agent: Generates code based on architecture specifications, following coding standards and design patterns
- Reviewer Agent: Reviews generated code for bugs, security issues, performance problems, and style violations
- Tester Agent: Generates test suites for the produced code and runs them, reporting failures back to the Coder Agent
- Orchestrator: Coordinates the agents, manages the workflow, handles communication between agents, and presents results to the user
- Session Management: Maintains conversation history and project state across multiple interactions
- Human-in-the-Loop: Allows the user to review and approve outputs at critical decision points before proceeding
Architecture
CodeForge uses an orchestrator pattern where a central controller manages agent interactions:
┌─────────────────────────────────────────────┐
│ Human User │
│ "Build me a REST API for a blog platform" │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ Orchestrator │
│ - Workflow state machine │
│ - Agent dispatch and coordination │
│ - Result aggregation and conflict resolution│
│ - Human approval gates │
└──┬───────┬───────┬───────┬───────┬──────────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐
│Spec │ │Arch │ │Code │ │Review│ │Test │
│Agent│ │Agent│ │Agent│ │Agent│ │Agent│
└─────┘ └─────┘ └─────┘ └─────┘ └─────┘
The workflow proceeds through well-defined phases:
- Specification Phase: The Specification Agent converts the user's description into structured requirements. The user reviews and approves them.
- Architecture Phase: The Architect Agent designs the system based on approved requirements. The user reviews key decisions.
- Implementation Phase: The Coder Agent generates code component by component, following the architecture.
- Review Phase: The Reviewer Agent examines each component and sends feedback to the Coder Agent for revisions.
- Testing Phase: The Tester Agent generates and runs tests. Failures are sent back to the Coder Agent for fixes.
- Delivery Phase: The Orchestrator assembles the final output -- code, tests, documentation, and deployment configuration.
41.10 Project 3: Requirements, Implementation, and Reflection
Implementation Walkthrough
Building CodeForge requires careful design of agent interfaces, prompt templates, and orchestration logic.
Agent Base Class:
Each agent is implemented as a class with a standard interface:
Prompt: "Create a base class for AI agents in a multi-agent development
system. Each agent should have: a name, a role description, a system prompt
template, a method to execute a task given input context, and a method
to format its output as structured data. The base class should handle
API communication with the AI model, token counting, error handling,
and logging. Implement concrete agents for: SpecificationAgent,
ArchitectAgent, CoderAgent, ReviewerAgent, and TesterAgent. Each agent
should have a specialized system prompt that defines its role, expertise,
and output format."
The agent base class provides common infrastructure:
class Agent:
"""Base class for all AI agents in the CodeForge system."""
def __init__(self, name: str, role: str, system_prompt: str):
self.name = name
self.role = role
self.system_prompt = system_prompt
self.conversation_history: list[dict[str, str]] = []
async def execute(self, task: str, context: dict) -> AgentResult:
"""Execute a task with the given context and return structured result."""
messages = self._build_messages(task, context)
response = await self._call_model(messages)
parsed = self._parse_response(response)
self.conversation_history.append(
{"role": "assistant", "content": response}
)
return AgentResult(
agent_name=self.name,
output=parsed,
tokens_used=self._count_tokens(messages, response),
)
Orchestrator Implementation:
The orchestrator manages the workflow as a state machine:
Prompt: "Implement a workflow orchestrator for CodeForge. The orchestrator
should manage a state machine with phases: SPECIFICATION, ARCHITECTURE,
IMPLEMENTATION, REVIEW, TESTING, DELIVERY. In each phase, it dispatches
work to the appropriate agent, collects results, and determines whether
to proceed, retry, or request human intervention. Implement conflict
resolution for when the Reviewer Agent and Coder Agent disagree.
Add a human approval gate between phases where the user can review
outputs, provide feedback, and approve progression to the next phase.
Track the total cost (tokens used) across all agents."
The state machine pattern ensures orderly progression:
class WorkflowPhase(Enum):
SPECIFICATION = "specification"
ARCHITECTURE = "architecture"
IMPLEMENTATION = "implementation"
REVIEW = "review"
TESTING = "testing"
DELIVERY = "delivery"
COMPLETED = "completed"
class Orchestrator:
TRANSITIONS = {
WorkflowPhase.SPECIFICATION: WorkflowPhase.ARCHITECTURE,
WorkflowPhase.ARCHITECTURE: WorkflowPhase.IMPLEMENTATION,
WorkflowPhase.IMPLEMENTATION: WorkflowPhase.REVIEW,
WorkflowPhase.REVIEW: WorkflowPhase.TESTING,
WorkflowPhase.TESTING: WorkflowPhase.DELIVERY,
WorkflowPhase.DELIVERY: WorkflowPhase.COMPLETED,
}
async def run(self, user_request: str) -> ProjectOutput:
self.phase = WorkflowPhase.SPECIFICATION
context = {"user_request": user_request}
while self.phase != WorkflowPhase.COMPLETED:
agent = self._get_agent_for_phase(self.phase)
result = await agent.execute(
task=self._get_task_for_phase(self.phase),
context=context,
)
context[self.phase.value] = result.output
if self._requires_human_approval(self.phase):
approved = await self._request_approval(result)
if not approved:
continue # Retry the phase with feedback
self.phase = self.TRANSITIONS[self.phase]
return self._assemble_output(context)
Review-Revise Loop:
The most interesting interaction pattern is between the Reviewer and Coder agents. When the Reviewer identifies issues, the Coder receives the feedback and generates a revised version:
async def _handle_review_phase(self, context: dict) -> dict:
"""Manage the review-revise loop between Reviewer and Coder agents."""
code = context["implementation"]
max_iterations = 3
for iteration in range(max_iterations):
review = await self.reviewer.execute(
task="Review the following code for bugs, security issues, "
"and style violations.",
context={"code": code},
)
if review.output["approved"]:
logger.info(f"Code approved after {iteration + 1} review(s)")
return {"code": code, "review": review.output}
logger.info(f"Review iteration {iteration + 1}: "
f"{len(review.output['issues'])} issues found")
revision = await self.coder.execute(
task="Revise the code to address the following review feedback.",
context={"code": code, "feedback": review.output["issues"]},
)
code = revision.output["code"]
logger.warning("Max review iterations reached; proceeding with last version")
return {"code": code, "review": review.output}
Multi-Agent Insight: The review-revise loop is where multi-agent systems demonstrate clear value over single-agent approaches. A single AI agent reviewing its own code tends to overlook the same issues it made during generation -- a form of cognitive bias. By using separate agents with different system prompts and evaluation criteria, the system achieves a genuine check-and-balance dynamic. This mirrors the human practice of code review, where fresh eyes catch problems the original author missed.
Testing CodeForge
Testing a multi-agent system presents unique challenges because the AI responses are non-deterministic. The testing strategy uses three approaches:
1. Deterministic Unit Tests for the orchestrator, state machine, and utility functions:
def test_workflow_transitions():
orch = Orchestrator()
assert orch.TRANSITIONS[WorkflowPhase.SPECIFICATION] == WorkflowPhase.ARCHITECTURE
assert orch.TRANSITIONS[WorkflowPhase.TESTING] == WorkflowPhase.DELIVERY
def test_phase_requires_approval():
orch = Orchestrator()
assert orch._requires_human_approval(WorkflowPhase.SPECIFICATION) is True
assert orch._requires_human_approval(WorkflowPhase.IMPLEMENTATION) is False
2. Mock-Based Integration Tests that substitute pre-recorded agent responses for actual API calls:
@pytest.mark.asyncio
async def test_full_workflow_with_mocked_agents():
mock_agents = {
"spec": MockAgent(responses=FIXTURE_SPEC_RESPONSES),
"arch": MockAgent(responses=FIXTURE_ARCH_RESPONSES),
"coder": MockAgent(responses=FIXTURE_CODE_RESPONSES),
"reviewer": MockAgent(responses=FIXTURE_REVIEW_RESPONSES),
"tester": MockAgent(responses=FIXTURE_TEST_RESPONSES),
}
orch = Orchestrator(agents=mock_agents, auto_approve=True)
result = await orch.run("Build a REST API for a blog")
assert result.phase == WorkflowPhase.COMPLETED
assert "code" in result.outputs
assert "tests" in result.outputs
3. Golden Output Tests that run the full system with real AI responses against a fixed set of prompts and compare the structural quality of outputs (not their exact content):
def test_generated_code_is_valid_python(generated_output):
"""Verify that generated code is syntactically valid Python."""
try:
ast.parse(generated_output["code"])
except SyntaxError as e:
pytest.fail(f"Generated code has syntax error: {e}")
Deployment Considerations
CodeForge is primarily a developer tool, so deployment is simpler than the SaaS application:
- CLI Interface: Primary interface is a command-line tool that developers run locally
- API Server (optional): A FastAPI server that exposes the orchestration workflow for web-based interfaces
- Cost Management: Token usage tracking and configurable budget limits per project to prevent runaway API costs
- Model Configuration: Support for different AI models for different agents (for example, a smaller, faster model for the Reviewer and a more capable model for the Coder)
Reflection: What the Capstone Projects Teach
Building these three projects reveals several important truths about vibe coding at scale.
1. Prompts evolve with the codebase. Early prompts in a project are broad and generative -- "create a project structure," "implement authentication." As the codebase grows, prompts become narrow and specific -- "add rate limiting to the login endpoint," "fix the currency conversion for Japanese Yen edge case." The skill of vibe coding is knowing when to use each type.
2. Architecture decisions matter more than code quality. An AI assistant can refactor poorly written code in seconds. It cannot easily fix a fundamental architecture mistake, such as choosing the wrong database, coupling modules that should be independent, or skipping input validation at the API boundary. The human's most important contribution is making good architecture decisions upfront.
3. Testing is not optional. Every capstone project includes a comprehensive testing strategy not because it is academically expected, but because AI-generated code contains subtle bugs that only testing reveals. The combination of AI-generated code and AI-generated tests is more reliable than either alone.
4. The human remains essential. Despite the ambition of the multi-agent project, every workflow includes human approval gates. The user reviews requirements before architecture begins, reviews architecture before coding begins, and reviews code before deployment. Vibe coding does not remove the human from the loop -- it elevates the human to a supervisory role where their judgment has the most impact.
5. Integration is the hard part. Each individual component -- an API endpoint, a database model, a React component, a transformation function -- is straightforward to generate with AI. The challenge is making them work together. Consistent naming conventions, compatible data formats, proper error propagation across layers, correct authentication token handling -- these integration concerns require careful prompting and diligent testing.
Final Thought: These capstone projects are intentionally ambitious. You may not build all three, and the ones you build may look different from the descriptions here. That is fine. The goal is not to reproduce these projects exactly, but to demonstrate that vibe coding skills scale to real-world complexity. Whatever you build, the process will be the same: define clear requirements, make deliberate architecture choices, generate code through structured prompts, verify correctness through testing, and deploy with confidence. Those skills, practiced across 41 chapters, are what make you a vibe coder.
Chapter Summary
This chapter presented three comprehensive capstone projects that integrate skills from across the entire book:
-
TaskFlow (Full-Stack SaaS Application) demonstrated how vibe coding produces production-quality web applications with authentication, subscription management, team collaboration, and interactive dashboards. The project integrated frontend development, backend API design, database modeling, security practices, and deployment configuration.
-
DataLens (Data Pipeline and Analytics Platform) showed how vibe coding handles data-intensive applications with pluggable data connectors, configurable transformation pipelines, analytical storage schemas, and interactive dashboards. The project emphasized data correctness, pipeline reliability, and query performance.
-
CodeForge (Multi-Agent Development Tool) pushed into the frontier of AI-assisted development by building a system that orchestrates multiple specialized AI agents to collaboratively develop software. The project demonstrated advanced patterns including state machines, review-revise loops, and human-in-the-loop approval gates.
Across all three projects, common themes emerged: the importance of clear requirements, the critical role of architecture decisions, the necessity of comprehensive testing, and the irreplaceable value of human judgment at key decision points. Vibe coding does not replace software engineering discipline -- it amplifies it.
Looking Ahead
In Chapter 42, the final chapter, we step back from technical details to reflect on the vibe coding mindset as a whole. We examine how the practices and principles you have learned throughout this book fit together into a coherent philosophy for working with AI, and we look ahead to how this field will continue to evolve.