Deploy the ASGI application
Tenchi produces an ordinary ASGI application. Production readiness comes from explicit lifecycle ownership, middleware, health checks, observability, and a repeatable contract gate.
Start with the production handbook when the application still needs configuration, transaction, retry, worker, or telemetry decisions. This page covers the final process and release boundary.
Run an ASGI server
The generated application exposes app.server.asgi:app:
uv run uvicorn app.server.asgi:app \
--host 0.0.0.0 \
--port 8000 \
--proxy-headersUse your platform's process manager, worker count, graceful shutdown, and
request-timeout guidance. tenchi dev enables reload and is not the production
entrypoint.
Own resources with lifespan
Open database pools, SDK clients, and other process-scoped resources in the application lifespan. Yield a small state object and build each request context from it. Cleanup runs during graceful shutdown.
Use a request-scoped context manager for transactions or ports whose success
depends on the request outcome. Application exceptions pass through its
__aexit__ before Tenchi maps them to HTTP, allowing commit-on-success and
rollback-on-error behavior.
Load validated configuration before opening resources. Put database migrations in a controlled release step; use lifespan to open pools and verify the schema generation that the process expects.
Add ASGI middleware directly
from starlette.middleware import Middleware
from starlette.middleware.cors import CORSMiddleware
from starlette.middleware.trustedhost import TrustedHostMiddleware
middleware = [
Middleware(
TrustedHostMiddleware,
allowed_hosts=["api.example.com"],
),
Middleware(
CORSMiddleware,
allow_origins=["https://app.example.com"],
allow_methods=["*"],
allow_headers=["authorization", "content-type"],
),
]
app = create_app(
routes=routes,
context_factory=create_context,
lifespan=lifespan,
middleware=middleware,
)Configure HTTPS and forwarded headers at the trusted reverse proxy boundary. Keep host and origin allowlists explicit.
The generated Swagger UI loads pinned CDN assets with subresource integrity.
For an offline deployment, self-host the assets and pass their URLs to
swagger_ui_route(). A content-security policy must also allow the page's
fixed inline initializer and style, normally through their hashes. Protect the
UI and openapi_route() together when the API description is not public.
Separate liveness from readiness
Use different routes for the two deployment decisions. Liveness answers whether the process is running and responsive, so it must not depend on a database, cache, model provider, or another network service. Readiness answers whether this process should receive traffic and may check dependencies required by the operations it serves:
from tenchi.health import health_route
liveness = health_route(path="/live")
readiness = health_route(
path="/ready",
checks={"database": check_database},
check_timeout=2.0,
)Register both routes at the composition root. Configure the platform's
liveness probe to call /live and its readiness probe to call /ready. A
database outage can then remove this process from traffic without causing a
restart loop.
Readiness checks run concurrently and return 503 when any check is unhealthy
or exceeds check_timeout. Synchronous checks run in worker threads so
blocking I/O cannot hold the event loop past the health deadline; the
underlying thread may finish after the timed-out response, so prefer async
clients with their own deadlines. An async check that suppresses cancellation
may also finish in the background, but it cannot delay the response. Keep
checks read-only and independently bounded. Health routes are public by
default; pass public=False when infrastructure can authenticate probes.
Limits and deadlines
The server limits request bodies to 1 MiB by default. Set
max_request_bytes= globally or on an individual contract. A contract's
timeout= bounds only its HTTP route. Put an entrypoint-neutral
deadline around application work
that needs the same bound through tools, jobs, tasks, and direct execution.
Also configure transport and platform timeouts.
Observe outcomes
RequestInfo includes a validated correlation ID. Tenchi accepts a valid inbound
x-request-id or creates one and includes it on responses. Outcome observers
receive the request, final status, duration, and application/framework error
source after request-scoped cleanup.
Callers can choose an accepted x-request-id; validation does not establish
who supplied it. Configure your trusted proxy to replace it if you need
edge-generated identifiers.
Use request and use-case observers or ASGI middleware to feed structured logs, metrics, and traces. They should never contain request bodies, credentials, or error details unless the application deliberately redacts them.
See Observability and audit for label, correlation, worker, and redaction conventions.
Release sequence
Run the local application gate and compare every snapshotted boundary with one baseline commit from the version currently deployed:
uv run tenchi verify --base-ref "$BASE_SHA"Continue only when verification passes. Retain its report with the release.
Then release in this order:
- Verify production settings and secret references without printing their values.
- If the service stores data, back it up according to the datastore's recovery plan.
- If the release changes the database schema, run backward-compatible database migrations once.
- Start the new API generation without sending it traffic.
- If the application has preflight checks, run
tenchi preflightagainst the target environment. - For model-backed behavior, run the explicitly budgeted
tenchi eval rungate with the provider configuration this release will use. - If the service enqueues work, start or update background workers that can consume messages from both application generations. Deploy compatible consumers before new producers receive traffic.
- Wait for startup and readiness checks, then shift traffic gradually.
- Smoke-test an application operation through the production edge, including authentication when the operation is protected.
- Watch error rate, latency, database saturation, and queue age during the rollout.
tenchi check runs your local validation commands; it does not establish
production readiness. Those commands may contact services if your tests or
imports do so. Keep migrations as their own release step, then use
tenchi preflight for read-only, timeout-bounded dependency
checks where the target environment is available. AI evaluation execution
remains separate because it may be nondeterministic and incur provider cost;
the declared policy is still checked locally.
Plan rollback before rollout. Application rollback must remain compatible with the migrated schema and any work records already written by the new generation.
Understand verification failures
verify discovers the literal OpenAPI title, version, description, and
security configuration from app.server.routes:api_routes. It runs the same
deterministic checks, rejects an application map with diagnostics or unresolved
relationships, and compares
OpenAPI, durable job messages, application tools, and evaluation policy with
the resolved commit. It also compares tenchi.toml with that commit and keeps
the stronger current-or-deployed requirement active, so a release cannot avoid
a gate by weakening it in the same change. Retain the complete
receipt before making another edit: its source commit and tree digest identify the exact
application state that passed, and verification fails if that state changes
while the command is running.
Use its metadata flags only when the application does not keep those values as
literal route-module declarations.
When adding jobs, tools, or evaluations for the first time, verify handles
a missing historical snapshot automatically if its default composition module
was also absent at the baseline. If a job or evaluation module already existed
but its first snapshot did not, use --allow-missing-job-baseline or
--allow-missing-evaluation-baseline for that initial comparison. Confirm the
corresponding job manifest baseline or evaluation manifest baseline metadata
change. Do not use an override to bypass an existing snapshot at a renamed or
mistyped path. The CLI reference describes the
complete policy.
Raw httpx.ASGITransport does not run lifespan by itself. Prefer
tenchi.testing.open_client() or open_http(), which start and stop the
application correctly.