Building a multi-spec project

Building more complex spec relationships

👉 Closed beta notice

The full framework is currently part of Tessl’s closed beta program. You can request access here.

This guide will take you from a prototype with a single spec to a structured, scalable architecture. By the end, you’ll understand how to factor behavior into separate specs, declare dependencies between them (and their underlying code), and what project-level docs will help your project scale smoothly.

What we're building:

A spaced repetion app called memo that stores cards in a sqlite database and runs quizzes through a CLI or MCP server.

You can see a finished version of the app here.

Prerequisites

This guide assumes that you've run through our quickstart and have the latest version of Tessl installed.

  • Python: Any recent version is fine. We recommend using pyenv to manage python versions.

  • A package manager: We recommend uv, but poetry or pip are just as fine.

  • A coding agent: This walkthrough will use claude code, but Tessl works with all the most popular agents

  • Git: We strongly recommend that you have version control installed and configured on all your agentic projects!

Run the following commands:

tessl init && tessl setup agent

And copy some starter quiz data:

facts.csv
id,front,back
0,"What is the capital of France?","Paris"
1,"What is 2 + 2?","4"
2,"What year did World War II end?","1945"
3,"What is the largest planet in our solar system?","Jupiter"
4,"Who wrote Romeo and Juliet?","William Shakespeare"

Understand Product Requirements

Before you write a line of code — or spec! — it's important to clarify what you’re building. For complex projects, a good README is your first artifact.

  • Start with sections like vision, use cases, usage, and setup experience.

  • Don’t worry about being perfect — the goal is to get ideas out of your head and spot inconsistencies now, so you don't find them later as bugs and incorrect assumptions

  • Expect this to take 20–30 minutes, it's one of the harder parts!

  • Work iteratively: capture what you know now, then refine with your agent as the project evolves.

Example README
# Memo

A modern spaced repetition quiz application built with Python, featuring both CLI and MCP server interfaces for flexible learning workflows.

# Installation

```bash
# Install Python 3.13 with pyenv
pyenv install 3.13.1
pyenv local 3.13.1

# Install dependencies
uv sync
```

# Usage

```bash
# Review available cards
uv run memo main
uv run memo main --cards 2

# Add new cards
uv run memo add --front "question" --back "answer"
uv run memo add --csv facts.csv

# Start an mcp server to review with your agent
uv run memo mcp
```

# Core Features

**Multiple Interfaces**
CLI for direct use and MCP server for integration with AI assistants

**Local knowledge**

- Cards are saved in SQLite on your computer
- Add new cards through the CLI, MCP, or CSV import

**Spaced Repetition**

- Intelligent scheduling based on your performance and memory retention
- Calculates a nextDate based on your grade:
  - 0: wrong
  - 1: significant effort
  - ...
  - 5: effortless

# MCP Tools

The memo mcp server registers the following tools:

- `get_next_question`: Returns next scheduled question
- `save_score(question_id, grade)`: Update spaced repetition schedule
- `add_card(front, back)`: Adds a single new card
- `import_cards(csv_filepath)`: Bulk import cards from csv

Hello world

Once we have our README, it's tempting to hand the whole thing to our agent and ask it to build everything. But this is dangerous for a few reasons:

  • Agents work best when they have existing examples to work from. When your project is empty, they're much more likely to go off the rails.

  • Agents need clear, actionable feedback on their work - its hard to untangle logic bugs when you're also struggling to install dependencies and configure your environment.

  • In spec-driven development, you own your project architecture. The more your agent builds at once, the harder it is to keep track of how your system works.

Our recommendation is to work with agents like you work with teammates: ask for work in small chunks that are easy to test and easy to review. Let's start with something simple:

Build a simple cli quiz tool called memo using python and Typer.

The agent should provide a work plan that looks like this:

# Plan: Create Memo CLI Quiz Tool

Build a simple CLI quiz tool called "memo" using Python and Typer framework.

## Tasks

- [ ] Bootstrap project with Python/Typer configuration using uv and pyenv
- [ ] Interview user for quiz requirements.
- [ ] Create spec for memo CLI tool
- [ ] Build initial implementation from spec
- [ ] Test the CLI tool

If it doesn't, it's fine to be more explicit in your requests:

Build a simple cli quiz tool called memo using python and Typer. Use tessl plans.

Because we're saving the plan in a file, it's easy to edit. Planning is one of the most effective ways to get good results with agents. You should review your agent's plans and expect to make edits!

Since this is our first spec, we need to review what the agent writes to make sure it matches our preferred style. We'll also add a step to search the Tessl Registry and add usage specs for any dependencies. With that, the plan looks like this:

# Plan: Create Memo CLI Quiz Tool

Build a simple CLI quiz tool called "memo" using Python and Typer framework.

## Tasks

- [ ] Bootstrap project with Python/Typer configuration using uv and pyenv
- [ ] **Search Tessl for any usage specs related to the project dependencies**
- [ ] Interview user for quiz requirements.
- [ ] Create spec for memo CLI tool **and review with user**
- [ ] Build initial implementation from spec
- [ ] Test the CLI tool

Make sure the agent reads the edits and updates its plan:

Summarize the changes I made to the plan file, then get started on the first task.

The agent will start by interviewing you about your desired tech stack. If you're used to vibe coding, this might feel a bit different — the agent doesn't go racing ahead! Tessl steers your agent to ask clarifying questions about your project requirements before it gets going. When asked, provide the following information.

For project setup (assuming you're using the recommended dependencies):

Install the latest python using pyenv, then set up uv to manage dependencies. Add typer and pytest. I want the project organized into specs/, memo/, and tests/.

For product requirements:

Load all the facts from facts.csv and run through all of them. Give me a score at the end without storing anything.

Your agent should use tessl create to make a spec. Your results might look a bit different, but spend some time reviewing the spec. The spec format is intentionally flexible - so make it your own! This will pay dividends for all your future work. Ultimately, you should end up with something like this:

cli.spec.md
# Memo CLI

Command-line interface for the memo spaced repetition system. Provides commands to review cards, add new cards, and run the MCP server.

## Usage

The CLI provides three main commands for managing spaced repetition cards:

### Review Cards (Default Command)

```bash
# Review all available cards
memo

# Review a limited number of cards
memo --cards 10
```

### Add Cards

```bash
# Add a single card
memo add --front "What is Python?" --back "A programming language"

# Import cards from CSV file
memo add --csv cards.csv
```

### Run MCP Server

```bash
# Start the MCP server for external tool access
memo mcp
```

## Target

[@generate](../memo/cli.py)

## Capabilities

### Main command for reviewing cards

Default command that reviews available cards from the database.

- Initialize database on first run if needed
- Optional --cards parameter to limit number of cards reviewed (default: no limit)
- Load due cards from database using Repository
- Run interactive quiz with loaded cards
- Display quiz results showing cards reviewed

### Add command for creating cards

Subcommand for adding new cards to the database with two modes.

Single card mode:
- Uses --front and --back parameters to create individual cards
- Validates that both front and back are provided
- Creates card in database using Repository
- Shows success message

CSV import mode:
- Uses --csv parameter with path to CSV file
- Imports cards from CSV using Repository.import_from_csv
- Shows success message with count of imported cards

Both modes initialize database if needed.

### MCP server command

Command to run the MCP server for external tool access.

- Imports create_mcp_server from memo.mcp_server module
- Uses asyncio to run the server asynchronously
- Includes proper error handling

## API

```python { .api }
import typer
import asyncio
from rich.console import Console
from pathlib import Path
from typing import Optional
from memo.db import Repository
from memo.quiz import run_quiz
from memo.mcp_server import create_mcp_server

app = typer.Typer()
console = Console()

@app.command()
def main(cards: Optional[int] = typer.Option(None, help="Limit number of cards to review")) -> None:
    """Review available cards (default command)."""
    pass

@app.command()
def add(
    front: Optional[str] = typer.Option(None, help="Front side of the card"),
    back: Optional[str] = typer.Option(None, help="Back side of the card"),
    csv: Optional[Path] = typer.Option(None, help="Path to CSV file to import")
) -> None:
    """Add new cards to the database."""
    pass

@app.command()
def mcp() -> None:
    """Run the MCP server."""
    pass

if __name__ == "__main__":
    app()
```

## Dependencies

### Typer
CLI framework for building the command-line interface.
[@use](typer)

### Rich
Terminal formatting and user interface components.
[@use](rich)

### Pathlib
Built-in Python module for file path operations.
[@use](pathlib)

### Asyncio
Built-in Python module for asynchronous programming.
[@use](asyncio)

### Database Module
Repository class for database operations and card management.
[@use](./db.spec.md)

### Quiz Module
Quiz functionality for running interactive quizzes.
[@use](./quiz.spec.md)

### MCP Server Module
MCP server functionality for creating and running the server.
[@use](./mcp_server.spec.md)

When you're ready, ask the agent to keep going with the plan. It should use tessl build to create your hello world cli and then perform some manual testing on its own. Open a terminal and play around with the app yourself, reporting bugs or asking for tweaks.

Further prototyping

Tessl specs make it easy to explore before you commit to a final architecture. Let's try separating the quiz runner functionality into a separate module. All we need to do is add another [@generate] link to our existing cli spec.

Update the cli.spec.md to generate a cli.py and a quiz.py module

Review the updated spec and then have your agent build it with tessl build. For this tutorial, we already know the architecture we're aiming for. But in a new project, it's a good idea to try a few different architectures to see what your agent can build most effectively. Getting this foundation right will make all your future work a lot easier.

Adding MCP

Before we get going with the full implementation, let's work through the last piece of project setup and scaffolding: the MCP interface. First, we'll want our MCP server to reference the quiz functionality too, so let's factor that out into its own spec:

Create a spec for the quiz functionality, using the current cli spec as context. Then update the cli spec to depend on the quiz spec.

You should see a few changes:

  • [@generate](../memo/quiz.py) will be replaced with [@use](./quiz.spec.md)

  • The quiz spec will be responsible for generating the code via [@generate](../memo/quiz.py)

In this case, there's nothing to build because the modules already existed. Now that quiz is refactored, let's call it with the mcp server:

Add an mcp server using fastmcp. Update the cli spec to add a memo mcp command.

Remember to review and edit the plan file generated by the agent. In this case, we end up with something like this:

# Plan: Add MCP Server Module with FastMCP

Implement an MCP server module that provides a get_next_question tool and integrates with the existing quiz module.

## Tasks

- [ ] Research existing project structure and quiz module implementation
- [ ] Install FastMCP module and usage spec
- [ ] Create spec for MCP server module with get_next_question tool
- [ ] Update CLI spec to add mcp command
- [ ] Build CLI implementation with mcp command
- [ ] Test the MCP server functionality

Let your agent run until it has a working implementation, then you can test it manually using the mcp inspector. Assuming you've been using the recommended setup:

npx @modelcontextprotocol/inspector uv run memo mcp

Try listing and running a few tools to make sure everything is working.

Designing the architecture

Now that we have the basic building blocks in place, it's time to get a bit more detailed with our system design. Create a project-architecture.md and make sure it's linked from AGENTS.md.

Just like your README, expect to spend a bit of time here! You are the TL for your agent, so this is one of your key responsibilities. Generally the project architecture should contain:

  • Tech stack

  • Core objects and abstractions

  • Common design patterns

  • Desired project/folder layout

Of course, you can go much deeper with your design documents. But this is a good place to start. For us, we ended up with something like this:

project-architecture.md
# System design

## Tech Stack

- **Language**: Python 3.13
- **CLI Framework**: Typer for command-line interface
- **Database**: SQLite for persistent storage and progress tracking
- **MCP Framework**: FastMCP for Model Context Protocol server
- **Environment**: pyenv + uv for dependency management
- **Architecture**: Spec-driven development with Tessl

## Project Structure

```bash
memo/
├── specs/
│   ├── cli.spec.md   # CLI interface
│   ├── mcp.spec.md   # MCP server implementation
│   ├── quiz.spec.md  # Core quiz logic
│   ├── srs.spec.md   # Spaced repetition algorithm
│   └── db.spec.md
├── memo/
├── tests/
├── facts.csv         # Seed data
└── pyproject.toml
```

The other files in the repo either are either Tessl configuration files and context or files that were added as part of the video that this repo was created for.

Building the final implementation

This is where all our up front work really starts to pay off. With a good README and project-architecture.md, we can start to increase the scope that we hand to our agent:

Review the project and summarize the gaps in functionality from the README. Make a plan to implement all the missing functionality.

As usual, your main job is reviewing the implementation plan and ensuring it's sensible. Try to think about how you would build the feature - if you'd ask a co-worker to split it into a separate PR, your agent probably should too! Your plan should look something like this

implement-missing-functionality.plan.md
# Plan: Implement Missing Memo Functionality

Complete the memo spaced repetition quiz application by implementing the data layer, SRS algorithm, and integrating all components.

## Overview

Based on the README and existing specs, the following functionality needs to be implemented:
1. Database layer (SQLite) for persistent storage
2. Spaced Repetition System (SRS) using SM2 algorithm (Python port from sm2.js)
3. Update quiz module to work with database
4. Update CLI to support all commands (add cards, review, CSV import)
5. Update MCP server with all tools (get_next_question, save_score, add_card, import_cards)

## Tasks

### Phase 1: Data Layer

- [ ] Create database spec (db.spec.md) with Card model and repository
  - SQLite database with cards table
  - Card model: id, front, back, repetition, easiness, interval, next_date, created_at, updated_at
  - Repository pattern for CRUD operations
  - Methods: create_card, get_card, update_card, get_due_cards, import_from_csv

- [ ] Build database implementation from spec { @user }

- [ ] Interview user to determine testing plan

### Phase 2: Spaced Repetition Algorithm

- [ ] Create SRS spec (srs.spec.md) based on sm2.js
  - Port SM2 algorithm to Python
  - Calculate next review date based on grade (0-5)
  - Update card's repetition, easiness, and interval
  - Return updated scheduling parameters

- [ ] Build SRS implementation from spec

- [ ] Test SRS algorithm with various grades

### Phase 3: Update Quiz Module

- [ ] Edit quiz.spec.md to integrate with database
  - Load due cards from database instead of CSV
  - Update card scheduling after each answer
  - Support configurable number of cards to review

- [ ] Build updated quiz implementation

- [ ] Test quiz with database integration

### Phase 4: Update CLI

- [ ] Edit cli.spec.md to add all commands
  - Main command: review cards (with --cards parameter)
  - Add command: add single card (--front, --back)
  - Add command: import from CSV (--csv)
  - Integrate with database and SRS

- [ ] Build updated CLI implementation

- [ ] Test all CLI commands

### Phase 5: Update MCP Server

- [ ] Edit mcp_server.spec.md to implement all tools
  - get_next_question: return next due card from database
  - save_score: update card with grade and SRS calculation
  - add_card: add new card to database
  - import_cards: bulk import from CSV

- [ ] Build updated MCP server implementation

- [ ] Test MCP server tools

### Phase 6: Integration Testing

- [ ] Run full integration tests
- [ ] Fix any remaining issues
- [ ] Verify all functionality works as described in README

## Key Implementation Details

- **Database**: SQLite with single cards table
- **SRS Algorithm**: Direct port of sm2.js logic to Python
- **Card Selection**: Select cards where next_date <= today, ordered by next_date
- **Grade Scale**: 0 (wrong) to 5 (effortless)
- **Dependencies**: sqlite3, datetime, math (all built-in)

If you're wondering what makes a good plan, take a look at our best practices for plans.

This task has a particularly tricky refactor: replacing the stateless quiz behavior with a database backend and a bunch of new fields. Standard refactor rules apply:

  • First, get the new module working in isolation.

  • Next, migrate existing functionality to use the new module and test that everything works.

  • Finally, introduce the new functionality.

Once the agent finishes working through the plan, open up the CLI — or MCP server — and try it out. Import facts.csv and review all the new cards. When you run another review, you shouldn't get any cards because they're scheduled for future reviews.

What's next

In this tutorial, we focused on building from a single spec into a scalable architecture and touched on common files like README and project-architecture.md. For simplicity, we validated our work with manual testing, which is a great way to keep things loose while you're prototyping and exploring.

Last updated