Contributing to OpenSmartRoute
Development setup, quality gates, the public API and deprecation policy.
Thanks for helping build an open, intelligent route to the right decision.
Ground rules#
- Core stays dependency-free. Anything needing torch, numpy, FastAPI, etc. goes behind an
optional extra or into
opensmartroute-contrib-*. - Every strategy is inductive. It must work for a target it has never seen, using only the target's declared capabilities/examples.
- Every decision is explainable. New strategies return a
rationalestring. - Every learner is persistable. Implement
state()/load(). - No raw request text in logs. Hash + length only.
Workflow#
python3.12 -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\Activate.ps1
pip install -e '.[dev]'
ruff format src tests scripts
ruff check src tests scripts && mypy && bandit -q -r src -c pyproject.toml
pytest -q
osr -t examples/targets.yaml -r examples/rules.yaml eval examples/eval_dataset.jsonl --min-accuracy 0.85
osr -t examples/targets.yaml safety --learned-guard
python scripts/release.py check
CI runs the same commands on Linux (Python 3.10 to 3.13) and Windows (3.12), then builds the wheel, sdist and container image. Pull requests must keep:
- eval accuracy >= 0.85 on
examples/eval_dataset.jsonl(--min-accuracyexits non-zero otherwise); - the safety-routing suite green (
osr safety --learned-guard); - the public API snapshot (
tests/public_api.json) unchanged, or updated deliberately in the same PR with a CHANGELOG entry; pyproject.toml,opensmartroute.__version__and the top CHANGELOG section in agreement (scripts/release.py check);- every exported name with a one-line summary (a docstring, or a same-line comment for constants)
and
docs/REFERENCE.mdregenerated (python scripts/api_reference.py;tests/test_docs.pyfails on a stale reference or an undocumented name;... reportlists the gaps).
Every user-visible change gets a bullet under ## [Unreleased] in CHANGELOG.md
(Keep a Changelog format: Added / Changed / Deprecated / Removed / Fixed / Security).
If you touch the hot path (signals, policy, router, any Strategy.score), run
python scripts/bench.py before and after and paste both tables in the PR.
If you touch brand assets, edit scripts/brand_build.py and run it with --export; never edit
the generated SVG/PNG files by hand.
Public API and deprecation policy#
The public surface is every name in the __all__ of the packages listed in tests/public_api.json
plus the keyword arguments of Router.__init__. tests/test_public_api.py fails on any addition or
removal, so every change to that surface is a deliberate, reviewed diff of the snapshot.
- Additions are allowed in any minor release and get a CHANGELOG
Addedbullet. - Behaviour changes to an existing public symbol need a
Changedbullet; if the old behaviour can be kept behind a parameter, keep it as the default for one minor. - Removals and renames go through a deprecation: the old symbol keeps working for at least one
minor release, calls
opensmartroute.errors.deprecated(name, since=, removal=, replacement=)(which emitsOpenSmartRouteDeprecationWarning, aDeprecationWarningsubclass), is listed underDeprecatedin the CHANGELOG, and is deleted no earlier than theremovalversion named in the warning, with aRemovedbullet. Before 1.0 the minimum notice is one minor; from 1.0 on it is two minors, and removals happen only in a major release. - State formats (
state()/load()payloads,VersionedStateStoreschema) are versioned; a newer library must load older state, and loading newer state raises rather than guessing. - Settings (
OSR_<GROUP>_<FIELD>) follow the same rules as symbols; renamed keys are read under both names for the deprecation window. tests/public_api.jsonis regenerated from the actual__all__lists, never edited by hand, and the regenerating PR must show only additions unless it also carries the matchingRemovedentry.
Adding a strategy#
- Subclass
opensmartroute.strategies.Strategy, setname, implementscore(). - If it learns, implement
update(outcome)and, if it has a model,model.state()/load(). - Add a unit test with a deterministic seed.
- Add a row to the strategy table in
README.mdand the idea-to-module map indocs/RESEARCH.md.
Adding an adapter#
Adapters live in src/opensmartroute/adapters/. Anything needing a third-party package must be
imported lazily inside the function/class and raise ConfigurationError naming the extra to install.
Test transports against a real in-process server (see tests/test_adapters.py), not by mocking
urllib.
Adding math#
Put it in src/opensmartroute/math/, cite the source in the docstring, include the formula,
and add a numerical test against a known value.
Adding or editing an agent skill#
Skills live in .claude/skills/<name>/SKILL.md and follow the Agent-Skills format the SDK loads
with load_skills. The directory name must equal name; every fact in the body must be
verifiable against the code (tests assert the packages load and route). Keep osr-domains to
ontology names (opensmartroute.signals.ontology.DOMAIN_LEXICON or general) and omit
osr-actions for broad skills. Validate with osr skills and run tests/test_skills.py; when a
skill's behaviour changes, update the matching request in REPRESENTATIVE too. Every skill is
published on the documentation site at /docs/skills/<name>; no web change is needed.
Editing documentation#
The documentation site in platform/web (/docs/<slug>; see "Documentation site" in
platform/README.md) is for end users of the hosted platform, the Python SDK and the pip package. It
renders docs/*.md (except docs/sales/), the root README/CHANGELOG/SECURITY/this file,
deploy/README.md, spec/ocm/README.md, the skills, and a REST API reference generated from
platform/api/openapi.json. Internal documents (docs/PLATFORM_PLAN.md, docs/GO_TO_MARKET.md,
docs/BRAND.md, docs/sales/, platform/README.md) stay in the repository and are linked from
README.md only. Keep one H1 per file, use H2/H3 for structure (they become the table of contents and
the search index), give fenced code a language and use relative links to other Markdown files. A new
docs/*.md needs a link in README.md (tests/test_docs.py) and, when it is user-facing, an entry in
platform/web/src/lib/docs/catalogue.ts; run npm run build in platform/web to preview
(npm run dev for live reload). docs/REFERENCE.md is generated; never edit it by hand. After changing
a platform route or model, regenerate the OpenAPI snapshot with
python platform/api/scripts/export_openapi.py (CI runs --check). Documentation may only name things
that exist: tests/test_docs_claims.py verifies every osr <command> --flag and every OSR_* variable
mentioned in the Markdown against the CLI parser and the code, and
platform/api/tests/test_platform_docs_claims.py verifies documented endpoints against the app's routes,
/dashboard/<page> paths against the web app, the site catalogue against docs/ and the CHANGELOG
sections (date, body, compare link). Coding agents should start from AGENTS.md.
Commit style#
Conventional Commits (feat:, fix:, docs:, perf:, refactor:, test:, chore:).
Releasing#
Releases are cut by GitHub Actions; nobody edits version numbers or tags by hand.
- Prepare. Run the Prepare release workflow (Actions tab,
workflow_dispatch) with a bump (major/minor/patch) or an explicit version. It runsscripts/release.py prepare, which bumpspyproject.tomland__version__, turns## [Unreleased]into a dated section and rewrites the compare links, then opens a pull request labelledrelease. - Review. The PR diff is the release: version bump plus CHANGELOG. CI runs on it like any other PR.
- Merge. Merging a
release-labelled PR intomaintriggers the Release workflow, which re-verifies version consistency, runs the test suite, builds the sdist and wheel (with build provenance attestations), creates the annotated tagvX.Y.Zand the GitHub release with the CHANGELOG section as notes, publishes to PyPI through trusted publishing (no API token) and pushesghcr.io/isathish/opensmartroute:X.Y.Zand:latest.
The Release workflow can also be dispatched manually on main when the tree is already at a
releasable version (used for the first release, or to retry a failed publish; it refuses to run if the
tag already exists).
One-time repository setup for maintainers:
- PyPI: add a trusted publisher for project
opensmartroute(ownerisathish, repositoryOpenSmartRoute, workflowrelease.yml, environmentpypi). - GitHub: create the
pypienvironment (optionally with required reviewers); allow Actions to create pull requests (Settings > Actions > General) so Prepare release can open the PR. Optionally add a fine-grainedRELEASE_TOKENsecret (contents and pull-requests write) so CI runs on the release PR even though it was opened by automation. - Tags for historical versions so compare links resolve:
git tag -a v0.3.0 <sha> -m "opensmartroute 0.3.0".
scripts/release.py has no dependencies beyond the standard library and is covered by
tests/test_release_tooling.py.
1.0.0. python scripts/release.py readiness evaluates the v1.0 readiness table in
docs/ROADMAP.md from the repository. Release rows (frozen API, performance envelope,
reference deployments, security review pack, published leaderboard run, two consecutive non-breaking
releases verified against the tagged tests/public_api.json) block any 1.x version in release.py check;
adoption rows (an independent report row in docs/SECURITY_REVIEW.md, an accepted listing in
examples/leaderboard/results/README.md, two rows in ADOPTERS.md) are tracked and printed but never
block. The Release job prints the table in its summary on every run.
Code of conduct#
We follow the Contributor Covenant.