Contributor’s Guide
Interested in contributing to Vaibify? This page outlines the steps to make a meaningful contribution. Before you begin, contact Rory Barnes to confirm that your proposed changes are not duplicating work and will be of general interest.
Style Guide
Vaibify follows the style conventions described in the project’s global
development standards: camelCase with Hungarian prefixes for variables,
f-prefixed names for functions (with a return-type letter), files in
camelCase without Hungarian prefixes, functions under 20 lines, no
abbreviations for words shorter than 8 characters, and clear naming in
preference to inline comments. If you are developing with an AI coding
agent, read AGENTS.md at the repo root for the rules,
traps, and discovery commands the agent should follow.
Running Tests
Run the full test suite:
pytest tests/
Run only tests that require Docker:
pytest -m docker
Run with coverage:
pytest --cov=vaibify tests/
Run the architectural invariant tests directly to verify that route registration, leaf-module discipline, and the science-agnostic source rule are intact:
pytest tests/testArchitecturalInvariants.py -v
The suite has three kinds of test — unit/behavior tests, architectural
invariants, and falsification tests (kill-confirmed tests, proven to
fail when the guard they defend is broken) — plus a standing re-kill
harness (python tools/reconfirmFalsification.py) and a warn-only
cosmic-ray mutation gate that is run on demand, not per pull
request. See Testing for what each is, why falsification
testing matters, and how to run them all.
Portability and CI
All code must work on both macOS and Linux, and with Python versions 3.9 through 3.14. GitHub Actions runs unit tests on every pull request across all permutations of Ubuntu 22.04/24.04, macOS 15/26, and Python 3.9 through 3.14. Tests that require a running Docker daemon are excluded from CI and run locally. Documentation is rebuilt and deployed automatically on every merge to main.
A separate CI job (agent-docs-path-check) verifies that every path
reference in AGENTS.md and SKILL.md files resolves to an existing
file. This catches stale references after refactors rename or delete
files.
Verifying a change reaches the screen
The Python suite does not execute the frontend at all, so a green suite says nothing about whether the dashboard still loads. Three lanes cover progressively more reality, and each proves something the others do not.
Lane |
What is real |
When |
What it proves |
|---|---|---|---|
browser ( |
Chromium + uvicorn + real HTTP/WebSockets; Docker is a fail-closed fake |
every PR |
JS loads and evaluates; API and refusal behaviour reach the screen honestly |
container acceptance ( |
a real container, image keyed by build-input hash |
nightly / manual |
a real container answers the commands the browser lane’s fake models |
fresh image ( |
full build from scratch |
weekly / on |
the image still builds; the container user is unprivileged |
Run the browser lane locally when you want the fast signal:
pip install -e '.[browser]' && python -m playwright install chromium
python -m pytest tests/browser -m browser
What the browser lane does not cover. It drives a fail-closed fake Docker adapter, so it says nothing about container launch, file ownership on write, the real transport, terminal content, figure rendering, or the sync panel. Those belong to the container-acceptance lane, which runs nightly — so drift between the fake and a real container is caught up to a day late. The browser lane failing blocks merge; the container-acceptance lane failing blocks the next release, not retroactively.
The manual check
Still the right tool when you are working on something the lane does not assert — layout, wording, a specific interaction. About a minute, no Docker needed:
python -m vaibify --port 8137 # scratch port, not your usual hub
Then at http://127.0.0.1:8137/:
Read the console. Zero errors is the bar. A single
ReferenceErrormeans a module failed to evaluate and every feature below it in load order is dead.Enumerate the globals.
Object.keys(window).filter(k => /^Vaibify/.test(k)).length. Then check any global your change touched resolves as a bare identifier, not viawindow.: modules declared withconstcreate a global lexical binding, sowindow.VaibifyAppisundefinedwhileVaibifyAppworks. Probing the wrong one produces a false alarm.Confirm any new cross-module call resolves, e.g.
typeof VaibifyApp.fsGetLeaseId→"function".Look at the page. It should render, and any unavailable dependency (Docker down, no containers) must be reported honestly on screen rather than hidden.
Kill the scratch hub when done.
Container-dependent paths need a container. Anything touching the lease, the WebSockets, or the file-status poll is not verified by the above. Start Docker, open a project, and exercise the specific path.
Docker-dependent tests (tests/testContainerBuildIntegration.py)
are excluded from routine runs and are the only ones requiring a live
container. They are parametrized via VAIBIFY_INTEGRATION_CONFIG and
skip when it is unset.
What survives what
Three different deaths, three different answers. Getting these backwards is how a long run gets abandoned, or a dashboard gets believed when it should not be.
A logout does not touch the work. Session expiry commits an
orphan, never a release: the credential stops authorizing, and the
record keeps its flock, its keep-alive, its agent token, and any live
task. A dashboard-launched runAll survives the logout in full —
the loop runs hub-side as a durable task nobody cancels on
disconnect, and the status callback tolerates a dead socket by design.
The returning browser reconciles from pipelineState.
A hub restart does not stop a run — it stops vaibify watching one.
Measured against a live daemon, an in-container exec survives SIGKILL
of the process holding its stream, and its exit code stays readable
through exec_inspect on the journaled exec id. What is lost is the
output stream: re-attaching to a started exec yields nothing. So
anything that must survive a restart has to be recoverable from the
journal and the filesystem, never from the hub’s memory. The full
measurement is in architecture.md — “What survives
what”.
The terminal-backgrounded gap. A job the researcher backgrounds in
a terminal is not a vaibify pipeline run, so vaibify’s own bRunning
flag never rises for it and none of the run-shaped vetoes fire. Sleep
prevention no longer depends on those vetoes — sleepPrevention
re-derives the keep-alive from whether the daemon reports a running
exec — but note the honest limit: a setsid descendant whose parent
exec has exited is invisible to that signal, exactly as it is
invisible to terminalContainment’s process-group prover. Vaibify
cannot prove what is running inside a container. Treat “no exec
visible” as no evidence of work, never as proof of quiet; a
project in which a terminal has run reports quiescence UNPROVEN for
the same reason.
Verifying any of this by hand. The session windows are settable, so a test drive does not need twelve hours:
VAIBIFY_ABSOLUTE_SESSION_CAP_SECONDS=120 python -m vaibify --port 8137
The environment tier outranks the stored preference, so this cannot be
masked by whatever is in ~/.vaibify/preferences.json. Leave a tab
open past the cap and confirm two things: the run you started keeps
going, and the refusal the tab meets names the ending rather than
claiming the server restarted.
Pull Request Workflow
Fork the repository and create a feature branch.
Make your changes following the style guide.
Add or update tests as needed.
Run
pytestlocally and confirm all tests pass.Open a pull request against the
mainbranch with a clear description of the change.
Project Layout
vaibify/
cli/ Command-line interface (Click)
completions/ Bash and zsh tab-completion scripts
config/ Configuration dataclasses and parsers
containerImage/ Docker build context (Dockerfiles, entrypoint,
overlays, in-container skills and CLI)
docker/ Container lifecycle management
docs/ Docs that ship into the image; the five curated
ones are symlinks onto the Sphinx sources
gui/ FastAPI web application and pipeline runner
routes/ Route modules (one per endpoint group)
static/ JavaScript IIFE modules + CSS + HTML
AGENTS.md Backend subtree rules for coding agents
static/AGENTS.md Frontend subtree rules for coding agents
install/ Setup wizard and shell installer
reproducibility/ Zenodo, Overleaf, and LaTeX integration
templates/ Project templates (sandbox, workflow, toolkit)
tests/ Pytest test suite, including
testArchitecturalInvariants.py
tools/ On-demand helper scripts (listModules.py,
checkAgentDocsPaths.py)
docs/ Sphinx documentation (this site) including
architecture.md and vibeCoding.md
.claude/skills/ Conditional recipes for recurring extension tasks
AGENTS.md Repo-wide rules, traps, and discovery commands
for AI coding agents (symlinked from CLAUDE.md)
For the full architectural narrative including module responsibilities, dependency graph, state machine, and known technical debt, see architecture.md. For the methodology behind the agent documentation system, see vibeCoding.md.
Run python tools/listModules.py <subtree> to print the current
module layout with __all__ exports and docstring summaries, rather
than relying on a static module map that can drift.