paranoia-local
Allows Codex (GPT-5.6) to act as an adversarial reviewer, using its own subscription to analyze code changes, plans, and provide second opinions without API metering.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@paranoia-localCritique my working tree for security issues"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
paranoia-local
Get a cold, adversarial review of your code, your plans, and your decisions from the other frontier coding agent — running locally, on its own subscription, with full read access to your repository.
Install it into Claude Code and reviews are performed by Codex. Install it into Codex and reviews are performed by Claude Code. paranoia-local is the MCP server between them: it builds the prompt, runs the other agent read-only, and returns a structured critique.
┌──────────────┐ "paranoia: critique this branch" ┌───────────────┐
│ Claude Code │ ───────────────────────────────────► │ paranoia-local│
│ (your work) │ │ (MCP, local) │
└──────────────┘ └───────┬───────┘
│ codex exec (read-only)
┌───────▼────────┐
│ Codex / GPT-5 │ ← reads the repo,
│ cold reviewer │ decides what to open
└────────────────┘Contents · Quickstart · The five tools · How reviews work: the convergence loop · Tool reference · Output reference · Configuration · Safety model · Development
Quickstart
1. Prerequisites
Python 3.11+ and
gitonPATHThe reviewing agent's CLI, installed and signed in on a subscription: Codex CLI (
codex, ≥ 0.144) or Claude Code (claude)arbitrateneeds both CLIs; the four review tools need only the other one
2. Install
git clone https://github.com/subvertnormality/paranoia-local
cd paranoia-local
pip install -e .3. Wire it into your agent. --engine names the agent that performs reviews,
which is the opposite one from the caller.
claude mcp add paranoia -- paranoia-local --engine codexcodex mcp add paranoia -- paranoia-local --engine claudeThen edit ~/.codex/config.toml. Codex defaults to a 60-second tool timeout and a
10-second startup timeout; a review runs for minutes, so both must be raised or
every call fails:
[mcp_servers.paranoia]
command = "paranoia-local"
args = ["--engine", "claude"]
tool_timeout_sec = 3600
startup_timeout_sec = 30Verify with codex mcp get paranoia.
4. Ask for a review.
"Use paranoia to critique this branch against main. Intent: add overdraft protection to
withdraw()."
Your agent calls:
{
"name": "critique_branch",
"arguments": {
"repo_path": "/Users/you/Work/my-project",
"base_ref": "main",
"round": 1,
"diff_intent": "Add overdraft protection to withdraw()."
}
}You get back a five-section critique with severity-tagged findings, and a computed
CONVERGENCE: trailer telling you whether the loop may stop.
Related MCP server: mcp-agent-review
The five tools
Tool | Use it to | Needs |
Review a git branch, a diff, or the dirty working tree |
| |
Review a plan or design doc against the code it claims things about |
| |
Ask one question and get a cited answer — not a full review |
| |
Dispute a finding from a review you just got |
| |
Decide between 2–4 options using both vendors independently |
|
Every review returns a session_ref in its footer. Pass it to rebut to reopen
that exact reviewer session.
How reviews work: the convergence loop
A single review is rarely the end of it. You review, you fix, you review again. paranoia-local models that as a convergence loop, and gives you four controls over it plus one computed signal that tells you when to stop.
The loop
round 1 ──► review ──► fix ──► round 2 ──► review ──► fix ──► round 3 ──► CONVERGED
│ │ +
└── already_raised ────────────┴── already_raised ────► CONVERGENCE: NOT-BLOCKEDEach round is a fresh, cold reviewer — it has no memory of the last one. You carry state forward with the arguments below.
round — the severity floor
The 1-based round number. Increment it every round. At round >= 3 the
reviewer reports only merge-blocking findings and withholds [MINOR] and
[OUT-OF-SCOPE], writing CONVERGED when none remain. This is the lever that
makes a loop stop instead of grinding through diminishing findings.
round is required on critique_branch and critique_plan unless you pass
class_closure: false.
stakes — the scope boundary
The real deployment context, threat model, and scale the work operates in:
"stakes": "Internal booking API, single team, authenticated first-party callers, ~1k req/min."The reviewer treats it as the boundary of legitimate concern. Findings that
assume adversaries, scale, or failure modes beyond it are dropped or tagged
[OUT-OF-SCOPE], never must-fix. Omit it and the reviewer assumes a modest
internal tool; a review with no stakes ends with a STAKES: unstated line. Pass
stakes: "unstated" to accept that reading deliberately and silence the line.
Set it once per project in .paranoia.toml; override per call to
tighten it for a specific surface.
already_raised — what not to repeat
One-line, file:line-cited claims you have already accepted from earlier rounds.
The reviewer is told not to restate them and to hunt for what they missed. Pass
the claim and its citation, never the previous reviewer's prose.
"already_raised": [
"withdraw() ignores pending holds — accounts.py:88",
"the overdraft test asserts the fee, not the balance — test_accounts.py:210"
]class_closure — tracking defect classes across rounds
On by default. A finding is usually an instance of a class: one violated invariant, several sites. Class closure makes the class itself a tracked object that survives the round.
The reviewer ends its review with a register block:
=== CLASS REGISTER ===
CLASS: every public writer must validate its input before the first mutation
SEVERITY: MAJOR
PATTERN: def (create|update)_[a-z_]+\(.*\):\n(?!.*validate)
PATHSPEC: src/The server then, every round:
re-runs each registered regex itself against the reviewed snapshot (
git grep), and lists every surviving match to the next reviewer;refuses to report the loop unblocked while any
BLOCKER/MAJOR/FATALclass still matches;computes the verdict in Python and appends it as the
CONVERGENCE:trailer.
A class closes when its predicate returns zero matches and reopens the moment it
matches again. MINOR and OUT-OF-SCOPE classes are tracked but advisory — they
never block.
Where no regex can express the invariant, the reviewer registers a PROCEDURE:
instead. Those are unmechanized: nothing re-runs them, they are shown to every
later reviewer, and they close only when a reviewer explicitly writes
CLOSED: <class-id>.
On critique_plan, every class is unmechanized — a regex over prose closes as
soon as the wording changes, so predicates are not accepted there at all. Plan
closure gives you non-forgetting plus explicit closure, not automatic recurrence
detection.
Register transitions a reviewer can emit, besides a new class:
Record | Effect |
| An unmechanized class is judged closed |
| A closed unmechanized class is violated again |
| Correct a severity |
| Replace a class |
You cannot emit these yourself — ask for them in focus, e.g. "class 3f2a91c4 is
registered MAJOR but its effect is cosmetic; reclassify it if you agree."
lineage — which loop this round belongs to
Class state lives in ~/.paranoia/lineages/<lineage>.json.
critique_branchderives the key from repo +base_ref+ reviewed branch. Passlineageexplicitly when the reviewed ref is not a branch (a detached HEAD or a raw commit), where there is no stable key to derive.critique_planalways requires an explicitlineage— a plan has no branch, and nothing is derived from its text or path.
The key is used verbatim as the state filename with no namespacing, so make it
globally unique and mode-qualified: myproject-42-plan for a plan seam,
myproject-42-branch for the branch seam of the same work. A key already used by
the other tool is refused rather than merged.
When to stop
The stop condition is two-part:
the computed trailer reads
CONVERGENCE: NOT-BLOCKED, andthe round returns
CONVERGED, or only[MINOR]/[OUT-OF-SCOPE]items.
When the two disagree, the trailer governs — and says so in its own output.
One-shot reviews
For a review with no loop behind it — a design sketch, a quick second opinion —
pass class_closure: false. That is the single escape, and it also drops the
round and lineage requirements.
{ "repo_path": "/path/to/repo", "plan_text": "…", "class_closure": false }Handling a false positive
When a registered regex matches a line that does not actually violate the invariant, exempt that exact line:
"exempt": [{
"class_id": "3f2a91c4",
"path": "src/app.py",
"line": 17,
"line_text": " legacy_open(state)"
}]line_text must be byte-exact including indentation. The exemption is keyed on it
and goes void the moment that line changes, so the match resurfaces. Every
exemption is shown to every later reviewer, with the invariant attached, so it can
be challenged; unexempt takes the same class_id/path/line and revokes one.
A match inside a binary blob cannot be exempted — narrow the class's PATHSPEC
instead.
Tool reference
Arguments marked required are enforced; everything else has the default shown.
critique_branch
Adversarial review of a git branch, a committed range, or the dirty working tree.
Returns a five-section critique plus a
CONVERGENCE: trailer.
Argument | Type | Default | Description |
| string | required | Absolute path to the git repo |
| string |
| Base ref for the diff |
| string |
| Head ref to review |
| integer | required unless | 1-based round number; must be an integer ≥ 1 |
| boolean |
| Review the dirty working tree vs HEAD instead of a committed range. Runs in the live repo, not a worktree |
| boolean |
| Review inside a throwaway worktree of |
| boolean |
| Pre-gather a deterministic evidence packet (every touched file in full, plus the diff) and review it against an immutable materialized snapshot. Always materializes, overriding |
| integer |
| Character budget for that packet. |
| boolean |
| Track defect classes across rounds. |
| string | derived | Explicit class-closure key. Required when the reviewed ref is not a branch |
| array | — | Mark or revoke false positives of a class's regex — see above |
| string | — | The scope boundary |
| array |
| Claims already accepted from prior rounds |
| string | — | Neutral factual description of the project. The reviewer tests the diff against it |
| string | — | What the diff is supposed to achieve. Treated as a claim to verify, never a fact to accept |
| string | — | Narrow the review to a specific concern |
| — | see Common arguments |
converge: false falls back to a legacy in-place review that has no class closure,
so it must be paired with class_closure: false.
critique_plan
Adversarial review of a plan or design document. The reviewer reads the real code
to test every premise the plan makes about current behaviour. Returns the same
five sections, tagged [FATAL]/[MAJOR]/[MINOR]/[OUT-OF-SCOPE].
Argument | Type | Default | Description |
| string | required | The repo the plan concerns |
| string | one of these two | The plan as markdown |
| string | one of these two | Absolute path to a markdown plan file |
| integer | required unless | 1-based round number |
| string | required unless | Globally unique, mode-qualified key. Nothing is derived |
| boolean |
| Unmechanized classes only. |
| string | — | Background the reviewer needs to judge the plan fairly |
| string | — | Narrow the review to a specific concern |
| string | — | The scope boundary |
| array |
| Claims already accepted from prior rounds |
| — | see Common arguments |
class_closure and lineage are call arguments only here — .paranoia.toml
is not consulted for either.
query
One question, one answer. Not a full review: no five-section scaffold, lower reasoning effort by default. The reviewer reads the repo (when given one) and returns a direct answer, citations, and a stated confidence level.
Argument | Type | Default | Description |
| string | required | The specific question to double-check |
| string | — | Repo to ground the answer in |
| array |
|
|
| string | — | Extra framing for the question |
| — |
|
rebut
Dispute one finding from a review. Resumes that same reviewer session with your
counter-evidence, so it is cheaper and higher-resolution than a fresh round. The
reviewer replies CONCEDE or HOLD with fresh citations.
Argument | Type | Default | Description |
| string | required | Same repo the review ran against |
| string | required | From the prior review's footer |
| string | required | Your counter-evidence |
| — | see Common arguments |
arbitrate
Decides between 2–4 options. Both frontier vendors judge independently and cold over one pinned snapshot, and Python computes the verdict.
{
"repo_path": "/Users/you/Work/my-project",
"decision": "Choose the numeric type for the position-size threshold.",
"options": [
{"id": "opt-float", "statement": "Store it as a float."},
{"id": "opt-decimal", "statement": "Store it as a Decimal."}
],
"stakes": "Internal CLI, single team, threshold used only in a log line.",
"files": [{"path": "scripts/lib/registry.py", "reason": "the writer"}]
}What it does, in order:
Pins one snapshot. Each decider gets its own worktree of the same commit. Git refs and the reflog are digested before and after; if anything moved, the run returns
FAILEDrather than reporting agreement it cannot describe.Neutralizes the framing with an Opus agent — advocacy stripped, options equalized in detail — then has the other vendor attest that field by field.
stakesis passed through verbatim, never rewritten.Counterbalances presentation. One decider sees canonical order, the other reversed, under opaque per-decider labels. Neither is told the other exists.
Computes the verdict. No model adjudicates the adjudication.
On divergence, runs one reconciliation round carrying only
path:linecitations and bytes the server itself read — never the other model's prose — and only when there is genuinely novel evidence.
Argument | Type | Default | Description |
| string | required | Every decisive citation must be repo-verifiable |
| string | required | What is being decided (max 2500 chars) — not the evidence for it |
| array | required | 2–4 mutually exclusive |
| string | required | Pass |
| string | — | Shared facts and the full specification of whatever only one option adopts (max 20000 chars) |
| array |
|
|
| string | — | Short label for the paste-ready record block |
| boolean |
| Run the cleaner and its cross-vendor attestation |
| object | — |
|
| string |
| Override the cleaner model |
| string | — | Replay a previous run's |
| boolean |
| Create |
| — | see Common arguments |
arbitrate has no engine or model — it drives both vendors, so a single
override could only degrade it to one of them or send one vendor's model name to
the other CLI.
Input bounds, checked before anything is spent:
Bound | Limit |
option statement | 1200 chars |
longest ÷ shortest option | 2.0 |
| 2500 chars |
| 20000 chars |
The shape that passes these naturally: put every shared fact, and the full
specification of whatever only one option adopts, into context — prefaced as
"the rules under consideration, if adopted". Leave each option statement to say
only how much of it is adopted and what follows. ~800 chars each is typical.
Behaviour worth knowing before you rely on it:
It only decides what the repository can settle. A converging vote must cite a line that resolves. A decision that does not turn on repo-verifiable grounds will never return
CONVERGED.ADVISORYdoes not block. Each decider reports whether it judges that a named human owner should be authorizing the decision. That is reported, never gated:CONVERGEDwithADVISORY: human-owneris stillCONVERGED. Enforcing it is your policy.SNAPSHOTis provenance, not a replay handle. The snapshot commit is unreferenced andgit gcreclaims it. The audit log holds both prompts, both replies, and the carried evidence.retain_snapshot: truepins it behind a ref.On divergence, only a decider that moved must ground in the carried evidence. One that held its round-1 position needs only a citation that resolves — provided its round-1 decisive citation resolved too. A holder that was never substantiated must ground in gained evidence like a mover.
Bias is reduced, not eliminated. Order counterbalancing equalizes mean rank but not higher moments for 3–4 options; attestation is a model's judgement, not a proof; and a
fileslist pointing only at evidence favouring one option biases both deciders identically.docs/arbitration_plan.md§2 enumerates the residuals.
Common arguments
Accepted by the four review tools:
Argument | Values | Default |
|
| the server's configured engine |
| any model name | the engine's strongest: |
|
|
|
| boolean |
|
Output reference
Review output
Every review returns exactly five sections, in this order:
Section | Contains |
| Specific correct decisions, cited. "Nothing notable." when there are none |
| Actual defects: quoted lines, failure mechanism, observable symptom. Worst first |
| Failure modes the author didn't consider that the code is exposed to |
| What the change should do to reach its stated intent but doesn't |
| Concrete changes that alter the outcome under the stated stakes |
Every item in the last four sections carries exactly one severity tag:
Code review | Plan review | Meaning |
|
| Ships a bug / kills the plan as written |
|
| Fix before merge / before execution |
|
| Fix opportunistically |
|
| Real, but beyond the stated stakes — file separately |
A finding that recurs from a tracked class is marked [RECURRENCE <class-id>]
next to its severity tag.
The footer carries the session_ref for rebut.
Class-closure trailer
Appended below the review whenever class closure ran:
LINEAGE: 9f2c1a4b0e77 (rounds recorded: 8)
CLASS-REGISTER: parsed 1
CLASS-CLOSURE: 1 open, 2 closed, 3 surviving matches, 0 exempt, 1 unmechanized
CONVERGENCE: BLOCKED — 1 class(es) unclosed:
3f2a91c4 every public writer must validate before the first mutation (mechanized: 3 match(es))Line | Meaning |
| No blocking class is unclosed. Advisory classes may remain open |
| Named classes are still open; any |
| What the reviewer's register block contained |
| The predicate matched nothing at birth — usually too narrow. Ask the next reviewer to |
| Two attempts at a parseable register failed. The next round with a good register clears it |
| A semantic class no regex can check |
| Lineage state is unreadable, unwritable, or a previous write may not have completed. The message names the absolute path; repair or delete it, then re-run |
NOT-BLOCKED asserts only that no blocking class is unclosed. It never asserts the
change is correct — the reviewer's findings still govern that.
arbitrate outcomes
Outcome | Meaning |
| Unanimous, unblocked, and each vote substantiated by a resolved citation |
| They agree on an option and one of them tags it |
| A decider surfaced a better unlisted option. Give it an id and re-run |
| Still split, or agreement nobody could substantiate |
| Preflight, cleaning, parsing, or the repo's refs moved mid-run |
The reply ends with a machine-readable trailer whose fields are always present:
ARBITRATION, SELECTED, ADVISORY, AUTHORITY-POLICY, CLEANING, SNAPSHOT,
ORDER-SEED, REFS-MOVED, AUDIT, ROUNDS.
Configuration
.paranoia.toml
Drop one at the repo root so callers stop retyping context. Keys go at the top
level or under [paranoia]. Precedence: call argument > .paranoia.toml >
built-in default.
project_summary = "A booking API. Python/FastAPI, Postgres. Auth via short-lived JWTs."
base_ref = "develop"
stakes = "Internal booking API, single team, authenticated first-party callers, ~1k req/min."
web_search = true
isolate = trueHonoured keys: base_ref, project_summary, stakes, isolate, converge,
class_closure, max_packet_chars, model, effort, web_search.
critique_plan's class_closure and lineage are not read from here.
Command line
paranoia-local --engine {codex|claude} [--log-dir DIR]Flag | Default | Description |
| required | Which local engine performs reviews — the other agent from the caller |
|
| Audit-log directory |
State on disk
Path | Contents |
| One JSON audit record per call: engine, model, round, |
| Class-closure state, one file per lineage |
Lineage state deliberately does not follow --log-dir, so moving your logs
cannot silently reset a tracked lineage. Set PARANOIA_STATE_ROOT to relocate it.
Safety model
Read-only. Codex runs under its OS sandbox (
--sandbox read-only); Claude runs with a read-only tool allowlist (Read,Grep,Glob, scopedgitreads, web search) and write tools explicitly denied. The reviewer cannot edit your code, run your test suite, or reach the network except for opt-in web search.The audited repo cannot widen the reviewer. The Claude engine is spawned with
--setting-sources "", so it loads no.claudesettings files — otherwise the reviewed repo's.claude/settings.local.jsonand your global settings would merge on top of the allowlist, and those routinely grantBash(python3:*)and friends. This applies to the spawned reviewer subprocess only; it does not read, write, or affect your interactiveclaudesessions. Codex is covered by its OS-level sandbox, which no repo setting can loosen.Isolated. Committed reviews run inside a throwaway
git worktreeof the target ref, so they never collide with your working tree and can review a branch that isn't checked out. Dirty-working-tree reviews necessarily run in the live repo, read-only.No API keys, no telemetry. The server shells out to a CLI you are already signed into.
Minimal footprint. In
convergemode the server creates a short-lived worktree and a few unreferenced git objects in the target repo. Both are cleaned up on exit and no ref is created. A hard crash can leave the worktree registration until the nextgit worktree prune/git gc. Your working tree and index are never touched.One opt-in exception:
arbitratewithretain_snapshot: truecreatesrefs/paranoia/arbitrate/<stamp>so its evidence survivesgit gc. It is the only mode in the server that writes a ref. Remove one withgit update-ref -d <ref>.
Rate limits
Reviews draw on your subscription's agentic-usage pool, and a convergence loop is
many agent turns. Use query for quick checks and reserve multi-round
critique_branch loops for changes that warrant them.
arbitrate is the expensive one and the only tool that spends from both
subscriptions in a single call: typically 4 agent turns, 8 at worst (a cleaning
retry plus a reconciliation round).
Development
pip install -e '.[dev]'
python -m pytest # unit + integration; integration uses fake CLIs, no quotaThe engine subprocess boundary is dependency-injected, so the whole stack is
unit-tested without spending subscription quota. A separate integration test drives
the real subprocess runner against fake codex/claude binaries on PATH.
Design documents for the two non-obvious subsystems live in
docs/: class_closure_plan.md,
plan_class_closure_proposal.md, and
arbitration_plan.md.
License
MIT © 2026 Andrew Hillel
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseAqualityDmaintenanceMCP server that lets Claude Code ask GPT Codex for adversarial planning, code review, debugging, research, and risk triage without leaving your project workflow.Last updated9261MIT
- Alicense-qualityCmaintenanceAn MCP server that provides agentic code review powered by OpenAI-compatible models, designed for use with Claude Code.Last updated1MIT
- AlicenseAqualityBmaintenanceAn MCP server that enables code review by having two LLMs (Claude and GPT-4o) independently evaluate code and then synthesize their findings into a unified report.Last updated3MIT
- Alicense-qualityBmaintenanceMCP server enabling Claude to consult Codex (GPT-5.x) mid-task for second opinions, plan/diff review, brainstorming, and codebase exploration via structured debates and permission-controlled interactions.Last updated2MIT
Related MCP Connectors
Hosted MCP server connecting claude.ai, ChatGPT and other AI apps to your own computer
Augments MCP Server - A comprehensive framework documentation provider for Claude Code
MCP server for AI agents to plan, verify, and deploy Cloudflare-native apps.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/subvertnormality/paranoia-local'
If you have feedback or need assistance with the MCP directory API, please join our Discord server