First off — thank you for taking the time to contribute! Samyama is an open-source (Apache-2.0) distributed graph + vector database written in Rust, and contributions of all sizes are welcome: bug reports, docs, tests, examples, and code.
This guide explains how to get set up and how to get a change merged.
- Code of Conduct
- Ways to Contribute
- Development Setup
- Building, Testing, and Linting
- Making a Change
- Commit Message Convention
- Opening a Pull Request
- Where to Start
- Project Layout
- Releasing
Please be respectful and constructive in all interactions. Assume good intent, keep discussions technical, and help make this a welcoming project for newcomers.
You do not need to write Rust to be useful here:
- Report a bug — open an issue with steps to reproduce, expected vs. actual
behavior, and your OS / Rust version (
rustc --version). - Improve docs — the
docs/directory, theREADME, and inline doc comments can always be clearer. - Add or improve tests — the suite is large but coverage gaps exist; extra test cases for existing modules are always welcome.
- Add an example / case study — see
examples/andcase_studies/. - Fix a bug or add a feature — see Where to Start.
If in doubt, open an issue first to discuss the change before investing time in a large PR.
You will need a recent stable Rust toolchain (installed via
rustup) and a few system packages. zstd-sys generates its
bindings with bindgen, which needs libclang — without it the build fails part-way
through with a misleading 'stddef.h' file not found, so install these first:
# Debian / Ubuntu
sudo apt-get install -y build-essential cmake pkg-config libssl-dev clang libclang-dev
# Fedora / RHEL
sudo dnf install -y gcc gcc-c++ cmake pkgconf-pkg-config openssl-devel clang clang-devel
# macOS — the Xcode Command Line Tools already provide clang
xcode-select --installFork the repository on GitHub, then:
# Clone your fork
git clone https://github.com/<your-username>/samyama-graph.git
cd samyama-graph
# Add the upstream repo so you can stay in sync
git remote add upstream https://github.com/samyama-ai/samyama-graph.git
git fetch upstreamKeep your main in sync with upstream before starting new work:
git checkout main
git merge --ff-only upstream/main
git push origin main# Build
cargo build # Debug build
cargo build --release # Optimized build
# Test (full suite)
cargo test
cargo test graph::node # A specific module
cargo test -- --nocapture # Show test output
# Benchmarks (Criterion + domain suites in benches/)
cargo bench
# Code quality — these must pass before you open a PR
cargo fmt -- --check # Formatting
cargo clippy -- -D warnings # Lints (warnings are treated as errors)Integration tests require a running server:
cargo run # RESP on 127.0.0.1:6379, HTTP on :8080
# in another terminal:
cd tests/integration
python3 test_resp_basic.pyFor more detail on the architecture and available example demos, see
CLAUDE.md and docs/.
-
Create a topic branch off the latest upstream
main:git checkout -b fix/short-description upstream/main
Use a descriptive prefix:
fix/,feat/,docs/,test/,refactor/. -
Make your change. Keep the diff focused — one logical change per PR.
-
Before committing, make sure the checks pass:
cargo fmt -- --check cargo clippy -- -D warnings cargo test -
Add or update tests for any behavior you change.
This project uses Conventional Commits. The type prefix helps generate changelogs and communicates intent:
<type>(<optional scope>): <short summary>
Common types used in this repo:
| Type | Use for |
|---|---|
feat |
A new feature |
fix |
A bug fix |
docs |
Documentation only |
test |
Adding or fixing tests |
refactor |
Code change that neither fixes a bug nor adds a feature |
chore |
Build, tooling, or maintenance |
ci |
CI configuration |
Examples from the project history:
feat(vector): add HNSW rebuild after snapshot import
fix(scripts): correct example runner path
docs(quickstart): clarify RESP connection steps
-
Push your branch to your fork:
git push origin fix/short-description
-
Open a Pull Request against
samyama-ai/samyama-graph'smainbranch. -
In the PR description, explain what changed and why, and link any related issue (e.g.
Closes #123). -
Ensure CI is green. A maintainer (see
CODEOWNERS) will review your change. Please respond to review feedback by pushing additional commits to the same branch.
Small, well-scoped PRs are reviewed and merged faster than large ones.
| Workflow | Trigger | What it does | Typical time |
|---|---|---|---|
CI (ci.yml) |
every PR, push to main |
cargo test --workspace (dev profile) and cargo check --all-targets |
~7 min |
Nightly sweep (nightly.yml) |
02:30 UTC, or manual dispatch | scripts/verify-sweep.sh — tests, every example, every bench, every case study |
~50 min |
GPU CI (gpu-ci.yml) |
manual dispatch only | GPU-path tests on a self-hosted runner | — |
test (ubuntu-latest) is a required check on main. A PR cannot merge until
it passes.
Tests run in the dev profile, not release. Release compilation of the test binaries is what costs the wall-clock time; the tests themselves run in under a second either way. If your change needs release-profile timings, it belongs in the nightly sweep.
cargo fmt --check and cargo clippy -D warnings are not currently gated —
the tree has ~5,000 format diffs and ~680 clippy warnings, so either gate would
be red from its first run. Adopting them is tracked in
#487. Please do not
reformat files you are not otherwise touching; it makes review harder and the
mechanical reformat is planned as a single reviewable commit.
You can run the fast lane locally before pushing:
cargo test --workspace # what the PR check runs
./scripts/verify-sweep.sh # what the nightly runsThe nightly's bench timings come from a shared hosted runner and are not comparable between runs — they answer "does it still run", not "how fast". Published numbers come from a recorded host.
Good first contributions, roughly easiest to hardest:
- Docs & examples — fix inaccuracies or fill gaps in
docs/andREADME. - Tests — add cases for an under-tested module.
- Cypher functions — the supported function list is in
CLAUDE.md; standard OpenCypher has more that could be added (parser + executor + tests). - Open issues / roadmap items — see
ROADMAP.mdand known gaps noted indocs/CYPHER_COMPATIBILITY.md.
src/
├── graph/ # Property graph model (store, node, edge, property)
├── query/ # OpenCypher engine (parser, planner, executor)
├── protocol/ # RESP (Redis-compatible) protocol server
├── persistence/ # RocksDB storage, WAL, multi-tenancy
├── raft/ # High availability (openraft)
├── nlq/ # Natural-language-to-Cypher pipeline
├── vector/ # HNSW vector index
├── snapshot/ # Portable .sgsnap export/import
└── sharding/ # Tenant-level sharding
benches/ # Criterion + domain benchmarks
examples/ # Runnable demos and data loaders
tests/ # Integration tests
docs/ # Architecture docs, ADRs, compatibility notes
Releases publish three of the six workspace crates to crates.io, the
samyama package to PyPI, the samyama-sdk package to npm, and a multi-arch
Docker image to GHCR. A merge to main never publishes anything on its own —
CI validates, a published GitHub Release publishes. Cutting that Release is
itself two GitHub Actions runs, not a manual git tag.
Every crate shares one version, defined once in [workspace.package] in the
root Cargo.toml and inherited via version.workspace = true. sdk/python
and sdk/typescript are outside the cargo workspace (sdk/python is in the
root exclude list; sdk/typescript isn't cargo at all) so neither can
inherit — both are bumped to the same number by hand, or by
prepare-release.yml below.
Every publish workflow refuses to run if these numbers disagree with each other or with the release tag. Registries do not allow a version to be published twice, so this is checked before anything is uploaded rather than discovered halfway through.
- Actions → Prepare Release → Run workflow, enter the new version (e.g.
1.7.2, nov). This bumps all three version sources, refreshesCargo.lock, and opens a PR titledrelease: v1.7.2. - Review and merge that PR like any other — it changes nothing but version numbers and the lockfile.
- The merge itself is the trigger:
tag-release.ymlsees the new, never-tagged version onmain, re-checks that all three sources still agree, tagsv1.7.2, and publishes a GitHub Release with generated notes. - That Release fires
publish-crate.yml,publish-pypi.yml,publish-npm.yml, andpublish-docker.yml— in parallel, unattended.
No one runs git tag or fills in the GitHub Release form by hand. If any one
publish workflow fails, rerun that workflow alone — there is no need to cut
another release, and every one of the four is safe to rerun (each skips or
--skip-existings whatever already landed).
RELEASE_PAT. A PR opened by prepare-release.yml's default token sits in
GitHub's "approval-required" state and never fires ci.yml's own
pull_request trigger — the very check branch protection requires before
merge. Add a fine-grained PAT scoped to this repo only, with Contents: write
Pull requests: writeand nothing else, as theRELEASE_PATsecret, and that goes away. Without it the workflow still runs and the PR still opens — someone just has to click "approve and run" on the CI check once per release.
Only samyama-gpu, samyama-optimization, and samyama-graph-algorithms are
published. samyama, samyama-sdk, and samyama-cli are deliberately not:
crates.io names can be yanked but never released back, and none of the three
had any external consumer as of this decision (0 reverse dependencies on the
two crates that do exist — see samyama-cloud#146). Revisit if a real consumer
asks for cargo add samyama-sdk or cargo install samyama-cli.
crates.io resolves every path dependency's version against the real index,
so a crate cannot even be packaged until everything it depends on is
published — optional dependencies included. samyama-graph-algorithms has a
path dependency on samyama-gpu, so gpu publishes first regardless of the
gpu feature being off by default:
samyama-gpu → samyama-optimization → samyama-graph-algorithms
For the same reason the dry run cannot be hoisted into a single pass over all three: crate N only resolves once crate N-1 is genuinely on the index. Each crate is validated immediately before it is uploaded.
sdk/python is a PyO3 extension, so every platform needs a compiled wheel. It
builds with abi3-py38, so one wheel per platform serves CPython 3.8+ and the
matrix has no Python-version axis. publish-pypi.yml builds manylinux
x86_64/aarch64, macOS x86_64/arm64, Windows x64, plus an sdist.
The sdist is self-contained: maturin walks up to the repository root and
vendors the whole cargo workspace into it, crates/ sources included, so
building from it does not require the crates to be on crates.io. It is around
4 MB. Installing from it still needs a Rust toolchain on the target machine,
which is why the wheel matrix matters.
sdk/typescript is pure TypeScript with no native binding, so there is no
platform matrix — one build, one package. publish-npm.yml builds and
publishes it as samyama-sdk on npm.
publish-docker.yml builds ghcr.io/samyama-ai/samyama-graph for
linux/amd64 and linux/arm64 and runs a real smoke test — starts the container
and executes a Cypher query through the RESP protocol — before the run is
considered successful. It only fires on a semver tag (refs/tags/v*), so the
data-release tags this repo also uses (kg-snapshots-*) don't produce a
misleading image build.
GHCR packages default to private on first publish. Flip
ghcr.io/samyama-ai/samyama-graph to public in the package settings after the
first real release, or docker pull fails for anyone not authenticated.
| Registry | Mechanism | Where |
|---|---|---|
| PyPI | Trusted Publishing (GitHub OIDC) | pypi environment — no stored token |
| crates.io | Trusted Publishing (GitHub OIDC via rust-lang/crates-io-auth-action) |
crates environment — no stored token |
| npm | Trusted Publishing (GitHub OIDC) | npm environment — no stored token |
| GHCR | GITHUB_TOKEN (built in) |
— |
| release PRs | RELEASE_PAT (optional; see above) |
repo secret |
No registry credential is a stored, long-lived secret. That is a direct fix
for how this pipeline broke the first time: CRATES_IO_TOKEN went stale in
April, every release after that failed on 403 Forbidden, and nobody
noticed for four months because nothing was watching the token's expiry.
OIDC has no expiry to miss.
Each trusted publisher is registered on the registry's side (PyPI, crates.io, npm), naming this exact repository, workflow filename, and environment. All three fields are matched exactly — renaming a workflow file or an environment means re-registering it there.
Every publish workflow accepts workflow_dispatch. publish-crate.yml and
publish-npm.yml take a dry_run input (default on) that validates without
uploading. publish-pypi.yml takes a target input — testpypi, pypi, or
none. publish-docker.yml and prepare-release.yml/tag-release.yml are
safe to dispatch directly; the former only pushes on a real tag ref, and
cutting a release always stops at a PR for review.
Locally:
cargo package -p samyama-optimization # any crate whose deps are published
cd sdk/python && maturin build --release # a wheel for the current platform
cd sdk/typescript && npm publish --dry-run # validates without needing to be logged inThanks again for contributing to Samyama Graph! 🙏