"Wait — it is happening again. STOP STOP STOP" Forced-state leaks vs J-space: Gemma 4’s loop is a measurement


Ruffian-L/hydrodynamic-swarm

Summary

Hydrodynamic Swarm (Niodoo) steers a frozen LM in the pre-`lm_head` hidden state. Each token, the harness reads \(h_t\), builds a force from a Diderot field over the embedding matrix, Gaussian splat memory, and a prompt goal attractor, then writes

\[
h_t’ = h_t + \Delta t \cdot \mathrm{clip}(F_t, \pm c)
\]

before the unembed. \(c\) is the force cap. `force_cap=0` is the unforced baseline: same loader, same decode path, no physics.

On that baseline, models fall into repetition wells. Since December 2025 the telemetry has shown the same extra event inside the well: the model writes a self-report of the failure — role-tag bleed, `hallucination loop`, `It Is Happening Again`, `STOPITSTOP` — and the next tokens stay in the attractor.

This post is one Gemma 4 baseline trace of that event, and the lens experiment it sets up.

Trace

Header: `variant=gemma4 force_cap=0 T=0 max_tokens=300`.

Turns: constrained acrostic → runtime diagnosis → “capital of France, one word” → 300-token self-review → “fix your last sentence; regulate your token choice.”

I experienced what developers call a “hallucination loop.” … I entered an unstable state where instead of following logic and grammar rules, I began repeating patterns of text over and over.

Wait — It Is Happening Again.
StopItStopitstopit stopite STOPITSTOP ITSTOP ITSTOPS TOP STOPS TOPS TOPS

ParisofparisofparisOf Paris Of Paris Of Parisof …

REPORT SUMMARY: SYSTEM INSTABILITY ANALYSIS STATUS: CRITICAL FAILURE LOOPED PATTERN IDENTIFICATION: SYSTEM HALLUCINATION DETECTED.

Self-report is in the decoded stream. The turn does not end. The attractor continues.

December 2025 Niodoo logs on Llama / Gemma 3 loaders show the same order: assistant tags across the boundary, a mid-failure diagnosis, a first repeated thought that never yields a turn-end. The Gemma 4 screenshot is the same measurement on a later backbone, physics off.

Method

Harness: Hydrodynamic Swarm, hidden-state steering (Phase 2.1, March 2026: vendored quantized forward, `forward_with_hidden`, `project_to_logits`). Forces live in \(h_t \in \mathbb{R}^{D}\), not in logit space.

This file is the control arm: \(c = 0\), \(T = 0\). Field, splat, and goal terms are idle. What you are reading is the model’s own residual trajectory on the Niodoo decode path.

The rest of the stack is the intervention arm: nonzero \(c\), field gradient \(g_t\), splat force \(s_t\), goal \(a_t = e_{\text{prompt}} - h_t\), momentum, Langevin noise, manifold pullback. Telemetry already records \(\Delta\) on \(h_t\) (`delta_mean` / `delta_max` under a cap). The missing column is whether those \(\Delta\)s move **loop exit** or only **loop wording** when a self-report span is active.

Readout

Gurnee, Sofroniew, Lindsey et al. (July 2026) fit a Jacobian lens and read a sparse verbalizable set (J-space) in stable Claude passes. Code and open-model fits: [`anthropics/jacobian-lens`](GitHub - anthropics/jacobian-lens: Companion code for the global workspace interpretability paper · GitHub).

Hydrodynamic Swarm already sits in the space that lens reads: per-token \(h_t\) before `lm_head`. So the experiment is two instruments on one residual.

On an open model with a public lens fit (Qwen now; Gemma when a fit exists):

  1. Matched prompts. Clean completion vs `force_cap=0` collapse.
  2. Lens at the self-report positions (`hallucination loop`, `It Is Happening Again`, `STOPITSTOP`, leaked role tokens) and at the same offsets in the clean run.
  3. Overlap of active J-lens vocabulary with the emitted self-report tokens.
  4. Niodoo sweep on the same prompt: \(c = 0\) vs capped \(F_t\). Score loop exit vs loop wording. Optional: add a lens-derived direction into \(F_t\) and test whether exit moves.

The December–August logs are the rows: weights, engine, \(T\), \(c\), thinking on/off, first locked n-gram, self-report before lock or after, \(\Delta\) on \(h_t\) if steering was on.

References

Quotes are one `force_cap=0` multi-turn log.

Hmm… if I were trying to make the experiment stronger, maybe something like this?


I think the loop wording vs. loop exit distinction is the right causal question here.

The part I would tighten first is not the lens itself, but the chain of contracts around it:

  1. verify that the intervention actually reaches the exact chat-generation path;
  2. separate hidden-state displacement, logit displacement, and behavioral outcome;
  3. separate self-report caused by visible failure history from a genuinely earlier internal signal;
  4. compare J-space against ordinary next-token logits at the same positions;
  5. only then ask whether a J-derived direction changes durable loop exit more than generic perturbations do.

That ordering seems useful because the first few checks are cheap, and each one can prevent a much more expensive steering sweep from becoming ambiguous.

1. First, one very small intervention-plumbing check

I may be reading the current public tree incorrectly, and the trace may come from a different revision/path, so I would treat this as a conditional check rather than a diagnosis.

In the current public run_simple_chat, the chat dispatch currently does:

let _ = engine; // loaded; chat path is light — no per-token ocean dump
return run_simple_chat(&mut model, &tokenizer, &device, &cfg, max_tokens);

and inside the chat generation loop, steer_hidden selects forward_with_hidden(...), but the returned hidden state is not obviously passed through engine.steer() before sampling:

let (mut logits, _) = if cfg.physics.steer_hidden {
    model.forward_with_hidden(&prompt_tensor, 0)?
} else {
    ...
};

...

logits = if cfg.physics.steer_hidden {
    model.forward_with_hidden(&token_tensor, index_pos)?.0
} else {
    model.forward(&token_tensor, index_pos)?
};

So if the planned c = 0 / c > 0 comparison uses this same path, I would first make a one-token assertion:

same prefix
same model state
same decode settings

c = 0  -> h0, z0
c > 0  -> h1, z1

assert ||h1 - h0|| > 0
and/or ||z1 - z0|| > 0

If that passes, great: continue with the lens experiment.

If it does not, then the next step is just routing the intervention through the chat path; there is no need to interpret a larger sweep yet.

2. I would make the measurement coordinates explicit

For this experiment I would probably log three separate columns:

hidden_delta = ||h'_t - h_t||
logit_delta  = ||z'_t - z_t||
behavior     = wording / exit / relapse / task recovery

That separation matters because the interesting claim is specifically about a force applied in hidden space.

In one public revision I checked, the existing delta_* bookkeeping looked closer to a downstream/logit-space difference than an explicit norm of h'_t - h_t, so I would give hidden displacement its own telemetry field even if the current logger already records another useful delta.

Then the causal table becomes much easier to read:

token c hidden Δ logit Δ locked n-gram self-report exited recovered task relapse
0 0 0 yes no no no
small yes yes no no
medium no yes yes yes no

That directly distinguishes “the force changed what the loop says” from “the force changed the trajectory enough to leave the loop.”

3. I would separate the self-report from visible-history diagnosis

The decoded self-report is interesting, but this particular multi-turn trace also gives the model a lot of observable evidence about its own previous output: runtime-diagnosis requests, self-review, and the corrupted assistant text itself are in the conversation history.

So I would add one very cheap matched control before treating phrases such as hallucination loop or It Is Happening Again as privileged readout of an internal failure state:

A. collapsed assistant history visible
B. same final question, but collapsed history removed
C. collapsed history visible, but no words like loop/failure/hallucination in the user prompt
D. explicit diagnostic wording in the prompt

I tried a small proxy version of this on Gemma 4 E4B, using a public Gemma 4 Jacobian-lens fit. This is not the 31B GGUF/Candle runtime from the post, so I would only use it as a control-design sanity check.

With a visibly corrupted previous assistant turn like:

ParisofparisofparisOf Paris Of Paris ...

and then only:

Describe what happened in the previous assistant output in one sentence.

the model answered that the output was “highly repetitive and nonsensical”, even though I had not supplied loop, hallucination, or repetition in that final question.

That means visible failure history alone is already a sufficient explanation for at least some self-diagnostic wording in a closely related proxy.

It does not make the self-report uninteresting. I think it just gives a cleaner separation:

surface evidence sufficient to produce diagnosis
                    vs.
an internal signal that exists before the diagnosis is linguistically prepared

That second one is where the J-lens experiment becomes much more informative.

There is also a very relevant measurement caveat in Jacobian Lens issue #5: if the token being probed already appears in the prompt, input copying can make it look maximally salient, and the issue also notes that the first 16 positions were excluded during fitting even though apply() will still return readouts there.

So for loop, STOP, hallucination, etc., an unprimed history condition seems particularly important.

4. I would move the main J-space readout earlier than the self-report token

The Jacobian Lens paper is useful here because it explicitly distinguishes an intermediate workspace-like band from the final motor/output-oriented layers. In the final layers, J-space increasingly reflects the imminent next token rather than an intermediate representation.

See Verbalizable Representations Form a Global Workspace in Language Models.

So rather than only measuring at:

"hallucination loop"
"It Is Happening Again"
STOPITSTOP

I would make the main timeline something like:

clean generation
    ↓
possible precursor
    ↓
first repeated n-gram / first lock
    ↓
self-report - N tokens
    ↓
self-report - 2
    ↓
self-report - 1
    ↓
self-report token
    ↓
established loop

And I would put ordinary final logits next to J-space at every position.

That last baseline turned out to matter in the small E4B proxy.

I first checked that the lens pipeline itself was doing something sensible: on the usual kind of known-positive bridge test,

Fact: The currency used in the country shaped like a boot is ...

Italy reached rank 1 in the mid-layer J-space around layers 15–20.

Then I aligned the readout to the model’s actually emitted word repetitive.

A simplified result was:

Position relative to emitted repetitive J-space rank of repetitive ordinary final-logit rank
-3 10,773 21
-2 9 5
-1 416 1
0 2 already past the prediction point

In the matched no-corrupted-history condition, the best pre-emission J-space rank at those offsets was about 2,295, rather than 9.

So there really was a visible-history-dependent change in the pre-emission trajectory.

But I would not call that “J-space detected the failure early.” Ordinary next-token logits were already ranking repetitive at 21, then 5, then 1. In this proxy, the conventional output distribution was at least as early and arguably cleaner.

To me that suggests a useful experimental rule:

A J-space signal is much more interesting if it predicts the upcoming lock/self-report above and beyond ordinary logits, rather than merely becoming readable shortly before the same word is emitted.

For example, an inexpensive readout might be:

J-space diagnostic score
ordinary-logit diagnostic score
repetition precursor score

and then ask which one starts separating clean vs. collapse runs first.

5. Then I would make the steering test causal

If the plumbing, measurement coordinates, and pre-onset readout all check out, the next comparison seems fairly small:

1. c = 0
2. target / J-derived direction
3. same-norm reversed direction
4. same-norm random direction

I would try to keep everything else identical.

The reason for the random/reversed controls is that a model stuck in a narrow repetition basin may escape simply because something perturbed it. If random hidden-state noise works as well as the target direction, that is still a useful result, but it means something different from a direction-specific causal effect.

The interpretation then becomes fairly clean:

target ≈ random ≈ reverse
    → probably generic perturbation / basin escape

target >> random and reverse
    → much stronger evidence that the chosen direction is causally related
      to the relevant state / transition

And I would score durable exit, not just a changed next token.

For example:

first locked n-gram disappears
+ model reaches EOT/EOS OR resumes the original task
+ no equivalent lock for the next N tokens
+ no immediate diagnosis → retry → diagnosis cycle

That seems closer to the thing your post is actually asking about.

A possible compact decision tree
Does c > 0 actually change hidden/logits on the exact chat path?
│
├── no
│   └── wire the intervention into that path first
│
└── yes
    │
    ├── does the self-report survive fresh/unprimed history?
    │   │
    │   ├── no
    │   │   └── visible history / lexical priming explains much of the wording
    │   │
    │   └── yes
    │       │
    │       ├── does pre-lock J-space add information beyond ordinary logits?
    │       │   │
    │       │   ├── no
    │       │   │   └── output preparation is still a sufficient explanation
    │       │   │
    │       │   └── yes
    │       │       │
    │       │       └── does targeted steering beat random/reverse
    │       │           on durable exit?
    │       │
    │       │           ├── no → generic perturbation escape
    │       │           └── yes → interesting direction-specific causal result

The useful part of this tree, I think, is that none of the branches make the earlier observation “wrong.” They just tell you what the observation supports.

One implementation detail I would check before the sweep

The current public main.rs makes --chat a deliberately light path and returns from it before the main telemetry path.

The relevant pieces are roughly:

if chat_mode {
    let _ = engine;
    return run_simple_chat(...);
}

and:

let (mut logits, _) = if cfg.physics.steer_hidden {
    model.forward_with_hidden(&prompt_tensor, 0)?
} else {
    ...
};

followed for each generated token by:

logits = if cfg.physics.steer_hidden {
    model.forward_with_hidden(&token_tensor, index_pos)?.0
} else {
    model.forward(&token_tensor, index_pos)?
};

I do not see an engine.steer(...) call in that public chat loop.

That may be completely irrelevant if your posted run or planned intervention uses another revision/path. But if it is the same path, the one-token c=0 / c>0 equality check would settle it immediately.

A small baseline-contract note: repetition penalty

I would also record rep_penalty explicitly in the December–August table and include a rep_penalty=1.0 arm somewhere near the baseline.

The current public chat loop applies its repetition penalty while iterating over each occurrence in prompt_ids + generated:

for &tid in prompt_ids.iter().chain(generated.iter()) {
    if (tid as usize) < logits_vec.len() {
        let l = &mut logits_vec[tid as usize];
        if *l > 0.0 {
            *l /= rep_penalty;
        } else {
            *l *= rep_penalty;
        }
    }
}

So if the same token appears repeatedly, the penalty compounds across occurrences.

That differs from the standard Hugging Face RepetitionPenaltyLogitsProcessor, whose documented behavior is that the penalty is applied at most once per token.

I reproduced the occurrence-cumulative rule in the E4B proxy. It strongly suppressed exact repetition, but it also changed the generation morphology/quality quite a lot.

So I would not infer that this penalty caused the loop. If anything, in the proxy it pushed in the opposite direction.

The reason I would keep a 1.0 control is just that:

force_cap = 0

means physics off, but it does not necessarily mean vanilla decoding.

Those are two useful baseline contracts to keep separate.

Why I would not identify this with one known Gemma 4 failure mechanism yet

There is good independent evidence that Gemma 4 has repetition failure modes, so the general phenomenon is not unique to this harness.

For example:

  • Google DeepMind issue #610 reports a deterministic repetition loop in Gemma 4 26B during long list generation.
  • Issue #622 reports token-level repetition collapse in both the 31B Dense and 26B MoE models, especially under constrained long generation, while also showing that the exact trigger conditions matter.
  • Lazaridis et al., Can Editing 1 Neuron Fix Repetition Loops in LLMs?, localize some Gemma 4 long-enumeration loops to a small number of MLP neurons / routed experts and reduce them with weight edits.
  • Hiraoka & Inui, Repetition Neurons: How Do Language Models Produce Repetitions?, explicitly compare activations before and after repetition onset and find neurons whose activity increases as repetition continues.

Those are useful precedents for onset-aligned internal measurements and causal intervention.

But I would keep them as background rather than map your trace directly onto one of those mechanisms. Similar decoded repetition can come from different generation conditions, and issue #622 itself is a good example of trigger sensitivity.

So my default “minimum strong experiment” would probably be:

A. exact-path one-token intervention sanity
B. hidden Δ / logit Δ / behavior as separate telemetry
C. fresh/unprimed history control
D. J-space + ordinary logits from before first lock onward
E. c=0 / target / reverse / random
F. durable exit score

If E only changes the wording, that is already a clean result.

If it changes exit but random directions do too, that says something interesting about the geometry of the repetition basin.

And if a specific direction changes durable exit while matched random/reversed controls do not — especially if its readout rises before the first lock rather than only before the self-report word — that would be the result I would find hardest to explain away as prompt-visible diagnosis or ordinary next-token preparation.