Build your first Tenchi app
Create a working application, call its API, follow one request through the code, and change its behavior in a plain async function.
Requirements
Tenchi requires Python 3.12 or newer. The commands in this guide use uv to create the environment and run the application.
Create the application
uvx tenchi new my_app
cd my_app
uv syncThe generated application contains a todos feature and SQLite persistence.
It also includes tests, AGENTS.md, project-local MCP configuration, and a
tenchi.toml verification policy. People and agents can use the same
source structure and checks from the first change. Start it:
uv run tenchi devCall the API
From another terminal, create and list todos:
curl -i \
-H 'content-type: application/json' \
-d '{"title":"Buy milk"}' \
http://127.0.0.1:8000/todos
curl http://127.0.0.1:8000/todosThe create operation returns status 201, a validated Todo body, and a
Location header. The list operation returns the saved todo. Restart the
server and list again to confirm that SQLite persisted it.
Open http://127.0.0.1:8000/docs to inspect and call the same API through Swagger UI.
Follow the request through four files
Inspect the feature before editing:
uv run tenchi map --feature todosThe map identifies declarations, source locations, and relationships. Read those definitions to understand the behavior; the map does not replace the source or tests.
The create operation has four application-facing parts:
| Path | Responsibility |
|---|---|
app/features/todos/schemas.py | Defines the validated request and response data |
app/features/todos/contracts.py | Declares POST /todos and its HTTP behavior |
app/features/todos/use_cases/create_todo.py | Implements the application behavior |
app/features/todos/routes.py | Binds the contract to the use case |
The contract and route handle the boundary. The use case stays ordinary Python:
async def create_todo(request: CreateTodo, context: AppContext) -> Todo:
return await context.todos.create(title=request.title)context.todos is a TodoRepository port owned by the application. The
running server supplies a SQLite adapter; the direct use-case test supplies a
memory adapter. The use case does not need to know which one it received.
The generated project also includes server wiring, the SQLite and memory adapters, and an HTTP test. You do not need to understand or edit them to work on this operation.
Change the behavior
Trim surrounding whitespace before saving a title. In
app/features/todos/use_cases/create_todo.py, change the repository call:
async def create_todo(request: CreateTodo, context: AppContext) -> Todo:
- return await context.todos.create(title=request.title)
+ return await context.todos.create(title=request.title.strip())Update the direct test in
app/features/todos/tests/test_create_todo.py so it proves the new behavior:
- todo = await create_todo(CreateTodo(title="Buy milk"), context)
+ todo = await create_todo(CreateTodo(title=" Buy milk "), context)
assert todo.title == "Buy milk"Run that focused test:
uv run pytest app/features/todos/tests/test_create_todo.pyThe test calls the use case directly with a memory repository. It does not start an HTTP server or open SQLite.
Check the application
uv run tenchi checktenchi check runs the generated project's formatting, linting, type,
test, architecture, and boundary checks. The command reports every failed
step so one run gives you the complete repair list.
Your first change touched one plain function and one focused test. The contract, route, persistence wiring, OpenAPI, and HTTP behavior remained aligned without requiring changes.
The passing test gives evidence for the whitespace behavior you asserted.
Review its assertions along with the code. For changes with a committed
baseline, tenchi verify also records architecture and historical
compatibility evidence; the feature tutorial uses that
workflow on a new operation.
Continue from the core
- How Tenchi works explains the small application model.
- Build a feature end to end adds a persisted operation across a port, two adapters, a route, tests, and the OpenAPI snapshot.
- Develop with an agent applies the same loop through the CLI or agent MCP.
- Prepare for production covers operational decisions when the application is ready to ship.