From 716af31d02680eb5d8ac546b3f39f019a40af678 Mon Sep 17 00:00:00 2001 From: Heath Sanchez <32909488+heathsanchez@users.noreply.github.com> Date: Fri, 21 Aug 2026 12:42:49 +1200 Subject: [PATCH 1/4] experiment: freeze V129 tutor uptake residual --- .../trace_the_ace/v129_tutor_uptake.py | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 competitions/trace_the_ace/v129_tutor_uptake.py diff --git a/competitions/trace_the_ace/v129_tutor_uptake.py b/competitions/trace_the_ace/v129_tutor_uptake.py new file mode 100644 index 00000000..7421b662 --- /dev/null +++ b/competitions/trace_the_ace/v129_tutor_uptake.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +"""V129 TUTOR UPTAKE residual over frozen V97. + +Primary separator: whether the tutor's next utterance lexically/structurally takes +up the student's immediately preceding language. V75 raw hashing preserves words, +but not this cross-turn relation explicitly. + +Control: deterministically rotate tutor replies among student->tutor pairs inside +each session, preserving the exact student/tutor text multisets and pair count while +destroying local response alignment. No labels enter feature construction. No sweep. +""" +from __future__ import annotations +import argparse,csv,io,json,re,zipfile,hashlib +from pathlib import Path +import numpy as np +from scipy.sparse import load_npz +from sklearn.linear_model import LogisticRegression +from v110_residual_collider_state_discovery import ll,logit +from v121_pretrained_semantic_residual import p97_oof,collision_mask +from v75_canonical_trajectory import SEED + +TOK=re.compile(r"[a-z0-9]+(?:\.[0-9]+)?",re.I) +NEG=re.compile(r"\b(?:no|not quite|incorrect|wrong|careful|try again|almost|remember|instead|actually)\b",re.I) +POS=re.compile(r"\b(?:yes|yeah|correct|right|exactly|perfect|good|great|well done|that's it|thats it|you got it|spot on)\b",re.I) +QUESTION=re.compile(r"\?|\b(?:what|which|how|why|can you|could you|tell me|work out|calculate|solve|find)\b",re.I) +STOP={'the','a','an','and','or','to','of','in','on','for','with','is','are','be','as','by','from','this','that','these','those','you','your','we','it','its'} +EPS=1e-5 + +def toks(s): return {x for x in TOK.findall(str(s).lower()) if len(x)>1 and x not in STOP} +def jac(a,b): + A,B=toks(a),toks(b) + return len(A&B)/len(A|B) if A and B else 0.0 + +def pairs(rows): + out=[] + for i,r in enumerate(rows[:-1]): + if str(r.get('role','')).lower()!='student': continue + s=str(r.get('content','')) + j=None + for k in range(i+1,min(len(rows),i+4)): + if str(rows[k].get('role','')).lower()=='tutor': j=k; break + if str(rows[k].get('role','')).lower()=='student': break + if j is not None: out.append((s,str(rows[j].get('content','')))) + return out + +def feats(P,shift=0): + if not P: return np.zeros(10,float) + S=[p[0] for p in P]; T=[p[1] for p in P]; n=len(P) + if shift and n>1: T=T[shift%n:]+T[:shift%n] + o=np.array([jac(s,t) for s,t in zip(S,T)],float) + neg=np.array([bool(NEG.search(t)) for t in T]); pos=np.array([bool(POS.search(t)) for t in T]); q=np.array([bool(QUESTION.search(t)) for t in T]) + substantive=np.array([len(toks(s))>=2 or bool(re.search(r'\d|[=+\-/*×÷%]',s)) for s in S]) + def mean(mask): return float(o[mask].mean()) if mask.any() else 0.0 + return np.array([o.mean(),np.quantile(o,.75),np.quantile(o,.9),o.max(),mean(neg),mean(pos),mean(q),mean(substantive),float((o>0).mean()),np.log1p(n)],float) + +def residual_oof(P,X,y,splits): + q=np.zeros(len(y),float) + for tr,va in splits: + mu=X[tr].mean(0); sd=X[tr].std(0)+1e-6 + A=np.c_[logit(P[tr]),(X[tr]-mu)/sd]; B=np.c_[logit(P[va]),(X[va]-mu)/sd] + m=LogisticRegression(C=.05,max_iter=300,solver='liblinear',random_state=SEED).fit(A,y[tr]) + q[va]=m.predict_proba(B)[:,1] + return np.clip(q,EPS,1-EPS) + +def evalg(name,groups,X75,Xr,y,support,obj,Xreal,Xctrl): + P,splits=p97_oof(X75,Xr,y,groups,support); Q=residual_oof(P,Xreal,y,splits); C=residual_oof(P,Xctrl,y,splits) + b=ll(y,P); r=ll(y,Q); c=ll(y,C); mask=collision_mask(P,y,obj,.01) + out={'geometry':name,'baseline_v97_ll':b,'uptake':{'ll':r,'gain':b-r},'rotated_reply_control':{'ll':c,'gain':b-c},'uptake_minus_control_gain':c-r,'hard_collision':{'rows':int(mask.sum())}} + if mask.any(): + bb=ll(y[mask],P[mask]); rr=ll(y[mask],Q[mask]); cc=ll(y[mask],C[mask]) + out['hard_collision'].update({'baseline_ll':bb,'uptake_ll':rr,'uptake_gain':bb-rr,'control_ll':cc,'uptake_minus_control_gain':cc-rr}) + return out + +def main(a): + d=Path(a.dir); z=np.load(d/'arrays.npz',allow_pickle=True); y=z['y']; obj=z['objectives']; support=z['support']; sessions=z['sessions'] + X75=load_npz(d/'X75.npz'); Xr=load_npz(d/'Xr.npz'); cache={} + with zipfile.ZipFile(a.archive) as za: + names=set(za.namelist()) + for sid in np.unique(sessions): + name=f'{sid}.csv' + if name not in names: raise RuntimeError('missing '+name) + with za.open(name) as f: rows=list(csv.DictReader(io.TextIOWrapper(f,encoding='utf-8-sig',newline=''))) + P=pairs(rows); shift=1+(int(hashlib.sha256(str(sid).encode()).hexdigest()[:8],16)%max(1,len(P)-1)) if len(P)>1 else 0 + cache[str(sid)]=(feats(P,0),feats(P,shift)) + R=np.vstack([cache[str(s)][0] for s in sessions]); C=np.vstack([cache[str(s)][1] for s in sessions]) + res={'protocol':'V129_TUTOR_UPTAKE','rows':int(len(y)),'primary':'student -> next tutor lexical uptake','control':'deterministic within-session rotation of tutor replies','precommit':{'promote_gain_each_geometry':.0015,'phase_change_gain_each_geometry':.003,'real_minus_control_each_geometry':.001,'hard_collision_gain_each_geometry':'>0','no_parameter_sweep':True}} + res['objective_grouped']=evalg('objective_grouped',obj,X75,Xr,y,support,obj,R,C); res['session_grouped']=evalg('session_grouped',sessions,X75,Xr,y,support,obj,R,C) + def ok(x,t): return x['uptake']['gain']>=t and x['uptake_minus_control_gain']>=.001 and x['hard_collision'].get('uptake_gain',-1)>0 + po=ok(res['objective_grouped'],.0015); ps=ok(res['session_grouped'],.0015); ph=ok(res['objective_grouped'],.003) and ok(res['session_grouped'],.003) + verdict='PHASE_CHANGE_CANDIDATE' if ph else 'PROMOTE_TUTOR_UPTAKE_LAW' if po and ps else 'SUPPRESS_TUTOR_UPTAKE' + res['decision']={'objective_pass':bool(po),'session_pass':bool(ps),'verdict':verdict} + Path(a.out).write_text(json.dumps(res,indent=2)); print(json.dumps(res,indent=2),flush=True) +if __name__=='__main__': + p=argparse.ArgumentParser(); p.add_argument('--archive',required=True); p.add_argument('--dir',required=True); p.add_argument('--out',required=True); main(p.parse_args()) From afe2f0d14622879403ef4c1bd988b9671a8874f6 Mon Sep 17 00:00:00 2001 From: Heath Sanchez <32909488+heathsanchez@users.noreply.github.com> Date: Fri, 21 Aug 2026 12:43:00 +1200 Subject: [PATCH 2/4] workflow: run frozen V129 tutor uptake residual --- .../workflows/trace-ace-v129-tutor-uptake.yml | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 .github/workflows/trace-ace-v129-tutor-uptake.yml diff --git a/.github/workflows/trace-ace-v129-tutor-uptake.yml b/.github/workflows/trace-ace-v129-tutor-uptake.yml new file mode 100644 index 00000000..bbcf3e8c --- /dev/null +++ b/.github/workflows/trace-ace-v129-tutor-uptake.yml @@ -0,0 +1,58 @@ +name: Trace Ace V129 Tutor Uptake + +on: + pull_request: + branches: [agent/v121-cache-batch2] + paths: + - '.github/workflows/trace-ace-v129-tutor-uptake.yml' + - 'competitions/trace_the_ace/v129_tutor_uptake.py' + workflow_dispatch: + +env: + PREPARED_RUN_ID: '32400309220' + +jobs: + evaluate: + runs-on: ubuntu-24.04 + timeout-minutes: 12 + permissions: + actions: read + contents: read + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + cache: pip + - name: Install dependencies + run: python -m pip install --disable-pip-version-check numpy scipy scikit-learn pandas fastembed==0.8.0 + - name: Restore frozen transcript archive from GitHub cache only + id: transcript-cache + uses: actions/cache@v4 + with: + path: transcripts.zip + key: trace-ace-transcripts-v1-603547640 + - name: Require exact cache hit + run: | + test '${{ steps.transcript-cache.outputs.cache-hit }}' = 'true' + test "$(stat -c%s transcripts.zip)" = '603547640' + echo 'e685b85b04694e130c25b17d09cdd1892fbda5e9fa685e98b2300114b915aa2d transcripts.zip' | sha256sum -c - + - name: Download exact frozen prepared artifact + uses: actions/download-artifact@v4 + with: + name: v121-prepared + path: v121_prepared + repository: heathsanchez/mathgraph + run-id: ${{ env.PREPARED_RUN_ID }} + github-token: ${{ secrets.GITHUB_TOKEN }} + - name: Verify frozen sample + run: grep -q 'b1612f9fe4558680e468afb2a2452b75c603c244934fe62f7345feee68a61bc1' v121_prepared/manifest.json + - name: Run frozen V129 + run: python competitions/trace_the_ace/v129_tutor_uptake.py --archive transcripts.zip --dir v121_prepared --out v129_tutor_uptake.json + - name: Show decision + run: cat v129_tutor_uptake.json + - uses: actions/upload-artifact@v4 + with: + name: trace-ace-v129-tutor-uptake + path: v129_tutor_uptake.json + retention-days: 14 From 9b25da0edf9d1d5aa46cefcd68287665b7f2c580 Mon Sep 17 00:00:00 2001 From: Heath Sanchez <32909488+heathsanchez@users.noreply.github.com> Date: Fri, 21 Aug 2026 12:52:08 +1200 Subject: [PATCH 3/4] experiment: add V131 deployable crowding-gated uptake --- .../v131_crowding_gated_uptake.py | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 competitions/trace_the_ace/v131_crowding_gated_uptake.py diff --git a/competitions/trace_the_ace/v131_crowding_gated_uptake.py b/competitions/trace_the_ace/v131_crowding_gated_uptake.py new file mode 100644 index 00000000..0a0fb15c --- /dev/null +++ b/competitions/trace_the_ace/v131_crowding_gated_uptake.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +"""V131: deployable prediction-crowding gate for V129 tutor uptake. + +Derived from the residual constraint intersection: +- V129 uptake is globally harmful, +- but improves label-defined same-objective hard collisions and beats reply rotation, +- therefore the missing object may be an applicability predicate, not a new feature. + +This test removes the forbidden label clause from the hard-collision definition. +A row is gate-positive iff another row with the same objective has a V97 prediction +within 0.01. That predicate is batch-visible and label-free at inference time. +Inside the gate we use the frozen V129 uptake residual correction; outside it we +leave V97 unchanged. The control uses identically gated rotated-reply correction. +No threshold sweep. +""" +from __future__ import annotations +import argparse,csv,hashlib,io,json,zipfile +from pathlib import Path +import numpy as np +from scipy.sparse import load_npz + +from v110_residual_collider_state_discovery import ll +from v121_pretrained_semantic_residual import p97_oof +from v129_tutor_uptake import feats,pairs,residual_oof + +TOL=0.01 + + +def crowding_mask(P: np.ndarray, objectives: np.ndarray, tol: float=TOL) -> np.ndarray: + m=np.zeros(len(P),bool) + for o in np.unique(objectives): + z=np.where(objectives==o)[0] + if len(z)<2: continue + p=P[z] + D=np.abs(p[:,None]-p[None,:]) + np.fill_diagonal(D,np.inf) + m[z[np.min(D,axis=1)<=tol]]=True + return m + + +def evalg(name,groups,X75,Xr,y,support,obj,R,C): + P,splits=p97_oof(X75,Xr,y,groups,support) + Q=residual_oof(P,R,y,splits) + QC=residual_oof(P,C,y,splits) + gate=crowding_mask(P,obj,TOL) + G=P.copy(); GC=P.copy(); G[gate]=Q[gate]; GC[gate]=QC[gate] + b=ll(y,P); g=ll(y,G); gc=ll(y,GC) + out={ + 'geometry':name, + 'baseline_v97_ll':b, + 'gate_rows':int(gate.sum()), + 'gate_fraction':float(gate.mean()), + 'gated_uptake':{'ll':g,'gain':b-g}, + 'gated_rotated_control':{'ll':gc,'gain':b-gc}, + 'real_minus_control_gain':gc-g, + } + if gate.any(): + bb=ll(y[gate],P[gate]); rr=ll(y[gate],Q[gate]); cc=ll(y[gate],QC[gate]) + out['gate_only']={ + 'baseline_ll':bb, + 'uptake_ll':rr, + 'uptake_gain':bb-rr, + 'control_ll':cc, + 'real_minus_control_gain':cc-rr, + } + return out + + +def main(a): + d=Path(a.dir); z=np.load(d/'arrays.npz',allow_pickle=True) + y=z['y']; obj=z['objectives']; support=z['support']; sessions=z['sessions'] + X75=load_npz(d/'X75.npz'); Xr=load_npz(d/'Xr.npz') + cache={} + with zipfile.ZipFile(a.archive) as za: + names=set(za.namelist()) + for sid in np.unique(sessions): + name=f'{sid}.csv' + if name not in names: raise RuntimeError('missing '+name) + with za.open(name) as f: + rows=list(csv.DictReader(io.TextIOWrapper(f,encoding='utf-8-sig',newline=''))) + P=pairs(rows) + shift=1+(int(hashlib.sha256(str(sid).encode()).hexdigest()[:8],16)%max(1,len(P)-1)) if len(P)>1 else 0 + cache[str(sid)]=(feats(P,0),feats(P,shift)) + R=np.vstack([cache[str(s)][0] for s in sessions]); C=np.vstack([cache[str(s)][1] for s in sessions]) + res={ + 'protocol':'V131_CROWDING_GATED_UPTAKE', + 'rows':int(len(y)), + 'gate':'same objective has another row within abs(V97_i-V97_j)<=0.01; no labels', + 'precommit':{ + 'global_gain_each_geometry':0.0005, + 'real_minus_control_each_geometry':0.0005, + 'gate_only_gain_each_geometry':'>0', + 'threshold_sweep':False, + 'tol':TOL, + } + } + res['objective_grouped']=evalg('objective_grouped',obj,X75,Xr,y,support,obj,R,C) + res['session_grouped']=evalg('session_grouped',sessions,X75,Xr,y,support,obj,R,C) + def ok(r): + return r['gated_uptake']['gain']>=.0005 and r['real_minus_control_gain']>=.0005 and r.get('gate_only',{}).get('uptake_gain',-1)>0 + po,ps=ok(res['objective_grouped']),ok(res['session_grouped']) + res['decision']={ + 'objective_pass':bool(po),'session_pass':bool(ps), + 'verdict':'PROMOTE_DEPLOYABLE_UPTAKE_GATE' if po and ps else 'SUPPRESS_CROWDING_GATE' + } + Path(a.out).write_text(json.dumps(res,indent=2)); print(json.dumps(res,indent=2),flush=True) + +if __name__=='__main__': + p=argparse.ArgumentParser(); p.add_argument('--archive',required=True); p.add_argument('--dir',required=True); p.add_argument('--out',required=True); main(p.parse_args()) From af889dc50da5fca078faa0ed2a17dda3f3219004 Mon Sep 17 00:00:00 2001 From: Heath Sanchez <32909488+heathsanchez@users.noreply.github.com> Date: Fri, 21 Aug 2026 12:52:24 +1200 Subject: [PATCH 4/4] workflow: run frozen V131 crowding gate --- .../trace-ace-v131-crowding-gated-uptake.yml | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 .github/workflows/trace-ace-v131-crowding-gated-uptake.yml diff --git a/.github/workflows/trace-ace-v131-crowding-gated-uptake.yml b/.github/workflows/trace-ace-v131-crowding-gated-uptake.yml new file mode 100644 index 00000000..25ff980d --- /dev/null +++ b/.github/workflows/trace-ace-v131-crowding-gated-uptake.yml @@ -0,0 +1,57 @@ +name: Trace Ace V131 Crowding Gated Uptake +on: + pull_request: + branches: [agent/v121-cache-batch2] + paths: + - 'competitions/trace_the_ace/v129_tutor_uptake.py' + - 'competitions/trace_the_ace/v131_crowding_gated_uptake.py' + - '.github/workflows/trace-ace-v131-crowding-gated-uptake.yml' + workflow_dispatch: + +env: + PREPARED_RUN_ID: 32400309220 + +jobs: + evaluate: + runs-on: ubuntu-24.04 + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + cache: pip + - name: Install dependencies + run: python -m pip install --disable-pip-version-check numpy scipy scikit-learn pandas fastembed==0.8.0 + - name: Restore frozen transcript archive from GitHub cache only + id: transcript-cache + uses: actions/cache@v4 + with: + path: transcripts.zip + key: trace-ace-transcripts-v1-603547640 + - name: Require exact cache hit + run: | + test '${{ steps.transcript-cache.outputs.cache-hit }}' = 'true' + test "$(stat -c%s transcripts.zip)" = '603547640' + echo 'e685b85b04694e130c25b17d09cdd1892fbda5e9fa685e98b2300114b915aa2d transcripts.zip' | sha256sum -c - + - name: Download exact frozen prepared artifact + uses: actions/download-artifact@v4 + with: + name: v121-prepared + path: v121_prepared + repository: heathsanchez/mathgraph + run-id: ${{ env.PREPARED_RUN_ID }} + github-token: ${{ github.token }} + - name: Verify frozen sample + run: grep -q 'b1612f9fe4558680e468afb2a2452b75c603c244934fe62f7345feee68a61bc1' v121_prepared/manifest.json + - name: Run frozen V131 + run: python competitions/trace_the_ace/v131_crowding_gated_uptake.py --archive transcripts.zip --dir v121_prepared --out v131_crowding_gated_uptake.json + - name: Show decision + if: always() + run: cat v131_crowding_gated_uptake.json + - uses: actions/upload-artifact@v4 + if: always() + with: + name: trace-ace-v131-crowding-gated-uptake + path: v131_crowding_gated_uptake.json + retention-days: 14