Deterministic orchestrator for CLI coding agents (Claude Code, Codex, Gemini CLI, +40 more). No model in the coordination loop, so parallel runs in per-task git
Deterministic orchestrator for CLI coding agents (Claude Code, Codex, Gemini CLI, +40 more). No model in the coordination loop, so parallel runs in per-task git
Status: beta. Solo-maintained, under active development. The version number counts releases, not maturity - minor versions may change interfaces. Pin the version for anything you depend on; regressions get fixed fast, file them.
Bernstein is the open-source governance layer for AI agents. It runs on policy as code: you write the policy - who may do what, what needs approval, what must be recorded - and Bernstein enforces it and produces the verifiable record. A deterministic scheduler - no model in the coordination loop - runs agents in parallel, gates what they produce, and records every step, so a run can be verified after the fact, offline, from the artifacts alone. CLI coding agents work out of the box (Claude Code, Codex, Gemini CLI, and 40+ more), and the same layer governs any agent workload: the deliverable can be a diff, a research report, a dataset, or an audit evidence pack. Air-gap install profile included. Apache-2.0.
Four things set it apart; everything after is detail.
BERNSTEIN_AUDIT=1) adds receipts you verify offline. Non-determinism surfaces as a hash mismatch at the exact step, not a flaky re-run. Non-code deliverables get the same treatment: a task can declare an artifact contract (report, dataset, action log, ops result) and completes on a signed lineage receipt rather than a git commit..sdd/workspaces/. Agents share no mutable workspace by default; the only shared state is the task backlog, which is claimed atomically. Stricter filesystem enforcement is opt-in, from the sandbox backends. Disable worktrees and every task runs in the shared checkout.--prompt wrapper, file-based state, no SaaS hop, no third-party data plane.The full list is on the capabilities page; the feature matrix is the exhaustive index.
One YAML file declares the run: phases, roles, dependencies, and the conditions under which a node runs at all. The scheduler executes it as plain Python - nothing in the file is a prompt, and no model decides what happens next. This graph produces an audit evidence pack; the full file ships at .bernstein/workflows/audit-evidence-pack.yaml.
…
flowchart LR
inv[define-control-inventory] --> logs[collect-audit-logs]
inv --> sbom[collect-sboms-and-attestations]
inv --> rb[collect-runbooks-and-policies]
inv --> ev[collect-eval-results]
logs --> pack[assemble-pack]
sbom --> pack
rb --> pack
ev --> pack
pack --> gate{mock-auditor-pass}
gate -->|failed| fix["remediate-findings (retry x3)"]
gate -->|done| sign[sign-and-deliver]
Each node is claimed by an agent whose role the phase allows; role fences and approval gates hold no matter what the agent does inside the task. A coding node completes behind merge gates in its own git worktree. The nodes above complete differently: an artifact contract names the deliverable (report, dataset, scan, action log), and the node finishes on a signed lineage receipt instead of a commit. Same scheduler, same journal, same offline verification - whether the graph ships code, research, an ops change, or a mix of all three. Ready-made graphs for software, research, docs, enterprise, and contributor workflows live in .bernstein/scenarios/.
uv tool install bernstein # or: pipx install bernstein
bernstein init
bernstein doctor # checks a CLI agent is installed and authenticated
bernstein -g "fix the failing test in tests/test_foo.py"
pipx, pip, brew, dnf, npm, and Docker are covered in the install guide; the air-gapped wheelhouse has its own air-gap guide.
The recording above is a real run, and it ships with its own proof. The cast, the signed run receipt derived from that run's journal, and the public key that pins it all live in docs/assets/demo-run/. Verify the run you just watched, offline:
bernstein verify receipt docs/assets/demo-run/run-receipt.json \
--public-key docs/assets/demo-run/run-receipt.pub.pem
CI re-verifies the committed receipt on every push to main — and proves a tampered copy fails — so the published evidence cannot rot into a decorative file. scripts/record_demo.sh regenerates the recording, receipt, and key from a fresh real run; nothing inside the terminal is synthesised.
A run in flight is watchable from either operator surface. Both read the same task API, so neither is a lagging mirror of the other. In bernstein live, the left and right columns scroll independently as whole panes, so widgets below the fold remain reachable in shorter terminals.
bernstein live — the terminal dashboard |
bernstein gui serve — the browser dashboard |
Determinism here is something you check, not something you take on faith. Run once with audit enabled, then verify what was recorded:
…
The journal is written on every run; the lineage spine is always on and gains an entry for each lineage-bearing step, so a short run can finish with a valid, empty spine. bernstein audit verify only has a chain to check when the run was started with BERNSTEIN_AUDIT=1, a compliance preset, or bernstein run --audit. The --audit flag belongs to bernstein run; on the bernstein -g form above, set the environment variable.
One run receipt binds the journal head, the lineage-spine head when the run wrote spine entries, and, opt-in, an audit-chain range, under a single Ed25519-signed subject with the public key embedded. A reviewer holding that file and the operator's public key can confirm the embedded actions and chains were not changed: no HMAC key, no live .sdd/, and exit 2 naming the first divergent step on tamper. That receipt identifies the journal state it embeds; proving that state is the complete finished journal additionally requires an independent head/count seal. With the file alone and no --public-key pin, the check is integrity-only — it proves the receipt is internally consistent, not who signed it, and the verdict says so. Details in deterministic replay.
The same checkability applies to evaluation numbers. bernstein bench run --reliability k (also spelled bernstein eval --reliability k) runs every task k times under fixed coordination, then reports a pass^k floor (all k attempts must pass) alongside the pass@1 ceiling. That result is sealed in a signed receipt which bernstein bench reliability-verify recomputes offline, so a fabricated floor fails verification. Details: pass^k reliability floor.
Each goal moves through four stages:
Why the scheduler is plain Python, and what that trades away: why deterministic.
cd your-project
bernstein init # creates .sdd/ workspace, bernstein.yaml + templates/
bernstein -g "Add rate limiting" # agents spawn, work in parallel, verify, exit
bernstein live # watch progress in the TUI dashboard
bernstein run plan.yaml # multi-stage plan: skip LLM planning, execute directly
bernstein stop # graceful shutdown with drain
The full operator surface (PR automation, schedules, chat bridges, the autofix daemon) is in operator commands.
bernstein workflow runs declarative YAML DAGs of agent, command, and loop nodes - with resume support for interrupted runs:
bernstein workflow run idea-to-pr -g "Add JWT auth" # prints run_id
bernstein workflow resume # picks up at the first non-completed node
Run state checkpoints to .sdd/runs// on every node. Resume validates the manifest digest at run start, so a spec change is refused rather than silently executing a different manifest. See workflow manifests.
Repository hygiene gates: bernstein readme-l10n verify fails a PR whose translated READMEs drifted from the English source (naming the stale section), bernstein readme-l10n sync rebinds them after an English edit. See readme-l10n.
Claude Code, Codex CLI, Gemini CLI, GitHub Copilot CLI, Cursor, Aider, Goose, Muse Code, OpenAI Agents SDK, Amp, Cody, Continue, Devin Terminal, Junie, Kilo, Kiro, AWS Q Developer, Ollama, OpenCode, OpenHands, Open Interpreter, gptme, Plandex, AIChat, Letta Code, Qwen, and more. The adapter index carries install commands for 30 of them. bernstein integrations list enumerates all 54 wired-in integrations from src/bernstein/adapters/registry.py, the single source of truth for what resolves. 52 of them are selectable agent adapters; the other two rows are the mock test stub and the self-hosted-endpoints endpoint profile. Anything else with a --prompt flag works through the generic wrapper.
Mix agents in the same run: cheap local models for boilerplate, heavier cloud models for architecture. bernstein integrations list --installed shows what is available on your machine.
A project can mark issues as open to volunteers, and anyone can run one on their own machine without an account or a coordinator. The project declares what a task is allowed to do in a volunteer.json manifest - sandbox backend, network allowlist, wall-clock and memory ceilings - and a donor's own limits can only narrow that, never widen it. The receipt a finished task produces binds the result to the containment decision it ran under, so a main
No open issues yet, or sync has not completed.