Skip to content
OpenSmartRoute

Contributing to OpenSmartRoute

Development setup, quality gates, the public API and deprecation policy.

CONTRIBUTING.md

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 rationale string.
  • 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-accuracy exits 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.md regenerated (python scripts/api_reference.py; tests/test_docs.py fails on a stale reference or an undocumented name; ... report lists 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 Added bullet.
  • Behaviour changes to an existing public symbol need a Changed bullet; 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 emits OpenSmartRouteDeprecationWarning, a DeprecationWarning subclass), is listed under Deprecated in the CHANGELOG, and is deleted no earlier than the removal version named in the warning, with a Removed bullet. 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, VersionedStateStore schema) 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.json is regenerated from the actual __all__ lists, never edited by hand, and the regenerating PR must show only additions unless it also carries the matching Removed entry.

Adding a strategy#

  1. Subclass opensmartroute.strategies.Strategy, set name, implement score().
  2. If it learns, implement update(outcome) and, if it has a model, model.state()/load().
  3. Add a unit test with a deterministic seed.
  4. Add a row to the strategy table in README.md and the idea-to-module map in docs/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.

  1. Prepare. Run the Prepare release workflow (Actions tab, workflow_dispatch) with a bump (major / minor / patch) or an explicit version. It runs scripts/release.py prepare, which bumps pyproject.toml and __version__, turns ## [Unreleased] into a dated section and rewrites the compare links, then opens a pull request labelled release.
  2. Review. The PR diff is the release: version bump plus CHANGELOG. CI runs on it like any other PR.
  3. Merge. Merging a release-labelled PR into main triggers the Release workflow, which re-verifies version consistency, runs the test suite, builds the sdist and wheel (with build provenance attestations), creates the annotated tag vX.Y.Z and the GitHub release with the CHANGELOG section as notes, publishes to PyPI through trusted publishing (no API token) and pushes ghcr.io/isathish/opensmartroute:X.Y.Z and :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 (owner isathish, repository OpenSmartRoute, workflow release.yml, environment pypi).
  • GitHub: create the pypi environment (optionally with required reviewers); allow Actions to create pull requests (Settings > Actions > General) so Prepare release can open the PR. Optionally add a fine-grained RELEASE_TOKEN secret (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.