RFC 0001: Selective Hatch-Inspired UX for fyn
Status: Partially implemented
Author: Codex
Last updated: 2026-09-04
Summary
fyn should not copy Hatch wholesale.
fyn should copy only the workflow UX where both fyn and uv were still thin when this RFC was
written:
- Make the existing task runner match its documented surface. Complete as of
fyn0.10.17. - Make
fyn initgenerate more useful project scaffolds. - Add a small named workflow layer for lint/docs/test/typecheck tasks.
- Explicitly defer matrices and any plugin architecture.
This RFC is intentionally narrow. The goal is to improve common daily workflows for real users
without turning fyn into a second Hatch.
Why This Exists
When this RFC was drafted, upstream uv already covered much more surface than older comparisons
implied, including project initialization, version management, formatting, and auditing.
The remaining gaps identified here were workflow ergonomics:
- repeatable project tasks
- useful scaffolding for new projects
- isolated named workflows for docs/lint/test/typecheck work
fyn 0.10.17 closes the first gap. The scaffold and isolated-workflow proposals remain prospective.
Problem
When this RFC was drafted, fyn documented chained tasks and task-level environment variables, but
the runtime did not consistently implement them. Common project scaffolding still required manual
editing, and tool.fyn.environments described resolver scoping rather than named workflow
environments.
The task-runtime mismatch has since been resolved. The remaining problem in this RFC is narrower: projects still need manual setup or external tooling for richer scaffolds and isolated lint, docs, test, and type-check workflows.
Product Thesis
fyn should be the fastest Python package/project manager that also covers the most common
developer workflows directly, with simple declarative config and without a plugin framework.
Target Users
This RFC is aimed at the users who will get immediate value:
- Solo Python application developers who want repeatable local commands without adding another tool.
- Library maintainers who need docs, lint, test, and release workflows that are isolated and reproducible.
- Small teams standardizing a
pyproject.toml-first workflow. - Monorepo/workspace users who want per-project workflow config without inventing their own task conventions.
This RFC is not primarily aimed at:
- users who want a general plugin platform
- users who want arbitrary build hooks inside the package manager
- users who need a full CI matrix DSL on day one
Goals
- Close the gap between documented and actual task-runner behavior.
- Make
fyn initgenerate projects that are useful without immediate manual cleanup. - Support common detached workflows with minimal new concepts.
- Preserve
fyn's run-centric UX. - Avoid introducing a generic extension platform.
Non-Goals
- Reproducing Hatch's plugin architecture.
- Reproducing Hatch's environment inheritance model.
- Adding build hooks, metadata hooks, publisher plugins, or version-source plugins.
- Adding a full matrix/filter/collector system in the first iteration.
- Adding new top-level commands like
fyn testorfyn fmtin this RFC.
Proposal
Part 1: Finish the Task Runner — Complete
Shipped state
As of fyn 0.10.17, [tool.fyn.tasks] supports:
- string command tasks
- fail-fast arrays of command strings
- detailed tasks with
cmd,description, andenv - named
chaintasks with environment inheritance and cycle detection - graph-aware workspace execution with package filters
Workspace task runs synchronize once, run active workspace dependencies before dependents, and run independent members in parallel by default. Marker evaluation, selected extras and dependency groups, transitively activated extras, and workspace source selection contribute to the active graph.
Shipped behavior
The documented task surface is implemented:
[tool.fyn.tasks]
lint = "ruff check ."
test = { cmd = "pytest -q", env = { PYTHONWARNINGS = "error" } }
verify = ["ruff check .", "pytest -q"]
check = { chain = ["lint", "test"], description = "Run lint and tests" }
cmd
- Executes exactly as today.
Command sequences
- An array of command strings executes in order.
- Execution stops on the first failure.
env
- Merges into the child process environment for that task.
- If a chain task has
env, that environment is inherited by child tasks. - Child task
envvalues override parent chain values. - Effective precedence is: process environment, then chain-task
env, then leaf-taskenv.
chain
- Executes tasks sequentially in the declared order.
- Stops on first failure.
- Prints which child task is currently running.
- Rejects cycles with a clear error.
Extra CLI args
Initial version:
- Extra args continue to work for
cmdtasks. - Extra args are rejected for
chaintasks with a clear error message.
Rationale:
- this avoids ambiguous behavior
- it solves the main usefulness gap immediately
- it keeps the first implementation small and predictable
Workspace execution
fyn run --workspace <task>runs the named task in selected child members that define it.- The shared workspace environment is synchronized once before task execution unless
--no-syncis used. - Active workspace dependencies finish before dependents; independent members run in parallel by default.
- Exact package filters can be expanded through active dependencies or dependents.
Why this is useful
This completed the highest-value, lowest-risk part of the RFC:
- it closed the documented task/runtime mismatch
- it covers common fail-fast project command workflows directly
- it provides the foundation for scaffolded projects and isolated workflows later
Part 2: Make fyn init More Useful
Current state
fyn init already handles:
- app vs lib
- package vs non-package
- build backend selection
- script initialization
- workspace integration
That base is good. The gap is common workflow scaffolding after the project exists.
Proposed additions
Add a small set of high-value presets:
--cli--tests
These should be non-interactive first. If an interactive mode is added later, it should only be a selector for these same presets, not a separate scaffolding model.
Proposed semantics
--cli
For application projects:
- creates a packaged app layout
- adds a
[project.scripts]entrypoint - generates a
main()-style executable module if one does not already exist
This should be an ergonomic shortcut over the existing --app --package flow.
--tests
- creates a
tests/directory - adds a minimal example test
- adds a test dependency group or equivalent project dependency declaration
- adds useful default tasks, e.g.:
Explicit follow-up, not v1
Possible later preset:
--ci github
This should only be considered after the base presets prove useful. It is less universal than
--cli and --tests, and it should remain plain file generation rather than the start of a
templating framework.
Why this is useful
This makes fyn init generate something closer to what people actually keep after the first commit.
It reduces the current pattern of:
fyn init- hand-edit
pyproject.toml - add tests
- add tasks
- add CI
into a smaller, more direct path.
Part 3: Add Minimal Named Workflows
Problem this solves
Many projects want isolated environments for:
- linting
- documentation
- type checking
- integration tests with extra tooling
Today, these workflows usually become:
- ad hoc shell scripts
- external task runners
- hand-maintained local virtual environments
Tasks and workflows should serve different jobs:
- tasks are project commands that run in the project environment
- workflows are detached toolchain environments for repo maintenance tasks
Design principle
This should be a small workflow layer on top of fyn's existing resolver and environment machinery,
not a new environment framework.
This RFC intentionally does not use the name envs for the new feature, because fyn already uses
tool.fyn.environments for resolver marker scoping.
Proposed config
Add a new table:
[tool.fyn.workflows.lint]
python = "3.12"
dependencies = ["ruff>=0.7"]
env = { RUFF_OUTPUT_FORMAT = "full" }
[tool.fyn.workflows.lint.scripts]
check = "ruff check ."
format = "ruff format ."
[tool.fyn.workflows.docs]
python = "3.12"
dependencies = ["mkdocs-material", "mkdocs-redirects"]
[tool.fyn.workflows.docs.scripts]
serve = "mkdocs serve"
build = "mkdocs build --strict"
Version 1 semantics
Named workflows are intentionally limited:
- they resolve to detached cached environments
- they do not install the current project
- they do not inherit from each other
- they do not support matrices
- they do not define custom environment types
Supported fields in v1:
pythondependenciesenvscripts
Optional future field, but not required in v1:
description
Explicitly deferred in v1:
- dependency-group support
- extras support
CLI shape
To preserve fyn's run-centric UX, do not add a top-level fyn env namespace in v1.
Instead:
fyn run <workflow>:<script>fyn run --workflow <workflow> -- <command>...
Examples:
Behavior
- The workflow environment is created and cached automatically on first use.
- It is updated when its config or dependency inputs change.
- It uses the same index/auth/config machinery as the rest of
fyn. - Target parsing prefers workflow syntax only when the prefix matches a declared workflow name.
Otherwise, existing
fyn runtarget resolution rules continue to apply. - The workflow cache key must include at least: project root, workflow name, Python request, normalized dependency list, and a hash of the workflow config table.
Why this is useful
This is the smallest meaningful piece of Hatch worth borrowing after tasks:
- it covers real workflows people already have
- it keeps project
.venvfocused on the project itself - it replaces many one-off shell scripts
- it avoids a separate tool for common docs/lint/typecheck workflows
Deferred: Matrix UX
Matrix UX is useful, but it should be explicitly deferred.
Examples of deferred matrix behavior:
python = ["3.11", "3.12", "3.13"]- include/exclude filters
- env inheritance with matrix axes
- dedicated
testcommand semantics
Why defer it
- it adds a lot of behavior surface quickly
- it is less useful until workflows exist
- it risks recreating too much of Hatch's model
The right sequence is:
- make tasks real
- make init useful
- make workflows useful
- then evaluate matrices from real user demand
Explicitly Rejected
The following are out of scope for this RFC:
- Hatch-style build hooks
- metadata hooks
- version-source plugins
- publisher plugins
- environment collectors
- full environment inheritance
- custom environment types
- a new plugin API for
fyn
These are expensive to maintain and do not solve the most common user pain first.
User-Facing Examples
Example: small library
[project]
name = "acme-lib"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = []
[tool.fyn.tasks]
test = "pytest -q"
check = { chain = ["test"] }
[tool.fyn.workflows.lint]
python = "3.12"
dependencies = ["ruff>=0.7"]
[tool.fyn.workflows.lint.scripts]
check = "ruff check ."
format = "ruff format ."
Example: docs workflow
[tool.fyn.workflows.docs]
python = "3.12"
dependencies = ["mkdocs-material", "mkdocs-redirects"]
[tool.fyn.workflows.docs.scripts]
serve = "mkdocs serve"
build = "mkdocs build --strict"
Implementation Plan
Phase 1: Task runner parity — Complete
Completed by fyn 0.10.17:
- implemented
chainand inherited taskenv - added fail-fast command sequences
- added task validation and cycle diagnostics
- added graph-aware workspace execution, filtering, dependency ordering, and parallel scheduling
- aligned the reference documentation and integration tests with the shipped behavior
The current task reference is in Running commands in projects.
Phase 2: Better init presets
Scope:
- add
--cli - add
--tests - scaffold matching tasks where appropriate
Success criteria:
- a new project can be initialized with a useful local workflow in one command
Phase 3: Minimal named workflows
Scope:
- add config parsing for
tool.fyn.workflows - add
fyn run <workflow>:<script> - add
fyn run --workflow <workflow> -- <command>... - cache and invalidate workflow environments correctly
Success criteria:
- docs/lint/typecheck workflows can run in isolated named workflows without external tooling
Phase 4: Reassess matrices
Only begin this phase if:
- workflows are being used
- users are asking for multi-Python or multi-axis workflows
- the implementation pressure is coming from real use cases, not tool envy
Risks
- scope creep toward a full Hatch clone
- confusing overlap between tasks and workflow scripts
- surprising storage/update behavior for workflow environments
- over-scaffolding
initwith too many opinionated choices
Mitigations
- keep v1 of workflows detached and simple
- keep
runas the central UX instead of multiplying top-level commands - add only a few scaffold presets with strong defaults
- explicitly reject plugin work in this track
Open Questions
- Should
fyn init --testsadd atestdependency group,devgroup, or plain dependencies? - Should workflow environments be stored in the cache, under project-local metadata, or behind an internal abstraction with no user-visible promise yet?
- Should
--list-taskseventually gain awareness of<workflow>:<script>targets, or remain task-only?
Recommendation
Phase 1 is complete. Continue with Phase 2 before introducing named workflows or matrices.
The original gate—do not start workflows or matrices until task-runner parity is complete—was met by
fyn 0.10.17. The remaining phases should still proceed only from demonstrated user demand and with
the scope limits above.