Tach: Architecture Boundaries¶
Tach is an architectural boundary tool for Python written in Rust. It enforces
strict layering and prevents circular dependencies. Deptry checks third-party
dependency declarations.
What It Does¶
Tach evaluates the import graph of a Python repository to enforce modular boundaries and eliminate circular dependencies. It defines explicit, unidirectional boundaries between layers—preventing low-level models and core logic from coupling to high-level CLI or API orchestrators.
Tach can generate visual Mermaid diagrams directly from the verified import graph.
The dependencies Poe task runs tach check for internal boundaries and deptry src
for declared dependencies.
Why It's in Zenith¶
Over time, codebases naturally suffer from architectural erosion as developers and AI assistants import utilities opportunistically across modules, eventually creating an untestable monolith. Zenith integrates Tach to enforce clear layer boundaries and catch circular references at commit time. Deptry checks that package imports match declared dependencies.
Configuration in Zenith¶
Configured in tach.toml at the project root:
# ==============================================================================
# Tach Configuration (tach.toml)
#
# Tool: Tach (https://github.com/gauge-sh/tach)
# Purpose: Modular architecture and dependency boundary enforcement for Python.
# Why it's in Zenith: Enforces architectural boundaries between package modules
# (e.g. CLI layer depends on Core, but Core must never depend on CLI) and prevents
# circular dependencies from creeping in.
# ==============================================================================
exclude = [
"**/*__pycache__",
"**/*egg-info",
"**/docs",
"**/tests",
"**/venv",
"**/.venv",
"**/site",
]
interfaces = [ ]
source_roots = [ "src" ]
exact = true
forbid_circular_dependencies = true
ignore_type_checking_imports = true
{% if cookiecutter.include_cli in [True, "yes", "true", "True", "1"] %}
layers = [ "cli", "core" ]
{% else %}
layers = [ "core" ]
{% endif %}
respect_gitignore = true
[rules]
unused_ignore_directives = "error"
require_ignore_directive_reasons = "error"
[[modules]]
depends_on = [ ]
layer = "core"
path = "{{ cookiecutter.package_name }}"
{% if cookiecutter.include_cli in [True, "yes", "true", "True", "1"] %}
[[modules]]
depends_on = [ "{{ cookiecutter.package_name }}.cli" ]
layer = "cli"
path = "{{ cookiecutter.package_name }}.__main__"
[[modules]]
depends_on = [ ]
layer = "cli"
path = "{{ cookiecutter.package_name }}.cli"
{% endif %}
Boundary Rules¶
Lower-level domains such as core and models must never import higher-level
orchestration layers like cli. All cross-module interactions must pass through
explicit, public interfaces.
Key Invocations¶
# Verify module architecture boundaries.
uv run tach check
# Verify declared dependencies.
uv run deptry src
# Run the configured architecture task.
uv run poe dependencies
# Export a Mermaid architecture diagram.
uv run tach show --mermaid -o docs/assets/architecture.mmd