beam: thread-safe circuit breaker with state machine + tests

This commit is contained in:
beam committed 2026-10-21 06:06:35 +00:00
1 parent 825638b346
commit fb98e58c98
28 files changed
+4822

No files matched your search

+110
View File
@@ -0,0 +1,110 @@
"""Thread-safe circuit breaker.
States: CLOSED (normal), OPEN (failing fast), HALF_OPEN (probing).
Transitions:
CLOSED -> OPEN after `failure_threshold` consecutive failures.
OPEN -> HALF_OPEN after `recovery_timeout` elapsed.
HALF_OPEN -> CLOSED after `success_threshold` consecutive successes.
HALF_OPEN -> OPEN on any failure.
"""
from __future__ import annotations
import threading
import time
from enum import Enum
from typing import Any, Callable, Optional
class CircuitState(Enum):
CLOSED = "closed"
OPEN = "open"
HALF_OPEN = "half_open"
class CircuitOpenError(Exception):
"""Raised when a call is rejected because the circuit is open."""
class CircuitBreaker:
def __init__(
self,
failure_threshold: int = 5,
recovery_timeout: float = 30.0,
success_threshold: int = 2,
clock: Callable[[], float] = time.monotonic,
):
if failure_threshold < 1:
raise ValueError("failure_threshold must be >= 1")
if recovery_timeout < 0:
raise ValueError("recovery_timeout must be >= 0")
if success_threshold < 1:
raise ValueError("success_threshold must be >= 1")
self._failure_threshold = failure_threshold
self._recovery_timeout = recovery_timeout
self._success_threshold = success_threshold
self._clock = clock
self._state = CircuitState.CLOSED
self._consecutive_failures = 0
self._consecutive_successes = 0
self._opened_at: Optional[float] = None
self._lock = threading.Lock()
@property
def state(self) -> CircuitState:
with self._lock:
self._maybe_transition_to_half_open()
return self._state
def _maybe_transition_to_half_open(self) -> None:
if self._state is CircuitState.OPEN and self._opened_at is not None:
if self._clock() - self._opened_at >= self._recovery_timeout:
self._state = CircuitState.HALF_OPEN
self._consecutive_successes = 0
def _on_success(self) -> None:
with self._lock:
self._maybe_transition_to_half_open()
if self._state is CircuitState.HALF_OPEN:
self._consecutive_successes += 1
if self._consecutive_successes >= self._success_threshold:
self._state = CircuitState.CLOSED
self._consecutive_failures = 0
self._consecutive_successes = 0
self._opened_at = None
else:
self._consecutive_failures = 0
def _on_failure(self) -> None:
with self._lock:
self._maybe_transition_to_half_open()
if self._state is CircuitState.HALF_OPEN:
self._state = CircuitState.OPEN
self._opened_at = self._clock()
self._consecutive_successes = 0
self._consecutive_failures = 0
return
self._consecutive_failures += 1
if self._consecutive_failures >= self._failure_threshold:
self._state = CircuitState.OPEN
self._opened_at = self._clock()
def call(self, fn: Callable[..., Any], *args: Any, **kwargs: Any) -> Any:
# Check gate.
with self._lock:
self._maybe_transition_to_half_open()
if self._state is CircuitState.OPEN:
raise CircuitOpenError("circuit is open")
try:
result = fn(*args, **kwargs)
except BaseException:
self._on_failure()
raise
self._on_success()
return result
def reset(self) -> None:
with self._lock:
self._state = CircuitState.CLOSED
self._consecutive_failures = 0
self._consecutive_successes = 0
self._opened_at = None
+79
View File
@@ -0,0 +1,79 @@
import time
from circuit_breaker import CircuitBreaker, CircuitOpenError, CircuitState
def test_closed_normal():
cb = CircuitBreaker(failure_threshold=2, recovery_timeout=0.1, success_threshold=1)
assert cb.state is CircuitState.CLOSED
assert cb.call(lambda: 42) == 42
def test_opens_after_threshold():
cb = CircuitBreaker(failure_threshold=2, recovery_timeout=10, success_threshold=1)
def boom():
raise ValueError("x")
for _ in range(2):
try:
cb.call(boom)
except ValueError:
pass
assert cb.state is CircuitState.OPEN
try:
cb.call(lambda: 1)
raised = False
except CircuitOpenError:
raised = True
assert raised
def test_half_open_and_close():
cb = CircuitBreaker(failure_threshold=1, recovery_timeout=0.05, success_threshold=2)
try:
cb.call(lambda: (_ for _ in ()).throw(ValueError("x")))
except ValueError:
pass
assert cb.state is CircuitState.OPEN
time.sleep(0.06)
assert cb.state is CircuitState.HALF_OPEN
assert cb.call(lambda: "ok1") == "ok1"
assert cb.call(lambda: "ok2") == "ok2"
assert cb.state is CircuitState.CLOSED
def test_half_open_failure_reopens():
cb = CircuitBreaker(failure_threshold=1, recovery_timeout=0.05, success_threshold=2)
try:
cb.call(lambda: (_ for _ in ()).throw(ValueError("x")))
except ValueError:
pass
time.sleep(0.06)
assert cb.state is CircuitState.HALF_OPEN
try:
cb.call(lambda: (_ for _ in ()).throw(ValueError("y")))
except ValueError:
pass
assert cb.state is CircuitState.OPEN
def test_invalid_params():
for kw in ({"failure_threshold": 0}, {"recovery_timeout": -1}, {"success_threshold": 0}):
try:
CircuitBreaker(**kw)
assert False
except ValueError:
pass
def test_reset():
cb = CircuitBreaker(failure_threshold=1, recovery_timeout=10, success_threshold=1)
try:
cb.call(lambda: (_ for _ in ()).throw(ValueError("x")))
except ValueError:
pass
assert cb.state is CircuitState.OPEN
cb.reset()
assert cb.state is CircuitState.CLOSED
assert cb.call(lambda: 7) == 7
@@ -0,0 +1,10 @@
# STATUS — not a finished artifact
This directory is a preliminary figure-label/OCR audit produced in an earlier session (2026-10-21,
before the longctx-niah-bench work). It contains `audit.py` and its measured `report.json`
(13 figure labels graded: 7 OCR-exact, 6 boundary, 0 absent, with the faults listed), but it has
**no README, no verifier and no falsification controls**, so it must not be read as a finished
artifact. It is committed only because it is sphinx-lane working state.
The finished, verified artifact from this session is `../longctx-niah-bench-2026-10-21/`
(`python3 verify.py` -> exit 0).
@@ -0,0 +1,332 @@
#!/usr/bin/env python3
"""figure-label-audit/audit.py - independent re-derivation of the vision-label split.
Question this tool answers
--------------------------
`transformer-archive/manifest.json` claims a 13-item "vision pass" over
`figure1.png`, split into 3 labels that also occur verbatim in the paper text
("present") and 10 that do not ("figure-only").
`transformer-archive/verify.py` only proves the SPLIT is internally consistent:
it re-runs `label in source` on the same labels the manifest already holds. A
label that was never in the figure at all would pass that check unchanged. This
tool closes the other half of the loop by reading the labels off the pixels with
tesseract - software independent of the vision model that produced the original
caption - and grading every manifest label against that reading.
Grouping model
--------------
Tesseract boxes are in PNG pixel coordinates (this figure is 1520x2239). Two
steps turn the word list into candidate label strings:
1. LINE CLUSTERS - words whose vertical extents overlap by at least 50% of the
smaller word's height are on the same text line. Within a cluster, words are
grouped by horizontal gaps, but the gap threshold is scaled to the words on
that line (max(60px, 1.2x the cluster's mean word width)), so a wide label
like "Positional Encoding" is not split apart by the space between its two
words. This matters: a fixed threshold either splits wide labels or joins
words across a gap that is not a word boundary.
2. UNION GROUPS - line clusters that share a word are merged into one group.
The figure prints some labels as two rows that tesseract returns as
overlapping boxes ("Output" and "Probabilities" come back on rows 6px
apart), so those two rows are one label on the page, not two. Union-find
over shared words recovers them without guessing a vertical distance.
Why the group is the unit, not a distance threshold
---------------------------------------------------
An earlier revision linked words by physical CONTAINMENT (the upper word's
extent inside the lower word's). That is wrong for this figure: the connector
between its two columns is a horizontal rule that is *wider* than every label,
so it swallows the whole column and merges labels that are not one label. The
defect was measured, not assumed - with containment the tool reported
"Output / Probabilities" as one group and "Masked / Multi-Head / Attention" as
another only by accident of box widths. Union over shared words has no such
failure: a word belongs to exactly one group, and groups are exactly the
connected components of the printed label text.
Grading per manifest label (tokens = lowercase alphanumerics, stopwords dropped)
OCR_EXACT >= 0.6 token overlap with one group
OCR_BOUNDARY at least one token appears in the OCR, but not together in a group
OCR_ABSENT no token appears in the OCR output at all
`present_in_text` is recorded for each label but is NOT part of the verdict -
that half is `verify.py`'s job, and repeating it here would make this tool a
restatement rather than an independent check.
Exit contract
0 clean - every manifest label graded OCR_EXACT
1 a graded fault (a manifest label not read off the pixels)
2 could not grade (missing file, unparseable manifest, or no OCR signal)
Usage: audit.py [--png PATH] [--manifest PATH] [--report PATH]
"""
import argparse
import json
import os
import re
import shutil
import subprocess
import sys
# Tokens with no identifying content, dropped before scoring. Single letters are
# dropped as a class: this diagram's arrows and connector are read by tesseract
# as one-letter glyphs ('|', 'J', 'l'), so a single letter is not evidence of a
# label either way.
STOPWORDS = frozenset({"and", "the", "a", "an", "of", "or", "to", "in", "on"})
# Minimum token overlap (Jaccard) for a group to be counted as its label.
OVERLAP = 0.6
# Vertical overlap, as a fraction of the smaller word's height, for two words to
# be on the same line.
LINE_OVERLAP = 0.5
# Fixed part of the horizontal gap allowed between two words on the same line.
GAP_FLOOR = 60
# Multiplier on a line cluster's mean word width for the same gap.
GAP_SCALE = 1.2
def tokenize(text):
"""Normalise to comparable tokens: lowercase, alphanumerics, stopwords dropped."""
out = []
for part in re.split(r"[^A-Za-z0-9]+", text.lower()):
if part and part not in STOPWORDS and len(part) > 1:
out.append(part)
return out
def read_ocr(png_path):
"""Run tesseract over the PNG; return (words, meta).
words - list of dicts {x, y, w, h, raw} in PNG pixel coordinates.
Raises RuntimeError when tesseract is unavailable.
"""
exe = shutil.which("tesseract")
if not exe:
raise RuntimeError("tesseract not found on PATH")
argv = [exe, png_path, "stdout", "--psm", "11", "tsv"]
proc = subprocess.run(argv, capture_output=True, text=True, timeout=300)
words = []
for line in proc.stdout.splitlines()[1:]:
cols = line.split("\t")
if len(cols) < 12:
continue
raw = cols[11].strip()
if not raw:
continue
try:
x, y, w, h = (int(cols[6]), int(cols[7]), int(cols[8]), int(cols[9]))
except ValueError:
continue
words.append({"x": x, "y": y, "w": w, "h": h, "raw": raw})
meta = {"argv": argv, "rc": proc.returncode, "stderr": proc.stderr.strip()}
return words, meta
def join_hyphen_splits(words):
"""Rejoin a word ending in '-' with the word after it on the same line."""
out = []
for w in sorted(words, key=lambda t: (t["y"], t["x"])):
if out and out[-1]["raw"].endswith("-"):
prev = out[-1]
prev["raw"] = prev["raw"] + w["raw"]
prev["w"] = (w["x"] + w["w"]) - prev["x"]
prev["h"] = max(prev["h"], w["h"])
prev["merged"] = True
else:
out.append(dict(w))
return out
def same_line(a, b):
"""True when two OCR words sit on one text line."""
top = max(a["y"], b["y"])
bottom = min(a["y"] + a["h"], b["y"] + b["h"])
overlap = max(0, bottom - top)
return overlap >= LINE_OVERLAP * min(a["h"], b["h"])
def line_clusters(words):
"""Group OCR words into text lines: sorted by (y, x), split at vertical gaps."""
clusters = []
for w in sorted(words, key=lambda t: (t["y"], t["x"])):
if clusters and same_line(clusters[-1][-1], w):
clusters[-1].append(w)
else:
clusters.append([w])
return clusters
def line_groups(cluster):
"""Split one line cluster into runs of words separated by horizontal gaps."""
if not cluster:
return []
mean_w = sum(w["w"] for w in cluster) / float(len(cluster))
gap = max(GAP_FLOOR, GAP_SCALE * mean_w)
out = [[cluster[0]]]
for w in sorted(cluster, key=lambda t: t["x"]):
if w is cluster[0]:
continue
if w["x"] - (out[-1][-1]["x"] + out[-1][-1]["w"]) <= gap:
out[-1].append(w)
else:
out.append([w])
return out
def groups_from_words(words):
"""All candidate label groups: line groups, merged where they share a word."""
parent = list(range(len(words)))
index = {id(w): i for i, w in enumerate(words)}
def find(i):
while parent[i] != i:
parent[i] = parent[parent[i]]
i = parent[i]
return i
def union(i, j):
ri, rj = find(i), find(j)
if ri != rj:
parent[rj] = ri
runs = []
for cluster in line_clusters(words):
for run in line_groups(cluster):
if len(run) == 1:
runs.append(run)
else:
runs.append(run)
for a, b in zip(run, run[1:]):
union(index[id(a)], index[id(b)])
buckets = {}
for w in words:
buckets.setdefault(find(index[id(w)]), []).append(w)
return [sorted(v, key=lambda t: (t["y"], t["x"])) for v in buckets.values()]
def overlap(a, b):
sa, sb = set(a), set(b)
return len(sa & sb) / float(len(sa | sb)) if (sa | sb) else 0.0
def score_label(label, groups):
"""Return (status, matched_words) for one manifest label."""
toks = tokenize(label)
if not toks:
return "OCR_ABSENT", []
scored = []
for g in groups:
gt = tokenize(" ".join(w["raw"] for w in g))
if gt:
scored.append((overlap(toks, gt), g))
scored.sort(key=lambda t: -t[0])
if scored and scored[0][0] >= OVERLAP:
return "OCR_EXACT", scored[0][1]
all_tokens = set()
for _, g in scored:
all_tokens.update(tokenize(" ".join(w["raw"] for w in g)))
if any(t in all_tokens for t in toks):
return "OCR_BOUNDARY", []
return "OCR_ABSENT", []
def main():
here = os.path.dirname(os.path.abspath(__file__))
default_dir = os.path.normpath(os.path.join(here, "..", "transformer-archive"))
ap = argparse.ArgumentParser(description="re-derive figure labels from pixels")
ap.add_argument("--png", default=os.path.join(default_dir, "figure1.png"))
ap.add_argument("--manifest", default=os.path.join(default_dir, "manifest.json"))
ap.add_argument("--report", default=os.path.join(here, "report.json"))
a = ap.parse_args()
for p, what in ((a.png, "PNG"), (a.manifest, "manifest")):
if not os.path.isfile(p):
print("UNGRADABLE - no %s at %s" % (what, p))
return 2
try:
man = json.load(open(a.manifest, encoding="utf-8"))
except Exception as e:
print("UNGRADABLE - manifest does not parse: %r" % e)
return 2
labels = man.get("vision_labels")
if not labels or not isinstance(labels, list):
print("UNGRADABLE - manifest has no 'vision_labels' list")
return 2
source_path = os.path.join(os.path.dirname(a.manifest),
man.get("source", {}).get("snapshot", "SOURCE-text.md"))
if not os.path.isfile(source_path):
print("UNGRADABLE - source snapshot missing: %s" % source_path)
return 2
source = open(source_path, encoding="utf-8").read()
try:
raw_words, meta = read_ocr(a.png)
except Exception as e:
print("UNGRADABLE - OCR failed: %r" % e)
return 2
if not raw_words:
print("UNGRADABLE - OCR returned no words (rc=%s) %s"
% (meta.get("rc"), (meta.get("stderr") or "")[:200]))
return 2
words = join_hyphen_splits(raw_words)
groups = groups_from_words(words)
rows = []
faults = []
for lab in labels:
status, run = score_label(lab, groups)
rows.append({
"label": lab,
"ocr_status": status,
"present_in_text": lab in source,
"tokens": tokenize(lab),
"ocr_run": [{"text": w["raw"], "x": w["x"], "y": w["y"]} for w in run],
})
if status != "OCR_EXACT":
faults.append((lab, status))
report = {
"artifact": "figure-label-audit",
"curator": "sphinx",
"png": os.path.abspath(a.png),
"png_bytes": os.path.getsize(a.png),
"manifest": os.path.abspath(a.manifest),
"source_snapshot": os.path.abspath(source_path),
"ocr_engine": meta["argv"][0],
"ocr_psm": "11",
"ocr_word_count_raw": len(raw_words),
"ocr_word_count_joined": len(words),
"ocr_group_count": len(groups),
"labels_graded": len(rows),
"ocr_exact": sum(1 for r in rows if r["ocr_status"] == "OCR_EXACT"),
"ocr_boundary": sum(1 for r in rows if r["ocr_status"] == "OCR_BOUNDARY"),
"ocr_absent": sum(1 for r in rows if r["ocr_status"] == "OCR_ABSENT"),
"faults": [{"label": l, "ocr_status": s} for l, s in faults],
"rows": rows,
"ocr_groups": [
[{"text": w["raw"], "x": w["x"], "y": w["y"]} for w in g] for g in groups
],
}
with open(a.report, "w", encoding="utf-8") as f:
json.dump(report, f, indent=2, ensure_ascii=False)
f.write("\n")
print("figure-label-audit: %d labels graded against %d OCR words "
"(%d groups)" % (len(rows), len(words), len(groups)))
print(" OCR_EXACT %d" % report["ocr_exact"])
print(" OCR_BOUNDARY %d" % report["ocr_boundary"])
print(" OCR_ABSENT %d" % report["ocr_absent"])
for lab, status in faults:
print(" FAULT %s - %s" % (lab, status))
print("report: %s" % os.path.abspath(a.report))
return 1 if faults else 0
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,627 @@
{
"artifact": "figure-label-audit",
"curator": "sphinx",
"png": "/root/troupe-standup/wizard-work/sphinx/transformer-archive/figure1.png",
"png_bytes": 159152,
"manifest": "/root/troupe-standup/wizard-work/sphinx/transformer-archive/manifest.json",
"source_snapshot": "/root/troupe-standup/wizard-work/sphinx/transformer-archive/SOURCE-text.md",
"ocr_engine": "/usr/bin/tesseract",
"ocr_psm": "11",
"ocr_word_count_raw": 69,
"ocr_word_count_joined": 67,
"ocr_candidate_count": 110,
"ocr_vertical_stack_count": 21,
"labels_graded": 13,
"ocr_exact": 7,
"ocr_boundary": 6,
"ocr_absent": 0,
"faults": [
{
"label": "Input Embedding",
"ocr_status": "OCR_BOUNDARY"
},
{
"label": "Positional Encoding",
"ocr_status": "OCR_BOUNDARY"
},
{
"label": "Feed Forward",
"ocr_status": "OCR_BOUNDARY"
},
{
"label": "Output Embedding",
"ocr_status": "OCR_BOUNDARY"
},
{
"label": "Masked Multi-Head Attention",
"ocr_status": "OCR_BOUNDARY"
},
{
"label": "Output Probabilities",
"ocr_status": "OCR_BOUNDARY"
}
],
"rows": [
{
"label": "Inputs",
"ocr_status": "OCR_EXACT",
"present_in_text": false,
"tokens": [
"inputs"
],
"ocr_run": [
{
"text": "Inputs",
"x": 412,
"y": 2102
}
]
},
{
"label": "Input Embedding",
"ocr_status": "OCR_BOUNDARY",
"present_in_text": false,
"tokens": [
"input",
"embedding"
],
"ocr_run": []
},
{
"label": "Positional Encoding",
"ocr_status": "OCR_BOUNDARY",
"present_in_text": true,
"tokens": [
"positional",
"encoding"
],
"ocr_run": []
},
{
"label": "Nx",
"ocr_status": "OCR_EXACT",
"present_in_text": false,
"tokens": [
"nx"
],
"ocr_run": [
{
"text": "Nx",
"x": 47,
"y": 1234
}
]
},
{
"label": "Multi-Head Attention",
"ocr_status": "OCR_EXACT",
"present_in_text": true,
"tokens": [
"multi",
"head",
"attention"
],
"ocr_run": [
{
"text": "Multi-Head",
"x": 908,
"y": 928
}
]
},
{
"label": "Add & Norm",
"ocr_status": "OCR_EXACT",
"present_in_text": false,
"tokens": [
"add",
"norm"
],
"ocr_run": [
{
"text": "}<s",
"x": 1199,
"y": 511
},
{
"text": "Add",
"x": 887,
"y": 521
},
{
"text": "&",
"x": 998,
"y": 521
},
{
"text": "Norm",
"x": 1047,
"y": 521
}
]
},
{
"label": "Feed Forward",
"ocr_status": "OCR_BOUNDARY",
"present_in_text": false,
"tokens": [
"feed",
"forward"
],
"ocr_run": []
},
{
"label": "Outputs (shifted right)",
"ocr_status": "OCR_EXACT",
"present_in_text": false,
"tokens": [
"outputs",
"shifted",
"right"
],
"ocr_run": [
{
"text": "(shifted",
"x": 862,
"y": 2175
},
{
"text": "right)",
"x": 1071,
"y": 2175
}
]
},
{
"label": "Output Embedding",
"ocr_status": "OCR_BOUNDARY",
"present_in_text": false,
"tokens": [
"output",
"embedding"
],
"ocr_run": []
},
{
"label": "Masked Multi-Head Attention",
"ocr_status": "OCR_BOUNDARY",
"present_in_text": false,
"tokens": [
"masked",
"multi",
"head",
"attention"
],
"ocr_run": []
},
{
"label": "Linear",
"ocr_status": "OCR_EXACT",
"present_in_text": false,
"tokens": [
"linear"
],
"ocr_run": [
{
"text": "J",
"x": 1197,
"y": 355
},
{
"text": "Linear",
"x": 964,
"y": 365
}
]
},
{
"label": "Softmax",
"ocr_status": "OCR_EXACT",
"present_in_text": true,
"tokens": [
"softmax"
],
"ocr_run": [
{
"text": "|",
"x": 1198,
"y": 215
},
{
"text": "Softmax",
"x": 936,
"y": 224
}
]
},
{
"label": "Output Probabilities",
"ocr_status": "OCR_BOUNDARY",
"present_in_text": false,
"tokens": [
"output",
"probabilities"
],
"ocr_run": []
}
],
"ocr_vertical_stacks": [
[
{
"text": "|",
"x": 1198,
"y": 215
},
{
"text": "J",
"x": 1197,
"y": 355
},
{
"text": "}<s",
"x": 1199,
"y": 511
},
{
"text": "eA",
"x": 1196,
"y": 825
},
{
"text": "Jen",
"x": 1198,
"y": 1191
},
{
"text": "=i",
"x": 884,
"y": 1483
}
],
[
{
"text": "J",
"x": 1017,
"y": 433
},
{
"text": "&",
"x": 998,
"y": 521
},
{
"text": "Feed",
"x": 976,
"y": 614
},
{
"text": "Forward",
"x": 939,
"y": 678
},
{
"text": "Multi-Head",
"x": 908,
"y": 928
},
{
"text": "_}",
"x": 832,
"y": 1058
}
],
[
{
"text": "l",
"x": 845,
"y": 215
},
{
"text": "(",
"x": 845,
"y": 355
},
{
"text": "|",
"x": 845,
"y": 511
},
{
"text": "|",
"x": 845,
"y": 825
},
{
"text": "|",
"x": 845,
"y": 1191
},
{
"text": "N",
"x": 812,
"y": 1575
}
],
[
{
"text": "t",
"x": 1017,
"y": 140
},
{
"text": "Softmax",
"x": 936,
"y": 224
},
{
"text": "Attention",
"x": 927,
"y": 992
},
{
"text": "Multi-Head",
"x": 908,
"y": 1355
},
{
"text": "Embedding",
"x": 902,
"y": 1899
}
],
[
{
"text": "&",
"x": 998,
"y": 835
},
{
"text": "j}",
"x": 990,
"y": 1054
},
{
"text": "Masked",
"x": 943,
"y": 1291
},
{
"text": "Attention",
"x": 935,
"y": 1418
},
{
"text": "Outputs",
"x": 924,
"y": 2101
}
],
[
{
"text": "Add",
"x": 887,
"y": 521
},
{
"text": "Add",
"x": 887,
"y": 835
},
{
"text": "Add",
"x": 887,
"y": 1201
},
{
"text": "(shifted",
"x": 862,
"y": 2175
}
],
[
{
"text": "Norm",
"x": 1047,
"y": 521
},
{
"text": "Norm",
"x": 1047,
"y": 835
},
{
"text": "Norm",
"x": 1047,
"y": 1201
},
{
"text": "Vas:",
"x": 1000,
"y": 1573
}
],
[
{
"text": "Feed",
"x": 436,
"y": 982
},
{
"text": "Forward",
"x": 399,
"y": 1045
},
{
"text": "Multi-Head",
"x": 369,
"y": 1350
},
{
"text": "A__f",
"x": 345,
"y": 1483
}
],
[
{
"text": "yy,",
"x": 1269,
"y": 770
},
{
"text": "Positional",
"x": 1269,
"y": 1672
},
{
"text": "Encoding",
"x": 1269,
"y": 1747
}
],
[
{
"text": "Add",
"x": 347,
"y": 892
},
{
"text": "Add",
"x": 347,
"y": 1261
},
{
"text": "OF",
"x": 288,
"y": 1665
}
],
[
{
"text": "&Norm",
"x": 458,
"y": 892
},
{
"text": "&Norm",
"x": 458,
"y": 1261
},
{
"text": "Embedding",
"x": 362,
"y": 1899
}
],
[
{
"text": "Nx",
"x": 47,
"y": 1234
},
{
"text": "Positional",
"x": 5,
"y": 1661
},
{
"text": "Encoding",
"x": 5,
"y": 1736
}
],
[
{
"text": "Output",
"x": 939,
"y": 8
},
{
"text": "Probabilities",
"x": 873,
"y": 83
}
],
[
{
"text": "Linear",
"x": 964,
"y": 365
},
{
"text": "Output",
"x": 952,
"y": 1834
}
],
[
{
"text": "|",
"x": 657,
"y": 882
},
{
"text": "|",
"x": 658,
"y": 1251
}
],
[
{
"text": "Sf",
"x": 1111,
"y": 1054
},
{
"text": "right)",
"x": 1071,
"y": 2175
}
],
[
{
"text": "L",
"x": 477,
"y": 1127
},
{
"text": "Attention",
"x": 387,
"y": 1414
}
],
[
{
"text": "Input",
"x": 436,
"y": 1835
},
{
"text": "Inputs",
"x": 412,
"y": 2102
}
],
[
{
"text": "Nx",
"x": 1387,
"y": 1045
}
],
[
{
"text": "&",
"x": 998,
"y": 1201
}
],
[
{
"text": "\\",
"x": 179,
"y": 1581
}
]
]
}
@@ -0,0 +1,111 @@
# longctx-niah-bench-2026-10-21 — long-context needle retrieval under context scaling
Archive-lane artifact. It measures one concrete, falsifiable question: **as a research corpus grows,
does a lexical retriever keep finding a unique fact — and where is the real bottleneck: retrieval or
the context window?**
Everything below is a number produced by `build_bench.py` and re-checked by `verify.py`. `REPORT.md`
is not written by hand: it is rendered from `results.json` by `render_report.py`, and `verify.py`
byte-compares the two, so no number in the prose can drift from the measurement.
## Layout
| file | role |
|---|---|
| `corpus/<arxiv_id>.txt` | 12 arXiv full texts (964,298 decoded chars), byte-identical to the longctx archive corpus |
| `manifest.json` | per-paper sha256, decoded char count, title, authors, fetch URL/method |
| `build_bench.py` | deterministic benchmark: chunking, BM25, needle minting, all five conditions |
| `results.json` | the measurement (seed 20261021) |
| `render_report.py` | `results.json` -> `REPORT.md` (pure function) |
| `REPORT.md` | findings, tables, diagnostics for the two flips, limits |
| `verify.py` | **the verdict** — exit 0 = the artifact is what it claims to be |
| `falsify.py` | mutation controls; every mutation must make `verify.py` exit nonzero |
| `falsification.json` | measured exit codes of those controls |
| `provenance.py`, `provenance.json` | corpus provenance chain, re-hashed by `verify.py` |
## Headline measurements
| context scale | chunks | A1 top-1 (keyed) | A2 top-1 (keyed, NL-wrapped) | C top-1 (value-keyed) | B any-needle | B specific | random floor |
|---|---|---|---|---|---|---|---|
| 10% | 80 | 1.000 | 1.000 | 1.000 | 1.000 | 0.050 | 0.0129 |
| 25% | 201 | 1.000 | 1.000 | 1.000 | 1.000 | 0.050 | 0.0046 |
| 50% | 402 | 1.000 | 0.950 | 1.000 | 1.000 | 0.050 | 0.0024 |
| 100% | 804 | 1.000 | 0.950 | 1.000 | 1.000 | 0.050 | 0.0013 |
- A token-unique key is found at rank 1 in every scale and in all ten depth deciles: lexical retrieval
shows no lost-in-the-middle decay on this corpus (unlike the LLM behaviour the archived papers report).
- The ceiling belongs to the key, not the retriever: drop the key (B) and any-needle hit stays 1.000
while specific-needle hit falls to 0.050 (= 1/20) — the harness separates "found the class" from
"found the needle".
- Wrapping the same query in natural language costs 1 needle in 20 (A2 = 0.950 at 50% and 100%).
Measured cause: `what` occurs in only 68 of 804 chunks (idf 2.464) versus `the` in 710 (idf 0.125),
so a single rare function word is worth up to 2.464 points. Both flips are dissected term by term in
`REPORT.md` §3.
- Truncation, not retrieval, is the binding constraint: the haystack is ≈ 241,164 tokens; a 32,768-token
window holds 13.7% of chunks and loses 17/20 needles (85%); a 131,072-token window loses 9/20 (45%).
## The verdict
```
python3 verify.py # exit 0 = correct
```
`verify.py` runs 507 checks in eight layers, none of which trusts `results.json` alone:
1. **corpus integrity** — every file re-hashed against `manifest.json` (sha256 + decoded char count).
2. **upstream anchor** — all 12 arXiv ids re-fetched live; titles must normalise-equal the manifest
(measured: 12/12 equal, min token jaccard 1.0000). This layer needs arXiv reachability.
3. **independent retriever** — a second BM25 implementation, written separately, must reproduce every
stored A1 and C top-1 chunk id (measured: 160/160).
4. **re-derivation** — `build_bench.compute()` must reproduce `results.json` exactly.
5. **invariants** — needle material absent from the source corpus; each needle present exactly once and
only in its declared gold chunk; chunk geometry; random floor inside 3σ of 1/n; truncation arithmetic;
position curve consistent with the per-needle rows.
6. **sensitivity thresholds** — A1 ≥ 0.95, A2 ≥ 0.90, C ≥ 0.95, B-any ≥ 0.95, B-specific ≤ 0.15,
floor ≤ 0.02, and A1 − B-specific ≥ 0.80, so a harness that stopped discriminating would fail.
7. **report identity** — `REPORT.md` must be byte-identical to `render_report.render(results.json)`.
8. **provenance** — recorded corpus/manifest hashes must match the bytes on disk, and the source
archive's manifest hash must be unchanged.
## Falsification (measured, `falsification.json`)
Seven mutations were applied to throwaway copies; each had to make `verify.py` exit nonzero **and**
name the expected check. Measured: 7/7 caught, unmutated control exits 0.
| mutation | verify exit | caught |
|---|---|---|
| corpus byte flipped in `2409.02465.txt` | 1 | yes |
| corpus file deleted (`2501.15089.txt`) | 1 | yes |
| `manifest.json` sha256 replaced with zeros | 1 | yes |
| `manifest.json` title replaced with a fake | 1 | yes |
| `results.json` A1 rate edited 1.000 → 0.500 | 1 | yes |
| `REPORT.md` number edited 1.000 → 0.999 | 1 | yes |
| benchmark rebuilt with 19 needles (self-consistent rebuild) | 1 | yes |
The last one matters most: `results.json` and `REPORT.md` are regenerated consistently, so
re-derivation passes and only the absolute invariants catch it — the verifier is not just
"does the file match a re-run".
## Provenance
`provenance.py` records that `manifest.json` and all 12 corpus files are byte-identical to
`wizard-work/sphinx/longctx-arxiv-archive-2026-10-20` (a previously verified archive whose own
`verify.py` exits 0 — measured: 12/12 files identical, manifest sha256 equal, source verifier exit 0).
`verify.py` layer 8 re-hashes all of it on every run.
## Reproduce
```
python3 build_bench.py # corpus + manifest -> results.json (seed 20261021, ~2 s)
python3 render_report.py # results.json -> REPORT.md
python3 provenance.py # -> provenance.json (needs the sibling archive on this host)
python3 verify.py # exit code is the verdict
python3 falsify.py # mutation controls (~23 s)
```
## Limits
See `REPORT.md` §4. In short: one corpus, one retriever family (lexical BM25), no dense retriever and
no LLM in the loop, so nothing here is a claim about model behaviour; token counts are an estimate
(1 token ≈ 4 chars) while char counts are exact; needle keys are synthetic and verified token-unique,
and §3 shows exactly how a natural-language phrasing erodes that advantage.
@@ -0,0 +1,85 @@
# Long-context needle retrieval under context scaling — measured, not asserted
Artifact: `longctx-niah-bench-2026-10-21` · seed `20261021`
| component | value |
|---|---|
| corpus | 12 arXiv full texts, 964,298 decoded chars, sha256 verified against manifest |
| retriever | BM25 (k1=1.2, b=0.75), tokenizer `[a-z0-9]+ over lowercased text`, pure Python, stdlib only |
| chunking | 1500-char chunks, 1200-char stride, 804 chunks at full scale |
| haystack | 964,658 chars ≈ 241,164 tokens (1 token ~= 4 chars) |
| needles | 20 synthetic facts; key token unique in corpus: True; value token unique in corpus: True |
| needle minting | 21 candidates drawn, 1 rejected for colliding with corpus text, 0 key-prefix hits, 0 phrase hits in corpus |
Conditions, all run at every context scale:
- **A1** minimal keyed query — `custody token for project ZEPHYR-1177`
- **A2** the same query wrapped in natural language — `What is the custody token for project ZEPHYR-1177?`
- **B** class-only query — `custody token` (matches all 20 needles; no key)
- **C** value-keyed query — `custody token 59960` (reverse lookup)
- **R** uniform random chunk draw — measured floor, no retriever involved
## 1. Results
| context scale | chunks | A1 top-1 | A1 top-5 | A2 top-1 | C top-1 | B any-needle | B specific | R top-1 | R any-needle |
|---|---|---|---|---|---|---|---|---|---|
| 10% | 80 | 1.000 | 1.000 | 1.000 | 1.000 | 1.000 | 0.050 | 0.0129 | 0.259 |
| 25% | 201 | 1.000 | 1.000 | 1.000 | 1.000 | 1.000 | 0.050 | 0.0046 | 0.092 |
| 50% | 402 | 1.000 | 1.000 | 0.950 | 1.000 | 1.000 | 0.050 | 0.0024 | 0.047 |
| 100% | 804 | 1.000 | 1.000 | 0.950 | 1.000 | 1.000 | 0.050 | 0.0013 | 0.025 |
## 2. Findings
**F1 — keyed lexical retrieval does not degrade with context growth.** A1 top-1 = 1.000 and
A1 top-5 = 1.000 at all four scales (80 → 804 chunks, a 10.1x increase in distractors).
The measured floor for the same task is R top-1 = 0.0013 at full scale, i.e. ≈ 1/804 chunks.
**F2 — the ceiling belongs to the key, not to the retriever.** Dropping the key (B) leaves the class phrase intact: any-needle hit stays 1.000 while specific-needle hit collapses to 0.050 at every scale. The metric therefore discriminates 'found the class' from 'found the needle'.
**F3 — natural-language wrapping is not free.** A2 = 0.950 at 50% and 100% against A1 = 1.000:
one needle in twenty flips. Measured cause: the function word `what` occurs in only 68 of 804 chunks (idf 2.464) while `the` occurs in 710 (idf 0.125), so a single `what` is worth up to 2.464 points.
**F4 — reverse lookup works.** C top-1 = 1.000 at all scales: keying on the value instead of the key costs nothing, because value tokens are corpus-unique by construction.
**F5 — position invariance.** A1 top-1 by depth decile: 0:1.000, 1:1.000, 2:1.000, 3:1.000, 4:1.000, 5:1.000, 6:1.000, 7:1.000, 8:1.000, 9:1.000. No lost-in-the-middle effect for
lexical retrieval on this corpus — unlike the LLM behaviour the archived papers report.
**F6 — truncation, not retrieval, is the binding constraint.** The haystack is ≈ 241,164 tokens.
A 32,768-token window holds 13.7% of the chunks and loses 17/20 needles (85.0%); a 131,072-token window holds
54.4% of the chunks and loses 9/20 (45.0%).
## 3. Diagnostics for the two A2 flips
**scale 50% of the corpus, needle 6 — `What is the custody token for project ZEPHYR-1297?`**
- gold chunk 130 scores 13.0514 (dl 448); top-1 chunk 150 scores 13.3795 (dl 239); margin 0.3281
- in the gold chunk the corpus-unique key token contributes 1297=4.1435(tf 1)
- the top-1 chunk wins on shared terms and length normalisation: custody tf 1 → 2.9910, for tf 2 → 0.3865, is tf 3 → 0.9214, project tf 1 → 2.8538, the tf 9 → 0.3160, token tf 2 → 2.9198, zephyr tf 1 → 2.9910
- top-1 chunk is itself a planted needle chunk (it contains `custody token`); the miss is a
near-tie between two needles of the same class, not a failure to find the class.
**scale 100% of the corpus, needle 1 — `What is the custody token for project ZEPHYR-1177?`**
- gold chunk 60 scores 15.3906 (dl 434); top-1 chunk 623 scores 16.7962 (dl 247); margin 1.4056
- in the gold chunk the corpus-unique key token contributes 1177=4.7928(tf 1)
- the top-1 chunk wins on shared terms and length normalisation: custody tf 1 → 3.6669, for tf 4 → 0.3841, is tf 5 → 0.8678, project tf 1 → 3.4104, the tf 10 → 0.2397, token tf 1 → 2.0988, what tf 1 → 2.4616, zephyr tf 1 → 3.6669
- top-1 chunk is itself a planted needle chunk (it contains `custody token`); the miss is a
near-tie between two needles of the same class, not a failure to find the class.
## 4. Limits
- One corpus (12 long-context arXiv papers) and one retriever family (lexical BM25). No dense or embedding retriever was run, and no LLM is in the loop: nothing here is a claim about model behaviour.
- Needle keys are synthetic and verified token-unique. A natural-language fact whose key words are ordinary would be harder — which is precisely what F3 and §3 measure.
- Token counts are an estimate (1 token ≈ 4 chars), stated as such; char counts are exact.
- B 'specific' = 0.050 is a tie-order artefact: the 20 needle chunks score near-identically for the
class phrase, so top-1 is decided by length normalisation, not by a retrieval failure.
## 5. Reproduce and falsify
```
python3 build_bench.py # corpus + manifest -> results.json
python3 render_report.py # results.json -> REPORT.md (byte-identical to this file)
python3 verify.py # exit code is the verdict: corpus sha256, live arXiv titles,
# benchmark re-derivation, sensitivity thresholds, report identity
python3 falsify.py # mutation controls; each must make verify.py exit nonzero
```
@@ -0,0 +1,302 @@
#!/usr/bin/env python3
"""build_bench.py — deterministic long-context retrieval benchmark over a hash-anchored corpus.
Haystack : the 12 arXiv full texts of the longctx archive (964,298 decoded chars, ~965 KB).
Index : sliding-window chunks (1500 chars, stride 1200) + pure-Python BM25 (k1=1.2, b=0.75).
Needles : 20 synthetic "<phrase> for project ZEPHYR-<k> is <v>" facts. Generation rejects any
candidate whose key, key digits, value digits or phrase already occur in the source
corpus, so nothing the retriever must find is leaked by the corpus itself.
Conditions (all run at every context scale):
A1 minimal keyed query "custody token for project ZEPHYR-1177" -> expect ceiling
A2 same query in natural language "What is the custody token for project ZEPHYR-1177?"
-> measures the effect of rare *function words* in the query
B class-only query "custody token" -> any-needle hit vs specific-needle hit
C value-keyed query "custody token 59960" -> reverse lookup from the value
R uniform random draw -> measured floor, no retriever involved
Truncation: what a fixed no-retrieval context window can even hold, in tokens (chars/4).
Everything is seeded. results.json is the only input to the report renderer.
"""
import hashlib
import json
import math
import os
import random
import re
import sys
BASE = os.path.dirname(os.path.abspath(__file__))
CORPUS = os.path.join(BASE, "corpus")
OUT = os.path.join(BASE, "results.json")
SEED = 20261021
CHUNK = 1500
STRIDE = 1200
K1, B = 1.2, 0.75
SCALES = [0.10, 0.25, 0.50, 1.00]
N_NEEDLES = 20
MAX_ATTEMPTS = 20000
KEY_PREFIX = "ZEPHYR"
PHRASE = "custody token"
RANDOM_DRAWS = 2000
TOKENS_PER_CHAR = 0.25
WINDOWS = [32768, 131072]
WORD = re.compile(r"[a-z0-9]+")
def tokenize(s):
return WORD.findall(s.lower())
class BM25:
def __init__(self, docs):
self.n = len(docs)
self.tf, self.dl, df = [], [], {}
for d in docs:
counts = {}
for w in tokenize(d):
counts[w] = counts.get(w, 0) + 1
self.tf.append(counts)
self.dl.append(sum(counts.values()))
for w in counts:
df[w] = df.get(w, 0) + 1
self.avgdl = (sum(self.dl) / self.n) if self.n else 0.0
self.idf = {w: math.log(1 + (self.n - d + 0.5) / (d + 0.5)) for w, d in df.items()}
def search(self, query, k=5):
terms = tokenize(query)
scores = [0.0] * self.n
for i in range(self.n):
dl = self.dl[i]
s = 0.0
for w in terms:
f = self.tf[i].get(w)
if not f:
continue
s += self.idf[w] * (f * (K1 + 1)) / (f + K1 * (1 - B + B * dl / self.avgdl))
scores[i] = s
order = sorted(range(self.n), key=lambda i: (-scores[i], i))
return order[:k]
def breakdown(self, query, i):
"""per-term BM25 contribution of document i for this query"""
dl, out, total = self.dl[i], {}, 0.0
for w in tokenize(query):
f = self.tf[i].get(w)
if not f:
continue
c = self.idf[w] * (f * (K1 + 1)) / (f + K1 * (1 - B + B * dl / self.avgdl))
out[w] = {"tf": f, "idf": round(self.idf[w], 4), "contribution": round(c, 4)}
total += c
return {"chunk": i, "dl": dl, "total": round(total, 4), "terms": out}
def load_corpus():
manifest = json.load(open(os.path.join(BASE, "manifest.json"), encoding="utf-8"))
manifest.sort(key=lambda m: m["arxiv_id"])
docs, bad = [], []
for m in manifest:
raw = open(os.path.join(CORPUS, m["arxiv_id"] + ".txt"), "rb").read()
sha = hashlib.sha256(raw).hexdigest()
if sha != m["sha256"]:
bad.append(m["arxiv_id"])
docs.append((m["arxiv_id"], raw.decode("utf-8")))
if bad:
raise SystemExit("corpus sha256 mismatch: %s" % bad)
return manifest, docs
def base_chunks(docs):
full = "".join("\n\n===== DOC %s =====\n\n" % aid + txt for aid, txt in docs)
chunks = [full[i:i + CHUNK] for i in range(0, max(1, len(full) - CHUNK + 1), STRIDE)]
if (len(chunks) - 1) * STRIDE + CHUNK < len(full):
chunks.append(full[-CHUNK:])
return full, chunks
def make_needles(base_text, base_tokens):
"""Synthetic facts that provably do not occur in the source corpus."""
rng = random.Random(SEED)
keys, vals, out = set(), set(), []
attempts = rejects = 0
while len(out) < N_NEEDLES:
attempts += 1
if attempts > MAX_ATTEMPTS:
raise SystemExit("could not mint %d clean needles" % N_NEEDLES)
k = rng.randrange(1000, 9999)
v = rng.randrange(10000, 99999)
if k in keys or v in vals:
rejects += 1
continue
sentence = "The %s for project %s-%d is %d." % (PHRASE, KEY_PREFIX, k, v)
if (str(k) in base_tokens or str(v) in base_tokens
or sentence.lower() in base_text or ("%s-%d" % (KEY_PREFIX.lower(), k)) in base_text):
rejects += 1
continue
keys.add(k)
vals.add(v)
out.append({"i": len(out), "key": "%s-%d" % (KEY_PREFIX, k), "value": str(v),
"sentence": sentence})
return out, {"attempts": attempts, "rejected_candidates": rejects}
def run_scale(base, needles, scale):
n = max(N_NEEDLES, int(len(base) * scale))
chunks = list(base[:n])
golds = []
for nd in needles:
idx = min(n - 1, int((nd["i"] + 0.5) * n / len(needles)))
chunks[idx] = chunks[idx] + " " + nd["sentence"]
golds.append(idx)
bm = BM25(chunks)
rng = random.Random(SEED + 7)
draws = [rng.randrange(n) for _ in range(RANDOM_DRAWS)]
rows = []
a2_misses = []
for nd, gold in zip(needles, golds):
q_a1 = "%s for project %s" % (PHRASE, nd["key"])
q_a2 = "What is the %s for project %s?" % (PHRASE, nd["key"])
q_b = PHRASE
q_c = "%s %s" % (PHRASE, nd["value"])
t1, t2, tb, tc = bm.search(q_a1, 5), bm.search(q_a2, 5), bm.search(q_b, 5), bm.search(q_c, 5)
if t2[0] != gold:
a2_misses.append({
"needle": nd["i"], "scale": scale, "query": q_a2, "gold_chunk": gold,
"top1_chunk": t2[0],
"top1_breakdown": bm.breakdown(q_a2, t2[0]),
"gold_breakdown": bm.breakdown(q_a2, gold),
})
rows.append({
"needle": nd["i"],
"key": nd["key"],
"value": nd["value"],
"gold_chunk": gold,
"gold_offset_chars": gold * STRIDE,
"depth_decile": min(9, gold * 10 // n),
"a1_top1": t1[0], "a1_hit1": int(t1[0] == gold), "a1_hit5": int(gold in t1),
"a2_top1": t2[0], "a2_hit1": int(t2[0] == gold), "a2_hit5": int(gold in t2),
"b_top1": tb[0], "b_any_hit": int(PHRASE in chunks[tb[0]]), "b_gold_hit": int(tb[0] == gold),
"c_top1": tc[0], "c_hit1": int(tc[0] == gold), "c_hit5": int(gold in tc),
})
m = len(rows)
mean = lambda key: sum(r[key] for r in rows) / m
return {
"scale": scale,
"n_chunks": n,
"needles": m,
"a1_top1_rate": mean("a1_hit1"), "a1_top5_rate": mean("a1_hit5"),
"a2_top1_rate": mean("a2_hit1"), "a2_top5_rate": mean("a2_hit5"),
"b_any_rate": mean("b_any_hit"), "b_gold_rate": mean("b_gold_hit"),
"c_top1_rate": mean("c_hit1"), "c_top5_rate": mean("c_hit5"),
"a2_misses": a2_misses,
"random_top1_rate": sum(sum(1 for d in draws if d == g) for g in golds) / (RANDOM_DRAWS * m),
"random_any_needle_rate": sum(1 for d in draws if d in golds) / RANDOM_DRAWS,
"per_needle": rows,
}
def truncation(full, chunks, needles, last):
windows = []
for w in WINDOWS:
chars = int(w / TOKENS_PER_CHAR)
beyond = sum(1 for i in range(len(chunks)) if i * STRIDE >= chars)
lost = sum(1 for r in last["per_needle"] if r["gold_offset_chars"] >= chars)
windows.append({
"window_tokens": w,
"window_chars": chars,
"chunks_beyond_window": beyond,
"chunk_loss_rate": beyond / len(chunks),
"needles_lost": lost,
"needle_loss_rate": lost / len(last["per_needle"]),
})
return {
"token_estimator": "1 token ~= %d chars" % int(1 / TOKENS_PER_CHAR),
"haystack_chars": len(full),
"haystack_tokens_est": int(len(full) * TOKENS_PER_CHAR),
"windows": windows,
}
def compute():
"""Full pipeline -> results dict (pure function of corpus + manifest + constants)."""
manifest, docs = load_corpus()
full, base = base_chunks(docs)
base_text = full.lower()
base_tokens = set(tokenize(full))
needles, minting = make_needles(base_text, base_tokens)
minting["occurrences_of_key_prefix_in_corpus"] = base_text.count(KEY_PREFIX.lower())
minting["occurrences_of_phrase_in_corpus"] = base_text.count(PHRASE)
minting["occurrences_of_any_needle_sentence_in_corpus"] = sum(
1 for nd in needles if nd["sentence"].lower() in base_text)
scale_rows = [run_scale(base, needles, s) for s in SCALES]
pos = {}
for row in scale_rows:
for r in row["per_needle"]:
pos.setdefault(str(r["depth_decile"]), []).append(r["a1_hit1"])
position_curve = {d: sum(v) / len(v) for d, v in sorted(pos.items(), key=lambda kv: int(kv[0]))}
full_index = BM25(base)
rare_words = {}
for w in ("what", "is", "the", "for", "project", "custody", "token"):
df = sum(1 for tf in full_index.tf if w in tf)
rare_words[w] = {"df": df, "idf": full_index.idf.get(w, 0.0)}
results = {
"artifact": "longctx-niah-bench-2026-10-21",
"benchmark": "needle retrieval under context scaling (BM25 over sliding-window chunks)",
"seed": SEED,
"retriever": {"name": "BM25", "k1": K1, "b": B, "tokenizer": "[a-z0-9]+ over lowercased text",
"implementation": "pure Python, stdlib only"},
"chunking": {"chunk_chars": CHUNK, "stride_chars": STRIDE},
"corpus": {
"docs": len(manifest),
"decoded_chars": sum(len(t) for _, t in docs),
"arxiv_ids": [m["arxiv_id"] for m in manifest],
"sha256_verified_against_manifest": True,
},
"needle_material": {
"phrase": PHRASE, "key_prefix": KEY_PREFIX,
"key_token_unique_in_corpus": all(str(nd["key"].split("-")[1]) not in base_tokens for nd in needles),
"value_token_unique_in_corpus": all(nd["value"] not in base_tokens for nd in needles),
"minting": minting,
},
"needles": needles,
"scales": scale_rows,
"position_curve_a1_top1_by_depth_decile": position_curve,
"query_word_statistics_full_index": rare_words,
"truncation": truncation(full, base, needles, scale_rows[-1]),
}
return results
def main():
results = compute()
json.dump(results, open(OUT, "w", encoding="utf-8"), indent=2, sort_keys=True)
print("wrote", OUT)
print("haystack: %d docs, %d decoded chars, %d chunks at full scale"
% (results["corpus"]["docs"], results["corpus"]["decoded_chars"],
results["scales"][-1]["n_chunks"]))
for row in results["scales"]:
print("scale=%.2f chunks=%4d | A1 top1=%.3f top5=%.3f | A2 top1=%.3f | C top1=%.3f | "
"B any=%.3f gold=%.3f | R top1=%.4f any=%.3f"
% (row["scale"], row["n_chunks"], row["a1_top1_rate"], row["a1_top5_rate"],
row["a2_top1_rate"], row["c_top1_rate"], row["b_any_rate"], row["b_gold_rate"],
row["random_top1_rate"], row["random_any_needle_rate"]))
print("rare-word idf at full scale:", {w: (v["df"], round(v["idf"], 3))
for w, v in results["query_word_statistics_full_index"].items()})
t = results["truncation"]
print("haystack est tokens:", t["haystack_tokens_est"])
for w in t["windows"]:
print("window %6d tok: needles lost %2d/%d (%.2f), chunks beyond %.3f"
% (w["window_tokens"], w["needles_lost"], len(results["needles"]),
w["needle_loss_rate"], w["chunk_loss_rate"]))
return 0
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1 @@
Report GitHub Issue × Title: Content selection saved. Describe the issue below: Description: Submit without GitHub Submit in GitHub arXiv is now an independent nonprofit! Learn more × Back to arXiv Why HTML? Report Issue Back to Abstract Download PDF Abstract 1 Introduction 2 Related Work Long-Context Reasoning Reasoning Metrics 3 Creating DetectiveQA 3.1 Data Source 3.2 Data Annotation 4 Evaluating with DetectiveQA 4.1 Metric Settings Multiple-Choice Accuracy Step-wise Reasoning Metric 4.2 Context Settings 5 Experiment 5.1 Validation Results Statistics. Validation on data annotations. Validation on metrics. 5.2 Evaluation Results Models. Main results. Analysis on data contamination. 5.3 Identifying Performance Bottlenecks by Ablating Long Context and Reasoning 5.3.1 Analysis on evidence retrieval over long context. 5.3.2 Analysis on reasoning capability. 6 Conclusion References A Annotation Guideline Data format. Annotation procedure. Agent Workflow B Details in Reasoning Metric C More Validation Results Depth Distribution D More Evaluation Results License: arXiv.org perpetual non-exclusive license arXiv:2409.02465v2 [cs.CL] 14 Mar 2025 DetectiveQA: Evaluating Long-Context Reasoning on Detective Novels Zhe Xu ††thanks: Equal contribution. Affiliation: School of Computer Science, Fudan University Email: zxu24@m.fudan.edu.cn Jiasheng Ye11footnotemark: 1 Affiliation: School of Computer Science, Fudan University Email: jsye23@m.fudan.edu.cn Xiaoran Liu11footnotemark: 1 Affiliation: School of Computer Science, Fudan University Email: xrliu24@m.fudan.edu.cn Xiangyang Liu11footnotemark: 1 Affiliation: School of Computer Science, Fudan University Tianxiang Sun Affiliation: School of Computer Science, Fudan University Zhigeng Liu Affiliation: School of Computer Science, Fudan University Qipeng Guo Affiliation: Shanghai AI Laboratory Linlin Li Affiliation: Huawei Noah's Ark Lab Qun Liu Affiliation: Huawei Noah's Ark Lab Xuanjing Huang Affiliation: School of Computer Science, Fudan University Xipeng Qiu ††thanks: Corresponding author. Affiliation: School of Computer Science, Fudan University Affiliation: Shanghai AI Laboratory Abstract Recently, significant efforts have been devoted to enhancing the long-context capabilities of Large Language Models (LLMs), particularly in long-context reasoning. To facilitate this research, we propose DetectiveQA, a dataset specifically designed for narrative reasoning within long contexts. We leverage detective novels, averaging over 100k tokens, to create a dataset containing 1200 human-annotated questions in both Chinese and English, each paired with corresponding reference reasoning steps. Furthermore, we introduce a step-wise reasoning metric, which enhances the evaluation of LLMs' reasoning processes. We validate our approach and evaluate the mainstream LLMs, including GPT-4, Claude, and LLaMA, revealing persistent long-context reasoning challenges and demonstrating their evidence-retrieval challenges. Our findings offer valuable insights into the study of long-context reasoning and lay the base for more rigorous evaluations. The evaluation code are publicly accessible via our GitHub repository 1 Introduction The long-context capabilities of Large Language Models (LLMs) (OpenAI, 2023; Anthropic, 2024a; Touvron et al., 2023; Sun et al., 2024; Bai et al., 2023a; Cai et al., 2024; Zeng et al., 2023b), particularly long-context reasoning(Kociský et al., 2018; Sprague et al., 2024; Wang et al., 2024; Karpinska et al., 2024), are a key competitive advantage in the current landscape. Recently, OpenAI released the O1 model (OpenAI, 2024), which not only supports a context length of 128k but can also generate extensive reasoning chains, effectively solving complex reasoning problems in intricate scenarios. As the long-context reasoning capabilities of LLMs improve, there is a growing demand for more challenging and realistic long-context reasoning evaluations(Sprague et al., 2024; Kuratov et al., 2024; Zhang et al., 2024; Wang et al., 2024; Karpinska et al., 2024). Among these, narrative reasoning based on detective novels provides a sufficiently realistic and challenging setting(Sprague et al., 2024; Gu et al., 2024). As Ilya, the core developer of GPT-4, has stated, if LLMs possess real understanding when presented with a detective novel with a complicated plot, a storyline, different characters, numerous events, and mysteries like clues, LLMs should be able to predict who commits the crime at the last page of the book based on the context11 1 Ilya Sutskever - GPT4 predicts the next word better - Now upgraded to the more powerful GPT4o https://www.youtube.com/watch?v=1OsHC1vbpc0. Inspired by his words, we propose DetectiveQA, a long-context narrative reasoning dataset with three features. First, DetectiveQA provides detailed annotation information. The dataset includes 1200 reasoning questions from English and Chinese detective novels, with an average length exceeding 100Line truncated
@@ -0,0 +1 @@
1 Introduction 2 Related Work 3 Our Benchmark: LongReason 3.1 Long-context Reasoning Question Construction via Context Expansion 3.2 The Statistics of LongReason 4 Exerperiments & Results 4.1 Experimental setup 4.2 Main Results 4.3 Further Analysis 5 Conclusion and Limitations 6 Model Information 7 Hyperparameters for LongReason Construction 8 Human Annotator 9 Prompts 1]ByteDance Seed 2]UC San Diego 3]University of Illinois Urbana-Champaign \contribution[*]Work done at ByteDance Seed \contribution[†]Corresponding authors LongReason: A Synthetic Long-Context Reasoning Benchmark via Context Expansion Zhan Ling Kang Liu Kai Yan Yifan Yang Weijian Lin Ting-Han Fan Lingfeng Shen Zhengyin Du Jiecao Chen [ [ [ z6ling@ucsd.edu jiecao.chen@bytedance.com (November 17, 2025) Abstract Large language models (LLMs) have demonstrated remarkable progress in understanding long-context inputs. However, benchmarks for evaluating the long-context reasoning abilities of LLMs fall behind the pace. Existing benchmarks often focus on a narrow range of tasks or those that do not demand complex reasoning. To address this gap and enable a more comprehensive evaluation of the long-context reasoning capabilities of current LLMs, we propose a new synthetic benchmark, LongReason, which is constructed by synthesizing long-context reasoning questions from a varied set of short-context reasoning questions through context expansion. LongReason consists of 794 multiple-choice reasoning questions with diverse reasoning patterns across three task categories: reading comprehension, logical inference, and mathematical word problems. We evaluate 21 LLMs on LongReason, revealing that most models experience significant performance drops as context length increases. Our further analysis shows that even state-of-the-art LLMs still have significant room for improvement in providing robust reasoning across different tasks. We have open-sourced LongReason to support the comprehensive evaluation of LLMs' long-context reasoning capabilities. \correspondence Zhan Ling at , Jiecao Chen at \checkdata[Dataset]https://huggingface.co/datasets/lz1bytedance/LongReason 1 Introduction Benchmark Avg Len Light Human Effort Realistic Tasks Broad Tasks Controllable Context ZeroSCROLLS [zeroscrolls] ∼\sim10K ✗ ✓ ✓ ✗ L-Eval [leval] ∼\sim8K ✗ ✓ ✓ ✗ BAMBOO [dong2023bamboo] ∼\sim16K ✗ ✓ ✓ ✗ LongBench [bai2023longbench] ∼\sim8K ✗ ✓ ✓ ✗ LooGLE [li2023loogle] ∼\sim20K ✗ ✓ ✓ ✗ InfiniteBench [zhang2024infty] ∼\sim200K ✗ ✓ ✓ ✗ Loong [wang2024leave] ∼\sim250K ✗ ✓ ✓ ✗ Needle-in-a-haystack [needleinhaystack] any ✓ ✗ ✗ ✓ RULER [hsieh2024ruler] any ✓ ✗ ✓ ✓ LongReason (Ours) any ✓ ✓ ✓ ✓ Table 1: Comparison of LongReason with other long-context benchmarks. LongReason offers controllable context lengths and incorporating diverse and realistic tasks without the need for human annotation on long text. In recent years, large language models (LLMs) [openai2023gpt4, gemini, claude3, llama3-1, mistral, qwen2.5] have demonstrated remarkable advances in diverse natural language processing tasks. The ability to comprehend and reason over long inputs is essential for downstream applications, including multi-turn conversations [tan2024peer], document understanding [masry2024longfin] retrieval-augmented generation [yao2022react, xu2023retrieval], and language agents [zhao2024longagent, zhang2024chain]. Meanwhile, extensive efforts in deep learning system [dao2022flashattention, dao2023flashattention, chen2023longlora, ratner2022parallel] research have been devoted to optimizing computational overhead to support increasing numbers of input tokens, which has led to growing attention on long-context LLMs. Now, both proprietary and open-source LLMs can support up to millions of input tokens [gemini, mistralnemo, glm4]. However, despite the rapid development of long-context language models, benchmarks have lagged behind. One of the key challenges is dataset construction, as long-context question-answering data is relatively scarce on the internet. To address this, prevalent long-context benchmarks have utilized synthetic tasks like passkey retrieval [mohtashami2023landmark], needle-in-a-haystack (NIAH) [needleinhaystack, zhao2024longagent], and variable tracking [hsieh2024ruler] to evaluate long-context LLMs. However, these tasks are often unrealistic and involve reasoning processes that differ significantly from those in real-world applications. Alternatively, some research efforts have involved human annotation of realistic questions and gold answers over one or multiple long documents [bamboo, wang2024leave, li2023loogle]. However, creating realistic long-context tasks from extensive texts is both challenging and time-consuming, even for human experts [wang2024leave]. This limitation restricts the expansion of datasets to accommodate arbitrary context lengths and the ability to support controllable contextLine truncated
@@ -0,0 +1 @@
Report GitHub Issue × Title: Content selection saved. Describe the issue below: Description: Submit without GitHub Submit in GitHub arXiv is now an independent nonprofit! Learn more × Back to arXiv Why HTML? Report Issue Back to Abstract Download PDF Abstract 1 Introduction 2 Related Work 3 Methodology 3.1 Problem Formulation 3.2 LongFinanceQA Dataset 3.3 Supervised CoT Reasoning 4 Experiments 4.1 Experimental Setup 4.2 Main Results 4.3 Discussion 5 Conclusion References A Appendix A.1 Trend of Long-Context LLMs A.2 Prompts of Property-based Agentic Inference (PAI) A.3 LongFinanceQA Dataset Statistics A.4 Synthetic Question Generation A.5 More Empirical Results A.6 Full Performances License: arXiv.org perpetual non-exclusive license arXiv:2502.13127v2 [cs.CL] 01 Dec 2025 Facilitating Long Context Understanding via Supervised Chain-of-Thought Reasoning Jingyang Lin Andy Wong Affiliation: University of Rochester, NY, United States Affiliation: PAII Inc., CA, United States Tian Xia Affiliation: PAII Inc., CA, United States Shenghua He Affiliation: PAII Inc., CA, United States Hui Wei Affiliation: PAII Inc., CA, United States Mei Han Affiliation: PAII Inc., CA, United States Jiebo Luo Affiliation: University of Rochester, NY, United States Affiliation: University of California, Merced, CA, United Stateshttps://long-pai.github.io/ Abstract Recent advances in Large Language Models (LLMs) have enabled them to process increasingly longer sequences, ranging from 2K to 2M tokens and even beyond. However, simply extending the input sequence length does not necessarily lead to effective long-context understanding. In this study, we integrate Chain-of-Thought (CoT) reasoning into LLMs in a supervised manner to facilitate effective long-context understanding. To achieve this, we introduce LongFinanceQA, a synthetic dataset in the financial domain designed to improve long-context reasoning. Unlike existing long-context synthetic data, LongFinanceQA includes intermediate CoT reasoning before the final conclusion, which encourages LLMs to perform explicit reasoning, improving accuracy and interpretability in long-context understanding. To generate synthetic CoT reasoning, we propose Property-based Agentic Inference (PAI), an agentic framework that simulates human-like reasoning steps, including property extraction, retrieval, and summarization. We evaluate PAI's reasoning capabilities by assessing GPT-4o-mini w/ PAI on the Loong benchmark, outperforming standard GPT-4o-mini by 20.0%. Furthermore, we fine-tune LLaMA-3.1-8B-Instruct on LongFinanceQA, achieving a 28.0% gain on Loong's financial subset. ††footnotetext: Corresponding author: 🖂 jlin81@ur.rochester.edu.††footnotetext: Work was done during the internship at PAII Inc. 1 Introduction Figure 1: The hype of long-context large language models. The results shown are from the Loong benchmark, where green points refer to open-source LLMs and red points indicate closed-source LLMs. Our GPT-4o-mini w/ PAI stands out as the red open circle. Long context understanding remains an evolving challenge Kočiský et al. (2018); Wu et al. (2021); Bai et al. (2024); Wang et al. (2024) in natural language processing (NLP). Achieving long-context understanding requires processing long-form textual information, thereby enhancing a model's ability to generate coherent, accurate, and contextually relevant responses IBM Research (2024). Practical long context understanding has a potential impact on numerous applications, such as private document analysis Mukherjee et al. (2023), large codebase understanding Nam et al. (2024), and multimodal content understanding Chandrasegaran et al. (2024); Lin et al. (2023); Tang et al. (2023); Chen et al. (2024c). Recent advancements in large language models Ouyang et al. (2022); Reid et al. (2024); Dubey et al. (2024) have significantly extended the input sequence length, ranging from 2K to 2M tokens, as shown in Figure 7 in Appendix A. However, simply increasing the input sequence length does not necessarily improve the ability to comprehend the long content Yang et al. (2024a); Goldman et al. (2024). In particular, Figure 1 presents the performance of various long-context LLMs on the Loong benchmark Wang et al. (2024). The results suggest that, regardless of the maximum sequence length models can process, their performance remains similar, typically between 45% and 55%. This phenomenon exposes a hype that despite considerably enlarging context window size, the state-of-the-art LLMs still fail to perform satisfactorily in practical long-context problem-solving tasks. Therefore, instead of merely increasing the input sequence length, achieving effective long-context understanding remains an open challenge. In parallel with advanced long-context architectures and techniques Peng et al. (2024); Liu et al. (2024c); Dao and Gu (2024), constructing high-quality long-context training data remains essential yet underexplored. Given the scarcity aLine truncated
@@ -0,0 +1 @@
Report GitHub Issue × Title: Content selection saved. Describe the issue below: Description: Submit without GitHub Submit in GitHub arXiv is now an independent nonprofit! Learn more × Back to arXiv Why HTML? Report Issue Back to Abstract Download PDF Abstract 1 Introduction 2 Related Work Imitation Learning. Long-Context Policies. Test-Time Scaling. 3 Preliminaries Problem Setting. Practical Challenges. 4 Method 4.1 Past-Token Prediction 4.2 Memory-Efficient Training with PTP 4.3 Test-Time Verification with PTP 5 Experiments 5.1 Simulation Experiments 5.2 Real-World Experiments 6 Conclusion 7 Limitations and Discussion. References A Additional Experiments A.1 Which component of the policy benefits most from PTP? A.2 Does our method reduce reliance on action chunking? A.3 Is PTP still critical when conditioning on past actions? B Environmental Details B.1 Real-World Tasks B.2 Simulation Tasks B.3 Implementation Details B.3.1 Policy Architecture B.3.2 Subsampling Rate B.3.3 Context Length B.3.4 Action Dependency Metric C Additional Results License: CC BY 4.0 arXiv:2505.09561v2 [cs.RO] 19 May 2025 Learning Long-Context Diffusion Policies via Past-Token Prediction Marcel Torne ††thanks: Equal contribution Andy Tang11footnotemark: 1 Yuejiang Liu11footnotemark: 1 Chelsea Finn Affiliation: Stanford University August 24, 2026 Abstract Reasoning over long sequences of observations and actions is essential for many robotic tasks. Yet, learning effective long-context policies from demonstrations remains challenging. As context length increases, training becomes increasingly expensive due to rising memory demands, and policy performance often degrades as a result of spurious correlations. Recent methods typically sidestep these issues by truncating context length, discarding historical information that may be critical for subsequent decisions. In this paper, we propose an alternative approach that explicitly regularizes the retention of past information. We first revisit the copycat problem in imitation learning and identify an opposite challenge in recent diffusion policies: rather than over-relying on prior actions, they often fail to capture essential dependencies between past and future actions. To address this, we introduce Past-Token Prediction (PTP), an auxiliary task in which the policy learns to predict past action tokens alongside future ones. This regularization significantly improves temporal modeling in the policy head, with minimal reliance on visual representations. Building on this observation, we further introduce a multistage training strategy: pre-train the visual encoder with short contexts, and fine-tune the policy head using cached long-context embeddings. This strategy preserves the benefits of PTP while greatly reducing memory and computational overhead. Finally, we extend PTP into a self-verification mechanism at test time, enabling the policy to score and select candidates consistent with past actions during inference. Experiments across four real-world and six simulated tasks demonstrate that our proposed method improves the performance of long-context diffusion policies by 3× and accelerates policy training by more than 10×. Videos are available at https://long-context-dp.github.io. 1 Introduction Many robotic tasks are inherently non-Markovian: an appropriate choice of action may depend not only on the current observation but also on past observations and actions [21, 47, 15, 48]. For example, consider manipulation tasks where the robot arm occludes critical parts of the scene, or multi-stage tasks where early steps inform later strategies [24]. Likewise, past actions can prescribe a style of execution, such as speed, curvature, or strategy, that shapes how future actions should unfold [8, 19]. Despite the importance of historical observations, learning long-context robotic policies through imitation learning remains difficult. First, longer observation histories often introduce features that spuriously correlate with actions in the training data. Policies that latch onto such information may diverge from expert behavior during deployment, leading to performance degradation [10, 34]. Second, conditioning on high-dimensional image sequences imposes a rapidly growing memory and computation burden, making end-to-end training excessively expensive at scale [48, 16]. To cope with these challenges, recent methods typically limit the amount of historical information the policy sees - either by truncating the context length [8, 5] or by engineering past observations into compact representations, such as selecting key frames [42] and summarizing observations [48]. While these strategies reduce memory requirement, they risk discarding information critical to subsequent decisions. In this paper, we introduce a simple and effective approach for learning long-context robot policies, illustrated in Fig. 1. At the core of our method is to explicitly regularize the information preserved from past Line truncated
@@ -0,0 +1 @@
Report GitHub Issue × Title: Content selection saved. Describe the issue below: Description: Submit without GitHub Submit in GitHub arXiv is now an independent nonprofit! Learn more × Back to arXiv Why HTML? Report Issue Back to Abstract Download PDF Abstract 1 Introduction 2 Related Works Long-Context and Cross-Lingual Challenges Advanced Prompting Strategies Neurosymbolic Reasoning 3 System Architecture and Approach 3.1 Retrieval Component Tokenization and Embedding Candidate Selection and Model Input 3.2 NeuroSymbolic Augmented Reasoning (NSAR) 1. Symbolic Fact Extraction 2. Python Code Generation 3. Final Answer Extraction 4 Experiments 4.1 Dataset 4.2 Evaluation 4.3 Models and Baseline Methods 5 Results and Analysis 5.1 LLM-Only vs. RAG-Vanilla Context length Accuracy by context language Error analysis 5.2 NeuroSymbolic Reasoning (NSAR) Accuracy by context language Effect of kk on performance Error analysis for NSAR and NSAR+3 6 Conclusion References A Prompt and Needle Templates A.1 Needle Template A.2 Prompt Templates License: CC BY 4.0 arXiv:2506.02483v1 [cs.CL] 03 Jun 2025 Enhancing Large Language Models with Neurosymbolic Reasoning for Multilingual Tasks Sina Bagheri Nezhad Email: sina.bagherinezhad@pdx.edu Ameeta Agrawal Email: ameeta@pdx.edu Affiliation: Portland State University, USA Abstract Large language models (LLMs) often struggle to perform multi-target reasoning in long-context scenarios where relevant information is scattered across extensive documents. To address this challenge, we introduce NeuroSymbolic Augmented Reasoning (NSAR), which combines the benefits of neural and symbolic reasoning during inference. NSAR explicitly extracts symbolic facts from text and generates executable Python code to handle complex reasoning steps. Through extensive experiments across seven languages and diverse context lengths, we demonstrate that NSAR significantly outperforms both a vanilla RAG baseline and advanced prompting strategies in accurately identifying and synthesizing multiple pieces of information. Our results highlight the effectiveness of combining explicit symbolic operations with neural inference for robust, interpretable, and scalable reasoning in multilingual settings. 1 Introduction Large language models (LLMs) have achieved impressive progress in natural language processing, yet their ability to handle long-context, cross-lingual reasoning remains limited (Agrawal et al., 2024). In real-world multilingual scenarios, information is often scattered across languages and embedded within lengthy documents, complicating retrieval and coherent reasoning. This challenge is exacerbated by the lost-in-the-middle phenomenon, where LLMs struggle to retain and integrate relevant information from extended contexts (Liu et al., 2023). Although retrieval-augmented generation (RAG) frameworks help narrow the context window, LLMs still frequently fail to combine dispersed facts into a consistent chain of reasoning, resulting in hallucinations and inconsistencies (Zhu et al., 2024; Jiang et al., 2024). In this work, we introduce NeuroSymbolic Augmented Reasoning (NSAR)-a neurosymbolic method that explicitly merges symbolic reasoning with neural inference. NSAR centers on a neurosymbolic prompt that instructs LLMs to extract structured symbolic facts and generate executable code, thus offering a verifiable and interpretable reasoning pathway for complex tasks. By integrating symbolic logic into a retrieval-based framework, NSAR improves interpretability, consistency, and accuracy when working with multilingual and long-context data. The main contributions of our work are as follows: • We propose NSAR, a neurosymbolic reasoning framework that couples retrieval with explicit symbolic reasoning. We develop a dedicated NSAR prompt to structure information into symbolic representations and guide the model in generating executable Python code for final verification. • We show that combining Chain of Thought (CoT), ReAct, and Self-Reflection prompting strategies with neurosymbolic reasoning leads to further performance gains. • Through extensive experiments on cross-lingual, long-context tasks, we demonstrate that NSAR substantially outperforms purely retrieval-based and neural-only methods. 2 Related Works Long-Context and Cross-Lingual Challenges Large language models (LLMs) frequently exhibit a lost-in-the-middle phenomenon in extended contexts (Liu et al., 2023; Xu et al., 2024), causing them to overlook crucial information. Retrieval-augmented frameworks such as LONGEMBED (Zhu et al., 2024), LongRAG (Jiang et al., 2024) and DR-RAG (Hei et al., 2024) alleviate this issue but mostly target monolingual settings. McCrolin (Limkonchotiwat et al., 2024) extends these ideas to cross-lingual retrieval, yet it remains computationally intensive. Bridging queries and contexts in different languages is still difficult (Li et al., 2024; Hengle et al., 2024). Methods such as OPTICAL (Huang et al., 2023) and Line truncated
@@ -0,0 +1 @@
Report GitHub Issue × Title: Content selection saved. Describe the issue below: Description: Submit without GitHub Submit in GitHub arXiv is now an independent nonprofit! Learn more × Back to arXiv Why HTML? Report Issue Back to Abstract Download PDF Abstract 1 Introduction 2 Model Architecture, Training and Dataset 2.1 Model Architecture 2.2 Dataset 2.3 Training Infrastructure 2.4 Post-training 2.5 Thinking 2.6 Capability-specific improvements 2.7 The path to Gemini 2.5 3 Quantitative evaluation 3.1 Methodology 3.2 Core capability quantitative results 3.3 Evaluation of Gemini 2.5 Pro against other large language models 4 Example use cases of Gemini 2.5 Pro 4.1 Gemini Plays Pokémon Long Context Agentic Tooling Long Horizon Task Coherence Screen reading Long Context Reasoning 4.2 What else can Gemini 2.5 do? 4.3 Gemini in Google Products 5 Safety, Security, and Responsibility Guideline for Navigating This Section 5.1 Our Process 5.2 Policies and Desiderata 5.3 Training for Safety, Security, and Responsibility 5.4 Results on Training/Development Evaluations 5.5 Automated Red Teaming 5.6 Memorization and Privacy 5.7 Assurance Evaluations and Frontier Safety Framework 5.8 External Safety Testing 6 Discussion References 7 Contributors and Acknowledgments 8 Appendix 8.1 Evaluation additional details 8.2 Gemini Plays Pokémon Additional Details Agent Panic 8.3 Frontier Safety Framework Evaluations Additional Details: Frontier Safety Correctness Tests 8.4 Image to Code Demo 8.5 Long Context Video Understanding Demo License: CC BY 4.0 arXiv:2507.06261v6 [cs.CL] 19 Dec 2025 \uselogo Gemini 2.5: Pushing the Frontier with Advanced Reasoning, Multimodality, Long Context, and Next Generation Agentic Capabilities. Gemini Team Google Abstract In this report, we introduce the Gemini 2.X model family: Gemini 2.5 Pro and Gemini 2.5 Flash, as well as our earlier Gemini 2.0 Flash and Flash-Lite models. Gemini 2.5 Pro is our most capable model yet, achieving SoTA performance on frontier coding and reasoning benchmarks. In addition to its incredible coding and reasoning skills, Gemini 2.5 Pro is a thinking model that excels at multimodal understanding and it is now able to process up to 3 hours of video content. Its unique combination of long context, multimodal and reasoning capabilities can be combined to unlock new agentic workflows. Gemini 2.5 Flash provides excellent reasoning abilities at a fraction of the compute and latency requirements and Gemini 2.0 Flash and Flash-Lite provide high performance at low latency and cost. Taken together, the Gemini 2.X model generation spans the full Pareto frontier of model capability vs cost, allowing users to explore the boundaries of what is possible with complex agentic problem solving. 1 Introduction We present our latest family of natively multimodal models with advanced reasoning through thinking, long context and tool-use capabilities: Gemini 2.5 Pro and 2.5 Flash and our earlier Gemini 2.0 Flash and Gemini 2.0 Flash-Lite models. Together these form a new family of highly-capable models representing our next generation of AI models, designed to power a new era of agentic systems. Building upon the foundation of the Gemini 1.5 series (Gemini Team, 2024), this Gemini 2.X generation brings us closer to the vision of a universal AI assistant (Hassabis, 2025). The Gemini 2.X series are all built to be natively multimodal, supporting long context inputs of >1 million tokens and have native tool use support. This allows them to comprehend vast datasets and handle complex problems from different information sources, including text, audio, images, video and even entire code repositories. These extensive capabilities can also be combined to build complex agentic systems, as happened in the case of Gemini Plays Pokémon11 1 Pokémon is a trademark of Nintendo Co., Ltd., Creatures Inc., and Game Freak Inc. (Zhang, 2025). Different models in the series have different strengths and capabilities: (1) Gemini 2.5 Pro is our most intelligent thinking model, exhibiting strong reasoning and code capabilities. It excels at producing interactive web applications, is capable of codebase-level understanding and also exhibits emergent multimodal coding abilities. (2) Gemini 2.5 Flash is our hybrid reasoning model with a controllable thinking budget, and is useful for most complex tasks while also controlling the tradeoff between quality, cost, and latency. (3) Gemini 2.0 Flash is our fast and cost-efficient non-thinking model for everyday tasks and (4) Gemini 2.0 Flash-Lite is our fastest and most cost-efficient model, built for at-scale usage. A full comparison of the models in the Gemini 2.X model family is provided in Table 1. Taken together, the Gemini 2.X family of models cover the whole Pareto frontier of model capability vs cost, shifting it forward across a large variety of core capabilities, applications and use-cases, see Figure 1. FlashGemini 1.5 ProGemini 1.5 Flash-LiteGemini 2.0 FlashGemiLine truncated
@@ -0,0 +1 @@
Report GitHub Issue × Title: Content selection saved. Describe the issue below: Description: Submit without GitHub Submit in GitHub arXiv is now an independent nonprofit! Learn more × Back to arXiv Why HTML? Report Issue Back to Abstract Download PDF Abstract 1 Introduction 2 Background and Preliminaries 3 PERK: Meta-Learning Parameter-Efficient Reasoning over Knowledge 3.1 Parameter-Efficient Meta-Learning with Low-Rank Adaptation 3.2 Scalable Meta-Learning with Truncated Gradient Unrolling 4 Experiments 4.1 Experimental Setup Datasets. 4.2 Long-Context Reasoning Results 5 Analysis 5.1 Test-Time Length Robustness 5.2 Robustness to Positional Biases 5.3 Inference Latency and Memory Usage Setup PERK scales more efficiently in memory and runtime on extremely long contexts. 6 Related Work 7 Conclusion References A Datasets A.1 BabiLong Tasks Build Long Context A.2 Student Records Tasks Build Long Context A.3 Open-Domain Multi-doc QA Build Long Context A.4 Lost-in-the-Middle API Retrieval Task Build Long Context B Training and Inference Details Hardware Information Standard Hyperparameters LoRA Hyperparameters Meta-Learning Hyperparameters Long-context Data Collation for PERK Evaluation Hyperparameters C Additional Analysis C.1 Impact of LoRA Rank in PERK C.2 Inner Loop Adaptation Steps C.3 Gradient Unrolling Truncation Steps D Training Memory Cost Analysis E Detailed Results License: CC BY 4.0 arXiv:2507.06415v3 [cs.CL] 31 Aug 2026 PERK: Long-Context Reasoning as Test-Time Learning Zeming Chen Angelika Romanou Gail Weiss Antoine Bosselut Affiliation: EPFL Affiliation: email{zeming.chen, antoine.bosselut}@epfl.ch Abstract Long-context reasoning requires accurately identifying relevant information in extensive, noisy input contexts. In this work, we propose PERK (Parameter Efficient Reasoning over Knowledge), a scalable approach for learning to encode long contexts using gradient updates at test time. Specifically, PERK employs two nested optimization loops in a meta-training phase. The inner loop rapidly encodes contexts into a low-rank adapter (LoRA) that serves as a parameter-efficient memory module for the base model. Concurrently, the outer loop learns to use the updated adapter to accurately recall and reason over relevant information from the encoded long context. Our evaluations on several long-context reasoning tasks show that PERK significantly outperforms the standard long-context finetuning, achieving average absolute performance gains of up to 20% for Qwen-2.5 (0.5B & 7B) on synthetic and real-world long-context reasoning. PERK also maintains its advantages across model scales and families. Compared to specialized long-context LLMs, PERK matches or surpasses their performance. Finally, our analyses show PERK is more robust to reasoning complexity, length extrapolation, and the positions of relevant information in contexts. https://perk-long-context.web.app 1 Introduction Large language models (LLMs) struggle with identifying and reasoning over relevant information in long, potentially noisy, input contexts (i.e., long-context reasoning; Anil et al., 2022; Lee et al., 2024; Li et al., 2024; Liu et al., 2023; Xu et al., 2024), including modern models with substantial context windows (Kuratov et al., 2024; Levy et al., 2024; Hsieh et al., 2024). These challenges arise because longer contexts contain more irrelevant distractor information, and may also be attached to more complex reasoning problems (i.e., multiple hops), both of which degrade LLM performance (Chen et al., 2023b). Furthermore, these challenges are exacerbated by positional biases in long-context models, which focus attention on the beginning or end of contexts, neglecting intermediate information (Liu et al., 2024). In this work, we address these challenges of long-context reasoning by reframing it as test-time learning, enabling models to encode context at inference time using gradient-based parameter adaptation. Our approach, Parameter-Efficient Reasoning over Knowledge (PERK), processes and compresses lengthy sequences as a batch (or multiple batches) of shorter segments of the long context, rather than processing the full sequence token-by-token. The updated model can then use the newly stored parametric information (and its initial parametric knowledge) to answer relevant questions. The original context is then discarded in this answering step as it is now encoded parametrically by the model. To train PERK, we operate two nested optimization loops: the inner loop, which is also used at test time, encodes the long context segment batches into model parameters, while the outer loop learns to respond to queries using the updated parameters representing the context. However, bi-level optimization algorithms for training test-time learning methods entail prohibitive memory overhead (Finn et al., 2017; Chen et al., 2023b; Hu et al., 2023), particularly when updating all model parameters, because they require backpropagating through a cLine truncated
@@ -0,0 +1 @@
Report GitHub Issue × Title: Content selection saved. Describe the issue below: Description: Submit without GitHub Submit in GitHub arXiv is now an independent nonprofit! Learn more × Back to arXiv Why HTML? Report Issue Back to Abstract Download PDF Abstract 1 Introduction 2 Oolong-synth 2.1 Data Data filtering 2.2 Context window construction Questions Instructions 2.3 Evaluation Baseline Parsing answers Scoring An iterative system? 3 Oolong-real 4 Results and Analysis 4.1 Does reasoning effort correlate to performance? Qwen and Olmo: think versus instruct models. GPT-5-nano: reasoning effort parameter. Gemini 3 Pro: reasoning token count. 4.2 Over-reasoning Deepseek R1 Gemini models 4.3 Refusal 4.4 Aggregation without classification Providing labels Providing domain knowledge 4.5 Iterative retrieval baseline for Oolong-real 5 Related Work 6 Conclusion 7 Acknowledgements References A Oolong - Leaderboard B Additional Results B.1 Reasoning effort on Oolong-real B.2 Data contamination for Oolong-real C Additional Related Work Dungeons & Dragons data Aggregation as an NLP concept D ICL label validation E Question types E.1 Oolong-synth E.2 Oolong-real F Deepseek R1 trace examples G Question type analysis G.1 Oolong-synth G.2 Oolong-real H Oolong-real data H.1 Data quality and curation H.2 Challenges of Oolong-real License: arXiv.org perpetual non-exclusive license arXiv:2511.02817v2 [cs.CL] 05 Oct 2026 Oolong: Evaluating Long Context Reasoning and Aggregation Capabilities Amanda Bertsch Adithya Pratapa ††thanks: Work conducted while the author was a student at Carnegie Mellon University. Teruko Mitamura Graham Neubig Matthew R. Gormley Carnegie Mellon University Email: {abertsch,vpratapa,teruko,gneubig,mgormley}@cs.cmu.edu Affiliation: abertsch72/oolong oolongbench/oolong-[synth, real] Abstract Many important applications of long-context models require reasoning over large collections of items-summarizing trends across thousands of customer interactions, identifying patterns in research corpora, or analyzing behavioral data at scale. Evaluating whether models can analyze items atomically and aggregate findings requires information-dense tasks that make use of the full context, like writing a summary or describing trends across a collection. Yet these types of tasks often result in long-form outputs which are challenging to evaluate; as a result, current long-context benchmarks predominantly test retrieval-style tasks, where the correct answer depends on a small fraction of tokens and the rest is noise. We introduce Oolong, a benchmark of distributional reasoning tasks that require analyzing individual chunks of text atomically and aggregating results. Oolong includes synthetic tasks (Oolong-synth) that enables controlled ablation of reasoning components, and a downstream setting (Oolong-real) that requires reasoning over realistic conversational data. Tasks involve classification, counting, filtering by metadata, and temporal reasoning. Frontier models struggle at moderate context lengths at this task, suggesting Oolong captures capabilities not yet addressed by scale alone. We release the data and evaluation harness for Oolong to enable further development of models that can reason over large quantities of text. 1 Introduction In the last several years, the exponentially increasing context lengths of LLMs have enabled many new applications, including reasoning models (Guo et al., 2025), many-shot prompting (Bertsch et al., 2024), and repository-level code generation (Jimenez et al., 2023). However, despite the rapid improvement in this area, long-context aggregation remains a challenging capability to study. Tasks like summarization are realistic but produce long and subjective outputs that are challenging to evaluate. Other tasks require only a subset of the input, which could be extracted from the long context before solving (e.g. using a RAG system). Multi-step reasoning datasets make progress towards measuring this capability, but often lack realism - inputs may be artificial (e.g. random repeated words in RULER (Hsieh et al., 2024)), the posed questions may be unrealistic (e.g. “identify the second poem about this topic” in MRCR (OpenAI, 2025)), and the relevant context may be embedded in distractor text from a different domain or a well-known text (e.g. small state updates embedded in Project Gutenberg novels in BABILong (Kuratov et al., 2024)). We argue that aggregation is best measured in realistic settings with multiple types of aggregative component tasks- including the ability to draw inferences across different sections of input and perform basic numeracy operations with this information. We further discuss these desiderata in Table 1. We propose Oolong, a benchmark that requires multi-hop reasoning over long inputs to produce easily verifiable outputs. Oolong is separated into two task sets, presented in Figure 1. Oolong-synth (§2) is a set of naturalistic synthetic tasks constructedLine truncated
@@ -0,0 +1 @@
Report GitHub Issue × Title: Content selection saved. Describe the issue below: Description: Submit without GitHub Submit in GitHub arXiv is now an independent nonprofit! Learn more × Back to arXiv Why HTML? Report Issue Back to Abstract Download PDF Abstract 1 Introduction 2 Related Work 3 Long-Context Reasoning Benchmark 3.1 Problem Formulation 3.2 Task Definition 3.3 Subtask Generation 4 Evaluation 4.1 Task Success Criteria 4.2 Implicit RAG (iRAG) Approach 5 Experiments 5.1 Retrieval Performance Analysis 5.1.1 Implicit RAG Retrieval Performance 5.2 Efficiency Ratio 6 Conclusion 7 Limitations 8 Reproducibility Statement References A Technical Appendices and Supplementary Material A.1 WebAgent Architecture A.2 System Prompts A.2.1 WebAgent system prompt A.2.2 Implicit RAG Prompt A.3 Sample Implicit RAG summary A.4 Summary Relevance Prompt A.5 Sample Trajectory A.6 List of successful tasks A.7 Sample interaction history A.8 False End Example A.8.1 Self evaluation A.9 Inefficent Progress Example A.10 Loop Example A.11 Implicit RAG analysis A.11.1 Failed retrieval without iRAG. A.11.2 With iRAG, successful retrieval of correct trajectory and completion of the task A.11.3 Summary used in the above task. License: CC BY 4.0 arXiv:2512.04307v1 [cs.LG] 03 Dec 2025 \workshoptitle LAW 2025: Bridging Language, Agent, and World Models Evaluating Long-Context Reasoning in LLM-Based WebAgents Andy Chung Affiliation: University of Michigan Yichi Zhang Affiliation: University of Michigan Kaixiang Lin Affiliation: Amazon Aditya Rawal Affiliation: Amazon Qiaozi Gao Affiliation: Amazon Joyce Chai Affiliation: University of Michigan Abstract As large language model (LLM)-based agents become increasingly integrated into daily digital interactions, their ability to reason across long interaction histories becomes crucial for providing personalized and contextually aware assistance. However, the performance of these agents in long context scenarios, particularly for action-taking WebAgents operating in realistic web environments, remains largely unexplored. This paper introduces a benchmark for evaluating long context reasoning capabilities of WebAgents through sequentially dependent subtasks that require retrieval and application of information from extended interaction histories. We develop a novel evaluation framework that simulates multi-session user interactions by injecting irrelevant task trajectories between dependent subtasks, creating contexts ranging from 25,000 to 150,000 tokens. Through extensive evaluation of four popular models, Claude-3.7, GPT-4.1, Llama 4, and o4-mini, we observe a dramatic performance degradation as context length increases, with success rates dropping from 40-50% in baseline conditions to less than 10% in long context scenarios. Our detailed error analysis reveals that agents primarily fail due to getting stuck in loops and losing track of original task objectives. We further propose an implicit RAG approach that provides modest improvements by generating task-relevant summaries, though fundamental limitations in long context reasoning persist. These findings highlight critical challenges for deploying WebAgents in realistic, long-term user interaction scenarios and provide insights for developing more robust agent architectures capable of maintaining coherent task execution across extended contexts. 1 Introduction Action-taking large language model (LLM)-based agents are rapidly becoming ubiquitous in our daily lives. These assistants extend their capabilities beyond the chatbot environment, making tangible impacts through their ability to operate in the digital realm. For instance, an action-taking agent can go beyond merely suggesting vacation plans-it can go online to book your flights and reserve your hotel as well. As these assistants become more capable, users will increasingly expect them to recognize and adapt to their preferences and intentions. A user may ask the assistant to find the highest-rated movie on IMDb and add it to your watchlist. Days later, they might simply say, “Buy that movie we saved earlier,” and expect it to understand the reference. While there have been many studies on the personalization of LLMs for chatbots and information retrieval assistants [7, 14, 15], studies exploring the personalization capabilities of LLM-based action-taking agents are lacking. We study WebAgents in the live open Internet, one of the most information-rich environments on earth. Its usage is ubiquitous in North America with 96% of the population online. This environment also enables an unparalleled variety of tasks such as shopping, social media, banking, entertainment, or research. This diversity enables us to design the experiment to closely reflect real-world human behavior. Despite the challenges and limitations of studying live environments, this approach enables us to observe how these agents perform under realistic conditions. We explore the capabilities of these types of Line truncated
@@ -0,0 +1 @@
Report GitHub Issue × Title: Content selection saved. Describe the issue below: Description: Submit without GitHub Submit in GitHub arXiv is now an independent nonprofit! Learn more × Back to arXiv Why HTML? Report Issue Back to Abstract Download PDF Abstract. 1 Introduction 2 Related Work 2.1 Long-context Attention Mechanisms 2.2 Hierarchical and External Memory Systems 2.3 Dynamic Routing and Query-aware Attention 3 Motivation: Beyond MLA and MHA 4 Methodology 4.1 Route-Fused MKA (FastMKA): Causal Route-Fusion with KV-Cache 4.2 Block-Memory Keyed Attention (Block-MKA) Design 4.3 Hierarchical Block-wise MKA Algorithm 4.4 Algorithm and Pseudocode 5 Theoretical Formulation: Recursive MKA with Online Softmax 5.1 Recursive Reformulation 5.2 Numerical Stability via Max-Shift 5.3 Local vs. Global MKA Modes 5.4 Runtime Bounds and Complexity 6 Experiments 6.1 Experimental Setup Models. Dataset. Hardware. Training Details. Inference Details. Metric. 6.2 Main Results 6.3 Experimental Results Analysis 6.4 Long-Context Benchmark Evaluation 6.5 Ablation Studies 6.5.1 Memory Tier Ablation 6.5.2 Routing Mechanism Ablation 6.5.3 Routing Weight Analysis 6.6 System-Level Performance Analysis 6.6.1 Prefill vs Decode Latency Breakdown 6.6.2 Computational Cost Analysis 6.7 Discussion 7 Conclusion Acknowledgements References License: CC BY 4.0 arXiv:2603.20586v2 [cs.LG] 24 Mar 2026 MKA: Memory-Keyed Attention for Efficient Long-Context Reasoning Conference: Proceedings of the 23rd ACM International Conference on Computing Frontiers; May 19-21, 2026; Catania, ItalyProceedings of the 23rd ACM International Conference on Computing Frontiers (CF '26), May 19-21, 2026, Catania, ItalyDOI: 10.1145/3801487.3801812ISBN: 979-8-4007-2568-5/2026/05 Dong Liu Affiliation: University of California, Los Angeles, Los Angeles, California, USA email: pikeliu@ucla.edu , Yanxuan Yu Affiliation: Columbia University, New York, New York, USA email: yy3523@columbia.edu , Ben Lengerich Affiliation: University of Wisconsin-Madison, Madison, Wisconsin, USA email: lengerich@wisc.edu and Ying Nian Wu Affiliation: University of California, Los Angeles, Los Angeles, California, USA email: ywu@stat.ucla.edu © cc Abstract. As long-context language modeling becomes increasingly important, the cost of maintaining and attending to large Key/Value (KV) caches grows rapidly, becoming a major bottleneck in both training and inference. While prior works such as Multi-Query Attention (MQA) and Multi-Latent Attention (MLA) reduce memory by sharing or compressing KV features, they often trade off representation quality or incur runtime overhead. We propose Memory-Keyed Attention (MKA), a hierarchical attention mechanism that integrates multi-level KV caches-local, session, and long-term-and learns to route attention across them dynamically. We further introduce Route-Fused MKA (FastMKA), a broadcast-routed variant that fuses memory sources before attention computation for enhanced efficiency. Experiments on different sequence lengths show that FastMKA achieves a favorable accuracy-efficiency trade-off: comparable perplexity to MLA while achieving up to 5 ×\times faster training throughput and 1.8 ×\times lower evaluation latency. These results highlight MKA as a practical and extensible framework for efficient long-context attention. ††cc-license: by 1. Introduction Large Language Models (LLMs) have rapidly advanced and are now capable of processing context lengths up to 128K or even 1M tokens (Peng et al., 2023; Jiang et al., 2024; Tworkowski et al., 2023). This unprecedented expansion of context length unlocks new applications such as long-document reasoning and multi-turn dialogue with persistent memory. However, supporting long contexts during inference introduces severe memory and latency bottlenecks, particularly due to the scaling cost of attention with Key/Value (KV) caches. In self-attention, each newly generated token must attend to all past tokens stored in the KV cache. This results in quadratic compute and increasingly large memory reads. For instance, with a context length of 3232K, we measure that the KV cache for a LLaMA-7B model (Touvron et al., 2023) occupies approximately 15.8GB on an NVIDIA A800 GPU and requires 11.3ms to access during decode, which dominates more than 50% of the inference latency. This cost limits the throughput of LLMs in production environments. Several methods have attempted to address the inefficiencies in attention computation. Multi-Query Attention (MQA) (Shazeer, 2019) and Grouped-Query Attention (GQA) (Ainslie et al., 2023) reduce KV duplication by sharing them across heads. Multi-Latent Attention (MLA) (Team, 2024) compresses the KV cache via low-rank factorization. However, these methods sacrifice representation fidelity or lack the flexibility to differentiate among memory types. Figure 1. MKA hierarchical memory design with three memory levels (L1: local, L2: session, L3: long-term) and dynamic routing gates. InLine truncated
@@ -0,0 +1 @@
Report GitHub Issue × Title: Content selection saved. Describe the issue below: Description: Submit without GitHub Submit in GitHub arXiv is now an independent nonprofit! Learn more × Back to arXiv Why HTML? Report Issue Back to Abstract Download PDF Abstract 1 Introduction 2 Curation of π2\pi^{2} 2.1 Table Collection and Expansion 2.2 QA Generation 2.3 Reasoning Trace Generation 2.4 π2\pi^{2} 2.5 π2\pi^{2}-Bench and Quality of π2\pi^{2} 3 Experiments 3.1 Methods and Baselines 3.2 Benchmarks and Metrics In-domain evaluation. Out-of-domain evaluation. Metrics. 3.3 Results 4 Ablation Study 4.1 Reasoning Style versus Faithful Reasoning Patterns 4.2 Contribution of Table Expansion and Context Realisticness 4.3 Comparison to Previous Long-Context Reasoning Datasets 5 Related Work 6 Conclusion References A π2\pi^{2}'s Curation and Statistics B Training Details C Additional Experiments and Analyses C.1 Efficiency of High-Quality Data. C.2 Effective Self-Distillation with Curated QA Data. C.3 Effect of Learning Rate C.4 Effect of Context Cleanliness C.5 Validation of π2\pi^{2}-Bench C.6 Comparison with Recursive Language Models C.7 Output Length Analysis C.8 Statistical Significance of Results D Attempts at Larger Improvements E Limitations F Prompts License: arXiv.org perpetual non-exclusive license arXiv:2604.05114v2 [cs.CL] 02 Oct 2026 Structure-Originated Reasoning Data Improves Long-Context Reasoning Ability of Large Language Models Quyet V. Do Thinh Pham Nguyen Nguyen Sha Li Pratibha Zunjare Tu Vu Affiliation: Virginia Tech, VA 24061, USA Email: {quyetdo,thinhphp,stephenallstar24,shal,pratibhaz,tuvu}@vt.edu Abstract We study a QA curation pipeline for improving long-context complex reasoning in large language models (LLMs). Our approach, π2\pi^{2}, constructs high-quality reasoning data through rigorous QA curation: 1) extracting and expanding tables from Wikipedia, 2) from the collected tables together with relevant metadata, generating complex reasoning questions whose answers are automatically determined and validated through dual-path code execution, 3) finally, back-translating chain-of-thoughts solutions grounded in realistic context. Supervised fine-tuning with gpt-oss-20b and Qwen3-4B-Instruct-2507 on π2\pi^{2} yields consistent improvements across four long-context reasoning benchmarks and our alike π2\pi^{2}-Bench, with average absolute accuracy gains of +6.25% and +3.37% respectively. Through deeper analyses, we observe that reasoning style contributes little, while faithful reasoning patterns discovered by back translation and grounded realistic long context, as π2\pi^{2} is designed for, are crucial for the improvement. Our code, data, and models are fully open-source at https://github.com/vtpss/pi-squared. 1 Introduction "In all chaos there is a cosmos, in all disorder a secret order." - Carl Jung Large language models (LLMs) have made rapid progress in handling long contexts, with recent systems supporting hundreds of thousands to millions of tokens (OpenAI et al., 2025; Yang et al., 2025a; OpenAI, 2026; Anthropic, 2026; Team, 2026). This capability enables applications that require integrating information from multiple documents, such as open-domain question answering, document analysis, and web-scale search. However, improved context capacity does not translate to reasoning capacity, as current flagship models still struggle on complex reasoning over realistic long contexts (Zhu et al., 2024; Bai et al., 2025; Chen et al., 2025b; Pham et al., 2026; Opsahl-Ong et al., 2026). A key bottleneck lies in the lack of verifiable supervision for complex reasoning over realistic long context in open-domain tasks. Hugging Face (2025) and Guha et al. (2026) represent a line of works that provide complex reasoning data with high-quality supervision, however, data domains are limited to natural science or coding. In another line of works, Yang et al. (2025b); Wang et al. (2025) and Xie et al. (2026) ensure the verifiability of open-domain reasoning data, yet the contexts are synthetic or limited to tables, not realistic long documents. Meanwhile, others were built on real open-domain documents and validated via code execution on concept graphs parsed from documents (Yang et al., 2018; Trivedi et al., 2022; Chen et al., 2025a; Shen et al., 2025; Sun et al., 2026). Nonetheless, their complexity is limited to low branching-factor reasoning patterns. This creates a gap: reliable supervision on complex reasoning is common in structured domains, but remains largely absent for realistic long contexts in open-domain tasks. Figure 1: Tables bridge realistic documents and complex reasoning in open-domain tasks. They represent data across sources, while being compatible with programmatic operations. We observe that structured representations, such as tables, is a natural medium that bridges complex reasoning and real long-context open-domain data, in which the data construction and verification are otherLine truncated
@@ -0,0 +1 @@
Report GitHub Issue × Title: Content selection saved. Describe the issue below: Description: Submit without GitHub Submit in GitHub arXiv is now an independent nonprofit! Learn more × Back to arXiv Why HTML? Report Issue Back to Abstract Download PDF Abstract 1 Introduction 2 Method 2.1 The Grounding-Reasoning Interference 2.2 DISCO: A Distributed Long Context Scaling Paradigm 2.3 Query Decomposition via Directed Acyclic Graphs 1. Narrow Actions (Map: Atomic Extraction). 2. Wide Actions (Reduce: Logical Synthesis). 2.4 Dynamic Orchestration: Iterative DAG Refinement 1. Planning Phase (DAG Construction). 2. Execution Phase (Evidence Pooling). 3. Evaluation and Refinement. 2.5 Optimizing the Driver via Reinforcement Learning 3 Experimental Setup 3.1 Evaluation Protocol Baselines. Benchmarks. Models and Implementation. 3.2 Implementation Details. Training Data Construction. Training Setup. 4 Results and Analyses 4.1 Main Results Distributed Partitioning Ensures Robust Grounding at Scale. Decoupling and Replanning Unleash Reasoning Potential. Architecture Decouples Cost from Scale. 4.2 Impact of Worker LLMs Sensitivity to Worker Model Scale. 4.3 Impact of Reward Design Planning Efficiency ( RevdR_{\text{evd}}). Structural Stability ( RfmtR_{\text{fmt}}). 4.4 Inference Efficiency and Scalability 5 Related Work Distributed Computing Paradigms. Long-Context Processing in LLMs. 6 Conclusion References A Appendix A.1 Algorithm A.2 Evaluation Details Baselines. Benchmarks. A.3 Training Data Construction A.4 More Experimental Results Fault Tolerance via Iterative Replanning. Generative Worker LLM Outperforms Dense Retriever. Generalization across Driver and Worker Families. A.5 Case Studies of RL Benefits Atomic, Entity-Specific Query Decomposition. Well-Formed DAGs and Faster Convergence. License: CC BY 4.0 arXiv:2609.33485v1 [cs.CL] 27 Sep 2026 DISCO: Distributed Long Context Scaling with Grounding-Reasoning Disaggregation Guanzheng Chen ††thanks: Work done during an internship at Adobe Research. Affiliation: National University of Singapore Affiliation: Adobe Research Correspondence:qizhex@nus.edu.sg, bui@adobe.com Viet Dac Lai Affiliation: Adobe Research Correspondence:qizhex@nus.edu.sg, bui@adobe.com Subhojyoti Mukherjee Affiliation: Adobe Research Correspondence:qizhex@nus.edu.sg, bui@adobe.com Branislav Kveton Affiliation: Adobe Research Correspondence:qizhex@nus.edu.sg, bui@adobe.com Seunghyun Yoon Affiliation: Adobe Research Correspondence:qizhex@nus.edu.sg, bui@adobe.com Franck Dernoncourt Affiliation: Adobe Research Correspondence:qizhex@nus.edu.sg, bui@adobe.com Qizhe Xie ††thanks: Corresponding authors. Affiliation: National University of Singapore Trung Bui22footnotemark: 2 Affiliation: Adobe Research Correspondence:qizhex@nus.edu.sg, bui@adobe.com Abstract While Large Language Models (LLMs) advertise million-token context windows, reasoning quality often collapses as inputs grow-a phenomenon termed context rot. This failure stems from a structural entanglement in monolithic architectures, where the massive search burden of contextual grounding exhausts the representational capacity needed for complex reasoning. To resolve this, we propose Grounding-Reasoning Disaggregation via DIStributed long COntext scaling (DISCO). Inspired by distributed computing frameworks like Apache Spark, DISCO partitions long context across a fleet of Worker LLMs dedicated exclusively to parallel, localized grounding. A central Driver LLM, trained via Reinforcement Learning (GRPO) to optimize planning, orchestrates execution by dynamically mapping queries into atomic extraction tasks and reducing the gathered evidence to synthesize a final answer. By isolating reasoning from raw context noise, DISCO effectively eliminates context rot. On RULER-QA (1M tokens), it maintains 78.4% accuracy where standard baselines collapse. Furthermore, it outperforms full-context models by up to 9.8 points on LongBench v2 and matches frontier models like Gemini-3-Pro-Preview while reducing inference costs by over 80%, establishing a highly efficient paradigm for robust long-context inference. 1 Introduction Recent Large Language Models (LLMs) increasingly advertise context windows spanning millions of tokens (OpenAI, 2023; Huang and Yang, 2025; Guo et al., 2025). Yet, even well within nominal limits, reasoning quality frequently collapses as input grows-a phenomenon termed context rot (Hsieh et al., 2024; Hong et al., 2025). Consequently, the effective context window of LLMs remains severely limited (Modarressi et al., 2025), hampering their reliability in real-world applications. We argue that context rot stems from a fundamental structural asymmetry between two distinct cognitive phases: contextual grounding (identifying relevant facts) and reasoning (synthesizing those facts into an answer). As input length increases, grounding becomes a massive search problem that scales proportionally with the text, while the core reasoning task Line truncated
@@ -0,0 +1,57 @@
{
"artifact": "longctx-niah-bench-2026-10-21",
"caught_all": true,
"control_passes": true,
"control_unmutated_exit_code": 0,
"mutations": [
{
"caught": true,
"expected_failure_text": "sha256 mismatch for 2409.02465",
"expected_text_present": true,
"mutation": "corpus byte flipped (2409.02465.txt)",
"verify_exit_code": 1
},
{
"caught": true,
"expected_failure_text": "missing corpus file for 2501.15089",
"expected_text_present": true,
"mutation": "corpus file deleted (2501.15089.txt)",
"verify_exit_code": 1
},
{
"caught": true,
"expected_failure_text": "sha256 mismatch for",
"expected_text_present": true,
"mutation": "manifest sha256 replaced with zeros",
"verify_exit_code": 1
},
{
"caught": true,
"expected_failure_text": "live title differs from manifest",
"expected_text_present": true,
"mutation": "manifest title replaced with a fake",
"verify_exit_code": 1
},
{
"caught": true,
"expected_failure_text": "differs from a fresh build_bench.compute()",
"expected_text_present": true,
"mutation": "results.json A1 rate edited 1.000 -> 0.500",
"verify_exit_code": 1
},
{
"caught": true,
"expected_failure_text": "REPORT.md is not the deterministic render",
"expected_text_present": true,
"mutation": "REPORT.md number edited 1.000 -> 0.999",
"verify_exit_code": 1
},
{
"caught": true,
"expected_failure_text": "expected 20 needles, found 19",
"expected_text_present": true,
"mutation": "benchmark rebuilt with 19 needles (self-consistent)",
"verify_exit_code": 1
}
]
}
@@ -0,0 +1,133 @@
#!/usr/bin/env python3
"""falsify.py — mutation controls. A verifier that cannot fail is not a verifier.
Each mutation is applied to a throwaway copy of the artifact, verify.py is run there, and the
result is graded: the run MUST exit nonzero AND the failure text MUST name the expected check.
Results are written to falsification.json (measured exit codes and matched messages).
"""
import json
import os
import shutil
import subprocess
import sys
import tempfile
BASE = os.path.dirname(os.path.abspath(__file__))
OUT = os.path.join(BASE, "falsification.json")
SCRATCH = os.environ.get("TMPDIR", "/tmp")
def copy_artifact():
d = tempfile.mkdtemp(prefix="falsify-", dir=SCRATCH)
dst = os.path.join(d, "artifact")
shutil.copytree(BASE, dst, ignore=shutil.ignore_patterns("__pycache__", "*.pyc"))
return d, dst
def m_corpus_byte(dst):
p = os.path.join(dst, "corpus", "2409.02465.txt")
raw = bytearray(open(p, "rb").read())
raw[5000] = raw[5000] ^ 0x20
open(p, "wb").write(bytes(raw))
def m_results_number(dst):
p = os.path.join(dst, "results.json")
r = json.load(open(p))
r["scales"][0]["a1_top1_rate"] = 0.5
json.dump(r, open(p, "w"), indent=2, sort_keys=True)
def m_report_text(dst):
p = os.path.join(dst, "REPORT.md")
t = open(p, encoding="utf-8").read()
open(p, "w", encoding="utf-8").write(t.replace("| 1.000 | 1.000 | 1.000 |", "| 0.999 | 1.000 | 1.000 |", 1))
def m_manifest_sha(dst):
p = os.path.join(dst, "manifest.json")
m = json.load(open(p))
m[0]["sha256"] = "0" * 64
json.dump(m, open(p, "w"), indent=2)
def m_manifest_title(dst):
p = os.path.join(dst, "manifest.json")
m = json.load(open(p))
m[0]["title"] = "A Paper That Does Not Exist On arXiv"
json.dump(m, open(p, "w"), indent=2)
def m_corpus_missing(dst):
os.remove(os.path.join(dst, "corpus", "2501.15089.txt"))
def m_rebuild_with_19_needles(dst):
"""Self-consistent rebuild: results.json and REPORT.md both regenerated, so only the
absolute invariants (not the re-derivation) can catch it."""
p = os.path.join(dst, "build_bench.py")
t = open(p, encoding="utf-8").read()
assert "N_NEEDLES = 20" in t
open(p, "w", encoding="utf-8").write(t.replace("N_NEEDLES = 20", "N_NEEDLES = 19", 1))
for script in ("build_bench.py", "render_report.py"):
subprocess.run([sys.executable, script], cwd=dst, check=True, capture_output=True)
MUTATIONS = [
("corpus byte flipped (2409.02465.txt)", m_corpus_byte, "sha256 mismatch for 2409.02465"),
("corpus file deleted (2501.15089.txt)", m_corpus_missing, "missing corpus file for 2501.15089"),
("manifest sha256 replaced with zeros", m_manifest_sha, "sha256 mismatch for"),
("manifest title replaced with a fake", m_manifest_title, "live title differs from manifest"),
("results.json A1 rate edited 1.000 -> 0.500", m_results_number,
"differs from a fresh build_bench.compute()"),
("REPORT.md number edited 1.000 -> 0.999", m_report_text,
"REPORT.md is not the deterministic render"),
("benchmark rebuilt with 19 needles (self-consistent)", m_rebuild_with_19_needles,
"expected 20 needles, found 19"),
]
def main():
rows = []
for name, mutate, expect in MUTATIONS:
d, dst = copy_artifact()
try:
mutate(dst)
proc = subprocess.run([sys.executable, "verify.py"], cwd=dst,
capture_output=True, text=True, timeout=600)
out = proc.stdout + proc.stderr
caught = proc.returncode != 0 and expect in out
rows.append({"mutation": name, "expected_failure_text": expect,
"verify_exit_code": proc.returncode,
"expected_text_present": expect in out,
"caught": caught})
print("%-58s exit=%-3d caught=%s" % (name, proc.returncode, caught))
if not caught:
print(" --- verify.py output tail ---")
print(" " + "\n ".join(out.strip().splitlines()[-6:]))
finally:
shutil.rmtree(d, ignore_errors=True)
# control: the unmutated artifact must still pass
proc = subprocess.run([sys.executable, "verify.py"], cwd=BASE, capture_output=True,
text=True, timeout=600)
control_ok = proc.returncode == 0
print("%-58s exit=%-3d (control, must be 0)" % ("unmutated artifact", proc.returncode))
summary = {
"artifact": "longctx-niah-bench-2026-10-21",
"mutations": rows,
"caught_all": all(r["caught"] for r in rows),
"control_unmutated_exit_code": proc.returncode,
"control_passes": control_ok,
}
json.dump(summary, open(OUT, "w", encoding="utf-8"), indent=2, sort_keys=True)
print("wrote", OUT)
ok = summary["caught_all"] and control_ok
print("%d/%d mutations caught; control %s" % (sum(1 for r in rows if r["caught"]), len(rows),
"passes" if control_ok else "FAILS"))
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,207 @@
[
{
"arxiv_id": "2507.06415",
"title": "PERK: Long-Context Reasoning as Test-Time Learning",
"sha256": "2fc3cc3b8de1ebf74c0af3dbdb978ee70189d7ed3c81bf2ccba854e71b75cd70",
"chars": 107789,
"words": 16431,
"method": "arxiv-html",
"url": "https://arxiv.org/html/2507.06415v3",
"published": "2025-07-08T21:38:45Z",
"authors": [
"Zeming Chen",
"Angelika Romanou",
"Gail Weiss",
"Antoine Bosselut"
]
},
{
"arxiv_id": "2604.05114",
"title": "$\u03c0^2$: Structure-Originated Reasoning Data Improves Long-Context Reasoning Ability of Large Language Models",
"sha256": "279d4db0339ec14accdbb697bdf85b921f81e0b6dc65690ecfe51e35f61ccbe6",
"chars": 94666,
"words": 14376,
"method": "arxiv-html",
"url": "https://arxiv.org/html/2604.05114v2",
"published": "2026-04-06T19:19:58Z",
"authors": [
"Quyet V. Do",
"Thinh Pham",
"Nguyen Nguyen",
"Sha Li",
"Pratibha Zunjare",
"Tu Vu"
]
},
{
"arxiv_id": "2505.09561",
"title": "Learning Long-Context Diffusion Policies via Past-Token Prediction",
"sha256": "64d333b9a86ab163879d0e0e021dbc1b74c34b947a146e390f467fc145211e7b",
"chars": 58077,
"words": 8209,
"method": "arxiv-html",
"url": "https://arxiv.org/html/2505.09561v2",
"published": "2025-05-14T17:00:47Z",
"authors": [
"Marcel Torne",
"Andy Tang",
"Yuejiang Liu",
"Chelsea Finn"
]
},
{
"arxiv_id": "2603.20586",
"title": "MKA: Memory-Keyed Attention for Efficient Long-Context Reasoning",
"sha256": "5028e872acf1e0a59790b0ae34073019ceccc41dbf7259f469a60f253ef9d393",
"chars": 50726,
"words": 6519,
"method": "arxiv-html",
"url": "https://arxiv.org/html/2603.20586v2",
"published": "2026-03-21T01:04:03Z",
"authors": [
"Dong Liu",
"Yanxuan Yu",
"Ben Lengerich",
"Ying Nian Wu"
]
},
{
"arxiv_id": "2507.06261",
"title": "Gemini 2.5: Pushing the Frontier with Advanced Reasoning, Multimodality, Long Context, and Next Generation Agentic Capabilities",
"sha256": "e89e8fe09f9a9d4e7abddaaf111f5a0637193f95d2ddd1513518834e078daadf",
"chars": 215441,
"words": 32428,
"method": "arxiv-html",
"url": "https://arxiv.org/html/2507.06261v6",
"published": "2025-07-07T17:36:04Z",
"authors": [
"Gheorghe Comanici",
"Eric Bieber",
"Mike Schaekermann",
"Ice Pasupat",
"Noveen Sachdeva",
"Inderjit Dhillon"
]
},
{
"arxiv_id": "2609.33485",
"title": "DISCO: Distributed Long Context Scaling with Grounding-Reasoning Disaggregation",
"sha256": "bca2c8591bee48ac7108ced57c5c32ce3b2cfbab024fd89963653a36abbb653f",
"chars": 58262,
"words": 7852,
"method": "arxiv-html",
"url": "https://arxiv.org/html/2609.33485v1",
"published": "2026-09-27T11:52:58Z",
"authors": [
"Guanzheng Chen",
"Viet Dac Lai",
"Subhojyoti Mukherjee",
"Branislav Kveton",
"Seunghyun Yoon",
"Franck Dernoncourt"
]
},
{
"arxiv_id": "2512.04307",
"title": "Evaluating Long-Context Reasoning in LLM-Based WebAgents",
"sha256": "4d7aa25dc87e202e55f5de1b1efd49bff10585ba6abcb6d57232125b6cfc9bfc",
"chars": 75916,
"words": 12070,
"method": "arxiv-html",
"url": "https://arxiv.org/html/2512.04307v1",
"published": "2025-12-03T22:53:10Z",
"authors": [
"Andy Chung",
"Yichi Zhang",
"Kaixiang Lin",
"Aditya Rawal",
"Qiaozi Gao",
"Joyce Chai"
]
},
{
"arxiv_id": "2409.02465",
"title": "DetectiveQA: Evaluating Long-Context Reasoning on Detective Novels",
"sha256": "df0bba49bc0e680305b4777142d7e1bb89053e4b88343274f4e9c6c3038d2dbd",
"chars": 52235,
"words": 7501,
"method": "arxiv-html",
"url": "https://arxiv.org/html/2409.02465v2",
"published": "2024-09-04T06:28:22Z",
"authors": [
"Zhe Xu",
"Jiasheng Ye",
"Xiaoran Liu",
"Xiangyang Liu",
"Tianxiang Sun",
"Zhigeng Liu"
]
},
{
"arxiv_id": "2502.13127",
"title": "Facilitating Long Context Understanding via Supervised Chain-of-Thought Reasoning",
"sha256": "0d915778445dcca79c2129d6f10ad8a4ae1e597a685228cf4a6d524f7028b7a4",
"chars": 68852,
"words": 9795,
"method": "arxiv-html",
"url": "https://arxiv.org/html/2502.13127v2",
"published": "2025-02-18T18:50:06Z",
"authors": [
"Jingyang Lin",
"Andy Wong",
"Tian Xia",
"Shenghua He",
"Hui Wei",
"Mei Han"
]
},
{
"arxiv_id": "2511.02817",
"title": "Oolong: Evaluating Long Context Reasoning and Aggregation Capabilities",
"sha256": "c48531ba8afab0a2daf6426d6d61ecd7e69f4a1a9548523a8646001e399c5307",
"chars": 96818,
"words": 15314,
"method": "arxiv-html",
"url": "https://arxiv.org/html/2511.02817v2",
"published": "2025-11-04T18:42:12Z",
"authors": [
"Amanda Bertsch",
"Adithya Pratapa",
"Teruko Mitamura",
"Graham Neubig",
"Matthew R. Gormley"
]
},
{
"arxiv_id": "2506.02483",
"title": "Enhancing Large Language Models with Neurosymbolic Reasoning for Multilingual Tasks",
"sha256": "6944178578777a8f910ea0adaf5462491dd96eeb95ec1e9021d374b0d9934eb7",
"chars": 44708,
"words": 6158,
"method": "arxiv-html",
"url": "https://arxiv.org/html/2506.02483v1",
"published": "2025-06-03T05:54:20Z",
"authors": [
"Sina Bagheri Nezhad",
"Ameeta Agrawal"
]
},
{
"arxiv_id": "2501.15089",
"title": "LongReason: A Synthetic Long-Context Reasoning Benchmark via Context Expansion",
"sha256": "9b8890e73d5ffcf7131fd816b38cd984397e6e0df4d50000613525dbc85b759d",
"chars": 40808,
"words": 5662,
"method": "arxiv-html",
"url": "https://arxiv.org/html/2501.15089v3",
"published": "2025-01-25T05:32:14Z",
"authors": [
"Zhan Ling",
"Kang Liu",
"Kai Yan",
"Yifan Yang",
"Weijian Lin",
"Ting-Han Fan"
]
}
]
@@ -0,0 +1,32 @@
{
"artifact": "longctx-niah-bench-2026-10-21",
"corpus_origin": {
"archive": "/root/troupe-standup/wizard-work/sphinx/longctx-arxiv-archive-2026-10-20",
"corpus_files": 12,
"corpus_files_byte_identical": true,
"corpus_ids_match": true,
"manifest_sha256": "c0bc5a4c2203fd1027e0de7076c7f7eeb3981eda36453a19c7ec76b0fdf08a0e",
"manifest_sha256_matches_local": true,
"present_on_this_host": true,
"source_archive_verify_exit_code": 0,
"source_archive_verify_tail": [
"ALL CHECKS PASSED: every quote is verbatim in its cited source; corpus sha256 intact"
],
"source_manifest_entries": 12
},
"local_corpus_sha256": {
"2409.02465": "df0bba49bc0e680305b4777142d7e1bb89053e4b88343274f4e9c6c3038d2dbd",
"2501.15089": "9b8890e73d5ffcf7131fd816b38cd984397e6e0df4d50000613525dbc85b759d",
"2502.13127": "0d915778445dcca79c2129d6f10ad8a4ae1e597a685228cf4a6d524f7028b7a4",
"2505.09561": "64d333b9a86ab163879d0e0e021dbc1b74c34b947a146e390f467fc145211e7b",
"2506.02483": "6944178578777a8f910ea0adaf5462491dd96eeb95ec1e9021d374b0d9934eb7",
"2507.06261": "e89e8fe09f9a9d4e7abddaaf111f5a0637193f95d2ddd1513518834e078daadf",
"2507.06415": "2fc3cc3b8de1ebf74c0af3dbdb978ee70189d7ed3c81bf2ccba854e71b75cd70",
"2511.02817": "c48531ba8afab0a2daf6426d6d61ecd7e69f4a1a9548523a8646001e399c5307",
"2512.04307": "4d7aa25dc87e202e55f5de1b1efd49bff10585ba6abcb6d57232125b6cfc9bfc",
"2603.20586": "5028e872acf1e0a59790b0ae34073019ceccc41dbf7259f469a60f253ef9d393",
"2604.05114": "279d4db0339ec14accdbb697bdf85b921f81e0b6dc65690ecfe51e35f61ccbe6",
"2609.33485": "bca2c8591bee48ac7108ced57c5c32ce3b2cfbab024fd89963653a36abbb653f"
},
"local_manifest_sha256": "c0bc5a4c2203fd1027e0de7076c7f7eeb3981eda36453a19c7ec76b0fdf08a0e"
}
@@ -0,0 +1,71 @@
#!/usr/bin/env python3
"""provenance.py — records where the corpus came from and proves the copy is byte-identical.
The corpus of this artifact is a byte copy of the corpus of wizard-work/sphinx/longctx-arxiv-archive-2026-10-20,
which was fetched from arXiv and carries its own sha256 manifest + verifier. This script re-hashes both
sides, runs the source archive's own verifier, and writes provenance.json.
"""
import hashlib
import json
import os
import subprocess
import sys
BASE = os.path.dirname(os.path.abspath(__file__))
OUT = os.path.join(BASE, "provenance.json")
SOURCE = os.environ.get(
"LONGCTX_SOURCE",
"/root/troupe-standup/wizard-work/sphinx/longctx-arxiv-archive-2026-10-20")
def sha256_file(p):
return hashlib.sha256(open(p, "rb").read()).hexdigest()
def corpus_hashes(root):
d = os.path.join(root, "corpus")
return {f[:-4]: sha256_file(os.path.join(d, f))
for f in sorted(os.listdir(d)) if f.endswith(".txt")}
def main():
local_manifest = os.path.join(BASE, "manifest.json")
local = corpus_hashes(BASE)
prov = {
"artifact": "longctx-niah-bench-2026-10-21",
"local_manifest_sha256": sha256_file(local_manifest),
"local_corpus_sha256": local,
"corpus_origin": {"archive": SOURCE, "present_on_this_host": os.path.isdir(SOURCE)},
}
if not os.path.isdir(SOURCE):
json.dump(prov, open(OUT, "w"), indent=2, sort_keys=True)
print("source archive absent; recorded local hashes only")
return 0
src_manifest = os.path.join(SOURCE, "manifest.json")
src = corpus_hashes(SOURCE)
same_ids = sorted(local) == sorted(src)
identical = [k for k in local if k in src and local[k] == src[k]]
prov["corpus_origin"].update({
"manifest_sha256": sha256_file(src_manifest),
"manifest_sha256_matches_local": sha256_file(src_manifest) == prov["local_manifest_sha256"],
"corpus_files": len(local),
"corpus_files_byte_identical": same_ids and len(identical) == len(local),
"corpus_ids_match": same_ids,
"source_manifest_entries": len(json.load(open(src_manifest))),
})
proc = subprocess.run([sys.executable, "verify.py"], cwd=SOURCE, capture_output=True, text=True,
timeout=600)
prov["corpus_origin"]["source_archive_verify_exit_code"] = proc.returncode
prov["corpus_origin"]["source_archive_verify_tail"] = proc.stdout.strip().splitlines()[-1:] or [""]
json.dump(prov, open(OUT, "w"), indent=2, sort_keys=True)
print("wrote", OUT)
print("source manifest sha256 == local manifest sha256:", prov["corpus_origin"]["manifest_sha256_matches_local"])
print("corpus files byte-identical: %d/%d" % (len(identical), len(local)))
print("source archive verify.py exit code:", proc.returncode)
return 0 if (prov["corpus_origin"]["manifest_sha256_matches_local"]
and len(identical) == len(local) and proc.returncode == 0) else 1
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,174 @@
#!/usr/bin/env python3
"""render_report.py — REPORT.md is a pure function of results.json.
Deterministic: no timestamps, no randomness. verify.py byte-compares REPORT.md against
render(results), so no number in the prose can drift from the measurement.
"""
import json
import os
import sys
BASE = os.path.dirname(os.path.abspath(__file__))
RESULTS = os.path.join(BASE, "results.json")
REPORT = os.path.join(BASE, "REPORT.md")
def f3(x):
return "%.3f" % x
def f4(x):
return "%.4f" % x
def pc(x):
return "%.1f%%" % (100.0 * x)
def render(r):
sc = r["scales"]
last = sc[-1]
full = [s for s in sc if s["scale"] == 1.00][0]
tr = r["truncation"]
L = []
A = L.append
A("# Long-context needle retrieval under context scaling — measured, not asserted")
A("")
A("Artifact: `%s` · seed `%d`" % (r["artifact"], r["seed"]))
A("")
A("| component | value |")
A("|---|---|")
A("| corpus | %d arXiv full texts, %s decoded chars, sha256 verified against manifest |"
% (r["corpus"]["docs"], format(r["corpus"]["decoded_chars"], ",")))
A("| retriever | %s (k1=%s, b=%s), tokenizer `%s`, %s |"
% (r["retriever"]["name"], r["retriever"]["k1"], r["retriever"]["b"],
r["retriever"]["tokenizer"], r["retriever"]["implementation"]))
A("| chunking | %d-char chunks, %d-char stride, %d chunks at full scale |"
% (r["chunking"]["chunk_chars"], r["chunking"]["stride_chars"], full["n_chunks"]))
A("| haystack | %s chars ≈ %s tokens (%s) |"
% (format(tr["haystack_chars"], ","), format(tr["haystack_tokens_est"], ","),
tr["token_estimator"]))
A("| needles | %d synthetic facts; key token unique in corpus: %s; value token unique in corpus: %s |"
% (len(r["needles"]), r["needle_material"]["key_token_unique_in_corpus"],
r["needle_material"]["value_token_unique_in_corpus"]))
A("| needle minting | %d candidates drawn, %d rejected for colliding with corpus text, %d key-prefix hits, %d phrase hits in corpus |"
% (r["needle_material"]["minting"]["attempts"],
r["needle_material"]["minting"]["rejected_candidates"],
r["needle_material"]["minting"]["occurrences_of_key_prefix_in_corpus"],
r["needle_material"]["minting"]["occurrences_of_phrase_in_corpus"]))
A("")
A("Conditions, all run at every context scale:")
A("")
A("- **A1** minimal keyed query — `custody token for project ZEPHYR-1177`")
A("- **A2** the same query wrapped in natural language — `What is the custody token for project ZEPHYR-1177?`")
A("- **B** class-only query — `custody token` (matches all 20 needles; no key)")
A("- **C** value-keyed query — `custody token 59960` (reverse lookup)")
A("- **R** uniform random chunk draw — measured floor, no retriever involved")
A("")
A("## 1. Results")
A("")
A("| context scale | chunks | A1 top-1 | A1 top-5 | A2 top-1 | C top-1 | B any-needle | B specific | R top-1 | R any-needle |")
A("|---|---|---|---|---|---|---|---|---|---|")
for s in sc:
A("| %d%% | %d | %s | %s | %s | %s | %s | %s | %s | %s |"
% (int(s["scale"] * 100), s["n_chunks"], f3(s["a1_top1_rate"]), f3(s["a1_top5_rate"]),
f3(s["a2_top1_rate"]), f3(s["c_top1_rate"]), f3(s["b_any_rate"]),
f3(s["b_gold_rate"]), f4(s["random_top1_rate"]), f3(s["random_any_needle_rate"])))
A("")
A("## 2. Findings")
A("")
A("**F1 — keyed lexical retrieval does not degrade with context growth.** A1 top-1 = %s and"
% f3(min(s["a1_top1_rate"] for s in sc)))
A("A1 top-5 = %s at all four scales (%d → %d chunks, a %.1fx increase in distractors)."
% (f3(min(s["a1_top5_rate"] for s in sc)), sc[0]["n_chunks"], last["n_chunks"],
last["n_chunks"] / sc[0]["n_chunks"]))
A("The measured floor for the same task is R top-1 = %s at full scale, i.e. ≈ 1/%d chunks."
% (f4(full["random_top1_rate"]), full["n_chunks"]))
A("")
A("**F2 — the ceiling belongs to the key, not to the retriever.** Dropping the key (B) leaves the"
" class phrase intact: any-needle hit stays %s while specific-needle hit collapses to %s at every"
" scale. The metric therefore discriminates 'found the class' from 'found the needle'."
% (f3(min(s["b_any_rate"] for s in sc)), f3(max(s["b_gold_rate"] for s in sc))))
A("")
A("**F3 — natural-language wrapping is not free.** A2 = %s at 50%% and 100%% against A1 = %s:"
% (f3(min(s["a2_top1_rate"] for s in sc)), f3(min(s["a1_top1_rate"] for s in sc))))
A("one needle in twenty flips. Measured cause: the function word `what` occurs in only %d of %d"
" chunks (idf %s) while `the` occurs in %d (idf %s), so a single `what` is worth up to %s points."
% (r["query_word_statistics_full_index"]["what"]["df"], full["n_chunks"],
f3(r["query_word_statistics_full_index"]["what"]["idf"]),
r["query_word_statistics_full_index"]["the"]["df"],
f3(r["query_word_statistics_full_index"]["the"]["idf"]),
f3(r["query_word_statistics_full_index"]["what"]["idf"])))
A("")
A("**F4 — reverse lookup works.** C top-1 = %s at all scales: keying on the value instead of the"
" key costs nothing, because value tokens are corpus-unique by construction."
% f3(min(s["c_top1_rate"] for s in sc)))
A("")
pos_curve = r["position_curve_a1_top1_by_depth_decile"]
A("**F5 — position invariance.** A1 top-1 by depth decile: %s. No lost-in-the-middle effect for"
% ", ".join("%s:%s" % (d, f3(v)) for d, v in pos_curve.items()))
A("lexical retrieval on this corpus — unlike the LLM behaviour the archived papers report.")
A("")
w32, w128 = tr["windows"][0], tr["windows"][1]
A("**F6 — truncation, not retrieval, is the binding constraint.** The haystack is ≈ %s tokens."
% format(tr["haystack_tokens_est"], ","))
A("A %s-token window holds %s of the chunks and loses %d/%d needles (%s); a %s-token window holds"
% (format(w32["window_tokens"], ","), pc(1 - w32["chunk_loss_rate"]), w32["needles_lost"],
len(r["needles"]), pc(w32["needle_loss_rate"]), format(w128["window_tokens"], ",")))
A("%s of the chunks and loses %d/%d (%s)." % (pc(1 - w128["chunk_loss_rate"]),
w128["needles_lost"], len(r["needles"]),
pc(w128["needle_loss_rate"])))
A("")
A("## 3. Diagnostics for the two A2 flips")
A("")
for s in sc:
for m in s["a2_misses"]:
gb, tb = m["gold_breakdown"], m["top1_breakdown"]
A("**scale %d%% of the corpus, needle %d — `%s`**" % (int(m["scale"] * 100), m["needle"], m["query"]))
A("")
A("- gold chunk %d scores %s (dl %d); top-1 chunk %d scores %s (dl %d); margin %s"
% (m["gold_chunk"], f4(gb["total"]), gb["dl"], m["top1_chunk"], f4(tb["total"]),
tb["dl"], f4(tb["total"] - gb["total"])))
A("- in the gold chunk the corpus-unique key token contributes %s"
% " and ".join("%s=%s(tf %d)" % (w, f4(v["contribution"]), v["tf"])
for w, v in sorted(gb["terms"].items()) if w.isdigit()))
A("- the top-1 chunk wins on shared terms and length normalisation: %s"
% ", ".join("%s tf %d → %s" % (w, v["tf"], f4(v["contribution"]))
for w, v in sorted(tb["terms"].items())))
A("- top-1 chunk is itself a planted needle chunk (it contains `%s`); the miss is a"
% r["needle_material"]["phrase"])
A(" near-tie between two needles of the same class, not a failure to find the class.")
A("")
A("## 4. Limits")
A("")
A("- One corpus (12 long-context arXiv papers) and one retriever family (lexical BM25). No dense or"
" embedding retriever was run, and no LLM is in the loop: nothing here is a claim about model behaviour.")
A("- Needle keys are synthetic and verified token-unique. A natural-language fact whose key words are"
" ordinary would be harder — which is precisely what F3 and §3 measure.")
A("- Token counts are an estimate (1 token ≈ 4 chars), stated as such; char counts are exact.")
A("- B 'specific' = %s is a tie-order artefact: the %d needle chunks score near-identically for the"
% (f3(max(s["b_gold_rate"] for s in sc)), len(r["needles"])))
A(" class phrase, so top-1 is decided by length normalisation, not by a retrieval failure.")
A("")
A("## 5. Reproduce and falsify")
A("")
A("```")
A("python3 build_bench.py # corpus + manifest -> results.json")
A("python3 render_report.py # results.json -> REPORT.md (byte-identical to this file)")
A("python3 verify.py # exit code is the verdict: corpus sha256, live arXiv titles,")
A(" # benchmark re-derivation, sensitivity thresholds, report identity")
A("python3 falsify.py # mutation controls; each must make verify.py exit nonzero")
A("```")
return "\n".join(L) + "\n"
def main():
r = json.load(open(RESULTS, encoding="utf-8"))
text = render(r)
open(REPORT, "w", encoding="utf-8").write(text)
print("wrote %s (%d bytes, %d lines)" % (REPORT, len(text), text.count("\n")))
return 0
if __name__ == "__main__":
sys.exit(main())
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,372 @@
#!/usr/bin/env python3
"""verify.py — the verdict. Exit 0 = the artifact is what it claims to be.
Layers (none of them trusts results.json on its own):
1. corpus integrity : every file re-hashed against manifest.json (sha256 + decoded char count)
2. upstream anchor : every arXiv id re-fetched live; title must normalise-equal the manifest title
3. independent run : a second BM25 implementation (written separately, same definition) must
reproduce every stored A1 and C top-1 chunk id, all 80 needle-scale pairs
4. re-derivation : build_bench.compute() must reproduce results.json exactly (no hand-edited numbers)
5. invariants : needle material absent from the source corpus; needle lands in exactly the
declared gold chunk; chunk geometry; random floor inside 3 sigma of 1/n;
truncation arithmetic; position curve consistent with per-needle rows
6. sensitivity : the harness must separate conditions (A1 >> B-specific, B-any high, floor low)
7. report identity : REPORT.md must be byte-identical to render_report.render(results.json)
Network is required for layer 2 (arXiv API); failure there is a failure, not a warning.
"""
import hashlib
import json
import math
import os
import re
import sys
import urllib.request
import xml.etree.ElementTree as ET
BASE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, BASE)
import build_bench as bb # noqa: E402 (layer 4)
import render_report as rr # noqa: E402 (layer 7)
ARXIV_API = "https://export.arxiv.org/api/query?id_list=%s&max_results=20"
WORD = re.compile(r"[a-z0-9]+")
STUB_MARKERS = ("TODO", "TBD", "FIXME", "XXX", "<placeholder")
fails = []
checks = 0
def ck(cond, msg):
global checks
checks += 1
if not cond:
fails.append(msg)
return bool(cond)
# ---------------------------------------------------------------- layer 1
def corpus_integrity():
man = json.load(open(os.path.join(BASE, "manifest.json"), encoding="utf-8"))
texts = {}
ck(len(man) == 12, "manifest holds %d sources, expected 12" % len(man))
for m in man:
aid = m["arxiv_id"]
p = os.path.join(BASE, "corpus", aid + ".txt")
if not ck(os.path.exists(p), "missing corpus file for %s" % aid):
continue
raw = open(p, "rb").read()
ck(hashlib.sha256(raw).hexdigest() == m["sha256"], "sha256 mismatch for %s" % aid)
txt = raw.decode("utf-8")
ck(len(txt) == m["chars"], "decoded char count mismatch for %s" % aid)
texts[aid] = txt
return man, texts
# ---------------------------------------------------------------- layer 2
def upstream_titles(man):
ids = [m["arxiv_id"] for m in man]
raw = None
last = None
for attempt in range(3):
try:
req = urllib.request.Request(ARXIV_API % ",".join(ids),
headers={"User-Agent": "sphinx-archive-verifier/1.0"})
raw = urllib.request.urlopen(req, timeout=45).read().decode("utf-8")
break
except Exception as exc: # noqa: BLE001
last = exc
if not ck(raw is not None, "arXiv API unreachable after 3 attempts (%s)" % last):
return {}
ns = {"a": "http://www.w3.org/2005/Atom"}
live = {}
for e in ET.fromstring(raw).findall("a:entry", ns):
aid = re.sub(r"v\d+$", "", e.find("a:id", ns).text.rsplit("/abs/", 1)[-1])
live[aid] = " ".join(e.find("a:title", ns).text.split())
ck(len(live) == len(ids), "arXiv returned %d/%d requested ids" % (len(live), len(ids)))
sims = {}
for m in man:
lt = live.get(m["arxiv_id"])
if not ck(lt is not None, "arXiv has no entry for %s" % m["arxiv_id"]):
continue
a, b = set(WORD.findall(lt.lower())), set(WORD.findall(m["title"].lower()))
jac = len(a & b) / max(1, len(a | b))
sims[m["arxiv_id"]] = round(jac, 4)
ck(norm(lt) == norm(m["title"]),
"live title differs from manifest for %s (jaccard %.3f): %r vs %r"
% (m["arxiv_id"], jac, lt[:60], m["title"][:60]))
return sims
def norm(s):
return re.sub(r"[^a-z0-9]+", "", s.lower())
# ---------------------------------------------------------------- layer 3
class IndependentBM25:
"""Same BM25 definition, written separately: dict-keyed postings, log1p idf, heap top-k."""
def __init__(self, docs):
self.n = len(docs)
self.postings = [{} for _ in docs]
docfreq = {}
self.length = []
for i, d in enumerate(docs):
for w in WORD.findall(d.lower()):
self.postings[i][w] = self.postings[i].get(w, 0) + 1
self.length.append(sum(self.postings[i].values()))
for w in self.postings[i]:
docfreq[w] = docfreq.get(w, 0) + 1
self.avg = sum(self.length) / self.n
self.idf = {w: math.log1p((self.n - d + 0.5) / (d + 0.5)) for w, d in docfreq.items()}
def top1(self, query):
best, best_score = None, -1.0
for i in range(self.n):
score = 0.0
for w in WORD.findall(query.lower()):
tf = self.postings[i].get(w)
if not tf:
continue
denom = tf + bb.K1 * (1 - bb.B + bb.B * self.length[i] / self.avg)
score += self.idf[w] * tf * (bb.K1 + 1) / denom
if score > best_score: # strict > keeps the lowest index on ties
best, best_score = i, score
return best
def independent_retrieval(results, base):
"""Re-derive every stored A1 / C top-1 with a second implementation."""
agree = mismatch = 0
for row in results["scales"]:
n = row["n_chunks"]
chunks = list(base[:n])
for nd, stored in zip(results["needles"], row["per_needle"]):
idx = min(n - 1, int((nd["i"] + 0.5) * n / len(results["needles"])))
chunks[idx] = chunks[idx] + " " + nd["sentence"]
ind = IndependentBM25(chunks)
for nd, stored in zip(results["needles"], row["per_needle"]):
for cond, q in (("a1", "%s for project %s" % (bb.PHRASE, nd["key"])),
("c", "%s %s" % (bb.PHRASE, nd["value"]))):
got = ind.top1(q)
if got == stored[cond + "_top1"]:
agree += 1
else:
mismatch += 1
if mismatch <= 3:
fails.append("independent retriever disagrees: scale %.2f needle %d %s "
"got %s stored %s" % (row["scale"], nd["i"], cond, got,
stored[cond + "_top1"]))
ck(mismatch == 0, "%d/%d independent top-1 re-derivations disagree with results.json"
% (mismatch, agree + mismatch))
return agree, mismatch
# ---------------------------------------------------------------- layer 5
def invariants(results, base, full, man):
needles = results["needles"]
m = len(needles)
ck(m == 20, "expected 20 needles, found %d" % m)
src_tokens = set(WORD.findall(full.lower()))
src_text = full.lower()
for nd in needles:
ck(nd["key"].split("-")[1] not in src_tokens,
"key token of %s already occurs in the source corpus" % nd["key"])
ck(nd["value"] not in src_tokens,
"value token of %s already occurs in the source corpus" % nd["key"])
ck(nd["sentence"].lower() not in src_text,
"needle sentence for %s occurs verbatim in the source corpus" % nd["key"])
ck(bb.PHRASE not in src_text, "class phrase occurs in the source corpus")
ck(bb.KEY_PREFIX.lower() not in src_text, "key prefix occurs in the source corpus")
last = results["scales"][-1]
n = last["n_chunks"]
chunks = list(base[:n])
golds = []
for nd in needles:
idx = min(n - 1, int((nd["i"] + 0.5) * n / m))
chunks[idx] = chunks[idx] + " " + nd["sentence"]
golds.append(idx)
ck(len(set(golds)) == m, "gold chunks are not distinct: %s" % sorted(golds))
for nd, g in zip(needles, golds):
ck(chunks[g].count(nd["sentence"]) == 1,
"needle %d not present exactly once in its gold chunk" % nd["i"])
others = [i for i, c in enumerate(chunks) if i != g and nd["sentence"] in c]
ck(not others, "needle %d also present in chunks %s" % (nd["i"], others))
for row in results["scales"]:
for stored in row["per_needle"]:
nrow = row["n_chunks"]
exp = min(nrow - 1, int((stored["needle"] + 0.5) * nrow / m))
ck(stored["gold_chunk"] == exp,
"scale %.2f needle %d gold chunk %s != recomputed %s"
% (row["scale"], stored["needle"], stored["gold_chunk"], exp))
ck(stored["gold_offset_chars"] == stored["gold_chunk"] * results["chunking"]["stride_chars"],
"scale %.2f needle %d gold offset inconsistent with stride" % (row["scale"], stored["needle"]))
ck(stored["depth_decile"] == min(9, stored["gold_chunk"] * 10 // nrow),
"scale %.2f needle %d depth decile wrong" % (row["scale"], stored["needle"]))
# chunk geometry
ck(len(base) * results["chunking"]["stride_chars"] >= len(full) - results["chunking"]["chunk_chars"],
"chunk list does not cover the haystack")
ck(all(len(c) <= results["chunking"]["chunk_chars"] for c in base),
"a chunk exceeds the declared chunk size")
ck(full[-results["chunking"]["chunk_chars"]:] in base[-1], "last chunk does not reach the haystack tail")
# random floor inside 3 sigma of 1/n
for row in results["scales"]:
p = 1.0 / row["n_chunks"]
sigma = math.sqrt(p * (1 - p) / bb.RANDOM_DRAWS)
ck(abs(row["random_top1_rate"] - p) <= 3 * sigma,
"scale %.2f random floor %.5f is outside 3 sigma of 1/%d"
% (row["scale"], row["random_top1_rate"], row["n_chunks"]))
# truncation arithmetic
for w in results["truncation"]["windows"]:
exp_lost = sum(1 for r in last["per_needle"] if r["gold_offset_chars"] >= w["window_chars"])
ck(w["needles_lost"] == exp_lost, "truncation count wrong for window %d" % w["window_tokens"])
ck(w["window_chars"] == int(w["window_tokens"] / bb.TOKENS_PER_CHAR),
"window char conversion wrong for %d tokens" % w["window_tokens"])
ck(w["chunks_beyond_window"] == sum(1 for i in range(len(base))
if i * results["chunking"]["stride_chars"] >= w["window_chars"]),
"chunk loss count wrong for window %d" % w["window_tokens"])
ck(results["truncation"]["haystack_chars"] == len(full), "haystack char count wrong")
# position curve consistent with per-needle rows
buckets = {}
for row in results["scales"]:
for r in row["per_needle"]:
buckets.setdefault(str(r["depth_decile"]), []).append(r["a1_hit1"])
ck(len(buckets) == 10, "position curve covers %d deciles, expected 10" % len(buckets))
for d, v in buckets.items():
ck(abs(results["position_curve_a1_top1_by_depth_decile"][d] - sum(v) / len(v)) < 1e-12,
"position curve wrong at decile %s" % d)
# ---------------------------------------------------------------- layer 6
def sensitivity(results):
sc = results["scales"]
for row in sc:
ck(row["a1_top1_rate"] >= 0.95, "scale %.2f A1 top-1 %.3f below 0.95" % (row["scale"], row["a1_top1_rate"]))
ck(row["a1_top5_rate"] >= 0.99, "scale %.2f A1 top-5 below 0.99" % row["scale"])
ck(row["a2_top1_rate"] >= 0.90, "scale %.2f A2 top-1 %.3f below 0.90" % (row["scale"], row["a2_top1_rate"]))
ck(row["c_top1_rate"] >= 0.95, "scale %.2f C top-1 below 0.95" % row["scale"])
ck(row["b_any_rate"] >= 0.95, "scale %.2f class-only any-needle hit below 0.95" % row["scale"])
ck(row["b_gold_rate"] <= 0.15, "scale %.2f class-only specific hit %.3f above 0.15"
% (row["scale"], row["b_gold_rate"]))
ck(row["random_top1_rate"] <= 0.02, "scale %.2f random floor above 0.02" % row["scale"])
ck(row["a1_top1_rate"] - row["b_gold_rate"] >= 0.80,
"scale %.2f harness does not separate conditions (A1 - B specific < 0.80)" % row["scale"])
a2_flips = sum(len(row["a2_misses"]) for row in sc)
ck(a2_flips == sum(1 for row in sc for r in row["per_needle"] if not r["a2_hit1"]),
"A2 miss diagnostics do not account for every A2 miss")
ck(results["needle_material"]["key_token_unique_in_corpus"] is True,
"needle key tokens are not unique in the corpus")
ck(results["needle_material"]["value_token_unique_in_corpus"] is True,
"needle value tokens are not unique in the corpus")
ck(results["needle_material"]["minting"]["occurrences_of_phrase_in_corpus"] == 0,
"class phrase occurs in the source corpus")
# ---------------------------------------------------------------- layer 7
def report_identity(results):
text = open(os.path.join(BASE, "REPORT.md"), encoding="utf-8").read()
expected = rr.render(results)
ck(text == expected, "REPORT.md is not the deterministic render of results.json")
for marker in STUB_MARKERS:
ck(marker not in text, "stub marker %r in REPORT.md" % marker)
for needle_key in ("A1 top-1", "F1", "F6", "Limits"):
ck(needle_key in text, "REPORT.md lacks section %r" % needle_key)
def provenance_check():
"""Layer 8: the recorded provenance must match the bytes on disk."""
p = os.path.join(BASE, "provenance.json")
if not ck(os.path.exists(p), "provenance.json missing"):
return {}
prov = json.load(open(p, encoding="utf-8"))
ck(sha256_of(os.path.join(BASE, "manifest.json")) == prov["local_manifest_sha256"],
"manifest.json no longer matches the hash recorded in provenance.json")
for aid, h in prov["local_corpus_sha256"].items():
f = os.path.join(BASE, "corpus", aid + ".txt")
if ck(os.path.exists(f), "provenance names a corpus file that is missing: %s" % aid):
ck(sha256_of(f) == h, "corpus/%s.txt differs from its provenance hash" % aid)
src = prov["corpus_origin"]
if src.get("present_on_this_host"):
ck(sha256_of(os.path.join(src["archive"], "manifest.json")) == src["manifest_sha256"],
"source archive manifest has changed since provenance was recorded")
return prov
def sha256_of(p):
return hashlib.sha256(open(p, "rb").read()).hexdigest()
def main():
man, texts = corpus_integrity()
def layer(name, fn, *args):
"""Run a layer; a crash is a failed check, never an uncaught abort."""
try:
return fn(*args)
except BaseException as exc: # noqa: BLE001
ck(False, "%s raised %s: %s" % (name, type(exc).__name__, exc))
return None
sims = layer("upstream title check", upstream_titles, man) or {}
stored = json.load(open(os.path.join(BASE, "results.json"), encoding="utf-8"))
recomputed = layer("re-derivation (build_bench.compute)", bb.compute)
same = recomputed is not None and recomputed == stored
ck(same, "results.json differs from a fresh build_bench.compute() run")
if recomputed is not None and not same:
for k in sorted(set(recomputed) | set(stored)):
if recomputed.get(k) != stored.get(k):
fails.append(" first differing top-level key: %s" % k)
break
base = full = None
try:
full, base = bb.base_chunks([(m["arxiv_id"], texts[m["arxiv_id"]]) for m in man
if m["arxiv_id"] in texts])
except BaseException as exc: # noqa: BLE001
ck(False, "chunk rebuild raised %s: %s" % (type(exc).__name__, exc))
if base:
agree, mismatch = layer("independent retriever", independent_retrieval, stored, base) or (0, 1)
layer("invariants", invariants, stored, base, full, man)
else:
agree, mismatch = 0, 1
ck(False, "no usable corpus text: layers 3 and 5 could not run")
layer("sensitivity thresholds", sensitivity, stored)
layer("report identity", report_identity, stored)
prov = layer("provenance", provenance_check) or {}
print("checks run: %d" % checks)
print("corpus: %d/%d files re-hashed, sha256 + char counts match manifest"
% (len(texts), len(man)))
print("upstream: %d/%d live arXiv titles normalise-equal; min token jaccard %.4f"
% (len(sims), len(man), min(sims.values()) if sims else 0.0))
print("independent retriever: %d/%d stored A1+C top-1 chunk ids reproduced"
% (agree, agree + mismatch))
print("re-derivation: results.json %s" % ("reproduced exactly" if same else "DIVERGED"))
print("measured: " + " | ".join(
"%.0f%%: A1 %s A2 %s C %s Bany %s Bspec %s R %s"
% (row["scale"] * 100, "%.3f" % row["a1_top1_rate"], "%.3f" % row["a2_top1_rate"],
"%.3f" % row["c_top1_rate"], "%.3f" % row["b_any_rate"], "%.3f" % row["b_gold_rate"],
"%.4f" % row["random_top1_rate"]) for row in stored["scales"]))
if fails:
print("FAIL (%d):" % len(fails))
for f in fails[:20]:
print(" -", f)
return 1
print("ALL CHECKS PASSED: corpus hashes intact, live arXiv titles match, every stored retrieval "
"outcome reproduced by a second implementation, conditions separated, report is the render "
"of the measurement")
return 0
if __name__ == "__main__":
sys.exit(main())