Mental model
Tenchi has one architecture: typed contracts at the boundary, plain use cases at the center, and explicit dependency wiring around them.
Contract
A frozen declaration of one HTTP operation: method, path, validated inputs, successful responses, expected application errors, media types, metadata, and runtime limits. A contract contains no handler logic.
Route
A binding between a contract and a use case. route() inspects the use-case
signature immediately, so mismatched parameter names or annotations fail 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 does not receive a Starlette request.
Operational task
A stable name bound to a use case for a backfill, repair, replay, or maintenance command. A task validates its input and result and runs with application lifecycle resources. It is an invocation boundary, not a scheduler or queue.
Background job
A durable, stable name with validated request and result types. Producers
serialize a JobMessage; the composition root binds the declaration to a plain
async consumer; a dispatcher validates delivery. Queue persistence, retries,
acknowledgement, and scheduling stay in infrastructure.
Application tool
A stable machine-facing name with validated input, output, declared errors, and safety annotations, explicitly bound to a plain async use case. A tool runner supplies application lifecycle and context wiring. Model providers, agent loops, authentication, approval interfaces, and transport protocols stay outside the declaration.
Evaluation
A named suite of typed cases, normalized metrics, passing thresholds, and optional token and cost budgets for AI-powered behavior. An evaluation runner supplies application lifecycle and a scoped context to each case. Models, prompts, judges, and datasets stay application-owned; reports contain scores and usage, not evaluated payloads.
Port
An interface the application needs, declared with typing.Protocol. The
feature owns the port; infrastructure supplies an adapter. This keeps the
application independent of a 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. Tenchi does not use a service locator or dependency-injection container.
Hook
An HTTP-boundary function that sees RequestInfo and the current context. A
hook can authenticate a request and return an enriched context. Hooks are for
boundary concerns, not business authorization.
Policy
A pure application function that answers an authorization question about a known subject. The use case loads the subject through a port and asks the policy. Because policies perform no I/O, they are easy to test and reusable from HTTP, workers, scripts, and tests.
Adapter
A concrete implementation of a port. SQL repositories, external API clients,
and memory test doubles are adapters. They live under app/infra/ and are
wired at the server composition root.
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.
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.