Thanks for helping make robotics data infrastructure easier to use. This project is pre-v1: focused bug reports, documentation fixes, compatibility work, and small changes with clear outcomes are especially useful.
New to open-source contribution? GitHub's contributing to a project quickstart covers the fork, branch, and pull request flow this project uses. For questions at any point, ask in our Discord.
By submitting a contribution, you agree that it may be distributed under the repository's Apache-2.0 license.
You need:
- Git and uv
- Python 3.11 or newer
- ffmpeg and ffprobe on
PATHfor the test suite - Docker with Compose v2 only when working on the Airflow runtime integration
- lychee when editing Markdown links
Linux and macOS both work for native development. CI runs on Linux only, so run the quality checks yourself before opening a pull request on macOS.
Native Windows does not work: HFlow imports fcntl for file locking
(src/hflow/storage.py), and that module does not exist on Windows, so
import hflow fails before any test can run. Work inside WSL2 with an Ubuntu
distribution instead, following the setup steps below from the WSL2 shell and
keeping both the clone and your data root on the Linux filesystem. The
runtime prerequisites explain why the data
root has to live there.
Clone the repository and create the locked development environment:
git clone https://github.com/Hebbian-Robotics/hflow.git
cd hflow
uv sync --lockedEvery extra the suite exercises is mirrored into the dev group, so a plain
sync is the whole development environment. Two extras stay opt-in because
nothing in the suite runs them -- openai, which talks to a real endpoint, and
mediapipe, which is a model that adds ~430 MB and a second OpenCV
distribution. Add either with --extra <name> when you are working on it.
Confirm the package and CLI are available:
uv run python -c "import hflow; print(hflow.__version__)"
uv run hflow --helpRun the smallest end-to-end development loop without Docker:
uv run python examples/quickstart.pyGenerated episodes, catalogs, and runtime bundles live under data/, which is
gitignored. Never commit robot recordings, generated media, credentials, or
runtime .env files.
Run the default suite after every behavior change:
uv run pytest -qCI runs the suite on Python 3.11 and 3.14. When changing compatibility-sensitive code, exercise both ends locally:
uv run --python 3.11 --locked pytest -q
uv run --python 3.14 --locked pytest -qFour integration test suites are intentionally opt-in because they need network access, Docker, a writable object-store prefix, or a model outside the default environment:
HFLOW_NETWORK_TESTS=1 uv run pytest tests/test_ffmpeg.py -q
HFLOW_DOCKER_TESTS=1 uv run pytest tests/test_runtime_integration.py -q
HFLOW_TEST_BUCKET_URL=gs://your-bucket/tmp-prefix uv run pytest tests/test_storage.py -q
HFLOW_MEDIAPIPE_TESTS=1 uv run --extra mediapipe pytest tests/test_mediapipe_hands.py -qThe MediaPipe one brings its own OpenCV, and the OpenCV wheels share one
cv2/ directory, so syncing back out can leave import cv2 broken while
uv sync still calls the environment correct. One command puts it back:
uv sync --locked --reinstall-package opencv-python-headlessRun the Docker test when changing the runtime bundle, DAG templates, REST client, runtime lifecycle, or task-venv behavior. It pulls large images on the first run, starts an isolated Compose project, and removes its containers and volumes afterward.
Tests must verify HFlow's observable business outcomes. Do not add tests whose only purpose is to assert mock interactions or third-party library behavior.
Prefer one test for each distinct contract or boundary. Before adding a regression test, search for existing coverage and extend the closest behavioral test when it already exercises the same setup and outcome. A test double is appropriate at a network, process, clock, or plugin boundary, but assertions should still describe what the caller observes rather than which private helper was invoked.
The documentation home uses the Diátaxis categories: tutorial, how-to guide, reference, and explanation. Give a new page one primary purpose, link it from that index, and link task guides to the corresponding runnable example when code is involved.
Examples are executable documentation. Each example must state its prerequisites and external side effects, provide an exact command from the repository root, and name the observable result. Keep examples on public APIs; tests belong to business logic and boundary behavior, not to checking that a documentation snippet copied a third-party SDK correctly.
Identities in HFlow are content hashes, and one of them -- pipeline_version
-- is stamped inside the canonical bytes that episode_id hashes. A release
number deliberately does not feed any of them: a CLI fix or a docs bump
must never invalidate somebody's corpus. What does feed them is
TRANSFORM_BEHAVIOR_VERSION in src/hflow/behavior.py.
Bump it in the same commit whenever your change makes the transform write different bytes for the same input -- encoder settings or defaults, chunking and grouping, timestamp handling, the provenance record's shape, or a bugfix to any of those. Bumping re-versions every existing corpus exactly once, which is the honest cost; not bumping when behavior changed silently mixes two behaviors under one version, which is worse. When in doubt, bump, and say so in the pull request.
Before HFlow 1.0, canonical MCAP files are derived artifacts and exact byte compatibility is not guaranteed. Retain the raw/source recording and regenerate canonical outputs after a behavior bump. The behavior version exists to make that rewrite explicit and detectable, not to require adapters for every old layout.
Changes to checks and enrichments do not require a transform behavior bump.
Instead, bump the step's explicit version when its new results are no longer
comparable with the old ones. Derived channels follow the same author-owned
rule, but their versions feed pipeline_version because their samples are
written into canonical episode bytes. tests/test_identity_stability.py pins
the transform rules.
Run the Python quality gate and fix every reported issue:
uv run ruff check --fix
uv run ruff format
uv run ty checkTo catch style issues before you commit, install pre-commit hooks (optional; CI will catch anything you miss):
uvx pre-commit install
# or: pipx run pre-commit installHooks will run automatically on git commit. To run them manually on all files:
uvx pre-commit run --all-filesHooks enforce the same style gates documented above: ruff check --fix and ruff format.
When Markdown changes, run the same link check as CI:
lychee --no-progress --include-fragments \
--exclude '^https://github\.com/Hebbian-Robotics/hflow/(issues|security/advisories/new)$' \
--exclude-path references/mcap-spec.md \
--exclude-path references/foxglove-CompressedVideo.proto .references/ contains pinned third-party source material. Do not edit a mirror
as if it were project documentation: update it from the authoritative upstream
URL, record the new retrieval time, and preserve its license notice in
references/LICENSE.
- Keep each pull request focused on one problem.
- Explain the user-visible outcome and why the change is needed.
- Include the exact validation commands you ran.
- Add or update documentation whenever behavior, flags, formats, or operational requirements change.
- Add outcome-focused regression coverage for bug fixes and business logic.
- Never delete or corrupt raw/source recordings. For pre-1.0 derived outputs, document the behavior bump and regeneration path instead of preserving old byte layouts by default.
- Verify
git statusdoes not include recordings, generated artifacts, caches, credentials, or runtime bundles.
Use the issue tracker for reproducible bugs and concrete feature requests. For security-sensitive reports, follow SECURITY.md instead of opening a public issue.
- Prefer explicit types over raw dicts -- make invalid states unrepresentable where practical
- Prefer typed variants over string literals when the set of valid values is known
- Use exhaustive pattern matching (
matchin Python and Rust,ts-patternin TypeScript) so the type checker can verify all cases are handled - Structure types to enable exhaustive matching when handling variants
- Prefer shared internal functions over factory patterns when extracting common logic from hooks or functions -- keep each export explicitly defined for better IDE navigation and readability
- Unknown values: Parse to an explicit
Unknown*variant (neverNone), log at warn level, preserve raw data, gracefully ignore instead of raising exception
- Verbose naming: Variable and function naming should read like documentation
- Strategic comments: Only for non-obvious logic or architectural decisions; avoid restating what code shows