Skip to content

Workers and scripts

HTTP is one caller of an application use case. execute() applies Tenchi's request validation and context-scoping rules from jobs, command-line scripts, message consumers, or tests without inventing a second contract.

Execute validated input

from tenchi.execution import execute


async def handle_job(payload: bytes, context: AppContext) -> Todo:
    return await execute(
        create_todo,
        request_json=payload,
        context=context,
    )

request_json= validates serialized input against the use case's annotated request parameter. Use request= for an already decoded Python value.

execute() deliberately supplies only request and context. A use case that requires HTTP-specific params, query, or headers is not portable to this entrypoint and fails with ExecutionError.

Open a context explicitly

from tenchi.execution import open_context


async with open_context(create_request_context()) as context:
    result = await execute(rebuild_index, context=context)

open_context() accepts a direct value, an awaitable, or an async context manager. This mirrors the server's context behavior, including cleanup for transactional or request-scoped resources.

Observe the use case

Pass the same observer to HTTP composition and direct execution:

import logging

from tenchi.execution import UseCaseOutcome, execute


logger = logging.getLogger("app.operations")


def observe_use_case(outcome: UseCaseOutcome) -> None:
    operation = str(
        getattr(outcome.use_case, "__name__", type(outcome.use_case).__name__)
    )
    logger.info(
        "use_case.complete",
        extra={
            "operation": operation,
            "entrypoint": outcome.entrypoint,
            "status": outcome.status,
            "duration_seconds": outcome.duration_seconds,
            "completed_at": outcome.completed_at.isoformat(),
            "error_code": outcome.error_code,
        },
    )


result = await execute(
    rebuild_index,
    context=create_request_context,
    use_case_observers=(observe_use_case,),
)

create_app(use_case_observers=(observe_use_case,)) reports the HTTP path through the same UseCaseOutcome shape. The observer runs after context cleanup, while duration_seconds measures only the use-case call and completed_at records when that call returned or raised.

No outcome is emitted when validation, context acquisition, or a boundary hook fails before the use case starts. Observer failures are logged and do not replace the use case's result or exception. Keep observers fast or hand work to a non-blocking telemetry pipeline because execute() waits for them before it returns.

Keep transport concerns outside

The message consumer acknowledges, retries, or dead-letters work. The use case owns application behavior. Convert message metadata into application input before calling execute() rather than passing a queue SDK object into the application layer.

Authorization remains in the use case. When a job acts for a user, construct a context with verified actor identity and let the same policy checks run.

For durable delivery, idempotent commands, and retry classification, continue with Background jobs and Retries and background work. Use execute() directly when the caller already owns the function identity and no durable name or producer contract is needed. For operator-invoked backfills, repairs, replays, and maintenance, use Operational tasks to add stable discovery plus result validation around the same use cases.