jev-claude-controller (jcc)

September 20, 2026 ยท View on GitHub

A coding agent where Python owns the loop. TypeSafe's Jev answers bounded typed questions in hundreds of milliseconds, deterministic code decides and executes, and Claude is invoked only when generating or reasoning is genuinely required.

USER GOAL
    |
    v
 normalize state  ------------------------------+
    |                                           |
    v                                           |
 generate valid candidates  (deterministic)     |
    |                                           |
    v                                           |
 decide without a model?  --yes-->  execute     |
    |no                                         |
    v                                           |
 ONE batched Jev request  (route, target,       |
   role, needs_system2, goal_complete, ...)     |
    |                                           |
    v                                           |
 deterministic policy reads typed answers       |
    |                                           |
    +--> execute a prepared tool                |
    +--> invoke a bounded Claude worker         |
    +--> verify                                 |
    +--> ask the operator                       |
    +--> run the completion gate                |
    |                                           |
    v                                           |
 capture observation -> reduce state -----------+

Everything below describes what this repository actually does. There are no performance claims, because no benchmark has been run yet. jcc bench exists to produce that evidence.


1. System 1 and System 2

Jev is a typed probabilistic decision function, not a generative model. It answers three primitives about a state you give it and returns calibrated probabilities:

PrimitiveAnswerCarries confidence?
Noulp_yesNo. The probability is the whole answer.
Choiceselected label, full distribution, confidenceYes, but it is a peakedness statistic over the options, not P(correct).
Scoreexpected score, per-level probabilities, confidenceYes, with the same peakedness caveat.

Claude is the expensive reasoning and generation engine. The division is not "Jev is Claude but cheaper". It is that a large fraction of what an agent loop spends a full model turn on is actually a bounded judgment: route, rank, classify, verify, choose among known alternatives. Those go to Jev. Writing code, diagnosing an unexplained failure, and deciding an architecture go to Claude.

Many independent questions travel in one request and are answered in parallel. A measured five-question batch returned in 730 ms for 517 input tokens.

2. Why an external controller, not a Claude Code plugin

A plugin cannot own the loop. Claude Code's hook surface can observe a tool call, add a note, or deny one, and by design it can never choose an action or approve one. There is no hook between "the agent has a goal" and "the agent picks a tool", which is exactly where routing would have to sit.

So the control loop lives here, in ordinary Python, where it can be read, tested, replayed and argued with. Claude is a subprocess this program calls, not the thing calling it.

3. What Jev decides

Bounded, closed-set judgments over a state this controller assembled:

  • route is one of execute_tool, invoke_claude, verify, ask_user, finish, blocked.
  • tool_target picks which already-constructed candidate id best advances the goal.
  • claude_role picks which configured worker role fits, from the registry.
  • needs_system2 asks whether the next move requires generation or multi-hop reasoning.
  • goal_complete asks whether the request is satisfied. One necessary input to the gate.
  • plan_stale asks whether evidence has contradicted an assumption the plan rests on.
  • error_transient asks whether repeating this failure unchanged could succeed.
  • ambiguity scores how much of the goal would have to be guessed at.
  • semantic_unsafe asks whether the selected action reaches beyond what the request asks for.

Jev returns a candidate id. It never emits a command, a path, an argument or a model name.

4. What deterministic code decides

Everything a model would only be guessing at, and everything that must not be negotiable:

  • Process exit codes, timeouts, whether a check passed, how many files changed, whether a path is inside the repository, whether a file exists, whether JSON parses.
  • The action space itself: which candidates are valid right now, and their typed inputs.
  • Safety: path containment, the executable allowlist, destructive-git denial, credential refusal.
  • Budgets: iterations, worker invocations, retries, wall time, cost.
  • The state machine and its legal transitions.
  • The completion gate.

The rule, asserted by a test that inspects the safety layer's own function signatures: no function in the safety layer accepts a model signal. A probability of 0.999 moves nothing there.

5. What Claude decides and generates

Six bounded roles. The controller assigns the role; Claude never picks its own.

RolePurposeToolsCan mutate
plannerdecompose the goal into checkable stepsRead, Grep, Globno
debuggerfind the cause of an unexplained failureRead, Grep, Globno
implementermake one already-decided changeplus Edit, Write, Bashyes
reviewerindependently judge the diff against the requestRead, Grep, Globno
architectchoose between competing designsRead, Grep, Globno
summarizercompact accumulated statenoneno

Every role is told, in its system prompt, that it cannot end the run and that claiming a check passed will be contradicted by the controller's own record.

6. Safety boundaries

Deterministic denial first, semantic judgment only for the ambiguous remainder, and semantic judgment can only ever add caution.

  • No shell, ever. Commands are argument vectors. shell=True appears nowhere.
  • Executable allowlist, plus a hard never-run list including rm, curl, powershell, bash and sudo.
  • Destructive git denied by default: reset --hard, clean -fd, push --force, rebase, filter-branch. Network git is denied unless enabled, and then confirmed.
  • Path containment: resolved before the containment test, so .., symlinks and Windows short names cannot escape. Mutations never leave the repository.
  • Credential refusal: dotenv files, keys, PEM files, credential JSON, .ssh/ and .aws/ are never read. Writing to one is refused even when reads are explicitly permitted.
  • Bounded everything: command duration, output bytes, read bytes, run duration, iterations, worker invocations, retries.
  • Secret redaction on every path to disk, to a log, to a prompt and to Jev, by known value and by credential shape.

docs/SAFETY.md has the full matrix.

7. Installation

Requires Python 3.12 or newer, uv, and a working Claude Code installation for the Agent SDK to authenticate against. ripgrep is optional; without it, search falls back to pure Python.

git clone <this repo> jev-claude-controller
cd jev-claude-controller
uv sync
uv run jcc doctor

8. Windows PowerShell setup

# Jev key, for this session only
$env:TYPESAFE_API_KEY = 'apikey_...'

# Or persist it for every future session
[Environment]::SetEnvironmentVariable('TYPESAFE_API_KEY', 'apikey_...', 'User')

uv run jcc doctor --live

A terminal opened before you set the user-level variable will not have it. Open a new one.

9. Authentication

Two credentials, both read from the environment only and never written to any file this project creates:

  • TYPESAFE_API_KEY for Jev. jcc doctor reports whether one is configured and never prints it.
  • Claude authenticates through your existing Claude Code installation. No API key is needed if claude already works on your machine.

.env.example lists the names with fake placeholders. .env is gitignored.

10. Example run

uv run jcc run "Make the retry helper in src/widget.py actually retry, with a test." --repo .
---------------- system1 run run-20260920-031455-a1b2c3 ----------------
repo: D:/some/project
goal: Make the retry helper in src/widget.py actually retry, with a test.

[JEV]     DISCOVERY     JEV -> accepted route=execute_tool candidate=search_4f2a ::
                        route confident at 0.96 with margin 0.94; tool confident at 0.91
[TOOL]    EXECUTING     Search the repository's text for terms from the goal.
[JEV]     EXECUTING     JEV -> accepted route=invoke_claude candidate=claude_8c1d
[CLAUDE]  PLANNING      planner: Produce a short ordered plan for: Make the retry helper...
[TOOL]    EXECUTING     Read src/widget.py, which a previous search surfaced.
[CLAUDE]  IMPLEMENTING  implementer: Carry out this step and nothing beyond it: ...
[VERIFY]  VERIFYING     Run the pytest check (test).
[VERIFY]  VERIFYING     completion gate refused: independent_review: code changed but no
                        independent reviewer approval was recorded
[CLAUDE]  REVIEWING     reviewer: Review the current diff against this request: ...
[VERIFY]  VERIFYING     completion gate passed: required_checks_executed, ...

Useful flags: --dry-run decides and displays but mutates nothing and launches no worker; --interactive lets the run ask you a question instead of escalating; --mode baseline runs the comparison arm.

11. Inspecting decision receipts

Every run writes a directory under .jcc/runs/:

FileContents
events.jsonlappend-only narrative, one line per thing that happened
decisions.jsonlappend-only Jev answers and policy receipts
claude.jsonlappend-only worker episodes with the SDK's own metrics
state.jsonlatest snapshot, written atomically
metrics.jsonfinal metrics
artifacts/full tool and worker output, referenced by id
uv run jcc inspect <run-id>         # events plus metrics
uv run jcc decisions <run-id>       # every decision, with the numbers behind it
uv run jcc decisions <run-id> --json
uv run jcc gate <run-id>            # re-run the completion gate against the snapshot
uv run jcc replay <run-id>          # re-derive decisions from recorded answers, no paid calls
uv run jcc calibrate                # distributions and threshold sweeps over your own runs

A decisions row shows the selected label, its confidence, its margin to the runner-up, and the predicate that accepted or rejected it. Nothing is left unexplained.

12. Baseline benchmarking

uv run jcc bench --init                      # write an example suite
uv run jcc bench benchmarks/example.yaml

Each task runs twice from equivalent repository states: once in System-1 mode, once as a single ordinary Claude coding worker. Both are graded by the same verification command, run by this controller from real exit codes. A dirty working tree is reported and skipped rather than silently reset; --allow-dirty overrides that, and the report says so.

Recorded per arm: success, wall time, Claude invocations, Claude turns, reported cost, Jev requests, Jev latency, local tool calls, retries, test outcome, reviewer outcome, and human interventions.

No claim about which arm is better appears anywhere in this repository. Run the benchmark.

13. Current limitations

  1. The thresholds are uncalibrated. Every default in Thresholds was chosen to be conservative, not because a measurement supports it. jcc calibrate exists to fix that against your own logs.
  2. No performance evidence yet. The architecture is a hypothesis until jcc bench has data.
  3. replay re-derives policy, not execution. It re-runs the predicates over recorded signals. It does not re-run tools or workers, and it is exact for a policy change only. Rewriting a question moves every probability it produces, so replaying old answers through new wording compares two different measurements.
  4. Baseline mode is graded on the deterministic conditions only, since it produces no Jev answer for the semantic condition. Its metrics record graded_on: deterministic_checks_only.
  5. Candidate generation is hand-written. It covers orientation, discovery, post-mutation inspection, verification, bounded retry and worker dispatch. A new kind of action needs a new generator branch, deliberately.
  6. No MCP adapter yet. The interfaces are designed for one; see below.
  7. Patch application requires a stored artifact. apply_patch reads a diff by id; nothing turns free model text into a patch at call time.

14. MCP extension architecture

MCP is deliberately not a V1 blocker, but the seams are cut for it:

  • Executor in tools/base.py is a protocol: a name plus execute(candidate, context) -> ToolOutcome. An MCP adapter implements it.
  • ExecutorRegistry maps names to executors. A candidate names its executor as a string.
  • CandidateKind.FUTURE_MCP_TOOL already exists in the enum.
  • A future adapter would discover tools from a server, normalize each into a Candidate with a validated typed input and a one-line description, register itself as the executor, and return a bounded ToolOutcome. Results then flow through the same reducer, the same ledger and the same gate as a local tool.

The core rule survives the extension: a new executor may appear, but candidates are still constructed in Python and selected by id, so no new free-text path into execution opens up.

What this project does not do: expose an MCP tool so Claude can call Jev. The controller already owns Jev. Handing Claude a jev_decide tool would put the decision back inside the turn this design exists to avoid.

15. Design principles

  1. Jev is a typed probabilistic decision function, not a generative coding model.
  2. Ask it bounded semantic questions.
  3. Batch independent questions about the same state into one request.
  4. Deterministic facts stay in code.
  5. Enumerate valid actions dynamically, from current state.
  6. Models choose among valid capabilities; code owns what is executable.
  7. Policy stays deterministic and auditable.
  8. Claude is an escalation and generation engine, not the loop.
  9. Final outcomes require independent verification.
  10. Record enough evidence to evaluate whether any of this actually helps.

Further reading

DocumentContents
docs/IMPLEMENTATION_NOTES.mdthe verified SDK interfaces this is built on
docs/ARCHITECTURE.mdcomponents, data flow, the loop in detail
docs/DECISION_POLICY.mdevery predicate, threshold and rejection reason
docs/CLAUDE_WORKERS.mdrole registry, isolation, structured output
docs/SAFETY.mdthe full deterministic safety matrix
docs/BENCHMARKING.mdtask format, pairing, metrics, how to read a result
docs/STATE_AND_EVENTS.mdstate machine, reducer, event schema, recovery

License

MIT.