Contributing.
A small project maintained by a very small team: what actually happens before code lands, not a process nobody follows.
0.1.0-alpha.N → -beta.N → -rc.N → 0.1.0 ladder. The wire protocol between hub and node, the on-disk state layout and the CLI surface can all change without a deprecation period; /v1 tracks the OpenAI shape and is the most stable thing here. Nothing is accredited or covered by a compatibility promise.Before you start
Security issues do not go in a public issue. Relay pools GPUs across machines people do not all own, so a report about what a node can see or reach deserves a private channel first — see Security.
For anything larger than a fix, open an issue before writing code. Relay makes some deliberate choices that look like omissions — sharding is the fallback and not the point, discovery is opt-in, size caps warn rather than delete — and it is kinder to disagree about a design before you have spent an evening on it.
Development setup
Go 1.26. No cgo anywhere, so cross-compilation needs nothing but the toolchain.
make build # ./bin/relay
make test # go test ./...
make gate # fmt, vet, lint, icon check, test, race, cross-platform vet+buildmake gate is the local bar: everything make check runs (fmt, vet, lint, icon check, test) plus the race detector and a vet-and-build pass across every cross-compile target. Run it before you push rather than after. STEP=lint or STEP=test narrows it to a quick slice.
CI runs make ci, which is gate plus a tidy go.mod, a vulnerability check, a release-config check, and a proof that the tests you changed actually fail without the change they test — a superset of the local gate, not the same target. A green make gate is the bar for pushing; it is not a guarantee CI will be green.
Tests and evidence
A behaviour change comes with a test, and the useful question about that test is whether it can fail. Several defects in this repo's history passed a full green matrix because the test asserted something that was true either way — a gate guarded on a path that did not exist, a glob that matched nothing, a check that ran a command and looked only at its exit code.
So: write the test, then break the code and watch it go red. If it stays green, it is not testing what you think. Saying so in the PR is worth more than a coverage number.
Verification that only a running system can do — does the stream actually stream, does the binary actually start, is the file actually still there — is worth doing by hand and pasting into the PR.
Architecture and boundaries
| Rule | Detail |
|---|---|
| The hub owns every model | Nodes never fetch from the internet; the hub pushes a hash-verified copy into a node's cache. Do not add a path where a node acquires weights itself. |
| Placement is deterministic | planner.Plan reads no clock, no probe and no shared state — the same inputs always produce the same plan, which is what makes losing a node safe. Live signals are captured into the snapshot by the caller. If you find yourself reaching for time.Now() inside the planner, something has gone wrong. |
/v1 is a byte-for-byte proxy | It forwards request bodies untouched, which is why response_format, grammars and tools work without relay knowing about them. The Ollama surface on /api/* is a genuine translation layer because that wire format differs. |
| Nothing is deleted on a machine you do not own | Not without an explicit human action. |
The architecture doc is the long version; Concepts covers the same ground here.
Commits and pull requests
One line. No body, no trailers.
feat(planner): rank nodes by workload and GPU contentionConventional-commit prefix, under about 72 characters. A commit-msg hook enforces it, including rejecting Co-Authored-By and generated-by footers. Releases are cut by release-please from these prefixes, so the prefix decides the version bump.
The reasoning goes in the pull request, not the commit. Say what was wrong, what you changed, and what you deliberately left alone. A PR that names its own known gaps is easier to trust than one that does not mention any.
Keep a branch to one change. Two unrelated fixes are two PRs.
Documentation
If behaviour changes, the docs change in the same PR. The README's configuration table, the architecture doc, and the known-issues doc are the ones that go stale fastest.
Be precise about what relay does not do. The README says plainly that sharding is slower and exists only for models nothing in the pool could hold otherwise — that honesty is load-bearing, and softening it into a feature bullet would be a regression.
Dependencies
go.mod has a deliberately small direct dependency list. Every release is cosign-signed with an SBOM and a provenance attestation, so a new module lands in artifacts people verify.
Prefer the standard library, then something already in the tree. If you do add one, pin it and say in the PR what it buys that the alternatives did not — and add it to THIRD_PARTY_NOTICES.md if it will be linked into the shipped binary.
Code of conduct
Members, contributors and leaders pledge to make participation in the community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, colour, religion, or sexual identity and orientation — and to act and interact in ways that contribute to an open, welcoming, diverse, inclusive and healthy community.
Report a violation through a private security advisory on the GitHub repository — the project's private channel to the maintainers, the same one used for security reports.
Licence
Apache-2.0. Contributions are accepted under the same licence. There is no CLA and no sign-off requirement.