How Tenchi works
Tenchi has one architecture: typed contracts at the boundary, plain use cases at the center, and explicit dependency wiring around them. Five pieces are enough to serve an HTTP API. Optional capabilities reuse this structure as you add other callers, operational checks, or AI behavior.
Contract
A frozen declaration of one HTTP operation: method, path, validated inputs, successful responses, and the application errors callers may observe. A contract contains no handler logic.
Route
A binding between a contract and a use case. route() inspects the use-case
signature immediately, so a mismatched parameter name or annotation fails while
the application is composed rather than on the first request.
Use case
A plain async function that implements one application action. Its parameters
are named boundary values such as request, params, query, headers, and
context. It never receives a Starlette request, so you can call it directly
from tests and scripts. Jobs, tasks, tools, and execute() supply request
and context; a function that also needs params, query, or headers
requires an input adapter for those callers.
Port and adapter
A port is an interface the application needs, declared with typing.Protocol.
The feature owns the port; infrastructure supplies an adapter that implements
it. A SQL repository and a memory test double are both adapters for the same
port, so the application stays independent of any database driver, HTTP SDK,
cache, or queue.
Context
A frozen dataclass carrying the dependencies and verified identity available to a use case. The application constructs it explicitly at the server composition root. Tenchi has no service locator or dependency-injection container.
Boundary flow
HTTP request
-> route match
-> request id and context factory / request scope
-> boundary hooks
-> size, media-type, and Pydantic input validation
-> plain use case
-> response presenter and header projector
-> Pydantic response validation
-> HTTP responseThe typed client runs the complementary checks: it serializes inputs from the same contract and validates the status, media type, body, headers, and declared application errors returned by a server.
Optional extensions
None of the following is required to define and serve an HTTP API. Each one gives an existing use case another entrypoint or another rule, and each has its own guide.
- A hook runs at the HTTP boundary before input validation. It can authenticate a request and return an enriched context. See authentication.
- A policy is a pure function that answers an authorization question inside a use case. See authentication.
- A task gives a use case a stable name for backfills, repairs, and maintenance commands. See operational tasks.
- A job is a durable, validated message bound to a consumer use case. See background jobs.
- A tool exposes a use case to machine callers with a stable name, typed input and output, and safety annotations. See application tools.
- An evaluation is a typed, thresholded suite that gates AI-powered behavior. See AI evaluations.
The key tradeoff
Tenchi asks for more structure than a one-file microframework example. In return, an endpoint's validation, client behavior, OpenAPI, compatibility policy, and application boundary do not become separate models as the service grows.