Metadata-Version: 2.4
Name: opensaddle
Version: 1.0.0
Summary: Local-first control plane for routing work across coding agents.
Project-URL: Homepage, https://github.com/AkeBoss-tech/opensaddle
Project-URL: Repository, https://github.com/AkeBoss-tech/opensaddle
Project-URL: Issues, https://github.com/AkeBoss-tech/opensaddle/issues
Project-URL: Changelog, https://github.com/AkeBoss-tech/opensaddle/blob/main/CHANGELOG.md
Author: OpenSaddle contributors
License-Expression: MIT
License-File: LICENSE
Keywords: agents,automation,cli,llm,routing
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: Software Development :: Testing
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: fastapi<1.0,>=0.110
Requires-Dist: pydantic<3.0,>=2.8
Requires-Dist: pyyaml<7.0,>=6.0
Requires-Dist: typer<1.0,>=0.16
Requires-Dist: uvicorn<1.0,>=0.27
Provides-Extra: dev
Requires-Dist: pytest<10.0,>=8.3; extra == 'dev'
Description-Content-Type: text/markdown

# 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.

V1.1 supports optional declarative governance profiles for route planning and decision receipts. They do not provide a tool broker, network controls, credential isolation, IAM, OAuth, or provider enforcement.

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.

## Install

### User install

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

```bash
uv tool install opensaddle
# or
pip install opensaddle
```

### Local development install

For local development in this repository:

```bash
uv sync --dev
```

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

```bash
uv pip install -e ".[dev]"
```

## Dev And Test

```bash
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.

```bash
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/`](examples/):

- [`examples/opensaddle.yaml`](examples/opensaddle.yaml)
- [`examples/benchmark-suite.yaml`](examples/benchmark-suite.yaml)

1. Initialize a new project layout:

```bash
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.

2. Estimate a task against the sample config:

```bash
uv run opensaddle estimate \
  --repo . \
  --config examples/opensaddle.yaml \
  --task "Improve the README command examples"
```

3. Run an agent command inside a worktree:

```bash
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:

```bash
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:

```bash
uv run opensaddle config schema > opensaddle.schema.json
```

4. Run a benchmark suite:

```bash
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.

5. Inspect local route outcome stats:

```bash
uv run opensaddle routes stats --db .opensaddle/opensaddle.db
```

6. Sync and inspect model metadata if you have an OpenRouter API key:

```bash
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.

7. Inspect prior agent history as dry-run proposals:

```bash
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.

8. Launch the dashboard:

```bash
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:

```text
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`](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

- [`specs/00-product-brief.md`](specs/00-product-brief.md)
- [`specs/01-architecture.md`](specs/01-architecture.md)
- [`specs/02-configuration.md`](specs/02-configuration.md)
- [`specs/03-execution-and-audit.md`](specs/03-execution-and-audit.md)
- [`specs/04-benchmarking.md`](specs/04-benchmarking.md)
- [`specs/05-dashboard.md`](specs/05-dashboard.md)
- [`specs/06-implementation-slices.md`](specs/06-implementation-slices.md)
- [`specs/08-v1-public-contract.md`](specs/08-v1-public-contract.md)
- [`specs/10-v2-enterprise-pilot-adr.md`](specs/10-v2-enterprise-pilot-adr.md)
- [`specs/99-open-questions.md`](specs/99-open-questions.md)
