Cognitive Inversion: The Engine Owns the Loop
Cognitive Inversion: The Engine Owns the Loop#
An agent that owns its own control flow is not an agent. It is a process with a suggestion box.
The 2024–2026 generation of coding agents inverted the wrong thing. They placed a stochastic decoder at the root of the call graph and asked it to remember the law: when to stop, what not to touch, how many tokens remain, whether the tests are actually green. That arrangement fails for a mechanical reason, not a moral one. A transformer does not possess a program counter. It samples. Sampling cannot be the authority that advances a state machine whose transitions have side effects on a repository.
Cognitive Inversion is the correction. The compiled Go kernel owns the lifecycle, the scheduler, and the capability budgets. The language model is a bounded heuristic subroutine — a stochastic ALU — invoked with an ephemeral Capability-Based Access Control (CapBAC) ticket and a hard step/token ceiling. When the ticket expires, the ALU is gone. State remains in SQLite. The next instruction is issued by the engine, not by the previous completion.
This chapter specifies that inversion as an engineering invariant, not a metaphor.
1. The Failure Mode of Model-Owned Loops#
A model-owned loop has this shape:
while not done:
thought = llm.complete(history)
action = parse(thought) # optional, often skipped
result = tools.exec(action) # filesystem, git, shell
history.append(thought, result)Four defects are structural, not accidental.
No durable program counter. history is a prompt, not a state. Context window pressure, summarization, and attention collapse all mutate the apparent PC. The agent can believe it is in REVIEW while the working tree is still dirty from EDIT.
No atomic transition. Tool calls and “I am done” claims are interleaved in the same token stream. A hallucinated agreement (tests pass) is observationally identical to a real one until something outside the model checks. By then the mutation is already on disk.
Unbounded capability. The model holds the keys for the entire session: shell, git, network, the trunk branch. Budget is a system prompt. System prompts are not capabilities.
Scheduler capture. Once the model decides to “just fix one more thing,” the outer loop has no remaining authority except a human hitting stop. That is not a scheduler. That is an abort button.
These are the failure modes Nomos names vibe coding: context saturation, prompt drift, hallucinated agreements, silent test regressions. They are not model-quality problems. They are control-flow problems. Scaling the decoder does not install a program counter.
2. Inverted Control Flow#
The kernel is a compiled, deterministic process. It is the only thing allowed to:
- Advance the lifecycle state machine.
- Allocate and revoke capabilities.
- Schedule the next stochastic call, or refuse to.
- Commit or reject mutations against ACID state and AST gates.
The model proposes. It never transitions.
┌─────────────────────────────────────┐
│ KERNEL (compiled Go) │
│ state machine · scheduler · CapBAC │
│ SQLite (ACID SSoT) │
└──────────────┬──────────────────────┘
│ invoke(ticket, budget, frame)
▼
┌─────────────────────────────────────┐
│ STOCHASTIC ALU (LLM / SLM) │
│ ephemeral · no FS · no git · no PC │
│ returns: proposal | patch | trace │
└──────────────┬──────────────────────┘
│ result (bytes)
▼
┌─────────────────────────────────────┐
│ VERIFIER (AST · tests · DoD) │
│ exit 0 → kernel may transition │
│ exit 1 → kernel retains phase │
└─────────────────────────────────────┘Call this inverted because it is the opposite of the ReAct-shaped default. The model is not the while. The model is the body of a call that the while may or may not issue.
The lifecycle the kernel owns is fixed:
TRIAGE → SPEC → PLAN → EDIT → REVIEW → SYNC → LEARNEach arrow is a compiled transition function. The function’s preconditions are machine-checkable. The model cannot fire the arrow by emitting the word REVIEW.
2.1 Why the kernel must be compiled#
A Python orchestrator that “usually” calls the right tool is still a model-adjacent loop. Interpretation, monkey-patching, and runtime reflection reintroduce the same authority leak: the thing that is supposed to be law can be talked into a different shape.
Go gives three properties the kernel actually needs:
- A closed instruction set for the control plane. No eval, no prompt-rewritable dispatch table.
- A single binary as the trust boundary. The agent does not import the scheduler; it is scheduled by it.
- Exit codes as physics.
nomos verifyreturning 1 is not a suggestion. It is the transition function returningfalse.
The kernel does not “think.” It executes. Thinking is rented, metered, and thrown away.
3. The LLM as Stochastic ALU#
Treat the model as an arithmetic-logic unit that happens to be wrong some of the time.
An ALU is not a computer. It does not own RAM, does not fetch the next instruction, does not hold the bus. It receives operands, emits a result, and is combinational with respect to architectural state. The stochastic ALU is the same object with a distribution over outputs:
ALU : (op, args) → bits
S-ALU : (op, args, θ) → Distribution(bits)The kernel’s contract with the S-ALU is therefore:
| Property | Kernel guarantee | S-ALU prohibition |
|---|---|---|
| Persistent state | SQLite rows, git objects, worktree paths | No writes except through returned bytes |
| Program counter | Phase enum in tasks |
Cannot set phase |
| Capability | CapBAC ticket, TTL, resource caps | No ambient authority |
| Time | Kernel wall-clock and step counter | No “one more try” after budget = 0 |
| Truth | AST gates, test traces, exitcode |
Claims are data, not facts |
The S-ALU is allowed to be creative inside the frame it is given. Creativity is the point of renting entropy. What it is not allowed to be is durable. After the call returns, the only things that exist are: (a) bytes the kernel chose to keep, (b) a budget decrement, (c) a log row.
This is why model upgrades are cheap. Sophia (the intent core) can be swapped for a larger or smaller decoder without touching Nomos (the substrate). The ALU changed. The motherboard did not.
4. The Kernel State Machine#
Phases are not labels in a prompt. They are rows.
CREATE TABLE tasks (
id TEXT PRIMARY KEY,
phase TEXT NOT NULL CHECK (phase IN (
'TRIAGE','SPEC','PLAN','EDIT','REVIEW','SYNC','LEARN'
)),
budget_steps INTEGER NOT NULL,
budget_tokens INTEGER NOT NULL,
worktree TEXT,
lock_holder TEXT,
updated_at INTEGER NOT NULL
);
CREATE TABLE transitions (
id INTEGER PRIMARY KEY,
task_id TEXT NOT NULL,
from_phase TEXT NOT NULL,
to_phase TEXT NOT NULL,
verifier TEXT NOT NULL, -- e.g. 'nomos-verify@sha256:…'
exitcode INTEGER NOT NULL,
receipt BLOB, -- Ed25519 over (task, from, to, tree)
at INTEGER NOT NULL,
FOREIGN KEY (task_id) REFERENCES tasks(id)
);A transition is a SQLite transaction:
func (k *Kernel) Transition(ctx context.Context, id TaskID, to Phase) error {
return k.db.Tx(ctx, func(tx *sql.Tx) error {
t, err := getTask(tx, id)
if err != nil {
return err
}
if !legal(t.Phase, to) {
return ErrIllegalTransition
}
if err := k.preconditions(tx, t, to); err != nil {
return err
}
// Verifier is in-process or a child with no ambient FS beyond the worktree.
code, trace := k.verify(t)
if code != 0 {
return &VerifyReject{Code: code, Trace: trace}
}
if err := putPhase(tx, id, to); err != nil {
return err
}
return putTransition(tx, t.Phase, to, code, k.sign(t, to))
})
}If verification fails, the transaction aborts. The phase does not move. The model is not consulted about whether the failure “really counts.” The next S-ALU invocation, if any, receives the trace as operands. That is the entire negotiation protocol: operands in, proposal out, physics decides.
4.1 Preconditions by phase (non-negotiable)#
| From → To | Kernel precondition | S-ALU role |
|---|---|---|
| TRIAGE → SPEC | Task claimed; worktree created; trunk hook armed | Classify, extract intent, name invariants |
| SPEC → PLAN | Spec artifact hashed into tasks; schema valid |
Emit DAG of subtasks, not prose |
| PLAN → EDIT | Plan signed by Tier-1 orchestrator; budgets allocated per node | Propose patches inside the worktree |
| EDIT → REVIEW | nomos verify exit 0 (AST, complexity, docstring density, tests) |
None. The gate is compiled |
| REVIEW → SYNC | Adversarial audit recorded; 2PC prepare succeeds | Argue; cannot merge |
| SYNC → LEARN | Fast-forward of worktree; Ed25519 receipt in refs/notes/agent |
Summarize deltas for the learning store |
| LEARN → (terminal) | Learning rows committed; worktree destroyed; ticket revoked | None |
The agent cannot skip REVIEW by being confident. Confidence is not a column.
5. Control Flow: Who Calls Whom#
The following is the only legal call graph for a mutation. Note the direction of arrows: the kernel is the caller on every stochastic edge.
sequenceDiagram
autonumber
participant K as Kernel (Go)
participant DB as SQLite (ACID)
participant C as CapBAC
participant S as Stochastic ALU
participant V as Verifier (AST/tests)
participant WT as Worktree
K->>DB: BEGIN
K->>DB: SELECT task WHERE id=? (phase, budgets)
K->>C: issue(ticket, ttl, fs=worktree, git=no-trunk, net=deny)
C-->>K: ticket
K->>S: invoke(frame, ticket, step_budget, token_budget)
Note over S: No PC, no ambient FS, no phase write
S-->>K: proposal (patch | spec | plan | audit)
K->>C: revoke(ticket)
alt proposal is a patch
K->>WT: apply patch (worktree only)
K->>V: nomos verify
V-->>K: exitcode, AST trace
alt exitcode = 0 AND legal(phase, next)
K->>DB: UPDATE phase; INSERT transition; COMMIT
else reject
K->>WT: revert patch
K->>DB: ROLLBACK / keep phase; INSERT reject
K->>S: (optional) re-invoke with trace, remaining budget
end
else proposal is not a patch
K->>DB: store artifact hash; COMMIT or keep
endTwo properties fall out of the diagram.
The S-ALU never holds a live ticket across a verify. Capabilities die before the gate runs. A model that “wants to fix the gate” must be called again, with a new ticket, under the same phase.
Commit is a kernel verb. Git fast-forward, 2PC, and note-signing are not tools the model may name. They are functions the transition function may call after exitcode == 0.
6. CapBAC: Step and Token Budgets as Capabilities#
Ambient authority is how model-owned loops escape. CapBAC is how inverted loops do not.
A ticket is a structured capability, not an API key in an environment variable:
type Ticket struct {
TaskID TaskID
Phase Phase
Worktree string // the only writable tree
FS FSPerm // {read: repo, write: worktree, deny: trunk}
Git GitPerm // {commit: worktree, deny: push, deny: checkout-main}
Net NetPerm // deny by default
StepsLeft int // kernel-decremented; S-ALU cannot reset
TokensLeft int // includes prompt+completion
Deadline time.Time
Parent TicketID // attenuation only: child ⊆ parent
Sig []byte // kernel Ed25519
}Attenuation is monotonic. A Tier-2 worker spawned for a PLAN node receives a child ticket whose StepsLeft and TokensLeft are ≤ the parent’s remainder, whose FS.write is a subdirectory of the parent worktree, and whose Git cannot widen. The S-ALU cannot mint tickets. It cannot even see the signing key.
6.1 Accounting#
Budgets are not prompt decorations. They are columns updated in the same transaction as the call record:
steps_left := steps_left - 1
tokens_left := tokens_left - (prompt_tokens + completion_tokens)When either counter hits zero, the kernel does not ask the model whether it is “almost done.” The scheduler’s next action is one of: requeue with a human-granted top-up, fail the task, or shed to a cheaper ALU for a residual classification. Those are kernel policies. They are not completions.
A typical EDIT frame:
ticket.StepsLeft = 8 // at most 8 S-ALU calls in this phase
ticket.TokensLeft = 48_000 // hard ceiling, not a hint
ticket.FS.write = worktrees/acme-SUB-123
ticket.Git = commit-in-worktree
ticket.Net = denyEight calls is a lot of entropy and a tiny amount of authority. That is the intended ratio.
6.2 Why tokens and steps are both required#
Token budgets bound cost and context pollution. Step budgets bound control-flow depth. A model that emits tiny completions can still infinite-loop the tool surface if you only meter tokens. A model that dumps a 32k patch in one shot can still bankrupt the run if you only meter steps. The kernel decrements both. Either zero is terminal for the ticket.
7. SQLite as the Architectural Totem#
The S-ALU lives in a dream. The dream needs a totem that does not dream.
SQLite is that totem for control-plane state, for the same reasons it is the totem for every other embedded system that cannot afford a second consensus story:
- Single-writer ACID. Phase updates serialize. Two workers cannot both believe they hold EDIT.
- The database is a file in the worktree’s parent, not in the model’s head. Killing the process does not invent a new phase.
- Readers see committed state only. A crashed verify cannot leave
phase = REVIEWwith a red test trace. - The schema is the protocol. Illegal phases are a
CHECKconstraint, not a scolding.
Git remains the totem for content. SQLite remains the totem for control. Mixing them — storing the phase in a markdown file the model can edit — reinstalls model-owned loops under a documentary disguise.
The kernel’s rule is crude and sufficient: if it must be true after a crash, it is a SQLite row or a git object. It is never a sentence.
Transactional discipline for a single EDIT attempt:
BEGIN IMMEDIATE;
-- lock task row
-- decrement budget
-- record invocation id
COMMIT; -- budget spent even if the ALU dies
-- S-ALU call (no DB lock held; ticket in memory / sealed file)
BEGIN IMMEDIATE;
-- apply or reject based on verifier
-- insert transition or reject
COMMIT;The first transaction makes waste visible. The second makes mutation atomic with the phase. There is no third transaction in which the model “confirms.”
8. Harness Superiority vs. Raw Model Scale#
The industry bet of 2024–2026 was: larger models will remember the rules. They will not. Remembering is not the mechanism of enforcement, and enforcement is the only mechanism that keeps a repository a repository.
State the comparison without romance.
| Axis | Scale the model | Strengthen the harness |
|---|---|---|
| Illegal transition | Less likely, still possible | Impossible (CHECK + compiled legal()) |
| Cyclomatic complexity > 15 | Sometimes noticed | exit 1; no transition |
| Trunk edit | Sometimes refused | Pre-commit hook + worktree; physically blocked |
| Hallucinated green tests | Better models lie less often | The test runner is not a model |
| Cost of a new model | Re-prompt the law into the new weights | Swap the ALU; kernel unchanged |
| Multi-hour loops | Context evaporates | Phase and budget are rows |
| Swarm fan-out | Coordination in prose | Child tickets, attenuated, ACID-locked |
Harness superiority is not a claim that models are useless. It is a claim about where authority lives. Intelligence without invariants is liability. Invariants without intelligence are rigidity. The kernel supplies the invariants; the S-ALU supplies the intelligence; the ticket supplies the join.
Empirically, a mid-size model behind nomos verify, worktree isolation, and CapBAC produces fewer silent regressions than a frontier model with a system prompt and a shell. The frontier model is a better ALU. It is not a better OS.
This is also why CLI-over-MCP is the mutation path. An MCP session is a capability leak with a reconnect story. nomos task transition EDIT is a stateless, transactional verb: open DB, check row, run gate, commit or don’t. Connection drops do not duplicate tasks. The exit code is the protocol.
9. The Inner Loop, Precisely#
For an EDIT-phase worker the kernel’s loop is:
for t.StepsLeft > 0 && t.TokensLeft > 0 && t.Phase == EDIT {
ticket := k.Issue(t)
frame := k.Frame(t) // spec hash, failing tests, last AST trace
out, usage, err := k.ALU.Invoke(ctx, ticket, frame)
k.Revoke(ticket)
t.Spend(usage)
if err != nil {
k.Record(t, err)
continue
}
if err := k.ApplyWorktree(t, out.Patch); err != nil {
k.Record(t, err)
continue
}
code, trace := k.Verify(t)
if code != 0 {
k.RevertWorktree(t, out.Patch)
t.LastTrace = trace // next frame operands
continue
}
return k.Transition(ctx, t.ID, REVIEW)
}
return k.FailBudget(t)Observe what is missing: there is no if aluSaysDone. Done is Transition succeeding. Transition succeeding is legal ∧ preconditions ∧ verify==0. The ALU can print “done” into out. That string is not in the condition.
Intercept in-flight. Do not wait for a human review to discover that cyclomatic complexity is 22. The verifier runs while the working memory of the ALU is still reconstructable from LastTrace. The next invocation is a repair against a structured diagnostic, not a new essay.
10. Dual-Core Consequence#
Cognitive inversion is the operational form of the dual-core split:
- Nomos / Substrate / Yin — compiled kernel, AST physics, ACID state, CapBAC, worktrees. The how that cannot be sampled.
- Sophia / Intent / Yang — the S-ALU. The what that should be sampled, because design is not a closed instruction set.
The cognitive bridge is not a chat. It is invoke(ticket, frame) → bytes. Bytes that do not survive verify do not survive. Bytes that do survive still do not move the phase until the kernel says so.
Upgrade Sophia as often as the market demands. Do not upgrade Nomos by asking Sophia to rewrite it.
11. Invariants (Chapter Contract)#
The following are not guidelines. They are the type of the system.
- The kernel is the only caller of the S-ALU. No completion may schedule the next completion.
- Phase is a SQLite column with a closed CHECK. Prose cannot write it.
- Every S-ALU call carries a ticket. No ticket, no operands, no I/O.
- Tickets attenuate; they never widen. Child ⊆ parent, including budgets.
steps_left == 0 ∨ tokens_left == 0terminates the ticket. No appeal in-band.- Verification is a child process with an exit code. Exit 1 aborts the transition transaction.
- Mutations land only in
worktrees/<repo>-<task>. Trunk is physically unwritable to the agent. - Commit, 2PC, and note-signing are kernel functions, not tools.
- Crash recovery replays from SQLite + git, never from a transcript.
- Harness changes require a human-compiled release. Model changes require a config row.
If a design violates one of these, it is a model-owned loop with extra steps. Extra steps are not inversion.
The engine owns the loop because the loop has effects that must still be true when the sampler has been deallocated. That is the whole argument. The rest is mechanism.