Skip to content

App architecture

Tenchi applications organize code by feature and make infrastructure and server composition explicit.

app/
  features/<feature>/
    contracts.py
    schemas.py
    ports.py
    policy.py
    routes.py
    jobs.py
    tasks.py
    tools.py
    use_cases/
    tests/
  shared/
  infra/
  server/
    context.py
    hooks.py
    webhooks.py
    routes.py
    jobs.py
    runtime.py
    preflight.py
    tasks.py
    tools.py
    mcp.py
    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.

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:

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

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.

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)