Skip to main content

opensaddle

opensaddle is a local-first control plane for routing work across coding agents, models, councils, auditors, and verification loops.

It ships with a Typer CLI, a SQLite episode store, benchmark tooling, history import helpers, and a local dashboard.

The desktop service now exposes granular authoritative domains for threads and messages, durable run lifecycle/output, discovered harness capabilities, project files and artifacts, wiki jobs, workflows, Git collaboration, and permission-aware search. New UI work should use these APIs instead of adding state to a broad GET/PUT /api/workspace document. See specs/12-authoritative-desktop-domain-api.md.

V1.1 supports optional declarative governance profiles for route planning and decision receipts. The experimental local daemon additionally provides an opt-in, OpenAI Responses-compatible gateway that can enforce model-call policy before forwarding to one configured upstream. It does not provide a general network proxy, sandboxing, IAM, OAuth, or broad credential isolation.

Permission policies default to mode: observe, which logs violations without stopping the run. You can also opt into mode: enforce for local policy enforcement that fails a run after diff capture when observed command or path rules are violated. This is not OS sandboxing or container isolation: the agent subprocess still runs locally, and enforcement happens from captured local evidence after execution.

V1 Boundary And Safety Posture

V1 is a single-user, local developer tool. It runs user-configured subprocesses against local repositories and stores run data in local SQLite/JSON files. Only run commands and benchmark suites you trust.

It does not provide sandboxing, credential or secret isolation, network isolation, enterprise IAM, a hosted or multi-user service, or a corporate tool broker. Local mode: enforce is post-run evidence-based policy handling, not a preventative security control.

Experimental OpenAI Responses Gateway

The local daemon can serve POST /openai/v1/responses for Codex-compatible model traffic and POST /anthropic/v1/messages for Claude Code-compatible traffic. It authenticates callers with the daemon token, evaluates model.infer against the explicit runtime policy, then streams the request to configured protocol-compatible upstreams. The upstream keys remain in the daemon process; only request/response digests and model-call metadata are recorded locally.

Gateway callers must send X-OpenSaddle-Project. When a policy returns approval_required, the response includes an opaque run ID and action digest. A local daemon administrator can create a short-lived, single-use approval at POST /api/v1/runs/{run_id}/approvals, and the client retries the byte-identical request with X-OpenSaddle-Run-ID and X-OpenSaddle-Approval-ID. Production approval identity and leases belong in the v2 control plane; the local endpoint is an interim single-user capability.

For a production gateway, the v2 control plane issues an opaque approval lease after authenticated human approval. The gateway submits the lease to POST /api/v2/approvals/{approval_id}/consume with the run ID, canonical request digest, policy hash, and authenticated principal. Consumption is atomic and one-use; the gateway stores no lease state or prompt content. Configure ControlPlaneApprovalLeaseClient with a per-principal trusted-proxy/OIDC assertion source—never a shared static user header—and send the returned lease ID plus X-OpenSaddle-Policy-Hash on the replay.

Start it with an explicit policy that allows the selected provider/model resource:

export OPENAI_API_KEY='your-upstream-key'
uv run opensaddle serve-daemon \
  --runtime-policy .opensaddle/runtime-policy.json \
  --gateway-upstream https://api.openai.com/v1 \
  --gateway-provider openai \
  --anthropic-gateway-upstream https://api.anthropic.com/v1 \
  --anthropic-gateway-provider anthropic

This is not an egress firewall. For a coding harness to be unable to bypass OpenSaddle, launch it inside an execution environment that blocks direct provider access. Cross-protocol model translation is not part of this initial gateway slice.

Use the launcher to configure a local coding harness without copying the daemon token into its command line:

uv run opensaddle gateway-run --harness codex --project project-1 -- codex exec --model gpt-5 "Inspect this repository"
uv run opensaddle gateway-run --harness claude --project project-1 -- claude

Install

User install

When the package is published, install the CLI for normal use with one of:

uv tool install opensaddle
# or
pip install opensaddle

Local development install

For local development in this repository:

uv sync --dev

If you want an editable environment instead of a synced project environment:

uv pip install -e ".[dev]"

Dev And Test

uv run opensaddle --help
uv run pytest
uv build

Manual Smoke Checklist

These commands mirror the automated CLI smoke suite. The direct run uses inline Python, and the configured-route run uses a temporary local-only config so the checklist never depends on paid agent CLIs being installed.

uv run opensaddle init --root .
uv run opensaddle config validate --config ./opensaddle.yaml
uv run opensaddle estimate --repo . --config ./opensaddle.yaml --task "Fix a small UI bug in the settings panel"
cat > ./opensaddle.smoke.yaml <<'YAML'
project:
  name: smoke
  default_mode: balanced
  storage:
    backend: sqlite
    path: .opensaddle/smoke.db
providers:
  openrouter:
    enabled: false
agents:
  smoke_agent:
    adapter: cli
    command: python
    modes:
      exec:
        argv:
          - python
          - -c
          - "from pathlib import Path; Path('SMOKE.txt').write_text('configured\\n')"
    models:
      - id: smoke-model
        success_prior: strong
success_priors:
  strong:
    p_success: 0.95
    expected_minutes: 3
    uncertainty: 0.1
permissions:
  observe_all:
    mode: observe
    allowed_paths: ["**"]
verification:
  pass:
    checks:
      - name: smoke
        command: "python -c \"raise SystemExit(0)\""
routes:
  smoke_route:
    kind: single_agent
    agent: smoke_agent
    model: smoke-model
    permissions: observe_all
    verification: pass
    artifact_target: patch
YAML
uv run opensaddle run --repo . --instruction "Update README.md with a smoke note" --agent-arg python --agent-arg -c --agent-arg "from pathlib import Path; Path('SMOKE.txt').write_text('ok\\n')" --verify "smoke=python -c \"raise SystemExit(0)\""
uv run opensaddle config validate --config ./opensaddle.smoke.yaml
uv run opensaddle run --repo . --config ./opensaddle.smoke.yaml --instruction "Run the configured smoke route"
uv run opensaddle episodes list --db ./.opensaddle/smoke.db --json
uv run opensaddle routes stats --db ./.opensaddle/smoke.db --json
uv run opensaddle bench run ./examples/benchmark-suite.yaml --db ./.opensaddle/smoke.db --json
uv run opensaddle dashboard --root . --config ./opensaddle.smoke.yaml

Quickstart

The repository includes ready-to-use examples in examples/:

Discover existing Codex and Claude Code projects

OpenSaddle can build a project-picker candidate list from local coding-agent metadata without modifying Codex or Claude Code state:

uv run opensaddle discover-projects
uv run opensaddle discover-projects --source codex --json

Discovery reads only working-directory, session-count, and timestamp metadata. It does not return or store prompts, replies, tool output, or credentials. Missing directories and linked Git worktrees are hidden by default; use --include-missing or --include-worktrees to show them.

The desktop API exposes the same read-only scan at GET /api/project-candidates. Registering a selected root is a separate, explicit POST /api/projects; that operation writes only OpenSaddle state.

Continue project sessions and attach KRAIL knowledge

After a folder is registered, OpenSaddle Desktop exposes a Project sessions resource. Its backend contract is:

GET /api/projects/{project_id}/sessions
GET /api/projects/{project_id}/knowledge

The sessions route returns only native harness, session ID, working directory, branch, version, and modification-time metadata for Codex or Claude Code sessions inside that registered root. The user then explicitly chooses Resume or Fork, plus whether future work remains source-managed, OpenSaddle-managed, or hybrid. Transcript bodies are not copied into the project picker or OpenSaddle workspace.

The knowledge route follows the repo-backed model from AkeBoss-tech/knowledge, now named KRAIL. When rail.yaml or krail.yaml already exists, OpenSaddle safely projects its project identity, knowledge folders, stable capability families, and the rail-mcp --local --path … launch specification. Discovery does not import KRAIL, run doctor or integrity repair, initialize folders, or execute workflows. Effectful KRAIL tools and krail-flow/v1 workflows remain explicit future actions behind the selected project policy.

External provider interoperability is separately defined by the versioned, read-only krail.provider.v1 contract. It uses issuer-qualified ResourceRef values and source-version-bound EvidencePacket values; it does not turn desktop discovery into a KRAIL runtime or a second knowledge store.

  1. Initialize a new project layout:
uv run opensaddle init --root .

The generated opensaddle.yaml now includes built-in CLI presets for codex, claude_code, github_copilot, aider, cursor, amp, antigravity, claw, hermes, and pi, while still using the same generic adapter: cli execution path.

Config compatibility in v1 stays intentionally lightweight: additive fields may land without migrations, but breaking config-shape changes will require an explicit new config version plus documented migration notes. opensaddle does not silently rewrite user configs.

  1. Estimate a task against the sample config:
uv run opensaddle estimate \
  --repo . \
  --config examples/opensaddle.yaml \
  --task "Improve the README command examples"
  1. Run an agent command inside a worktree:
uv run opensaddle run \
  --repo . \
  --instruction "Add a short status note to README.md" \
  --agent-arg codex \
  --agent-arg exec \
  --agent-arg=--model \
  --agent-arg gpt-5 \
  --agent-arg "{instruction}" \
  --verify "test=pytest"

For interactive or TUI-first CLIs, you can switch the direct runner onto a pseudo-terminal:

uv run opensaddle run \
  --repo . \
  --instruction "Inspect the repo and propose a plan" \
  --execution-mode pty \
  --agent-arg codex \
  --agent-arg "{instruction}"

Configured agents can also opt into PTY mode with modes.pty.enabled: true, an optional modes.pty.argv, and terminal settings such as rows, columns, and term. When modes.pty.argv is omitted, PTY mode falls back to the configured modes.exec.argv.

If you want to copy a built-in preset into another config, the example file shows practical command templates for Codex CLI, Claude Code, GitHub Copilot CLI, Aider, Cursor, Amp, Antigravity, OpenClaw, Hermes, and Pi under agents:.

Configured routes may also define ordered fallbacks:. These are route-level retries, not provider-request retries: if a route times out, exits non-zero, fails verification, trips enforced policy, or returns a non-pass audit status, opensaddle run can retry the task on the next configured route in the chain.

You can also export the current config schema for documentation or editor integration:

uv run opensaddle config schema > opensaddle.schema.json
  1. Run a benchmark suite:
uv run opensaddle bench run examples/benchmark-suite.yaml --db .opensaddle/opensaddle.db

The checked-in suite exercises both deterministic mock output and a configured local route. Configured routes use the normal local worktree execution path and evaluate their declared success_checks. The legacy existing route form instead runs its explicit argv directly in its configured working directory and reports command exit status; keep those commands non-destructive or use a disposable repository.

  1. Inspect local route outcome stats:
uv run opensaddle routes stats --db .opensaddle/opensaddle.db
  1. Sync and inspect model metadata if you have an OpenRouter API key:
uv run opensaddle models sync openrouter --config examples/opensaddle.yaml
uv run opensaddle models list --config examples/opensaddle.yaml
uv run opensaddle models health set --config examples/opensaddle.yaml --model openai/gpt-4o --status degraded --reason "local retry spikes"
uv run opensaddle models health list --config examples/opensaddle.yaml

opensaddle estimate now carries those local provider/model health signals into route scoring as conservative penalties with explicit rationale lines, but it does not block runtime execution.

  1. Inspect prior agent history as dry-run proposals:
uv run opensaddle import-history codex --source ~/.codex
uv run opensaddle import-history claude --source ~/.claude
uv run opensaddle import-history copilot --source ~/.github/copilot

opensaddle import-history is proposal-only in v1. It scans local transcript files, redacts secret-like values before emitting summaries, and prints structured hints such as observed agents, task patterns, estimated usage, and candidate route suggestions. It does not rewrite opensaddle.yaml, and it does not persist raw transcript contents into SQLite by default.

  1. Launch the dashboard:
uv run opensaddle dashboard --root .

When .opensaddle/opensaddle.db exists, the dashboard reads episode data from the SQLite EpisodeStore. If the database is absent, it falls back to JSON snapshots under .opensaddle/episodes/. The dashboard also surfaces lightweight environment and audit review signals through the HTML view and /api/reviews. The V1 operational view answers five questions directly: what ran, what failed, which routes perform best, which failures are recurring, and what route to try next. Route ops visibility now includes per-route aggregates, task-family routing stats, route comparison, recurring failures, benchmark summaries, and rolled-up verification/audit pass rates. Health is derived from local outcomes, and fallback-specific fields stay n/a until those slices are recorded in stored episode schemas. The dashboard API is local-first and intentionally has no authentication when bound to localhost. Do not expose it on a hosted or multi-user interface in V1.

Auditor Visibility

Auditors only receive the packet inputs explicitly granted by can_see, minus anything removed by cannot_see.

  • trajectory includes only auditor_visible events.
  • internal_only events are never forwarded to auditors.
  • visible command output, diff patches, and trajectory text are redacted for secret-like values and bounded before they are persisted or sent to auditors.
  • auditors do not receive full repo contents or hidden execution state unless you explicitly model and expose those inputs in a future slice.

What It Does

opensaddle sits above agent runtimes and chooses, runs, observes, verifies, audits, and learns from execution strategies.

The main conceptual object is AgentRouter.

An AgentRouter chooses a route such as:

agent runtime + model + context strategy + permission policy + audit policy + verification plan + fallback policy

ModelRouter is a lower-level primitive used inside routes, councils, and adapters when a model choice is needed.

First Target

V1 is local-first:

  • Python package named opensaddle
  • CLI named opensaddle
  • SQLite episode store
  • YAML configuration
  • local git worktree execution
  • observe-only and local policy enforcement modes
  • configurable CLI adapters for Codex, Claude Code, GitHub Copilot, Aider, Cursor, Amp, Antigravity, OpenClaw, Hermes, Pi, and other agent CLIs
  • built-in YAML presets for those CLIs on top of the generic CLI adapter
  • optional OpenRouter model registry integration
  • lightweight local web dashboard

V1 Support Matrix

Surface V1 status Notes
opensaddle CLI supported Stable command groups: init, config validate, estimate, run, episodes, routes stats, bench run, models sync/list/health, dashboard, and import-history.
opensaddle.yaml supported Strict user-owned YAML schema with stable top-level sections and route forms documented in specs/08-v1-public-contract.md.
Route kinds for opensaddle run supported single_agent, agent_with_auditors, and review_council.
Additional route kinds planned/experimental diverse_generation, diverse_generation_council, leader_veto_council, loop_council, and planner_worker_verifier_loop are recognized for config/planning but are not executable in v1.
Episode persistence supported Raw run JSON artifacts live under .opensaddle/episodes/; summarized episodes live in SQLite at .opensaddle/opensaddle.db.
Python imports provisional No stable Python API is promised for v1 beyond opensaddle.__version__; prefer the CLI, YAML, and storage formats as the public contract.
Explicitly post-v1 not included yet Container sandboxing, network/secret isolation, learned routing, and full execution for non-review_council council families.

Specs

Metadata

Release files for opensaddle 1.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for opensaddle 1.1.0
File Size Uploaded
opensaddle-1.1.0.tar.gz 563.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for opensaddle 1.1.0
File Interpreter ABI Platform
opensaddle-1.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 996.6 kB

Release files / opensaddle-1.1.0.tar.gz

Download URL opensaddle-1.1.0.tar.gz
Size 563.8 kB
Tags Source
SHA-256 checksum
How to use checksums
b64dc0b5a0d370d54556e4388204eb7c0f79ca700dc1e8f3baede07e9ea1d3be
BLAKE2b-256 checksum
How to use checksums
f98c07ca63a48408ed27b3467d644ac7041d7681b7979bf50df7b997ae70e285
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 11, 2026.

Transparency log

Release files / opensaddle-1.1.0-py3-none-any.whl

Download URL opensaddle-1.1.0-py3-none-any.whl
Size 432.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
37d3a2e2bd4912d693d76bfc54a0d8d34e46174702a02cef67763cbd0cf459f3
BLAKE2b-256 checksum
How to use checksums
8917ecec0322d2ed36e673b6c6c432c4461058541d40cd460c01d2d46219d0cf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 11, 2026.

Transparency log
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page