> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tilebox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Project Structure

> Initialize and structure a Python workflow project so releases can be built reproducibly and release runners can discover and execute its tasks.

A Python workflow project contains task classes, a `Runner` definition, and a `tilebox.workflow.toml` file. The Tilebox command-line tool uses these files to build a workflow release, discover the tasks it can execute, and make the release available to release runners after deployment.

<Tip>
  This project structure and `tilebox.workflow.toml` are required for [release runners](/workflows/concepts/runners#release-runners). For direct runners, you can organize your code however you want, as long as your runner process can import and register the tasks it executes. The same structure can still be useful when you want a small, importable workflow package.
</Tip>

Keep the project small and importable from its root. Release builds import the configured runner object, check that the Python runtime starts, and package the selected files into an immutable artifact.

## Scaffold a workflow project

For a new Python workflow project, use the CLI to create the Tilebox workflow and scaffold the local files.

```bash theme={"system"}
tilebox workflow init --name "Scene QA"
```

The `--name` flag is optional. When omitted, the command derives the local project slug from the current directory name. The command converts the name to a slug, truncates it to at most 40 characters, creates the remote workflow, and writes the API-returned workflow slug to `tilebox.workflow.toml`.

`tilebox workflow init` creates `tilebox.workflow.toml`, `pyproject.toml`, and `runner.py`, adds the `tilebox` Python dependency, and runs `uv sync` to create the local environment and `uv.lock` file. It requires `uv` on `PATH` and aborts without changing the directory if any of `tilebox.workflow.toml`, `pyproject.toml`, `runner.py`, or `uv.lock` already exists.

The generated project is intentionally small. Edit `runner.py` directly for prototypes, or move task code into a package as the workflow grows.

## Manual project structure

Use a layout where task code and the runner definition are importable from the project root.

<Tree>
  <Tree.Folder name="my-workflow" defaultOpen>
    <Tree.File name="pyproject.toml" />

    <Tree.File name="uv.lock" />

    <Tree.File name="tilebox.workflow.toml" />

    <Tree.Folder name="my_workflow" defaultOpen>
      <Tree.File name="__init__.py" />

      <Tree.File name="tasks.py" />

      <Tree.File name="runner.py" />
    </Tree.Folder>
  </Tree.Folder>
</Tree>

## Define the Python project

Use a minimal `pyproject.toml` with the dependencies your workflow needs. For this example, only the Tilebox Python package is required.

```toml pyproject.toml theme={"system"}
[project]
name = "my-workflow"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
  "tilebox",
]
```

Create the package directory and an empty `__init__.py` file so Python can import `my_workflow.runner` from the project root.

```bash theme={"system"}
mkdir -p my_workflow
touch my_workflow/__init__.py
uv lock
```

## Define tasks

Put task classes in a module that can be imported during release validation.

```python my_workflow/tasks.py theme={"system"}
# my_workflow/tasks.py
from tilebox.workflows import ExecutionContext, Task


class ProcessScene(Task):
    scene_id: str

    @staticmethod
    def identifier() -> tuple[str, str]:
        return "tilebox.com/example/ProcessScene", "v1.0"

    def execute(self, context: ExecutionContext) -> None:
        context.current_task.display = f"ProcessScene({self.scene_id})"
        context.logger.info("Processing scene", scene_id=self.scene_id)
```

Use explicit identifiers for workflow code that will be published. A stable identifier lets existing jobs continue to run after refactors and compatible bug fixes.

## Define the runner

Create a module that exports a `Runner` object. This object defines the task registrations for the workflow, and release builds import it during validation.

```python my_workflow/runner.py theme={"system"}
# my_workflow/runner.py
from tilebox.workflows import Runner
from tilebox.workflows.cache import LocalFileSystemCache

from my_workflow.tasks import ProcessScene


runner = Runner(
    tasks=[ProcessScene],
    cache=LocalFileSystemCache(),
)
```

## Configure the workflow release

Point `tilebox.workflow.toml` at the exported `Runner` object and include the files required by the release runner.

```toml theme={"system"}
[workflow]
slug = "my-workflow"
root = "."
runner = "my_workflow.runner:runner"

[build]
include = [
  "pyproject.toml",
  "uv.lock",
  "my_workflow/**",
]
exclude = [
  ".venv/**",
  "**/__pycache__/**",
  "**/*.pyc",
  ".pytest_cache/**",
]
use_gitignore = true
```

The Tilebox command-line tool imports the runner object during `build-release` and `publish-release`, discovers its task identifiers, and records them in the workflow release. A release runner later loads the release artifact and invokes the Python runtime through the command-line tool.

## Keep release artifacts small

Include source code, lock files, and small configuration. Exclude local virtual environments, test caches, downloaded provider data, model checkpoints, generated outputs, and other large runtime artifacts.

If a task needs a large model or reference file, fetch it lazily at runtime and cache it in a deterministic runner-local path such as `~/.cache/tilebox/...`. The workflow should still work when a release runner starts with an empty cache.
