Contributing code

How to set up a local Flux development environment, run the tests, and land a pull request.

Flux is open source under Apache 2.0. The framework lives at github.com/edurdias/flux. Bug fixes, new examples, additional LLM providers, and docs improvements are the easiest contributions to land. Architectural changes and breaking changes need a design conversation first — open an issue before writing code.

This page covers the framework repository (flux-core on PyPI). For documentation changes on this site, see Contributing docs. For new examples in the examples/ folder, see Contributing examples.

Repo layout

flux/
├── flux/             # the framework package
│   ├── tasks/        # built-in tasks + AI agent harness
│   ├── domain/       # ExecutionContext, events, schedule, resource_request
│   ├── security/     # auth providers, encryption, bootstrap tokens
│   ├── observability/# OpenTelemetry + Prometheus integration
│   ├── agents/       # first-class AI agent system
│   └── cli.py        # Click command groups
├── examples/         # runnable workflow examples
├── tests/
│   ├── flux/         # unit tests
│   ├── e2e/          # end-to-end (spawn server + worker subprocesses)
│   ├── examples/     # validate every file under examples/
│   └── security/     # auth and permissions
├── docs/             # legacy MkDocs site (the new site lives in a separate repo)
├── flux.toml         # default operator config (ships with the repo)
├── pyproject.toml    # Poetry, ruff, pytest, poe configuration
└── CLAUDE.md         # the canonical architecture and command reference

The framework is one Python package and three runtime entry points: flux start server, flux start worker, and flux start mcp. Most contributions touch one of flux/server.py (HTTP routes), flux/worker.py (claim loop), flux/tasks/ (built-in primitives), or flux/cli.py (Click commands).

flux-src/CLAUDE.md is the most useful single file in the repo to read before making changes. It documents the two execution paths, the server-worker handshake, the configuration layering, and the known gotchas (module-cache collisions, base64-encoded workflow source, custom _FluxModule class). AGENT.md documents the workflow conventions — branch off main, bump version, run pre-commit before pushing.

Fork, clone, install

git clone git@github.com:<your-fork>/flux.git
cd flux

# Python 3.12+ is required (CI tests 3.12-3.14); pyenv or asdf works.
pyenv install 3.12
pyenv local 3.12

# Poetry manages dependencies. Do not introduce uv — it is excluded on purpose.
pip install poetry
poetry install
poetry install --extras observability    # what CI installs for lint + unit
poetry install --extras postgresql       # adds psycopg (v3) if you touch PG code
poetry install --extras ai               # Ollama / OpenAI / Anthropic / Gemini providers

poetry install gives you a virtual environment with the dev tools — ruff, pyright, pytest, pre-commit, and the AI provider SDKs as dev-only deps. The base install is SQLite-only with no telemetry; opt into extras when your work needs them.

After poetry install, install the pre-commit hooks:

poetry run pre-commit install

This wires the lint hooks to run on every commit locally — the same checks CI runs.

Running the tests

The test suite has four layers. Run them in this order when developing:

# Unit tests (the layer most contributions extend)
poetry run pytest tests/ --ignore=tests/e2e

# A single file
poetry run pytest tests/flux/test_worker.py

# A single test
poetry run pytest tests/flux/test_worker.py::TestWorker::test_start_registers

# End-to-end (spawns real server + worker subprocesses)
poetry run pytest tests/e2e/ -m "not ollama" -v

# PostgreSQL-tagged tests (requires Docker)
make test-postgresql

The E2E suite takes about 10 minutes locally and uses pytest’s conftest.py to spawn flux start server and flux start worker as subprocesses on a free port. Set FLUX_E2E_KEEP_LOGS=1 if a test fails and you want the server and worker logs preserved for inspection.

Tests tagged ollama require a local Ollama install with a compatible model loaded; they are auto-skipped when ollama list fails. The postgresql marker requires the test Postgres container — make test-postgresql brings it up, runs the tests, and tears it down.

When a PR touches behavior that crosses the server-worker boundary, the CLI, or the scheduler, add an E2E test. The patterns are in tests/e2e/test_*.py.

Linting and formatting

Flux uses Ruff (100-char line length, double quotes) as the formatter of record and Pyright for type checking. Pre-commit is the gate that runs both.

# Full sweep (matches CI)
poetry run pre-commit run --all-files

# Single file or path
poetry run pre-commit run --files flux/worker.py

Do not run ruff or pyright directly during a contribution — the pre-commit hooks set the right arguments and the right ignore lists, and CI runs pre-commit run --all-files, not the underlying tools. Skipping pre-commit with --no-verify is not allowed; if a hook fails, fix the underlying issue or open a separate PR to adjust .pre-commit-config.yaml.

Pylint, Pyflakes, Bandit, and Prospector are available as dev dependencies for deeper analysis, run as standalone tools, but they are advisory; pre-commit and Pyright are the blocking checks.

Pull request workflow

1. Branch off main         git checkout -b feature/<short-name> or fix/<short-name>
2. Make the change         keep it small and focused; one logical change per PR
3. Add tests               unit minimum; E2E if you crossed server/worker/CLI
4. Bump pyproject.toml     patch for fixes, minor for features (CI enforces)
5. Run pre-commit          poetry run pre-commit run --all-files
6. Run unit tests          poetry run pytest tests/ --ignore=tests/e2e
7. Run e2e tests           poetry run pytest tests/e2e/ -m "not ollama"
8. Open the PR             describe the why, not the what
9. Respond to review       commit, push, then reply — do not reply with promises

The version bump in step 4 is the most common reason PRs fail CI on first push. The version-check job in .github/workflows/pull-request.yml reads the target branch’s pyproject.toml version and fails the PR if your branch’s version is not strictly greater. Patch bump for a bug fix (0.56.00.56.1), minor bump for a feature (0.56.00.57.0).

If your change touches the ORM models in flux/models.py, ship an Alembic migration with it — generate one with poetry run alembic -c /dev/null --raiseerr revision --autogenerate -m "describe change" (or hand-write it under flux/migrations/versions/), and keep it static so old revisions behave consistently regardless of later model changes. The schema is migration-managed; there is no create_all fallback.

PR titles follow no rigid convention but are read by humans — fix: scheduler double-fire when two replicas run is clearer than Update scheduler.py. Describe the why in the PR body; the diff already shows the what.

Do not add Co-Authored-By: lines on the Flux framework repo. The docs site repo allows them; the framework repo does not.

What lands easily

What needs discussion first

Where to ask

When in doubt, open an issue before opening a PR. Reviewing a small issue takes ten minutes; reviewing a large PR that took the wrong approach takes a week.