Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,23 @@ modes, `FAKE_COUNT_ALL` to count invocations).

## Changing a schema

Edit `schemas/*.v1.json`, then add/adjust a **positive** example in `schemas/examples/` **and** a
**negative** fixture in `schemas/examples/invalid/` that proves the new invariant fails.
`validate_schemas.py` discovers both by filename suffix.
Edit `schemas/*.v1.json` in place for an additive or invariant-preserving change, then add/adjust
a **positive** example in `schemas/examples/` **and** a **negative** fixture in
`schemas/examples/invalid/` that proves the new invariant fails. `validate_schemas.py` discovers
both by filename suffix. Bump to a new version file (`*.v2.json`) only for a **breaking** change —
one that would invalidate existing stored records, or make new output that current validators reject
— never for an in-place additive edit.

## Layout

- `scripts/` — stdlib helpers: `impasse_lib` (config, backends, run records, environment policy),
`impasse_consent` (consent store), `impasse_run` (process supervisor + `review()`), `impasse_report`.
- `schemas/` — `reviewer-response` + `reconciliation-result` + `examples/` (+ `invalid/`).
- `docs/` — `protocol`, `security-model`, `environments`, `backends/{codex,claude}`, proposals.
- Independence ladder: cross-provider (Codex) > same-provider (`claude -p`) > self-review
(sandbox/Cowork only, refused for code). Model choice: `--model` / `IMPASSE_{CODEX,CLAUDE}_MODEL`.
Project vocabulary: `docs/glossary.md`.
- Independence backends, strongest first: cross-provider (Codex) > same-provider (`claude -p`) >
self-review (sandbox/Cowork only, refused for code). (The full tier enum also has `undetermined`
for an unattributable host — see `docs/glossary.md`.) Model choice: `--model` / `IMPASSE_{CODEX,CLAUDE}_MODEL`.

## Never commit

Expand Down
25 changes: 16 additions & 9 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,31 @@ an issue to discuss before a large change.

- **Keep the shipped helpers stdlib-only.** No runtime pip dependencies in `scripts/`.
`jsonschema` is a dev/CI dependency (used by `tests/validate_schemas.py`) — that's the line.
- **Schemas are a contract.** Changes go through a new version file (`*.v2.json`), not an
edit that breaks existing validators. Add example fixtures under `schemas/examples/` and
keep them valid.
- **Schemas are a contract.** Edit `schemas/*.v1.json` in place for an additive or
invariant-preserving change, pairing it with a new **positive** example under
`schemas/examples/` and a **negative** fixture under `schemas/examples/invalid/` that proves
the new invariant fails. Bump to a new version file (`*.v2.json`) only for a **breaking**
change — one that would invalidate existing stored records, or make new output that current
validators reject. See CLAUDE.md's "Changing a schema".
- **The review path stays read-only.** Anything that edits an artifact belongs in delegate
mode ([`docs/delegate-mode.md`](docs/delegate-mode.md)), which is experimental and isolated.
- **Honesty over polish.** Don't claim platform support, provider neutrality, or safety the
code doesn't actually provide — document limitations instead.

## Before a PR
## Before a PR — the three gates

```bash
pip install jsonschema # dev only
python tests/validate_schemas.py
python tests/test_helpers.py
python3 tests/test_helpers.py # stdlib, no pytest: supervisor, consent, backends, env policy, records
.venv/bin/python3 tests/validate_schemas.py # jsonschema lives in the repo-root .venv, not on PATH
.venv/bin/ruff check scripts/ tests/ # ruff too
```

Both must pass (CI runs them). Please describe what you changed and why, and note any schema
or security-model implications.
All three must pass (CI runs them). `jsonschema` and `ruff` live in the repo-root `.venv`, not
on your PATH — invoke them through `.venv/bin/…` as shown, and run the stdlib helper test with
`python3`. Please describe what you changed and why, and note any schema or security-model
implications.

Project vocabulary: [`docs/glossary.md`](docs/glossary.md).

## Contributor terms

Expand Down
38 changes: 22 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ to the work, so a sloppy assumption or a missed edge case can sail straight thro
from a different provider** is less likely to share your AI's blind spots. Think of it as an
**independent advocate for one thing: the quality of the result you ship.** The second AI challenges
the work with evidence; then Impasse checks each objection against your actual work, fixes the
problems that hold up, and brings you the few calls that are genuinely yours to make.
problems that hold up, and brings you the few calls that are yours to make.

The reviewer **never edits your work** — the critic and the editor stay separate: fixes are applied by
the host you're already working in (Claude Code or Codex, which drives Impasse), or by you, never by
Expand Down Expand Up @@ -45,12 +45,15 @@ for one round, then to you if neither side can win.
**Status: pre-release.** The open implementation of the pattern — named in
[the CLAR essay](https://www.movingavg.com/essays/cross-lab-adversarial-review.html) and told as a
field story in [*AI's Second Opinion: When Rival Models Disagree*](https://www.movingavg.com/essays/ai-second-opinion-rival-model.html).
The Codex path, consent gate, and schemas are implemented and tested; verify → reconcile →
escalate is **directed by the host skill, not enforced in code** — a review is only as good as the
host's adherence to the protocol (see [How it works](#how-it-works)). Dogfooding it on its own source caught a real
shipping bug before release. It drives the Codex CLI it finds on your machine (see Install for how it's located). That CLI is a
fast-moving alpha, so behavior is best-effort and version-sensitive — the `docs/backends/codex.md`
observations may go stale. Expect rough edges.

**What's enforced in code:** the Codex review path, the consent gate, and the schemas (tested).
**What isn't:** verify → reconcile → escalate is directed by the host skill, so a review is only as
good as the host's adherence to the protocol (see [How it works](#how-it-works)).

Dogfooding it on its own source caught a real shipping bug before release. It drives the Codex CLI
it finds on your machine (see Install for how it's located). That CLI is a fast-moving alpha, so
behavior is best-effort and version-sensitive — the `docs/backends/codex.md` observations may go
stale. Expect rough edges.

## Example

Expand All @@ -62,18 +65,19 @@ It runs a cross-provider reviewer, verifies each finding against your artifact,
report — the problems worth acting on, the ones the host threw out, and the calls that are yours:

```text
📊 Findings: 4 raised → 🤝 2 resolved · ❌ 1 refuted · ⚖️ 1 escalated to you
📊 Decisions: 4 finding(s) raised → 2 resolved · 🤝 0 accepted · ❌ 1 rejected · ⚖️ 1 escalated to you
──────────────────────────────────────────────────────────────
F001, F003 🟢 🤝 resolved — host confirmed and fixed both (details elided)
F002 🟠 high ❌ refuted
F001, F003 🟢 resolved — host confirmed and fixed both (details elided)
F002 🟠 high ❌ rejected
🔎 Reviewer: the go-to-market is undifferentiated.
◀ Host: the memo already concedes the product itself is a commodity and stakes its case on
distribution — a rediscovered premise, not a gap. Refuted, with the quote.
F004 🟠 high ⚖️ ESCALATED — needs your decision
❓ Enter Europe to diversify beyond a single market, or protect the nine-month runway?
──────────────────────────────────────────────────────────────
⚖️ 1 decision(s) need you; the rest the models settled between themselves.
📈 Your Impasse record — 9 reviews reconciled
31 findings reviewed · 22 resolved · 4 accepted · 3 refuted with evidence · 2 escalated to you
31 findings reviewed · 4 accepted · 3 refuted with evidence · 22 resolved · 2 awaiting you
```

*Example output. The reviewer never edits your work; the host applies the fixes it verifies, and
Expand Down Expand Up @@ -118,8 +122,9 @@ submitted; Impasse's reviewer never holds the pen. The study's reviewer **cannot
produced the eighteen-point gain in the other direction, but it denies that reviewer any execution
feedback, where Impasse's host verifies each finding against the artifact with whatever evidence
and tools apply. And the study has **no evidence requirement and no verification step** — a claimed
defect goes directly into the submitted program, where Impasse requires anchored evidence, checks
each finding, and escalates deadlocks instead of applying them. The authors flag two of these
defect goes directly into the submitted program, where Impasse requires [anchored
evidence](docs/glossary.md), checks each finding, and escalates [deadlocks](docs/glossary.md)
instead of applying them. The authors flag two of these
themselves: static review understates what tool-using agents with sandboxes could achieve, and
reviewers always emit code with no separate non-intervention action, which may inflate harmful
rewrites.
Expand Down Expand Up @@ -183,7 +188,7 @@ It is **domain-general** — the same protocol reviews:
**See a second decision reviewed end to end** — a *different* memo (build-vs-buy on payments infrastructure), not code — from rival finding
to the call that needs a human: [`docs/walkthrough-decision.md`](docs/walkthrough-decision.md).

Full protocol: [`docs/protocol.md`](docs/protocol.md).
Full protocol: [`docs/protocol.md`](docs/protocol.md). Terms: see the [glossary](docs/glossary.md).

## What the reviewer checks for

Expand Down Expand Up @@ -242,7 +247,8 @@ of every event in every conversation:

**Escalation counts are deliberately not reported yet.** An important operational metric is how
often findings need a human ruling — no reliable historical rate exists. The counting rule only
recently became channel-independent (an operator ruling that decides a disposition now counts as
recently became channel-independent (a ruling by the operator — you, the human who owns the
decision — that decides a disposition now counts as
an escalation whether it arrived through a formal deadlock or through conversation), and the
operator attests that more judgment calls reached him than the pre-rule records captured.
Historical events whose exact wording is no longer recoverable can't be amended in (the rule
Expand Down Expand Up @@ -495,7 +501,7 @@ runs with decisions you haven't answered yet; `prune --older-than N` cleans up o
they're kept `0600` and never committed.

Every `show` closes with a **running recap across your reconciled runs** — findings reviewed,
resolved, accepted, refuted with evidence, and escalated to you — a plain reminder of what independent
accepted, refuted with evidence, resolved, and awaiting you — a plain reminder of what independent
review has surfaced. Deeper longitudinal reporting (trends over time, per-artifact history) is
still roadmap; each run is fully inspectable on its own.

Expand Down
2 changes: 1 addition & 1 deletion SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ backend is the cross-provider reviewer). The host is auto-detected (`IMPASSE_HOS
**Speed (Fast mode).** A separate **codex-only** service-tier knob, **independent of effort**.
Precedence: `--speed <standard|fast>` (this run) > `IMPASSE_CODEX_SPEED` env > persisted default
(`impasse_run.py set-speed <standard|fast>`, clear with `--clear`) > **`standard`** (Fast mode
**off**, the default). `fast` turns Codex **Fast mode** on — roughly ~1.5× faster serving at a
**off**, the default). `fast` turns Codex **Fast mode** on — faster serving at a
**higher credit cost** — via `-c service_tier="fast" -c features.fast_mode=true`; `standard`/unset
add nothing. Values are allowlisted at every entry; a bad `IMPASSE_CODEX_SPEED` is a structured
`backend_error` naming the var, not a traceback. The claude backend has no speed knob — nothing
Expand Down
13 changes: 8 additions & 5 deletions docs/backends/claude.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,14 @@ observations, not a durable API):
(`stdout_truncated`) rather than trying to parse a cut-off object.
- **Read-only is fail-closed, and it is NOT a process sandbox.** Unlike codex's
`--sandbox read-only` (a real OS-level sandbox), the Claude reviewer runs in your normal Claude
Code process. Its read-only posture is instead: an **empty allowlist** (`--allowed-tools ""`) so
*no* tool is permitted (the artifact is on stdin — the reviewer needs none); `--strict-mcp-config`
so no MCP servers load; and a pinned `--permission-mode default` so it can't inherit a permissive
ambient mode (`acceptEdits`/`bypassPermissions`) or a `settings.json` that pre-allows network
tools. An allowlist fails *closed* as Claude Code adds tools; the `--disallowed-tools` list is
Code process. Its read-only posture is three independent controls instead:
- an **empty allowlist** (`--allowed-tools ""`) so *no* tool is permitted (the artifact is on
stdin — the reviewer needs none);
- `--strict-mcp-config` so no MCP servers load;
- a pinned `--permission-mode default` so it can't inherit a permissive ambient mode
(`acceptEdits`/`bypassPermissions`) or a `settings.json` that pre-allows network tools.

An allowlist fails *closed* as Claude Code adds tools; the `--disallowed-tools` list is
defense-in-depth (it also names the exfiltration vectors `WebFetch`/`WebSearch` and the spawn
tool `Task`). Verified on 2.1.197: under this config the reviewer's attempts to `Read` a local
file and to `WebFetch` are both blocked, yet it still answers from stdin. **Caveat:** do not run
Expand Down
34 changes: 18 additions & 16 deletions docs/backends/codex.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ The reference reviewer backend. Impasse's protocol is backend-neutral; Codex is
implementation, alongside the `claude` fallback backend (`docs/backends/claude.md`).
`scripts/impasse_lib.py` resolves it and `scripts/impasse_run.py` supervises it.

Its independence tier is **host-relative**: to a Claude host (the usual case) it's the
Its independence tier is **host-relative**: to a Claude [host](../glossary.md) (the agent driving
Impasse — usually Claude Code) it's the
`cross_provider` reviewer; to a Codex host (`IMPASSE_HOST=codex`) it's `same_provider` and the
runner says so — use `--backend claude` there instead. See `docs/environments.md`.

Expand Down Expand Up @@ -34,7 +35,8 @@ codex exec --json --output-last-message <file> \
"<reviewer instruction + the reviewer-response schema>" # artifact piped on stdin, then EOF
```

Verified behaviors (on `codex-cli 0.144.0-alpha.4` — re-check with `codex exec --help`, these
Verified behaviors (on `codex-cli` 0.144.0-alpha.4; the ChatGPT.app rebrand noted above was seen on
0.145.0-alpha.18 — re-check with `codex exec --help`, these
are version observations, not a durable API):

- **stdin must reach EOF.** `codex exec` blocks indefinitely if stdin is an open, unwritten
Expand Down Expand Up @@ -106,20 +108,20 @@ The argv builder re-checks the allowlist before interpolating into `-c` (defense

## Fast mode / service tier

A separate codex-only knob, **independent of reasoning effort**: `--speed fast` selects Codex's
**Fast mode** service tier — roughly ~1.5× faster serving at a **higher credit cost** — by adding
`-c service_tier="fast" -c features.fast_mode=true`. `standard` (or unset) is the default and adds
no flags (Fast mode off). Precedence mirrors effort: `--speed` (per run) > `IMPASSE_CODEX_SPEED`
env > a persisted default (`impasse_run.py set-speed <standard|fast>`, stored in `settings.json`
beside the model/effort defaults) > `standard`. The two-value allowlist (`standard|fast`) is
enforced at every entry point — the CLI flag and `set-speed` by argparse choices, `set_default_speed`
on write, `get_default_speed` again on read (a hand-edited `settings.json` can't smuggle a bad
value), and the env var at resolution: an invalid `IMPASSE_CODEX_SPEED` fails as a structured
`backend_error` naming the variable, never a traceback. The argv builder re-checks the allowlist
before interpolating into `-c` (defense in depth, so a future caller can't inject config syntax).
The review result reports the resolved value in `speed` (`standard` or `fast`); the claude backend
has no speed knob — an `IMPASSE_CLAUDE_SPEED` is ignored, never an error, and it reports `speed:
null`. Speed and effort compose freely (e.g. high effort with fast mode).
`--speed fast` turns on Codex's **Fast mode** — a higher service tier: meaningfully faster serving
at a **higher credit cost** — by adding `-c service_tier="fast" -c features.fast_mode=true`.
`standard` (or unset) adds no flags (Fast mode off). It is **independent of reasoning effort** —
the two compose freely (e.g. high effort with fast serving). Precedence mirrors effort: `--speed`
(per run) > `IMPASSE_CODEX_SPEED` env > a persisted default (`impasse_run.py set-speed
<standard|fast>`, stored in `settings.json` beside the model/effort defaults) > `standard`. The
review result reports the resolved value in `speed` (`standard` or `fast`); the claude backend has
no speed knob — an `IMPASSE_CLAUDE_SPEED` is ignored, never an error, and it reports `speed: null`.

Validation: the two-value allowlist (`standard|fast`) is enforced at every entry point (CLI flag,
`set-speed`, the persisted-value read, and the env var at resolution) and re-checked by the argv
builder before interpolation into `-c` — exactly the defense-in-depth the effort section describes.
An invalid `IMPASSE_CODEX_SPEED` fails as a structured `backend_error` naming the variable, never a
traceback.

## Failure handling (limits & outages)

Expand Down
Loading
Loading