Structure your application
Use this structure when deciding where new code belongs and which direction its dependencies may point. Tenchi organizes application behavior by feature while keeping infrastructure and server composition explicit.
Start with the layout from your first application. The
required server modules are context.py, routes.py, and asgi.py, alongside
the Python package initializers. runtime.py is a useful place for shared
resource wiring, but is not required.
The tree below shows where additional capabilities belong. Add files marked optional only when you need them; a feature does not need every file shown.
app/
features/<feature>/
contracts.py
schemas.py
ports.py
policy.py # optional: authorization rules
routes.py
jobs.py # optional: durable messages
tasks.py # optional: operational commands
tools.py # optional: machine-facing calls
evaluations.py # optional: AI quality checks
use_cases/
tests/
shared/
infra/
server/
context.py
hooks.py # optional: HTTP boundary hooks
webhooks.py # optional: signed deliveries
routes.py
jobs.py # optional
runtime.py # optional: shared resource wiring
preflight.py # optional
evaluations.py # optional
tasks.py # optional
tools.py # optional
mcp.py # optional
asgi.py
tests/Feature boundary
contracts.py owns HTTP declarations. schemas.py owns Pydantic models shared
by contracts, ports, and use cases. ports.py defines the interfaces the
feature needs. policy.py owns pure authorization rules. routes.py binds
contracts to use cases. tasks.py gives selected use cases stable operational
names. jobs.py declares durable background messages without importing their
consumer use cases. tools.py gives selected use cases stable, typed
machine-facing contracts. evaluations.py declares typed cases, metrics, and
provider-neutral evaluators for behavior the feature needs to gate.
Each file in use_cases/ contains one plain async function. Unit tests sit
beside the feature and call those functions directly with memory adapters.
Shared kernel
app/shared/ contains application-wide errors and concepts shared by several
features, such as authenticated users. Shared code must not depend on a
feature; otherwise the dependency direction points both ways.
Infrastructure
app/infra/ implements feature-owned ports and exposes explicit wiring
functions. Infrastructure may depend on database drivers or external SDKs, but
never on use cases, routes, contracts, or server composition.
Server composition
The app/server/ package is the application root:
context.pydeclares the frozenAppContext.hooks.pyimplements HTTP-boundary concerns such as authentication.webhooks.pybinds signed-provider verification and service identity.routes.pycombines feature route groups and shared error declarations.jobs.pybinds feature job declarations to consumer use cases.runtime.pyowns resources shared by HTTP and operational entrypoints.preflight.pydeclares read-only checks of the target deployment environment.evaluations.pycomposes evaluations with application lifecycle and context wiring.tasks.pycomposes the application's operational task runner.tools.pycomposes application tools with authenticated context wiring.mcp.pyoptionally exposes those tools through authenticated MCP discovery and invocation.asgi.pycreates adapters, lifecycle resources, hooks, middleware, and the final Starlette application.
Only context.py, routes.py, and asgi.py are required. The other server
modules exist when the application uses that capability. tenchi map,
tenchi check, and tenchi verify treat an absent default module for jobs,
tasks, tools, or evaluations as not configured; tenchi preflight requires
its module when you run it.
The composition root may import anything. Everything else follows a narrower direction.
Dependency direction
routes -> use cases -> ports -> schemas/domain
server composition -> routes
server composition -> infrastructure -> ports
server composition -> job declarations + consumer use cases
server composition -> tool bindings + authenticated context
server composition -> evaluation declarations + application contextArrows point from the importing layer to the layer it depends on. Server composition owns the complete graph; routes and infrastructure never depend on it.
- Schemas, domain code, and ports never import infrastructure or the HTTP runtime.
- Use cases may import schemas, ports, policies, context types, and shared application code.
- Routes may import contracts and use cases, but never infrastructure.
- Job declarations may import schemas but not use cases or infrastructure;
producers may import declarations and
job_message(). - Tool modules may import schemas and use cases to bind them, but never infrastructure or server composition. Safety annotations describe behavior; authorization remains in the use case.
- Evaluation modules may import schemas, ports, policies, and use cases, but never infrastructure or server composition. They describe cases and scoring; provider adapters arrive through the runner's context.
- Infrastructure implements ports, but never imports use cases or routes.
- Server composition wires the complete graph.
Webhook verifiers live at server composition because they need secrets,
provider SDKs, and exact HTTP bytes. The verified service identity still enters
the use case through AppContext; provider payloads remain contract-owned
Pydantic input.
Run uv run tenchi map --feature <name> to inspect one feature and its direct
dependencies, uv run tenchi doctor to check these conventions alone, or
uv run tenchi check for the complete validation loop. Add --json to the map
when an agent or other tool will consume it. Doctor's structural rules are
listed under Constraints.
A normal from app.server.context import AppContext import is the clearest
default. If an application needs to avoid that runtime import, enable
postponed annotations with from __future__ import annotations, then import
AppContext inside an if TYPE_CHECKING: block. Without postponed or quoted
annotations, Python raises NameError when it defines the use case. Route
binding ignores this app-owned context annotation while still checking every
contract-owned boundary annotation.
from __future__ import annotations
from typing import TYPE_CHECKING
from app.features.todos.schemas import CreateTodo, Todo
if TYPE_CHECKING:
from app.server.context import AppContext
async def create_todo(request: CreateTodo, context: AppContext) -> Todo:
return await context.todos.create(title=request.title)Constraints
tenchi doctor enforces the dependency direction above and these structural
rules:
app/server/asgi.py,app/server/context.py, andapp/server/routes.pymust exist. The modules that compose jobs, tasks, tools, evaluations, and preflight checks are optional.- Imports inside feature package initializers are checked like any other feature module.
- Modules directly under
app/outsidefeatures,shared,infra, andserverare rejected. - Application files must live directly under the application root rather than behind symbolic links. Replace a symlink with a regular file or directory before running doctor.
- Test modules are exempt from the import rules.