Skip to content

fix(serve_harness): a short answer is not a token attractor - #703

Merged
Kaden-Schutt merged 1 commit into
masterfrom
fix/serve-harness-attractor-short-answers
Sep 4, 2026
Merged

fix(serve_harness): a short answer is not a token attractor#703
Kaden-Schutt merged 1 commit into
masterfrom
fix/serve-harness-attractor-short-answers

Conversation

@Kaden-Schutt

Copy link
Copy Markdown
Collaborator

Summary

_token_attractor splits the visible answer on whitespace and fails the turn when maxfreq(first) > 0.50. A one-"word" answer — compact JSON {"name":"Alice","age":34,"city":"Lisbon"}, Paris, 43 — is a single token, so maxfreq is 1.0 by construction and the turn is flagged !ATTRACTOR.

The battery is sampled, so the same fixture passes when the model spaces its JSON and fails when it doesn't. hw-gate run 33862054891 (#686 re-run) failed the qwen3.8:27b-mq4-xt battery on a correct, recall-6/6, 17-token JSON answer; the same daemon md5 passed the same fixture two hours earlier with {"name": "Alice", ...}. The gate hard-floors on any attractor, so this is a random red on every PR whose battery draws a compact answer.

Change

The frequency tests (uniq, maxfreq on the first/last 128 tokens) now require a window of at least ATTRACTOR_MIN_WINDOW = 8 tokens — repetition needs room to repeat in; runaway/empty already cover degenerate short output. gram3 is unchanged (already needs 6). One function, one constant.

Evidence

input before after
{"name":"Alice","age":34,"city":"Lisbon"} attractor ok
Paris attractor ok
yes yes attractor ok
the ×8 attractor attractor
the ×40 attractor attractor
a b c ×20 (gram3 loop) attractor attractor
12-word normal sentence ok ok

The harness self-test (_self_test_attractor_channels, run at startup) gains the short-answer cases and an 8×-repeat that must still trip; passes.

Which surface(s) does this touch?

  • docs / CI / scripts — scripts/serve_harness.py only. No Rust, no kernels.

Note for the gate: run.py executes the harness from the PR checkout, so PRs pick this up on rebase.

_token_attractor splits the visible answer on whitespace and fails the turn when maxfreq(first) > 0.50. A one-"word" answer — compact JSON `{"name":"Alice","age":34,"city":"Lisbon"}`, `Paris`, `43` — is a single token, so maxfreq is 1.0 by construction and the turn is flagged. The battery is sampled, so the same fixture passes when the model happens to space its JSON and fails when it does not: hw-gate run 33862054891 failed the qwen3.8:27b-mq4-xt battery on a correct (recall 6/6) 17-token JSON answer, and the gate hard-floors on any attractor.

The frequency tests now require a window of at least ATTRACTOR_MIN_WINDOW (8) tokens — repetition needs room to repeat; runaway/empty already cover degenerate short output. gram3 is unchanged (it already needs 6). Self-test gains the short-answer cases and an 8x-repeat that must still trip.
@Kaden-Schutt
Kaden-Schutt merged commit cf87b0b into master Sep 4, 2026
13 checks passed
Kaden-Schutt added a commit that referenced this pull request Sep 4, 2026
Third defect from tonight's ladder, and the one with real teeth: the hardware
lanes execute `pr/scripts/serve_harness.py` — the branch's own copy of the
instrument that judges it.

#682 is a fork PR based on master from before #703, so its harness has no
`ATTRACTOR_MIN_WINDOW` guard. Run 33921475093 ran that stale copy and flagged
the 3-token answer `Answer: 43` as a token attractor on BOTH lanes, exiting 1
on a fixture whose own rows record finish=stop, gen=9, recall=1/1, attractor
false, empty false, runaway false. Two green lanes turned red and the rung
hard-floored on evidence that was correct.

Two consequences, one worse than the other:
- any PR older than a harness fix inherits that harness's bugs, which is a
  false-failure generator that gets worse as the harness improves
- a branch can weaken its own oracle, which is the part that must never be
  possible in a gate

Fixtures, registry, prompts and review.py already resolve against the gate
checkout for exactly this reason (see the `_gate_root` comment above the
battery-prompt resolution). serve_harness.py and redline_daemon_harness.py now
do too, including their existence preconditions. The PR still supplies the
binaries under test — only the instrument moved.

Test: `test_serve_harness_runs_from_gate_root_not_pr` writes a different
harness into each tree and asserts the gate's is the one invoked. 106/106
hw-gate tests pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant