Thanks for your interest in the project. It is alpha: a small core is verified and the wider legacy surface is not. Read the product contract before making architectural changes — it names which implementation is canonical and which are frozen.
git clone https://github.com/ContextLab/orchestrator.git
cd orchestrator
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"Optional capabilities live behind extras (anthropic, web, multimedia,
viz, infra, crypto, langgraph). The core install deliberately pulls in
only 12 dependencies — please keep it that way. If your change needs a new
third-party package, put it in an extra and import it lazily.
Verify the install:
python -c "import orchestrator; print(orchestrator.__version__)"
pytest -m "unit or contract or e2e" -q # the blocking gateTests are separated by marker, and the separation is load-bearing — CI gates on some layers and not others.
| Marker | Meaning | Runs by default | Gates CI |
|---|---|---|---|
unit |
Hermetic. No network, no services, no API keys. | yes | yes |
contract |
Verifies a published interface. Hermetic. | yes | yes |
e2e |
Full pipeline through the installed CLI/API. | yes | yes |
integration |
Needs local services (Docker/Redis/Postgres). | no | no |
live |
Calls a real provider API and costs money. | no | no |
Opt in to the gated-off layers explicitly:
ORCHESTRATOR_RUN_INTEGRATION=1 pytest -m integration
ANTHROPIC_API_KEY=... pytest -m live # costs money
DARTMOUTH_CHAT_API_KEY=... pytest -m live -k dartmouth # costs nothingThe Dartmouth live tests are free — they only use models the live catalog reports at zero cost per token, and assert that before making a request. If you have a Dartmouth Chat account, run them: they are the cheapest real-model coverage available, and they exercise the same provider contract.
Two things about them are easy to get wrong:
- Free model endpoints flap. Each is served from its own cluster endpoint
and they go down independently (
Cannot connect to host vllm-qwen35...). A live test must skip onModelUnavailable, not fail — that is an upstream outage, not a defect. Preferprovider.generate_free(), which falls through the free set. - Several free models are reasoning models. They spend tokens on
reasoning_contentbefore emitting anycontent, so a smallmax_tokensyieldscontent: null— a truncation, not an empty answer. That raisesReasoningTruncatedrather than returning"".
Tests requiring absent prerequisites skip with a reason naming what is
missing. They must never silently pass. If you add a test that needs
something optional, gate it the same way (see tests/conftest.py).
New tests should carry a marker. --strict-markers is on, so an invented
marker is an error rather than a silent no-op.
These are firm, and reviewers will hold you to them:
- No mock objects. Ever. Not for external APIs, not as a fallback. If real
functionality cannot be exercised, the test must fail or skip with a clear
reason — never pass against a simulation. Write real files to
tmp_path, make real connections, run real models. - No cheater tests. A test that cannot fail is worse than no test. Design each one to reveal a specific defect.
- Tests are debugging tools. Make them verbose. When one fails, its output should be enough to diagnose the problem without re-running it.
- Never weaken a test to make it pass. If a test fails, fix the code. If the test itself is wrong, say so explicitly in the commit message and explain why — do not quietly relax an assertion.
The project has had two confirmed remote-code-execution defects, both from the same root cause: treating pipeline content as trusted code. Pipeline content is data.
- Never call
eval()orexec()on pipeline content. Useorchestrator.core.expressions.evaluate_expression/evaluate_condition, which allowlist over the parsed AST. - Restricting
__builtins__is not a sandbox. Any function object in scope carries__globals__, a live reference to the real builtins:json.loads.__globals__["__builtins__"]["__import__"]defeats it entirely. This is not theoretical — it was exploitable here. - Guards fail closed. A condition that is malformed, unsupported, or undefined must not run the step it gates.
- Never interpolate caller-supplied strings into generated code or shell commands. Validate, then pass an argument vector.
- Widening the expression evaluator requires adversarial review. Every capability added to it has introduced a defect on the first attempt — including a memory-amplification DoS from methods that were individually safe. Add attack tests alongside the capability.
- A cost check is only worth the field it checked. The Dartmouth adapter
approves a model at construction, so the request body may not override
model(ormessages, orstream) —payload.update(kwargs)once turned an approved free model into an unapproved paid one at call time. Controlled fields are written last, and overrides raiseReservedRequestField. - Unknown cost is paid, not free. A model built without consulting the live catalog has an unknown price. Defaulting that to free is how money gets spent by accident.
- Credentials only travel over TLS. Any configurable endpoint that receives a token must be scheme-checked before the first request; loopback is the only plaintext exemption.
- Fix root causes. Do not paper over a failure with a special case, and do not simplify production code to make a test pass.
- No partial implementations, no dead code, no duplicated logic — search for an existing helper before writing a new one.
- Match the surrounding code's naming and structure.
- Update the docs in the same change. If you change a test or example, update what documents it.
- Check for credentials and personal information before committing.
Commit messages should explain why, not just what. If a change alters user-visible behaviour, say so explicitly — for example, a guard that used to fail open and now fails closed will stop firing for some existing pipelines, and that belongs in the message.
Reference issues as Issue #123: description where applicable.
Before opening a PR, run the full local gate:
pytest -m "unit or contract or e2e" -q
ruff check src/orchestrator --select E9,F63,F7,F82,F821,F823,F601,F811
python -m build && twine check dist/*
# No source file may be hidden by .gitignore. An unanchored `debug_*` rule
# silently excluded a real module for ~11 months: local trees still had the
# file, so only fresh clones broke and no local test could notice.
git ls-files --others --ignored --exclude-standard -- 'src/**/*.py' # must be emptyOnly correctness-class Ruff rules gate the build. The style backlog is large and making it blocking would bury real bugs.
If you change dependencies, refresh the lockfile — CI installs from it, and a
stale lock fails uv lock --check:
uv lock
uv sync --locked --no-default-groups --group test # what CI installsCI runs lint, the blocking test matrix across Python 3.11–3.13 on Ubuntu and macOS, a wheel smoke test, and the legacy suite as a non-blocking job. The legacy job is expected to be red; it exists to keep the backlog visible rather than hidden, and it publishes its counts to the run summary. Do not "fix" it by deleting or skipping tests — see #354.
Include the pipeline YAML that reproduces it, the exact command, the full output, and your OS/Python version. A reproduction that fits in one file is worth more than a long description.