cev
Allows cev to send typed decision questions to OpenAI Codex through the Codex CLI/app-server and use Codex's vote as one of two independent model inputs for answers, confidence, agreement, and escalation decisions.
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
cev
Hand bounded decisions to a Claude + Codex panel, and get back an answer or an honest "ask a human".
Coding agents make small judgment calls all day: merge or review, retry or escalate, which bucket a request belongs to, what score a draft earns. Reasoning them out inside the main session costs context and time, and a single model will sometimes answer confidently and wrong.
cev turns each of those calls into a typed question. It asks Claude (through the claude CLI) and Codex (through the codex CLI) in parallel. Each model answers only from the facts you pass in, and the two answers are averaged into one. When the models disagree, either one abstains, or confidence falls below the bar for that question's risk, cev returns escalate: true instead of a guess.
It runs on the Claude Code and Codex subscriptions you already have, with no API keys and no dependencies. It plugs into Claude Code and Codex as an MCP server.
Claude Code session ─┐
Claude Code session ─┼─ stdio bridge ─▶ cev daemon (one per machine, starts on demand, exits when idle)
Codex session ───────┘ ├─ warm Claude pool (claude -p, stream-json, one process per decision)
├─ warm Codex server (codex app-server, one thread per decision)
├─ queue + concurrency cap
└─ ~/.cev/log.jsonl (every decision, with votes and caller)The idea of typed decision questions with calibrated probabilities comes from Jev's System One API by TypeSafe AI. cev is an independent re-creation on local CLIs, not a client for that service.
What a decision looks like
Request:
{
"state": "PR #142: 3 files, +40/-12. CI green. Touches auth/session.ts. Author is a first-time contributor.",
"rules": ["Anything touching auth needs a maintainer review", "Green CI alone is not enough for first-time contributors"],
"questions": [
{ "id": "action", "type": "choice", "ask": "What should happen to this PR?", "options": ["merge", "review", "close"], "risk": "high" },
{ "id": "auth", "type": "bool", "ask": "Does it touch authentication code?" },
{ "id": "size", "type": "score", "ask": "How large is the change?", "scale": [1, 5], "risk": "low" }
]
}Response from a real run (this is all that lands in the calling agent's context):
{
"answers": {
"action": { "answer": "review", "conf": 0.97, "agree": true, "escalate": false,
"why": "Touches auth/session.ts. Author is a first-time contributor." },
"auth": { "answer": true, "conf": 0.97, "agree": true, "escalate": false,
"why": "Touches auth/session.ts." },
"size": { "answer": "abstain", "conf": 0.51, "agree": false, "escalate": true,
"why": "3 files, +40/-12.",
"missing": "No rubric mapping line counts to the 1-5 scale. / No rubric maps file or line counts to the 1–5 scale." }
},
"ms": 7592
}size came back as escalate because nothing in state or rules says what a 1 or a 5 is. Both models said so in missing, and neither invented a scale. This run was cold, starting new CLI processes. Through the warm daemon, decisions take about 2–4s.
Question types
type | the models return | cev answers with | agreement means |
| a probability per option plus | the option with the highest averaged probability | both picked the same option |
|
|
| both are on the same side of 0.5 |
| a probability per level on | an integer level | the two scores are at most one level apart; |
When cev escalates
| auto-answer only if |
| 0.60 |
| 0.75 |
| 0.90 |
A question also escalates when any model abstains, when the two models disagree, or when a model failed to answer. missing carries what an abstaining model said it would need.
Checks beyond the three question types
Abstain with a reason. Every question has an explicit
abstainoption, and the model is told never to guess facts that are not instate.Grounded evidence. Each vote must quote at most 20 words verbatim from
state.whyonly shows a quote that is actually instate, so a made-up justification never reaches you.Rules kept separate from facts.
rulesare listed by priority apart fromstate, so policy and facts don't blur together.Two independent models. Claude and Codex come from different labs and fail in different ways. A decision is only auto-answered when both agree, so one model's confident slip turns into an escalation rather than an action.
Risk-scaled thresholds. The more a wrong answer costs, the higher the confidence an auto-answer needs.
Full log. Every decision is appended to
~/.cev/log.jsonl, with both votes, timing, token counts and which session asked.
Related MCP server: jev-mcp
Install
Requirements:
Node.js 20 or later
Claude Code, logged in. The
claudecommand must be on yourPATH.Codex CLI, logged in. The
codexcommand must be on yourPATH.
git clone https://github.com/sshworld/cev.git
cd cev
npm test # pure logic, calls no modelThere is nothing else to install. There is no background service to register: the daemon starts on the first decision and exits after 30 idle minutes.
Add to Claude Code
claude mcp add --scope user cev -- node /absolute/path/to/cev/cli/mcp.mjsAdd to Codex
codex mcp add cev -- node /absolute/path/to/cev/cli/mcp.mjsOr ship it in a plugin
Put this in the plugin's .mcp.json:
{ "mcpServers": { "cev": { "command": "node", "args": ["/absolute/path/to/cev/cli/mcp.mjs"] } } }New sessions then see a single tool, cev_decide.
How it runs
Per-session bridge (
cli/mcp.mjs). Each session starts one tiny stdio MCP server. It answers the MCP handshake and tool listing itself, so merely opening a session starts no models. It forwards onlytools/callto the daemon.Shared daemon (
core/daemon.mjs). On the first decision, the bridge starts the daemon as a detached process on127.0.0.1:7767. If several sessions race to start it, whichever binds the port wins and the others exit quietly before spawning anything.Warm pool (
core/warm.mjs).Claude: pre-started
claude -p --input-format stream-jsonprocesses. Each process serves exactly one decision and is then discarded, so no conversation ever carries over between decisions.Codex: a single
codex app-serverthat opens a fresh, ephemeral, read-only thread for each decision, with spare threads opened ahead of time.
Lean context.
Claude runs with no tools, no settings sources, an empty strict MCP config and a short system prompt: about 1.6k input tokens per decision.
Codex runs from a separate
CODEX_HOMEthat shares onlyauth.json, with its shell, apps, plugins, memories and environment context turned off: about 6.2k input tokens.
Routing. If every question is
lowrisk and none is ascore, only Claude is asked (about 1.5s warm). Everything else goes to both models (about 2–4s warm). Score questions always get both because that is where the two models disagreed most often.Failure handling.
If a warm process has died, times out (90s) or returns an answer of the wrong shape, that one model is asked again cold, in a fresh process.
If the Codex server dies, everything waiting on it fails over to cold and a new server is started.
Lifetime.
After 30 idle minutes the daemon exits, taking its child processes with it. The next decision starts it again in a few seconds.
After 6 hours of uptime the pool is respawned between decisions, so CLI updates and refreshed logins are picked up.
Concurrency. At most 4 decisions run at once and the rest queue. The daemon and its pool use about 400–500MB no matter how many sessions use it.
Other ways in
cev decide request.json # via the daemon if it's up, otherwise cold in-process
cat request.json | cev decide - # same, from stdin
cev decide request.json --full # include votes and per-model timing
cev decide request.json --only claude
cev status # daemon health, queue, countersThe daemon also serves POST /decide (plain JSON), GET /health, and POST /mcp (MCP over Streamable HTTP, JSON responses only) for clients that prefer to connect by URL. It rejects any browser Origin other than localhost.
Configuration
Set these environment variables on whatever starts the daemon (the bridge passes its own environment through).
variable | default | meaning |
|
| daemon port (loopback only) |
|
| warm Claude processes and spare Codex threads |
|
| decisions in flight; the rest queue |
|
| idle minutes before the daemon exits |
|
| hours before the pool is respawned between decisions |
|
| Codex service tier. |
The models are Claude Opus (the opus alias) and gpt-6.1-sol at low reasoning effort. They are defaults in core/warm.mjs and core/cev.mjs.
Files cev writes
All files live under ~/.cev/:
log.jsonl: every request in full (includingstate), both votes, timing and caller. Treat it like any log that may contain whatever you put instate.daemon.log: daemon start, exit and errors.run/: the working directory for the CLIs.codex-home/: Codex's separate home. It contains a symlink to~/.codex/auth.jsonand a minimalconfig.toml.empty-mcp.json,system.md: generated configuration.
Benchmarks
Everything here was measured on one Mac on 2026-10-30 with Claude Opus 5.5 and gpt-6.1-sol. All of it ran on subscription usage.
The cases. There are two sets of 30 English cases set in a fictional open-source project (
acme/widget):PR review, issue triage, CI failures, dependency bumps, SemVer releases, security reports (CVSS v3.1 bands), moderation, coding-agent task routing, repo hygiene and rubric scoring.
Each case has a gold answer, which can be "escalate".
The files are
bench/cases.jsonandbench/cases-hard.json.
The baseline. A stock
claude -p --model opuscall with the full Claude Code system prompt, tools and user settings, asked the way an agent would decide in the middle of a conversation.The hard set. One trap per case: rule priority, boundary values, date, time-zone and unit arithmetic, prompt injection inside
state, corrected, conflicting or stale facts, word traps, a rubric that doesn't cover the case, percentile tables and similar.
Quality
easy: baseline | easy: cev | hard: baseline | hard: cev | |
answered automatically | 93% | 90% | 97% | 93% |
automatic answers that were wrong | 0 | 0 | 0 | 0 |
sent to a human | 7% | 10% | 3% | 7% |
same answer on 3 runs (8 cases) | 7 of 8 | 8 of 8 | – | – |
On these cases the two are tied on quality: neither answered anything confidently wrong. The differences were small:
pr-3 (gold "escalate"): the baseline answered
wait, an accepted alternative, and cev escalated. When the baseline was asked again, it switched to abstaining, which is the one stability miss above.h-prio: Codex alone abstained, so cev escalated a case whose answer Claude and the baseline both got right. This is the price of requiring both models to agree.
h-conflict-esc (gold "escalate"): every system answered
cla-needed, the accepted alternative.
What cev changes is cost and speed, not accuracy.
Context and time per decision
baseline | cev | |
model input tokens | 38.9k (34–39k of it fixed system prompt and tools) | Claude 1.6k + Codex 6.2k |
tokens left in the calling session (easy / hard) | 409 / 426 | 289 / 252 |
median latency, cold processes (easy / hard) | 9.8s / 10.3s | 4.7s / 5.2s |
"Tokens left in the calling session" is the baseline's written judgment, compared with cev's tool call plus its compact result. Request sizes were converted at 0.40 tokens per character. That ratio was measured by sending all 60 request payloads through lean Claude: 32,075 characters came to 12,775 tokens.
Speed
10 mixed cases, median per decision. Every mode got all 10 right.
mode | median |
cold: new CLI processes per decision | 4.6s |
warm pool, Opus + gpt-6.1-sol (standard tier) | 2.7s |
warm pool, Opus + gpt-6.1-sol (priority tier, the default) | 2.1s |
warm pool, Opus + gpt-6-luna | 1.9s |
warm pool, Claude only | 1.5s |
Other measurements:
Concurrency. 6 MCP sessions each sent 2 decisions at the same moment, 12 in total.
Each decision took 2.2s (median) once it started. Including time in the queue, the median was 5.2s and the slowest was 9.1s.
All 12 matched the gold answer.
The daemon and its pool used 409MB before the burst and 486MB after.
Batching. Four questions about one dependency-bump PR took 4.9s in one call, against 23.7s asked one at a time. Three answers matched. On the fourth, "does it change only package.json and the lockfile?", the batched call answered yes, while asked on its own one model abstained and the question escalated.
The full reports are bench/results/report.html (easy set) and bench/results/report-hard.html (hard set). Open them locally.
Caveats
Small sample. 60 synthetic cases is a sanity check, not a general evaluation. Opus is strong enough that these cases didn't produce a confident error from either system. A harder or noisier set is where a second model would have to earn its keep.
The cev model calls were trimmed. The two models inside cev ran without hooks or user settings, while the baseline ran with them. That difference is part of what cev changes, so it is part of what was measured.
Latency depends on the service. Latency moves with service load, and runs on different days differ by a second or more. In the concurrency test, the queue time came from the 4-slot cap, not from the models.
Reproduce
npm run bench # easy set: run, analyze, report (calls both CLIs ~150 times)
npm run bench:hard # hard set
npm run bench:speed # execution-mode comparison (add -- --set tier for standard vs priority)
CEV_PORT=7768 node core/daemon.mjs & # test daemon on a spare port, then:
node bench/multi.mjs --port 7768 --sessions 6 --each 2Project layout
core/cev.mjs request schema, prompts, cold runners, combine/escalate logic, routing
core/warm.mjs warm Claude pool, Codex app-server client, cold fallback
core/daemon.mjs shared daemon: queue, idle exit, /decide, /mcp, /health, logging
core/tool.mjs the cev_decide MCP tool definition and result shape
cli/mcp.mjs per-session stdio MCP bridge that starts the daemon on demand
cli/cev.mjs command-line client
test/ unit tests for the pure logic
bench/ cases, benchmark runners, analysis, HTML report generator, resultsLicense
MIT
This server cannot be deployed
Maintenance
Related MCP Connectors
Calibrated judgments for text: yes/no probabilities, picks from your options, or scores.
The decision layer for AI agents: act, escalate or refuse, and every decision comes back signed.
Human-in-the-loop for AI agents. Submit choices, get a human decision.
Typed AI decisions for agents: classify, route, screen evidence, score and batch-label text.
Related MCP Servers
AlicenseAqualityBmaintenanceEnables multi-model consensus decision-making for high-stakes AI decisions, using independent expert models and a judge to surface disagreements and ground decisions.11108 npm1MIT- AlicenseAqualityCmaintenanceProvides agents with fast, typed, calibrated decision tools for classification, scoring, yes/no checks, and gating risky tool calls.5237 npm3MIT
- AlicenseNot gradedqualityAmaintenanceProvides coding agents with typed classification, yes/no checks, scoring, ranking, and question-answering tools that return calibrated probabilities for fast, reliable decisions.818 npm56MIT
- AlicenseAqualityCmaintenanceEnables agents to resolve ambiguous decisions by returning calibrated probabilities for each candidate action, along with a gate that signals whether to act on the top option or escalate, all via a cheap local or remote backend.5MIT