DopaKernel stops AI coding agents from declaring a task done before it is. It freezes the user's objective into explicit requirements, picks the next action from recorded progress rather than the model's judgment, and refuses completion until every requirement has fresh evidence from re-running its verifier. The model's own account of what it did never counts as evidence.
Behavior changes from the gap between expect and got, never from the
model's confidence that it is probably finished.
Proof: the current kernel passes 90 unit tests and 54 structural checks, and a replay harness asserts that protocol changes leave its decisions unchanged. See Tests.
utsapoddar-dopa-kernel-explained.mp4
Use absolute paths to the controller while keeping the target workspace as cwd:
python3 /path/to/dopa-kernel/kernel/decide.py start /tmp/dopa-goal.json
python3 /path/to/dopa-kernel/kernel/decide.py select /tmp/dopa-candidates.json
# make one authorized mutation, observe it
python3 /path/to/dopa-kernel/kernel/decide.py outcome /tmp/dopa-outcome.json
python3 /path/to/dopa-kernel/kernel/decide.py verify requirement-id
python3 /path/to/dopa-kernel/kernel/decide.py evaluateIf the user explicitly changes the objective, run
decide.py cancel /tmp/dopa-cancel.json before starting its replacement. See
the contract guide for the authorized cancellation and evidence-backed blocker
schemas. With the Claude hooks installed, both cancel and block force a
native permission prompt, including in auto mode.
See reference/goal-contract.md for both schemas
and the exact evidence semantics.
Keep one source of truth. Clone or place this repository at a stable path, then
link its loader adapter. Do not link the repository root: recursive skill
discovery may otherwise expose legacy/SKILL.md as a duplicate active skill.
ln -s /absolute/path/to/dopa-kernel/adapters/claude ~/.claude/skills/dopa-kernelThe adapter routes the agent to the canonical root SKILL.md; it contains no
independent behavioral copy.
Register the stable absolute hook paths in ~/.claude/settings.json:
{
"hooks": {
"PreToolUse": [{
"matcher": "*",
"hooks": [{"type": "command", "command": "python3 /absolute/path/to/dopa-kernel/kernel/gate_decide.py"}]
}],
"Stop": [{
"matcher": "*",
"hooks": [{"type": "command", "command": "python3 /absolute/path/to/dopa-kernel/kernel/gate_tests.py"}]
}]
}
}Activate only with Dopa mode or explicit dopa-kernel invocation.
The current kernel and the preserved v0.1 architecture are tested separately. The current kernel has 144 checks; the legacy suites add 224 more:
sh kernel/tests/structure.sh # current: 54 structural checks
cd kernel && python3 -m unittest discover -s tests # current: 90 unit tests
python3 tests/replay_equivalence.py main # current: decision equivalence
cd ../legacy && sh tests/structure.sh # legacy: 89 structural checks
cd runtime && python3 -m unittest discover -s tests # legacy: 135 unit testsThe tests cover structure, publication hygiene, deterministic mechanism behavior, every historical bypass case, and decision equivalence across protocol changes.
replay_equivalence.py drives a baseline checkout and the working tree through
identical command sequences and asserts identical exit codes and identical
final controller state. It is what lets the protocol get cheaper without the
kernel's decisions moving.
The threat model is a cooperative but fallible agent that may stop early, misread progress, or rely on stale evidence — not a deliberately malicious process running with the user's own access.
- Host sandbox and permissions own execution authority; DopaKernel owns semantic requirements, action selection, evidence freshness, and the completion decision. Auto mode changes approval friction, not whether those semantic requirements have been met.
- The shell classifier fails safe by construction: unknown commands are treated
as mutations, shell substitution is never read-only, verifier commands run
through a read-only allowlist without
shell=True, and controller errors fail closed while a goal is active. Direct tool access to.dopais blocked. It is a conservative classifier, not a full shell interpreter. - PreToolUse is registered for every tool name, so MCP and future mutation tools cannot bypass generation accounting, and an active workspace goal keeps the gate live in delegated transcripts that never repeated the skill invocation. Its authority ends at deliberate same-user modification of controller state, hooks, or settings, and at processes outside the installed hooks: this is agent control, not an operating-system sandbox.
- Candidate semantics and evidence-level labels originate in the goal contract; deterministic code validates and applies them. At high stakes, make verifier independence concrete and user-visible.
- The Claude PreToolUse hook uses the platform's
permissionDecision: "ask"for cancellation and impossibility. Direct CLI invocation is an operator interface and assumes the caller already has user authorization. - An earlier README reported
4/5from a small replay. That was development evidence rather than an effectiveness result, and it was withdrawn. - The standing claim is mechanical: the controller blocks the tested semantic premature-completion cases under this threat model. Behavioral effectiveness on unburned tasks is a separate question and wants its own preregistered evaluation.
Its core allocation rules are derived from primary-literature results on how the dopamine system actually encodes prediction error. "Dopamine-inspired" normally means a reward scalar and a learning rate; the measured code is neither, and taking that seriously is what produced a partial order instead of a score, a separate regression channel instead of a signed sum, and a stakes level frozen before the work begins. Four findings from Nature, Science, Neuron and Psychopharmacology, each mapped to a named line of the controller — three adopted, one deliberately rejected.
The mapping from paper to function is verifiable: That is what makes the name honest rather than decorative.
Never sum r and i. Dabney et al. (2020, Nature 577:671-675) recorded
40 VTA cells across 6 animals and found dopamine encodes reward prediction
error distributionally: each cell carries its own asymmetric scaling for
positive versus negative RPE, mean ratio 0.48, with significant diversity
(F(38,234) = 2.93, p = 4e-7). For one identical 5 uL reward, 13/40 cells
fired above baseline while 10/40 fired below — same reward, opposite sign,
simultaneously. A scalar would discard exactly the structure the separate axes
preserve, so no exchange rate between progress and information exists and none
may be invented. Live in policy.choose, which is a partial order rather than
a score.
A regression is not negative progress. Bayer & Glimcher (2005, Neuron
47:129-141) found firing rate encodes positive RPE but does not carry the
negative magnitude; Bayer, Lau & Glimcher (2007) located that magnitude in the
duration of the post-reward interspike pause instead. Two encodings, two units,
no signed scalar — which is why a regression gets its own channel and is never
netted against progress. Live as known_regression, which forces restoration
before other work proceeds.
Stakes are set at the cue, not the outcome. Tobler, Fiorillo & Schultz
(2005, Science 307:1642-1645) found dopamine neurons do not encode absolute
reward magnitude: responses shift relative to expected value and the gain
adapts to the variance of reward value, so an identical reward produces a
different response under a different stakes context. Critically the adaptation
is driven by reward-predicting stimuli — the scale is set before the outcome
arrives. That is why imp is declared with the goal contract, frozen into the
goal identity at start, and floors the evidence bar afterwards; it is never
inferred once results are in.
Pacing has a closed form, and adopting it would have been wrong. Niv, Daw,
Joel & Dayan (2007, Psychopharmacology 191:507-520) model an agent choosing
both an action and a latency tau, paying a vigor cost C_v / tau against the
opportunity cost of time Rbar * tau. Their Eq. 4 gives
tau* = sqrt(C_v / Rbar) — optimal latency depends only on the vigor constant
and the average reward rate, identically for every action, which is why one
tonic level retunes the pace of everything at once. It is a genuinely elegant
result and it does not transfer. The derivation assumes a free-operant setting
whose scarce resource is seconds forgone from an exogenous reward stream; a
session's scarce resource is context and turns, and the task waits while the
agent thinks. So it was built, instrumented as a passive diagnostic that no
gate was allowed to read, and left in legacy/runtime/pacing.py rather than
promoted into the kernel.
Three of four results survived that filter. The fourth is documented here precisely because rejecting it was the design decision — a result that fits the metaphor but not the problem is the easiest kind to import by accident.
Full derivations, the policy table, and the classification frame are in
legacy/reference/quantities.md and
legacy/reference/matrix.md.
/goal and DopaKernel solve different layers of the same failure mode:
| Layer | Responsibility |
|---|---|
Codex /goal |
Thread-persistent objective, continuation turns, a requirement-by-requirement completion-audit prompt, usage accounting, and user lifecycle controls |
Claude /goal |
Session goal restored on resume, driven by a prompt-based Stop hook whose separate small model reads the conversation and returns not-yet/met/impossible |
| DopaKernel | Requirement decomposition, semantic action allocation, path closure, frozen verification, and fresh-evidence completion |
For Codex, keep the platform goal and Dopa objective identical; Codex supplies continuation while Dopa supplies policy and evidence. For Claude, install the Dopa hooks so the Stop decision uses structured receipts rather than inferred test summaries. DopaKernel does not call a second external model API.
The lesson is not to duplicate platform loop machinery. Codex already tells the worker not to shrink the objective and to audit every requirement before calling its completion tool. Claude already uses a fresh model rather than the worker for the terminal decision, but that evaluator cannot run tools and only sees what the worker surfaced. DopaKernel fills the remaining gap: deterministic semantic selection plus verifier receipts tied to post-mutation state.
Current primary references:
- Codex Goal mode release and v0.147.0 continuation contract
- Codex v0.147.0 goal tool contract
- Claude Code
/goaldocumentation
The original lean kernel had two sound ideas: do not let prose choose the path,
and do not let a failing observation become victory. Its implementation was too
narrow: it remembered one selection, parsed the last recognizable test summary,
and allowed several shell and path exemptions. Semantic fields (C, r, i,
δ, imp) did not control runtime decisions.
The current controller connects those ideas end to end:
kernel/model.py— validated, goal-scoped.dopa/goal.json, frozen requirements, atomic state, mutation generations, attempts, and closed paths.kernel/policy.py— deterministic action selection with hard envelope and regression constraints, requirement priority, progress-first behavior, and decision-criticalreduce-firstresearch.kernel/evaluator.py— shell-free command/file verifiers, mutation detection, live final re-verification, evidence receipts, and independentmet / not_met / impossiblecompletion decisions.kernel/decide.py—start,select,outcome,verify,block,cancel,evaluate, andstatusCLI. Machine-readable output is compact, andstatusshows the lastSWITCH_AFTERattempts plus a recorded count rather than the whole history — that window is all path closure consults, so the display cost stays flat as a session grows..dopa/goal.jsonstill records every attempt.kernel/gate_decide.py— Claude PreToolUse gate. Exact control commands are exempt; unknown shell commands are mutations; authorization is locked and later mutations stale earlier evidence.kernel/gate_tests.py— compatibility-named Claude Stop gate. It reads the evaluator, never transcript test-like prose.
legacy/ keeps the routed architecture and the 18-cell research frame. The
matrix is not redundant: its semantic dimensions remain useful. The mandatory
module-reading and self-authored paperwork around it were.

