Skip to content

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:

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 context

Arrows point from the importing layer to the layer it depends on. Server composition owns the complete graph; routes and infrastructure never depend on it.

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.

Types without runtime coupling

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: