Building a multi-spec project

Building more complex spec relationships

👉 Closed beta notice

Tessl framework is in private beta. Join our community to be updated on our future product releases.

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

````markdown # Memo

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

Installation

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

# Install dependencies
uv sync

Usage

# 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

{% endcode %}

</details>

### 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](../best-practices/break-work-into-achievable-agent-friendly-tasks.md). 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:

<div data-gb-custom-block data-tag="code" data-overflow='wrap'>

```markdown
# 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

```

</div>

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

<div data-gb-custom-block data-tag="code" data-overflow='wrap'>

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

</div>

Because we're saving the plan in a file, it's easy to edit. Planning is one of the [most effective ways](../best-practices/break-work-into-achievable-agent-friendly-tasks.md) 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:

```markdown
# 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:

<div data-gb-custom-block data-tag="code" data-overflow='wrap'>

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

</div>

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):

<div data-gb-custom-block data-tag="code" data-overflow='wrap'>

```
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/.
```

</div>

For product requirements:

<div data-gb-custom-block data-tag="code" data-overflow='wrap'>

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

</div>

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](../reference/spec-syntax.md) 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:

<details>

<summary><code>cli.spec.md</code></summary>

<div data-gb-custom-block data-tag="code" data-overflow='wrap'></div>
````markdown
# 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)

{% endcode %}

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:

{% code overflow="wrap" %}

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.

{% endcode %}

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

{% code overflow="wrap" %}


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

Last updated