"""Rebuild The Cache Wars 2 (manuscript, thirteen vector figures, tabular data) from the accepted receipts.
Python 3.13 + matplotlib 3.10.3; run from any directory. No network or inference.
Outputs default to a NEW rebuild directory; --release writes into the release root.
"""
import argparse,json,html,re,csv
from pathlib import Path
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
ROOT=Path(__file__).resolve().parents[2]
ap=argparse.ArgumentParser();ap.add_argument('--output');ap.add_argument('--release',action='store_true');args=ap.parse_args()
OUT=ROOT if args.release else Path(args.output).resolve() if args.output else ROOT/'rebuild'
if not args.release:OUT.mkdir(parents=True,exist_ok=False)
FIG=OUT/'artifacts/figures';FIG.mkdir(parents=True,exist_ok=True)
D=json.loads((ROOT/'artifacts/data/measurements.json').read_text(encoding='utf8'));S={s['id']:s for s in D['series']}
PR=D['prices']['claude'];U_,R_,W5_,W1_=PR['input'],PR['cacheRead'],PR['write5m'],PR['write1h']
PX=D['prices']['codex']
DATE='September 8, 2026';ORIG='https://agnt.gg/whitepapers/the-cache-wars-prompt-cache-efficiency-llm-agent-harnesses'

# ------------------------------------------------------------------ measured parameters
def T5(i):return S[i]['turns']
I=lambda i,t:T5(i)[t-1]['input'];C=lambda i,t:T5(i)[t-1]['cacheRead'];Pm=lambda i,t:T5(i)[t-1]['input']-T5(i)[t-1]['cacheRead']
h5=lambda i:C(i,5)/I(i,5)
growth=lambda i:[I(i,t+1)-I(i,t) for t in range(1,5)]
m=I('agnt-claude',2)-I('agnt-claude',1)                       # user payload in Sonnet 5 tokens
mx=I('agnt-codex',2)-I('agnt-codex',1)                        # user payload in GPT-6 Astra tokens
assert m==I('omp-r2',2)-I('omp-r2',1)==I('omp-long',2)-I('omp-long',1)==5265 and mx==I('omp-codex',2)-I('omp-codex',1)==4455
c1=I('agnt-claude',1)-T5('agnt-claude')[0]['uncached']         # identical-content first-turn prefix (AGNT measured)
ovh={'agnt-claude':0,'claude':growth('claude')[0]-m,'omp-r2':growth('omp-r2')[0]-m,'openclaw':growth('openclaw')[0]-m,'hermes':growth('hermes')[0]-m}
assert ovh=={'agnt-claude':0,'claude':40,'omp-r2':0,'openclaw':20,'hermes':-1},ovh
o_model={'agnt-claude':0,'claude':40,'omp-r2':0,'openclaw':20,'hermes':0}   # tokenizer noise (Hermes -1) treated as zero overhead
codex_cli_ovh=[g-mx for g in growth('codex-r2')];codex_cli_mean_ovh=round(sum(codex_cli_ovh)/4)
codex_prem={i:sum(Pm(i,t) for t in range(2,6))/4 for i in ['agnt-codex','codex-r2','openclaw-codex-r2','omp-codex','hermes-codex']}
num=lambda n:f'{round(n):,}';pct1=lambda x:('0%' if x==0 else f'{100*x:.1f}%');pct2=lambda x:f'{100*x:.2f}%';usd2=lambda x:f'${x:,.2f}';usd3=lambda x:f'${x:.3f}';k=lambda n:f'{n/1000:.1f}k'
gap=lambda i:T5(i)[4]['gapMs']/1000
def prng(vals):
    vals=[100*v for v in vals];lo,hi=min(vals),max(vals);return f'{lo:.1f}%' if f'{lo:.1f}'==f'{hi:.1f}' else f'{lo:.1f}–{hi:.1f}%'
def xrng(vals,d=1):
    vals=list(vals);lo,hi=min(vals),max(vals);return f'≈{lo:.{d}f}×' if f'{lo:.{d}f}'==f'{hi:.{d}f}' else f'{lo:.{d}f}–{hi:.{d}f}×'
def rng(vals):
    vals=list(vals);lo,hi=round(min(vals)),round(max(vals));return f'${lo:,}' if lo==hi else f'${lo:,}–${hi:,}'
five=['omp-r2','openclaw','hermes'];one=['agnt-claude','claude'];longs=['omp-long','openclaw-long','hermes-long']
div_lo=min(Pm(i,5) for i in five)/max(Pm(i,5) for i in one);div_hi=max(Pm(i,5) for i in five)/min(Pm(i,5) for i in one)

# ------------------------------------------------------------------ cost model (Section 6), identical content
def session(n,ttl,o,burst=4):
    """Premium/read token totals and USD for n messages of payload m with per-turn overhead o over identical content c1.
    5 m: a break longer than the TTL after every `burst`-th message forces a full-context re-write on the next message."""
    g=m+o
    if ttl=='1h':
        prem=c1+(n-1)*g;reads=sum(c1+(t-2)*g for t in range(2,n+1));return prem,reads,(W1_*prem+R_*reads)/1e6
    prem=c1;reads=0
    for t in range(2,n+1):
        if (t-1)%burst==0:prem+=c1+(t-1)*g
        else:prem+=g;reads+=c1+(t-2)*g
    return prem,reads,(W5_*prem+R_*reads)/1e6
def control(n):tok=sum(c1+(t-1)*m for t in range(1,n+1));return tok,U_*tok/1e6
STRAT=[('agnt-claude','AGNT v0.6.6','1h'),('claude','Claude Code','1h'),('omp-r2','OMP (default 5 m)','5m'),('openclaw','OpenClaw','5m'),('hermes','Hermes','5m')]
proj={i:session(20,ttl,o_model[i]) for i,_,ttl in STRAT};ctl20=control(20)
r5={ttl:session(5,ttl,0) for ttl in ['1h','5m']};ctl5=control(5)
LENGTHS=[('15 m',5,.25),('30 m',10,.5),('1 h',20,1),('2 h',40,2),('4 h',80,4)]
mult={i:[session(n,ttl,o_model[i])[2]/control(n)[1] for _,n,_ in LENGTHS] for i,_,ttl in STRAT}
Ru=ctl20[1]                                                    # uncached burn rate of the reference workload, $/h
PROFILES=[('Light','2 h/day (44 h/mo)','2 × 1 h sessions','M(1 h)',2,1,2),('Moderate','4 h/day (88 h/mo)','2 × 2 h sessions','M(2 h)',2,2,3),('Heavy','8 h/day (176 h/mo)','2 × 4 h sessions','M(4 h)',2,4,4)]
monthly={i:[22*s*mult[i][li]*L*Ru for _,_,_,_,s,L,li in PROFILES] for i,_,_ in STRAT};monthly['none']=[22*s*L*Ru for _,_,_,_,s,L,li in PROFILES]
tax={i:monthly[i][2]-monthly['agnt-claude'][2] for i in ['omp-r2','openclaw','hermes']};tax['none']=monthly['none'][2]-monthly['agnt-claude'][2]
team={i:5*12*monthly[i][2] for i in ['agnt-claude','claude','omp-r2','openclaw','hermes','none']}
amp_event=100000*U_*1.25/1e6;amp_day=30*amp_event;amp_month=22*amp_day
omp_long_20=session(20,'1h',0)[2]
adv=[monthly['omp-r2'][p]/monthly['agnt-claude'][p] for p in range(3)],[monthly['openclaw'][p]/monthly['agnt-claude'][p] for p in range(3)]
scenario={'identicalContent':{'c1':c1,'m':m},'prices':PR,'oneHour':{i:{'premium':proj[i][0],'reads':proj[i][1],'usd':proj[i][2]} for i in proj},'control20':{'tokens':ctl20[0],'usd':ctl20[1]},'fiveTurn':{'1h':r5['1h'],'5m':r5['5m'],'none':ctl5},'multipliers':{i:mult[i] for i in mult},'lengths':[(l,n) for l,n,_ in LENGTHS],'burnRatePerHour':Ru,'monthly':monthly,'cacheTaxHeavy':tax,'teamAnnual':team,'amplifier':{'perEvent':amp_event,'perDay':amp_day,'perMonth':amp_month}}
(FIG/'scenario.json').write_text(json.dumps(scenario,indent=2))

# ------------------------------------------------------------------ figures (original palette and proportions)
plt.rcParams.update({'font.family':'serif','font.serif':['Georgia','Times New Roman','DejaVu Serif'],'font.size':9,'axes.labelsize':9,'legend.fontsize':8,'svg.fonttype':'none','svg.hashsalt':'cache-wars-2','pdf.fonttype':42,'figure.dpi':100,'axes.spines.top':False,'axes.spines.right':False,'axes.axisbelow':True,'axes.edgecolor':'#555','xtick.color':'#333','ytick.color':'#333'})
COL={'AGNT':'#c2185b','OMP':'#e65100','Claude Code':'#b8860b','Codex':'#2e7d32','OpenClaw':'#0277bd','Hermes':'#6a1b9a','payload':'#9e9e9e','overhead':'#c62828','reads':'#2e7d32','writes':'#c62828','none':'#616161'}
figures=[]
def savefig(n,fig,caption):
    fig.savefig(FIG/f'figure-{n}.svg',bbox_inches='tight',metadata={'Date':'2026-09-08'})
    fig.savefig(FIG/f'figure-{n}.pdf',bbox_inches='tight',metadata={'Title':f'The Cache Wars 2, Figure {n}','Author':'AGNT Labs','CreationDate':None,'ModDate':None})
    plt.close(fig);figures.append((n,caption))
def style(ax):ax.grid(axis='y',color='#e3e3e3',linewidth=.6);ax.tick_params(labelsize=8.5)
kfmt=lambda v,_:('0' if v==0 else f'{v/1000:.0f}k')
# Figure 1 — premium tokens per turn
fig,ax=plt.subplots(figsize=(6.4,3.1));X=[1,2,3,4,5]
for i,lab,key in [('agnt-claude','AGNT','AGNT'),('omp-r2','OMP (default 5 m)','OMP'),('claude','Claude Code','Claude Code'),('codex-r2','Codex','Codex'),('openclaw','OpenClaw','OpenClaw'),('hermes','Hermes','Hermes')]:
    y=[Pm(i,t) for t in X];ax.plot(X,y,color=COL[key],marker='o',markersize=3.4,linewidth=2,label=lab);ax.annotate(k(y[-1]),(5,y[-1]),xytext=(5,0),textcoords='offset points',fontsize=8,color=COL[key],va='center')
ax.axvline(4.5,color='#999',linestyle=':',linewidth=.9);ax.set_xticks(X,['T1','T2','T3','T4','T5 (post-pause)']);ax.yaxis.set_major_formatter(plt.FuncFormatter(kfmt));ax.set_ylabel('Premium input tokens (≥1× list)');ax.set_xlim(.8,5.6);style(ax);ax.legend(frameon=False,ncol=3,loc='upper center',bbox_to_anchor=(.5,1.16));fig.tight_layout()
savefig(1,fig,f'Premium input tokens billed per turn (rate ≥1× list). Turn 5 follows the ≥390 s idle: the 5-minute-TTL harnesses (OMP on its shipping default, OpenClaw, Hermes) re-purchase their full accumulated context at the 1.25× write premium, while the 1-hour harnesses (AGNT, Claude Code) resume at payload cost. Every Anthropic-path harness’s premium is the payload alone through turn 4 — the per-turn scaffolding of [1] is gone (§5.4). Codex (GPT-6 Astra tokenizer) is shown in its own token units.')
# Figure 2 — turn-5 cache-hit ratio
rows2=[('AGNT (Claude, 1 h)','agnt-claude','AGNT'),('AGNT (Codex, auto)','agnt-codex','AGNT'),('Codex CLI (auto)','codex-r2','Codex'),('OpenClaw =long (1 h)','openclaw-long','OpenClaw'),('Claude Code (1 h)','claude','Claude Code'),('OMP =long (1 h)','omp-long','OMP'),('Hermes ttl=1h','hermes-long','Hermes'),('OMP default (5 m)','omp-r2','OMP'),('OpenClaw (5 m)','openclaw','OpenClaw'),('Hermes (5 m)','hermes','Hermes')]
fig,ax=plt.subplots(figsize=(6.4,3.4));ys=range(len(rows2))
ax.barh(ys,[100*h5(i) for _,i,_ in rows2],color=[COL[c] for _,_,c in rows2],height=.62)
for y,(lab,i,_) in zip(ys,rows2):ax.text(100*h5(i)+1.2,y,pct1(h5(i)),va='center',fontsize=8.5)
ax.set_yticks(ys,[l for l,_,_ in rows2]);ax.invert_yaxis();ax.set_xlim(0,108);ax.set_xticks([0,20,40,60,80,100],['0%','20%','40%','60%','80%','100%']);ax.grid(axis='x',color='#e3e3e3',linewidth=.6);ax.tick_params(labelsize=8.5);fig.tight_layout()
savefig(2,fig,f'Turn-5 cache-hit ratio h = C₅/(C₅+P₅) after the ≥390 s pause. The outcome partitions exactly along the effective TTL: {prng([h5(i) for i in one+longs])} for 1-hour markers (including OMP, OpenClaw and Hermes with their retention options set), 0% for 5-minute markers (OMP default, OpenClaw, Hermes), with no intermediate value. AGNT posts the highest ratio on both provider paths ({pct2(h5("agnt-claude"))} Claude, {pct2(h5("agnt-codex"))} Codex).')
# Figure 3 — per-turn growth decomposition
rows3=[('AGNT','agnt-claude',m,ovh['agnt-claude'],'AGNT'),('OMP','omp-r2',m,ovh['omp-r2'],'OMP'),('Hermes','hermes',m,ovh['hermes'],'Hermes'),('OpenClaw','openclaw',m,ovh['openclaw'],'OpenClaw'),('Claude Code','claude',m,ovh['claude'],'Claude Code'),('Codex CLI (GPT-6 tokens)','codex-r2',mx,codex_cli_mean_ovh,'Codex')]
fig,ax=plt.subplots(figsize=(6.4,3.0));ys=range(len(rows3))
ax.barh(ys,[p for _,_,p,_,_ in rows3],color=COL['payload'],height=.6,label='User payload (5,265 tk Sonnet 5; 4,455 tk GPT-6)')
ax.barh(ys,[max(o,0) for _,_,_,o,_ in rows3],left=[p for _,_,p,_,_ in rows3],color=COL['overhead'],height=.6,label='Harness-injected overhead')
for y,(lab,_,p,o,_) in zip(ys,rows3):ax.text(p+max(o,0)+90,y,f'{o:+,}',va='center',fontsize=8.5,color='#222')
ax.set_yticks(ys,[l for l,_,_,_,_ in rows3]);ax.invert_yaxis();ax.xaxis.set_major_formatter(plt.FuncFormatter(kfmt));ax.set_xlim(0,8200);ax.grid(axis='x',color='#e3e3e3',linewidth=.6);ax.tick_params(labelsize=8.5);ax.legend(frameon=False,fontsize=8,loc='lower right');fig.tight_layout()
savefig(3,fig,f'Per-turn conversation growth decomposed into the byte-identical user payload (gray) and harness-injected overhead (red). AGNT, OMP and Hermes inject nothing (Hermes −1 token is tokenizer noise); OpenClaw adds {ovh["openclaw"]} tokens/turn and Claude Code {ovh["claude"]} — down from 6,817 (2.31× the payload) in [1]. Codex CLI adds a mean {codex_cli_mean_ovh:,} GPT-6 tokens/turn of its own turn scaffolding.')
# Figure 4 — Hermes intra-burst reads vs writes
fig,ax=plt.subplots(figsize=(6.4,2.9));xs=[2,3,4];w=.34
ax.bar([x-w/2 for x in xs],[C('hermes',t) for t in xs],w,color=COL['reads'],label='Cached reads (0.1×)');ax.bar([x+w/2 for x in xs],[Pm('hermes',t) for t in xs],w,color=COL['writes'],label='Premium writes (1.25×)')
for t in xs:ax.text(t-w/2,C('hermes',t)+250,k(C('hermes',t)),ha='center',fontsize=8.5);ax.text(t+w/2,Pm('hermes',t)+250,k(Pm('hermes',t)),ha='center',fontsize=8.5)
ax.set_xticks(xs,['Turn 2','Turn 3','Turn 4']);ax.yaxis.set_major_formatter(plt.FuncFormatter(kfmt));ax.set_ylim(0,21000);style(ax);ax.legend(frameon=False,loc='upper left');fig.tight_layout()
savefig(4,fig,f'Hermes intra-burst behaviour (Table 2 rows, turns 2–4, before any TTL expiry). In 0.21.1 cached reads grow with the conversation ({k(C("hermes",2))} → {k(C("hermes",3))} → {k(C("hermes",4))}) while premium writes stay pinned at the payload ({num(Pm("hermes",2))} tokens): the rolling-marker leak of [1], in which reads collapsed to the static prefix while writes grew, no longer occurs.')
# Figure 5 — one-hour projection
rows5=[(lab,i) for i,lab,_ in STRAT]
fig,ax=plt.subplots(figsize=(6.4,3.0));ys=range(len(rows5)+1)
pw=[(W1_ if ttl=='1h' else W5_)*proj[i][0]/1e6 for i,_,ttl in STRAT]+[ctl20[1]];rd=[R_*proj[i][1]/1e6 for i,_,_ in STRAT]+[0]
ax.barh(ys,pw,color=COL['writes'],height=.6,label='Premium writes');ax.barh(ys,rd,left=pw,color=COL['reads'],height=.6,label='Cached reads')
for y,(p,r) in enumerate(zip(pw,rd)):ax.text(p+r+.04,y,usd2(p+r),va='center',fontsize=8.5)
ax.set_yticks(ys,[l for l,_ in rows5]+['No caching']);ax.invert_yaxis();ax.set_xlim(0,ctl20[1]*1.18);ax.xaxis.set_major_formatter(plt.FuncFormatter(lambda v,_:f'${v:.0f}'));ax.grid(axis='x',color='#e3e3e3',linewidth=.6);ax.tick_params(labelsize=8.5);ax.legend(frameon=False,loc='lower right');fig.tight_layout()
savefig(5,fig,f'One-hour session projection (20 messages, 4 natural breaks; Table 7), decomposed into premium writes and cached reads over identical content. OMP (default), OpenClaw and Hermes spend on full-context re-writes at every break; AGNT and Claude Code resume at payload cost — the residual gap between them is Claude Code’s {ovh["claude"]}-token per-turn overhead.')
# Figure 6 — multipliers by session length
fig,ax=plt.subplots(figsize=(6.4,3.0));xs=range(5)
for i,lab,_ in STRAT:
    key=lab.split(' ')[0] if not lab.startswith('Claude') else 'Claude Code';ax.plot(xs,mult[i],color=COL[key],marker='o',markersize=3.4,linewidth=2,label=lab);ax.annotate(f'{mult[i][-1]:.2f}',(4,mult[i][-1]),xytext=(5,0),textcoords='offset points',fontsize=8,color=COL[key],va='center')
ax.set_xticks(xs,[l for l,_,_ in LENGTHS]);ax.set_ylim(0,.8);ax.set_yticks([0,.2,.4,.6,.8],['0.0×','0.2×','0.4×','0.6×','0.8×']);ax.set_ylabel('Cost multiplier vs. uncached');ax.set_xlim(-.2,4.6);style(ax);ax.legend(frameon=False,ncol=3,loc='upper center',bbox_to_anchor=(.5,1.16));fig.tight_layout()
savefig(6,fig,'Cost multiplier vs. the uncached baseline by session length (Table 8; lower is better). AGNT’s and Claude Code’s curves are monotone decreasing — the 2× build premium amortizes into 0.1× reads; OMP (5 m default), OpenClaw and Hermes flatten because every break re-charges a full, ever-larger re-write. Without their retention options the 5-minute-default harnesses never reach the 1-hour trajectory.')
# Figure 7 — monthly cost per seat
fig,ax=plt.subplots(figsize=(6.4,3.2));groups=['Light (2 h/day)','Moderate (4 h/day)','Heavy (8 h/day)'];series7=[(lab,i) for i,lab,_ in STRAT]+[('No caching','none')];w=.13
for j,(lab,i) in enumerate(series7):
    key='AGNT' if i=='agnt-claude' else 'OMP' if i=='omp-r2' else 'Claude Code' if i=='claude' else 'OpenClaw' if i=='openclaw' else 'Hermes' if i=='hermes' else 'none'
    xs=[g+(j-2.5)*w for g in range(3)];ax.bar(xs,monthly[i],w,color=COL[key],label=lab.replace(' (default 5 m)',''))
    for x,v in zip(xs,monthly[i]):ax.text(x,v+6,f'${v:,.0f}',ha='center',fontsize=6.6,rotation=90,va='bottom')
ax.set_xticks(range(3),groups);ax.yaxis.set_major_formatter(plt.FuncFormatter(lambda v,_:f'${v:,.0f}'));ax.set_ylim(0,max(monthly['none'])*1.28);style(ax);ax.legend(frameon=False,ncol=6,loc='upper center',bbox_to_anchor=(.5,1.14),fontsize=7.4);fig.tight_layout()
savefig(7,fig,f'Projected monthly cost per seat under the three usage profiles of Table 9 (22 workdays; Sonnet 5 list prices; identical content). The 5-minute-default harnesses cost {min(adv[0]):.2f}–{max(adv[1]):.2f}× AGNT at every profile; the gap is the TTL bit alone. Claude Code tracks AGNT to within its {ovh["claude"]}-token overhead.')
with (FIG/'figure-data.csv').open('w',newline='',encoding='utf8') as f:
    wr=csv.writer(f);wr.writerow(['arm','track','turn','input','cached','premium','cache_write','uncached','output','hit_ratio','gap_ms'])
    for s in D['series']:
        for t in s['turns']:wr.writerow([s['id'],s['track'],t['turn'],t['input'],t['cacheRead'],t['input']-t['cacheRead'],t['cacheWrite'],t['uncached'],t['output'],t['readShare'],t['gapMs']])

# ------------------------------------------------------------------ manuscript
blocks=[]
def para(t):blocks.append(('p',t))
def sec(t):blocks.append(('section',t))
def sub(t):blocks.append(('subsection',t))
def eq(tex,web):blocks.append(('equation',(tex,web)))
def table(headers,rows,caption,left=(0,),widths=None):blocks.append(('table',(headers,rows,caption,tuple(left),widths)))
def figure(n):blocks.append(('figure',n))
def bullets(items):blocks.append(('ul',items))
def listing(title,code):blocks.append(('pre',(title,code)))
def defs(items):blocks.append(('defs',items))   # "Term. Sentence." paragraphs (Threats to Validity style)
tt=lambda s:'\u2063'+s+'\u2063'                # inline code marker, rendered <code>/\texttt
V={'agnt':'v0.6.6','omp':'18.1.14','cc':'2.1.263','codex':'0.153.4','oc':'2026.9.2','hermes':'0.21.1'}
five_names='OMP, OpenClaw, Hermes'
abstract=(f"LLM agent harnesses re-transmit the full conversation context on every model invocation, making input cost quadratic in session length. Provider-side prompt caching discounts repeated prefix tokens by 90% at the tested model rates. Claude uses explicit cache policy; Codex uses automatic, provider-managed prefix reuse. Both require stable historical content, while retention controls and guarantees depend on the model and access path. We present a controlled measurement of prompt-cache efficiency across six production agent harnesses — AGNT {V['agnt']}, oh-my-pi (OMP) {V['omp']}, Claude Code CLI {V['cc']}, Codex CLI {V['codex']}, OpenClaw {V['oc']}, and Hermes Agent {V['hermes']} — using byte-identical workloads, version-pinned harnesses and an identified AGNT build, subscription-authenticated provider paths (Claude Sonnet 5 and GPT-6 Astra), and per-request billing counters obtained from provider telemetry with fail-loud validity guards. The central instrument is the pause test of our July 2026 study [1]: four rapid turns followed by a ≥390-second idle period that exceeds Anthropic's 5-minute cache TTL but not its 1-hour TTL. Thirteen configurations contribute 65 measured requests. We find a {div_lo:.1f}–{div_hi:.1f}× divergence in post-pause non-read input volume: harnesses emitting 1-hour markers (AGNT, Claude Code) retained {prng([h5('claude'),h5('agnt-claude')])} of the request in cache, while harnesses emitting 5-minute markers ({five_names} on their shipping defaults) retained 0% and re-purchased their entire context at a 1.25× write premium. AGNT posts the highest post-pause cache-hit ratio on both provider paths — {pct2(h5('agnt-claude'))} on Claude and {pct2(h5('agnt-codex'))} on Codex — and maintains Claude cache writes equal to the measured per-turn input increment. The two patterns reported in [1] are not reproduced in these current configurations: Claude Code's per-turn scaffolding injection has fallen from 6,817 to {ovh['claude']} tokens per turn, and Hermes' intra-burst cache leak no longer occurs. What remains is the default-TTL trap: three of the five Anthropic-path harnesses still ship 5-minute markers and forfeit the pause unless reconfigured (OMP via PI_CACHE_RETENTION=long, OpenClaw via cacheRetention: long, Hermes via cache_ttl: 1h), each of which then survives at {prng([h5('hermes-long'),h5('openclaw-long')])}. Extrapolated to a one-hour, 20-message session with four natural breaks over identical content, measured marker strategies yield totals of {usd2(proj['agnt-claude'][2])} (AGNT), {usd2(proj['claude'][2])} (Claude Code), {usd2(proj['omp-r2'][2])} (OMP, shipping default), {usd2(proj['hermes'][2])} (Hermes), and {usd2(proj['openclaw'][2])} (OpenClaw) against a {usd2(ctl20[1])} uncached ceiling. Composed over a working month (22 days, 8 h/day), the same measured parameters project ${monthly['agnt-claude'][2]:,.0f}/month (AGNT) versus {rng(monthly[i][2] for i in five)}/month for the 5-minute-default harnesses and ${monthly['none'][2]:,.0f} uncached — a per-seat “cache tax” of {rng(tax[i] for i in five)}/month, or {rng(12*tax[i] for i in five)}/year, attributable entirely to one client-side configuration bit. All artifacts, scripts, and raw counters are provided for independent replication.")
KEYWORDS='prompt caching, LLM agents, agent harness, cost measurement, cache TTL, context management, subscription access, reproducibility'

sec('Introduction')
para('Autonomous and semi-autonomous LLM agents operate in a loop: the harness assembles a request containing a system prompt, tool schemas, the full conversation history, and the newest user message; the model responds; the cycle repeats. Because the entire context is re-transmitted on every call, cumulative input tokens grow quadratically with turn count. For multi-hour, tool-heavy sessions this term dominates total cost.')
para(f"Anthropic and OpenAI both offer prompt caching: previously processed prefix tokens are re-served at a fraction of list price (10% on Anthropic [2]; 10% on OpenAI's published cached-input rate [3]). Realized reuse depends on the harness and provider routing/cache state. Three client-side decisions matter on Anthropic: (a) whether cache-control markers are emitted at all; (b) the TTL selected per marker (Anthropic: 5 minutes at a 1.25× one-time write premium, or 1 hour at 2× [2]); and (c) whether the serialized prefix remains byte-identical across turns, since provider caches are exact-prefix matches — any earlier-byte mutation invalidates everything after it.")
para('Vendor documentation describes intended behavior; it does not establish what shipped binaries do under realistic use. Our July 2026 study [1] measured that directly for the releases then current. This paper is the second installment of The Cache Wars: it repeats the protocol of [1] on the current releases of the same six harnesses, on the subscription-authenticated provider paths that most users actually run, and on both provider families. Our contributions:')
bullets(['A reproducible pause-test protocol that cleanly discriminates 5-minute from 1-hour cache retention using a ≥390 s idle gap, unchanged from [1].',
 'Per-turn billing telemetry for six production harnesses on byte-identical workloads across two subscription-authenticated provider paths (13 configurations, 65 requests), with instrumentation guards that abort on any validity violation.',
 f"A re-examination of the two cost pathologies identified in [1]: Claude Code's per-turn scaffolding injection has fallen from 6,817 to {ovh['claude']} tokens, and Hermes' intra-burst cache leak is gone; the default-TTL trap persists in three harnesses, each of which survives once its documented retention option is set.",
 'A parametric cost model, fit to the measurements at current Sonnet 5 list prices, projecting one-hour and multi-hour session costs, and its composition into monthly and annual per-seat spend under three usage profiles.',
 'A complete artifact set enabling third-party replication.'])
sec('Background: Prompt-Cache Semantics and Pricing')
para(f"Anthropic. A request may carry up to four {tt('cache_control')} breakpoints. Marking a content block caches the serialized request prefix up to and including that block. Each marker carries a TTL: ephemeral 5 m (default) or 1 h. Billing for Claude Sonnet 5 (list, per 10⁶ tokens): base input ${U_:.2f}; cache read ${R_:.2f} (0.1×); 5 m cache write ${W5_:.2f} (1.25×); 1 h cache write ${W1_:.2f} (2.0×); output ${PR['output']:.2f} [2]. Cache hits refresh the TTL. Caching is exact-prefix: a single differing byte at position N invalidates all cached content at positions ≥ N.")
para(f"OpenAI. Caching is automatic: requests sharing a sufficiently long prefix report {tt('cached_input_tokens')} at a discount. Since [1], OpenAI publishes explicit cache prices; for GPT-6 Astra (list, per 10⁶ tokens, standard short-context): input ${PX['input']:.2f}; cached input ${PX['cacheRead']:.2f} (0.1×); cache write ${PX['cacheWrite']:.2f} (1.25×); output ${PX['output']:.2f} [3]. No Anthropic-style cache markers are used in this track. Current Platform model-specific retention settings and their distinction from the subscription path are described below [12]. Codex CLI relies exclusively on this mechanism [4], and every Codex-path receipt in this study reports zero cache-write tokens.")
para('We use premium tokens to denote tokens billed at ≥1× list (uncached input plus cache writes) and cached tokens for those billed at 0.1×. For a harness with zero overhead, steady-state premium per turn equals the size of the newest message.')
sec('Systems Under Test')
table(['Harness','Version','Install','Provider path measured','Marker TTL (measured)'],[
 ['AGNT',V['agnt'],'designated build (source fingerprints in artifacts)','Claude subscription; Codex subscription','1 h (Claude, all writes); auto (Codex)'],
 ['oh-my-pi (OMP)',V['omp'],tt('bun i -g @oh-my-pi/pi-coding-agent@18.1.14'),'Claude subscription; Codex subscription','5 m default; 1 h via '+tt('PI_CACHE_RETENTION=long')],
 ['Claude Code CLI',V['cc'],'npm (official)','Anthropic (native, Claude subscription)','1 h (all writes)'],
 ['Codex CLI',V['codex'],'npm (official)','OpenAI (native, ChatGPT subscription)','n/a — automatic'],
 ['OpenClaw',V['oc'],tt('npm i -g openclaw@2026.9.2'),'Claude subscription; Codex subscription (embedded runtime)','5 m default; 1 h via '+tt('cacheRetention')+': long'],
 ['Hermes Agent',V['hermes']+' (v2026.9.7)','editable install from tagged source (Py 3.13)','Claude subscription; Codex subscription','5 m default; 1 h via '+tt('prompt_caching.cache_ttl: 1h')]],
 'Harnesses, versions, installation channel, and measured provider path.',left=(0,1,2,3,4),widths={0:.12,1:.10,2:.25,3:.23,4:.19})
para(f"The tested versions were resolved on 2026-09-08; the AGNT build is identified by the source fingerprints in the artifact set (OMP under Bun 1.3.14; OpenClaw under Node 24.20.0; Hermes from its tagged source with Python 3.13 and its declared dependencies; AGNT's provider code under Node 22.16.0). Anthropic-path harnesses used model {tt('claude-sonnet-5')}; Codex-path harnesses used {tt('gpt-6-astra')}. Every arm authenticated with a subscription credential rather than a metered API key — the Claude subscription for Anthropic paths and the ChatGPT subscription for Codex paths (§8). OpenClaw and Hermes were driven through their public entry points ({tt('openclaw agent --local --session-id … --json')} with all tools denied; Hermes' documented {tt('AIAgent')} API with {tt('run_conversation(…, conversation_history=…)')} and an empty toolset), each on the provider path where its caching demonstrably engages, so results reflect each system's best case, not a degraded default. OMP was driven through its own {tt('omp -p --mode json --continue')} print mode; its per-turn {tt('agent_end')} usage block reports a native per-TTL split ({tt('cttl:{ephemeral5m, ephemeral1h}')}), and it was measured on both its shipping default (5 m) and its {tt('PI_CACHE_RETENTION=long')} path (1 h). OpenClaw and Hermes were likewise measured on both their defaults and their documented 1-hour options ({tt('cacheRetention')+': long'}; {tt('prompt_caching.cache_ttl: 1h')}). Claude Code ran through {tt('claude -p --output-format json --resume')} with tools disabled; Codex CLI through {tt('codex exec --json')} / {tt('exec resume')}. AGNT was driven through its own orchestrator handler and provider adapters on an isolated loopback host, carrying its resident instruction context and tool schemas with tool execution disabled; on the Codex path OpenClaw was run on its embedded runtime rather than the separately available Codex app-server runtime, so that its cache behaviour is its own.")
sec('Experimental Design')
sub('Workload')
para(f"Each turn t sends the message “CACHE-WARS RUN ⟨run id⟩ / TURN t: This is a fixed-output cache benchmark. Reply with exactly OKt, nothing else. Do not use tools. Ignore the synthetic data below.” followed by the filler F(t), a deterministic function producing 260 rows and 7,878 UTF-8 bytes per turn (Listing 1) — ≈{m:,} tokens under the Sonnet 5 tokenizer and ≈{mx:,} under GPT-6 Astra. The deterministic numeric-row structure follows [1]; byte identity is asserted only for this edition’s shared within-track workload files, not the earlier edition’s different runner inputs. Payloads are byte-identical across harnesses within a provider track (each track carries its own run identifier). Instructing fixed, short replies (OKt: five billed output tokens on Claude, six on Codex) minimizes output-side confounds; every accepted reply is exactly OKt.")
listing('Listing 1 — deterministic per-turn filler (JavaScript; Python port identical)',"function filler(t) {\n  let s = '';\n  for (let i = 0; i < 260; i++)\n    s += `row ${t}-${i} a=${(i*7919)%104729} b=${(i*104729)%7919} c=${i%13}; `;\n  return s;\n}")
sub('Protocol: the pause test')
para(f"Four turns are issued back-to-back (inter-turn latency ≪ 5 m), followed by an idle period of at least 390 s, followed by turn 5. AGNT's measured gaps are {gap('agnt-claude'):.3f} s (Claude) and {gap('agnt-codex'):.3f} s (Codex); the other arms' gaps span {min(gap(i) for i in S if not i.startswith('agnt')):.1f}–{max(gap(i) for i in S if not i.startswith('agnt')):.1f} s, recorded from runner completion to next runner start (provider-visible inactivity is longer by client start-up). The idle length is chosen to strictly exceed the 5-minute TTL while remaining far below the 1-hour TTL, so post-pause telemetry classifies each harness's effective retention with no ambiguity: a 5 m cache must report {tt('cache_read_input_tokens = 0')} on turn 5; a 1 h cache must report a full-prefix read.")
sub('Instrumentation and validity guards')
para(f"Ground truth is the provider's own per-request accounting: Anthropic's usage block ({tt('input_tokens')}, {tt('cache_read_input_tokens')}, {tt('cache_creation_input_tokens')}, and the per-TTL split {tt('cache_creation.ephemeral_{5m,1h}_input_tokens')}); OpenAI's {tt('input_tokens')} with {tt('input_tokens_details.cached_tokens')}. These reach the runner through each harness's native telemetry — Claude Code's {tt('--output-format json')} result blocks, Codex's {tt('turn.completed')} usage events, OMP's {tt('agent_end')} usage with its {tt('cttl')} split, OpenClaw's {tt('lastCallUsage')}, and Hermes' canonical session-usage deltas with exactly one API call per turn (Hermes does not expose the per-TTL split, so its write tier is taken from its configured policy and corroborated by the post-pause read). For the AGNT arm the provider's raw usage events ({tt('message_start')}/{tt('message_delta')}; {tt('response.completed')}) were captured on every request and reconciled with the harness's own counters: all ten agree exactly.")
para('Driver scripts enforce three fail-loud invariants; violation aborts the run and discards its data: (G1) identity — every turn\'s reply must be exactly OKt and the reported model must be the requested model (defeats silent fallback to another model or a cached reply); (G2) payload — the SHA-256 of each outgoing prompt must equal the workload digest, byte-identical across every arm of a track (defeats drift in the controlled variable); (G3) continuity and timing — the session identifier is held constant across turns, provider-reported context must grow by the payload each turn (defeats non-accumulating history), inter-turn gaps inside the burst must be below 300 s and the gap before turn 5 at least 390 s. Every accepted record carries the raw usage object of its source so the transformations can be re-run offline (§10).')
sub('Cross-harness comparability')
para(f"Harnesses ship system prompts and tool schemas of different sizes ({k(min(I(i,1)-C(i,1) for i in ['omp-r2','openclaw','hermes','claude']))}–{k(c1)} tokens at turn 1 on the Claude path; AGNT was measured with its full resident instruction context and tool schemas, the other harnesses in isolated, reduced-tool configurations), so absolute token counts are not comparable across systems. We therefore compare only: (i) the post-pause cache-hit ratio (dimensionless); (ii) steady-state premium tokens per turn against the known {m:,}-token payload, which decomposes each bill into user payload + harness overhead; and (iii) dollar figures from applying each harness's measured marker strategy — its TTL policy and per-turn overhead — to fully identical content (§5.3, §6).")
para(f"As an internal consistency check, per-turn context growth must equal the payload plus any harness self-injection. Measured deltas (turns 2–4, tokens/turn, Claude path): AGNT +{m:,}; OMP +{m:,}; Hermes +{m+ovh['hermes']:,}; OpenClaw +{m+ovh['openclaw']:,}; Claude Code +{m+ovh['claude']:,}. The constant ≈{m:,} component appearing in all five confirms workload identity to within tokenizer noise; the excess (+{ovh['openclaw']} OpenClaw, +{ovh['claude']} Claude Code) is measured harness overhead (§5.4). On the Codex path (GPT-6 Astra tokenizer) the payload is {mx:,} tokens: AGNT, OMP and Hermes grow by {min(min(growth(i)) for i in ['agnt-codex','omp-codex','hermes-codex']):,}–{max(max(growth(i)) for i in ['agnt-codex','omp-codex','hermes-codex']):,} per turn, OpenClaw by {growth('openclaw-codex-r2')[0]:,}, and Codex CLI by {min(growth('codex-r2')):,}–{max(growth('codex-r2')):,} (a mean +{codex_cli_mean_ovh:,} tokens/turn of its own turn scaffolding).")
sec('Results')
sub('Per-turn billing telemetry')
def cp(i,t):return [num(C(i,t)),num(Pm(i,t))]
table(['Turn','AGNT C','P','Claude Code C','P','OpenClaw C','P','Hermes C','P'],[[('5 (post-pause)' if t==5 else str(t))]+cp('agnt-claude',t)+cp('claude',t)+cp('openclaw',t)+cp('hermes',t) for t in range(1,6)],
 'Per-turn provider counters, Claude path, shipping defaults (tokens). C = cached (billed 0.1×); P = premium (billed ≥1×). Turn 5 follows the ≥390 s pause.')
table(['Turn','AGNT C','P','Codex CLI C','P','OpenClaw C','P','OMP C','P','Hermes C','P'],[[('5 (post-pause)' if t==5 else str(t))]+cp('agnt-codex',t)+cp('codex-r2',t)+cp('openclaw-codex-r2',t)+cp('omp-codex',t)+cp('hermes-codex',t) for t in range(1,6)],
 'Per-turn provider counters, Codex path (GPT-6 Astra tokens). C = cached input; P = uncached input. Retention is provider-managed on this path; OMP’s turn-4 miss is retained as observed.')
para(f"AGNT's per-TTL split reports {tt('ephemeral_5m_input_tokens = 0')} on every Claude turn (all writes 1 h); its turn-1 counters include a {num(C('agnt-claude',1))}-token shared-prefix read the provider already held, as do Claude Code's ({num(C('claude',1))}). Every Anthropic-path harness bills a constant two uncached tokens per turn beside its writes. Claude Code's 1 h split is native ({tt('ephemeral_1h_input_tokens')}); OMP's is native ({tt('cttl')}); OpenClaw's is native ({tt('cacheWrite1h')}). OMP's Claude counters (both TTL paths) are reported separately in §5.6 (Table 5), since its default and opt-in configurations must be distinguished; OpenClaw's and Hermes' 1-hour options are in §5.7 (Table 6). On the Codex path Codex CLI already read {num(C('codex-r2',1))} tokens on its first request (a provider-side warm prefix), whereas AGNT read zero; OMP's turn 4 is a complete miss between two hits, retained as observed.")
figure(1)
sub('Pause survival')
table(['Harness','TTL','C₅','P₅','h','Classification'],[
 ['AGNT '+V['agnt']+' (Claude)','1 h',num(C('agnt-claude',5)),num(Pm('agnt-claude',5)),pct1(h5('agnt-claude')),'survived; premium = payload only'],
 ['OMP '+V['omp']+' (=long)','1 h',num(C('omp-long',5)),num(Pm('omp-long',5)),pct1(h5('omp-long')),'survived (opt-in); premium = payload only'],
 ['OMP '+V['omp']+' (default)','5 m',num(C('omp-r2',5)),num(Pm('omp-r2',5)),pct1(h5('omp-r2')),'expired; full-context re-write (shipping default)'],
 ['Claude Code','1 h',num(C('claude',5)),num(Pm('claude',5)),pct1(h5('claude')),'survived; premium = payload + 40'],
 ['OpenClaw (=long)','1 h',num(C('openclaw-long',5)),num(Pm('openclaw-long',5)),pct1(h5('openclaw-long')),'survived (opt-in); premium = payload + 20'],
 ['OpenClaw (default)','5 m',num(C('openclaw',5)),num(Pm('openclaw',5)),pct1(h5('openclaw')),'expired; full-context re-write at 1.25×'],
 ['Hermes (cache_ttl=1h)','1 h',num(C('hermes-long',5)),num(Pm('hermes-long',5)),pct1(h5('hermes-long')),'survived (opt-in); premium = payload only'],
 ['Hermes (default)','5 m',num(C('hermes',5)),num(Pm('hermes',5)),pct1(h5('hermes')),'expired; full-context re-write at 1.25×'],
 ['AGNT '+V['agnt']+' (Codex)','auto',num(C('agnt-codex',5)),num(Pm('agnt-codex',5)),pct1(h5('agnt-codex')),'survived this trial; retention unguaranteed [3]'],
 ['Codex CLI','auto',num(C('codex-r2',5)),num(Pm('codex-r2',5)),pct1(h5('codex-r2')),'survived this trial; retention unguaranteed [3]'],
 ['OpenClaw (Codex)','auto',num(C('openclaw-codex-r2',5)),num(Pm('openclaw-codex-r2',5)),pct1(h5('openclaw-codex-r2')),'survived this trial'],
 ['OMP (Codex)','auto',num(C('omp-codex',5)),num(Pm('omp-codex',5)),pct1(h5('omp-codex')),'survived this trial'],
 ['Hermes (Codex)','auto',num(C('hermes-codex',5)),num(Pm('hermes-codex',5)),pct1(h5('hermes-codex')),'survived this trial']],
 'Turn-5 cache-hit ratio h = C₅/(C₅+P₅).',left=(0,1,5),widths={0:.22,5:.34})
para(f"The result partitions exactly along declared TTL, consistent with Anthropic's contractual expiry semantics: all three 5 m defaults lost 100% of cache across the pause, re-billing {k(min(Pm(i,5) for i in five))}–{k(max(Pm(i,5) for i in five))} tokens at the write premium, while every 1 h configuration read its entire prior context at 0.1× — AGNT's {num(C('agnt-claude',5))}-token read is {100*C('agnt-claude',5)/I('agnt-claude',4):.3f}% of its previous request. AGNT's ratio is the highest measured on both paths ({pct2(h5('agnt-claude'))} Claude, {pct2(h5('agnt-codex'))} Codex), {100*(h5('agnt-claude')-max(h5(i) for i in ['claude']+longs)):.2f} points above the next Claude configuration (OpenClaw with its 1 h option) and {100*(h5('agnt-codex')-h5('codex-r2')):.2f} points above Codex CLI. The five Codex-path survivals are single observations of an explicitly unguaranteed mechanism and are not generalizable.")
figure(2)
sub('Controlled cost (identical content)')
para(f"Applying the two marker strategies to identical content (§4.4: the {num(c1)}-token first-turn prefix measured for AGNT — system prompt, tool schemas and the turn-1 payload — plus {m:,} tokens per subsequent turn) for the 5-turn protocol yields, at Sonnet 5 list prices: all-1 h strategy (AGNT, Claude Code layouts) {usd3(r5['1h'][2])}; 5 m strategies (OMP-default, OpenClaw, Hermes layouts) {usd3(r5['5m'][2])} (+{100*(r5['5m'][2]/r5['1h'][2]-1):.0f}%, driven by the post-pause re-write); no-cache control {usd3(ctl5[1])} (+{100*(ctl5[1]/r5['1h'][2]-1):.0f}%). Verification: all-1 h writes {num(c1)} + 4×{m:,} = {num(r5['1h'][0])} tk × ${W1_:.0f}/M = {usd3(W1_*r5['1h'][0]/1e6)}; reads {num(r5['1h'][1])} tk × ${R_:.2f}/M = {usd3(R_*r5['1h'][1]/1e6)}; total {usd3(r5['1h'][2])}. ✓ 5 m writes {num(c1)} + 3×{m:,} + ({num(c1)} + 4×{m:,}) = {num(r5['5m'][0])} tk × ${W5_:.2f}/M = {usd3(W5_*r5['5m'][0]/1e6)}; reads {num(r5['5m'][1])} tk × ${R_:.2f}/M = {usd3(R_*r5['5m'][1]/1e6)}; total {usd3(r5['5m'][2])}. ✓")
sub('Pathology I revisited — per-turn scaffolding injection (Claude Code)')
para(f"In [1], Claude Code 2.1.126 grew its context by 12,003 tokens/turn against a 5,186-token payload: 6,817 tokens/turn (2.31× payload) of self-injected system reminders and related scaffolding, re-billed at the 1 h write premium every turn. Claude Code {V['cc']} grows by {m+ovh['claude']:,} tokens/turn against the {m:,}-token payload: {ovh['claude']} tokens/turn ({100*ovh['claude']/m:.1f}% of payload). The earlier large excess is not reproduced in this reduced-tool workload. Its steady-state premium ({num(Pm('claude',2))} tokens/turn) is now within {ovh['claude']} tokens of AGNT's ({num(Pm('agnt-claude',2))}), and the perfect marker discipline it already showed in [1] now coexists with payload-clean spend. The residual is small enough that it is invisible in dollar terms (§6, {usd3(proj['claude'][2])} vs {usd3(proj['agnt-claude'][2])} per one-hour session).")
figure(3)
sub('Pathology II revisited — intra-burst cache leak (Hermes)')
para(f"In [1], Hermes 0.18.0 leaked within the rapid burst: cached reads decreased (22,837 → 17,339) while premium writes grew (5,186 → 15,869 → 21,054), because rolling message-marker placement never extended the cached prefix over accumulated history. Hermes {V['hermes']} shows the opposite, correct behaviour: cached reads grow with the conversation ({num(C('hermes',2))} → {num(C('hermes',3))} → {num(C('hermes',4))}) while premium writes stay pinned at the payload ({num(Pm('hermes',2))}, {num(Pm('hermes',3))}, {num(Pm('hermes',4))}). Its read volume extends with the accumulated history; the earlier leak pattern is not present in these receipts. Hermes still loses the remainder at the pause on its 5 m default (h = 0%, {num(Pm('hermes',5))}-token re-write) and survives with {tt('prompt_caching.cache_ttl: 1h')} (C₅ = {num(C('hermes-long',5))}, h = {pct1(h5('hermes-long'))}).")
figure(4)
sub('OMP — clean spend, but a default-TTL trap')
para(f"oh-my-pi bills premium tokens equal to the user payload with zero self-injection ({m:,} tokens/turn, measured; Table 2-consistent delta +{m:,}), as it did in [1]. On the tested subscription path OMP {V['omp']} emits 5-minute markers by default and fails the pause test identically to OpenClaw and Hermes (h = 0%, full {num(Pm('omp-r2',5))}-token re-write; verified per-TTL as {tt('ephemeral5m')} in its {tt('agent_end')} usage). With {tt('PI_CACHE_RETENTION=long')} it emits 1-hour markers and survives (C₅ = {num(C('omp-long',5))}, h = {pct1(h5('omp-long'))}; verified {tt('ephemeral1h')}). OMP demonstrates that payload-clean billing and the 5-minute default trap are independent axes: a harness can perfect the former and still forfeit the pause on the latter.")
table(['Turn','Default (5 m) C','P','=long (1 h) C','P'],[[('5 (post-pause)' if t==5 else str(t))]+cp('omp-r2',t)+cp('omp-long',t) for t in range(1,6)],'OMP per-turn counters, both TTL paths (tokens). C = cached; P = premium. Premium is pinned at the payload every turn; only the default-vs-flag TTL differs.')
sub('OpenClaw and Hermes — the same trap, the same cure')
para(f"OpenClaw {V['oc']} and Hermes {V['hermes']} ship the same 5-minute default and fail the pause the same way ({num(Pm('openclaw',5))} and {num(Pm('hermes',5))} tokens re-written at 1.25×). Each documents a one-line retention option — OpenClaw's per-model {tt('cacheRetention')+': long'} [5], Hermes' {tt('prompt_caching.cache_ttl: 1h')} [6] — and each survives with it set (h = {pct1(h5('openclaw-long'))} and {pct1(h5('hermes-long'))}), with first-turn writes within {abs(Pm('openclaw-long',1)-Pm('openclaw',1))} (OpenClaw) and {abs(Pm('hermes-long',1)-Pm('hermes',1))} (Hermes) tokens of the defaults'. The counters of both configurations are given in Table 6; premium is the payload (plus OpenClaw's constant {ovh['openclaw']} tokens) on every non-expiry turn.")
table(['Turn','OpenClaw 5 m C','P','OpenClaw 1 h C','P','Hermes 5 m C','P','Hermes 1 h C','P'],[[('5 (post-pause)' if t==5 else str(t))]+cp('openclaw',t)+cp('openclaw-long',t)+cp('hermes',t)+cp('hermes-long',t) for t in range(1,6)],'OpenClaw and Hermes per-turn counters, default vs 1-hour option (tokens). C = cached; P = premium.')
sec('Cost Model and One-Hour Extrapolation')
para(f"Let the session comprise n messages of payload m tokens, partitioned into bursts by breaks longer than the TTL. With prefix c₁ = first-turn write and per-turn overhead o, and prices r = ${R_:.2f}/M (read), w₅ = ${W5_:.2f}/M, w₁ = ${W1_:.2f}/M, u = ${U_:.2f}/M:")
eq(r'\begin{aligned}10^6\mathrm{Cost}_{1h}&=w_1\,[c_1+(n-1)(m+o)]+r\sum_{t=2}^{n}\mathrm{prefix}(t)\\ 10^6\mathrm{Cost}_{5m}&=w_5\Big[\sum_{\text{bursts}}\mathrm{rewrite}_b+\text{in-burst writes}\Big]+r\,(\text{in-burst reads})\end{aligned}','10⁶ Cost₁ₕ = w₁·[c₁ + (n−1)(m+o)] + r·Σₜ₌₂..ₙ prefix(t)      10⁶ Cost₅ₘ = w₅·[Σ_bursts rewrite_b + in-burst writes] + r·(in-burst reads)')
para(f"where prefix(t) = c₁ + (t−2)(m+o) is the cached context preceding message t, and rewrite_b = c₁ + (s_b−1)(m+o) is the full context re-purchased at the first message s_b of each burst after the first. Cache hits refresh the TTL, so the 1 h prefix persists across every break shorter than one hour. Because absolute footprints are incomparable across harnesses (§4.4), the model is instantiated over identical content — c₁ = {num(c1)}, m = {m:,} — with each harness's measured marker strategy: its shipping TTL and its per-turn overhead o. For n = 20 with four breaks (after messages 4, 8, 12, 16; breaks > 5 m, session ≤ 1 h):")
table(['Harness','Premium writes (tk)','@$','Cached reads (tk)','@$','Total','M'],[[(lab.replace(' (default 5 m)','')+(' (default 5 m, o=%d)'%o_model[i] if i=='omp-r2' else ' (%s, o=%d)'%(ttl.replace('m',' m').replace('h',' h'),o_model[i]))),num(proj[i][0]),usd2((W1_ if ttl=='1h' else W5_)*proj[i][0]/1e6),num(proj[i][1]),usd2(R_*proj[i][1]/1e6),usd3(proj[i][2]),f'{proj[i][2]/ctl20[1]:.2f}×'] for i,lab,ttl in STRAT]+[['No caching (control)',num(ctl20[0])+' @ u',usd2(ctl20[1]),'—','—',usd3(ctl20[1]),'1.00×']],
 f'One-hour projection (measured marker strategies over identical content; Sonnet 5 list prices). M = uncached multiplier vs. {usd3(ctl20[1])} control.')
figure(5)
para(f"Extending the same model to longer sessions (a break after every fourth message, ≈12 min apart, at 20 messages/hour) yields the multipliers of Table 8. AGNT's and Claude Code's are monotone decreasing — the 2× build premium amortizes into 0.1× reads. OMP's, OpenClaw's and Hermes' flatten: every break re-charges a full, ever-larger re-write. The model is conservative for 5 m harnesses: it assumes zero intra-burst expiry; the frequency of real-world pauses was not measured here, so the size of the modeled gap depends on the stated schedule. OMP, OpenClaw and Hermes are reported on their shipping 5-minute defaults; with their opt-in 1-hour options each collapses onto the AGNT curve ({usd3(omp_long_20)} at one hour for OMP =long, identical to AGNT to the token) — but that is not out-of-box behavior, so the default is used for the headline, consistent with the treatment of every other harness. Codex has its own price list, retention scenarios, one-hour projection and multi-hour multipliers in §6.2; its monthly, annual, team and tool-latency analyses follow in §7.4–7.6.")
shape={'agnt-claude':'monotone decreasing','claude':'monotone decreasing (offset by o)','omp-r2':'flattens (clean, but 5 m)','openclaw':'flattens','hermes':'flattens'}
table(['Harness']+[l for l,_,_ in LENGTHS]+['Shape'],[[lab]+[f'{v:.2f}' for v in mult[i]]+[shape[i]] for i,lab,_ in STRAT],'Cost multiplier vs. the uncached baseline by session length (lower is better).',left=(0,6))
figure(6)
sec('Monthly and Annual Extrapolation')
para(f"The Claude monthly analysis retains the original paper’s fixed-work-per-hour normalization: a one-hour reference burn rate multiplied by session-length cache-efficiency factors. It is a rate-normalized budget scenario, not the exact token sum of extending one growing transcript to 80 requests. Each scenario assumes cold starts and the stated marker policy. Let Rᵤ = {usd2(Ru)}/h denote the uncached burn rate of the reference workload (20 messages/h at {m:,} tokens each over the {num(c1)}-token prefix) and M_X(L) the session-length-dependent cost multiplier of harness X from §6 (Table 8). The rate-normalized monthly budget over D workdays with a fixed daily session schedule S is")
eq(r'\mathrm{Cost}^{\mathrm{month}}_X = D\cdot\sum_{L\in S} M_X(L)\cdot L\cdot R_u','Cost_X^month = D · Σ_L∈S M_X(L) · L · Rᵤ')
para('We instantiate three usage profiles at D = 22 workdays (Table 9) and evaluate with the measured multipliers (Table 10).')
table(['Profile','Daily usage','Session shape','Multiplier basis'],[[p,d,s,b] for p,d,s,b,_,_,_ in PROFILES],'Usage profiles for monthly extrapolation.',left=(0,1,2,3))
table(['Harness','Light','Moderate','Heavy'],[[lab]+[f'${v:,.0f}' for v in monthly[i]] for i,lab,_ in STRAT]+[['No caching (control)']+[f'${v:,.0f}' for v in monthly['none']]],'Projected monthly cost per seat (USD; 22 workdays; Sonnet 5 list prices; identical content).')
para(f"Two structural effects emerge at monthly scale. First, the TTL gap widens with load: because the 1-hour multipliers are monotone-decreasing in session length ({mult['agnt-claude'][2]:.2f} at 1 h → {mult['agnt-claude'][4]:.2f} at 4 h) while the 5-minute defaults flatten ({mult['omp-r2'][2]:.2f} → {mult['omp-r2'][4]:.2f}), AGNT's advantage over the 5-minute-default harnesses grows from {xrng([adv[0][0],adv[1][0]])} (light) to {xrng([adv[0][2],adv[1][2]])} (heavy). Second, the ranking has compressed since [1]: with both pathologies gone, Claude Code now tracks AGNT to within ${monthly['claude'][2]-monthly['agnt-claude'][2]:,.2f}/month at the heavy profile, and the three 5-minute-default harnesses cluster within ${max(monthly[i][2] for i in five)-min(monthly[i][2] for i in five):,.2f}/month of one another — the dominant difference in this specified default-policy model is retention.")
figure(7)
sub('The cache tax')
para(f"Define the cache tax of harness X as Cost_X − Cost_AGNT for identical work on the identical model. At the heavy profile (Table 11), the tax is {rng(tax[i] for i in five)} per seat per month; a heavy OMP, OpenClaw or Hermes seat on its shipping default spends AGNT's entire monthly budget by approximately day {22*monthly['agnt-claude'][2]/max(monthly[i][2] for i in five):.0f}.")
table(['Harness','$/month','$/year','Dominant mechanism'],[[lab,f'{tax[i]:,.0f}',f'{12*tax[i]:,.0f}',mech] for i,lab,mech in [('omp-r2','OMP (default 5 m)','full-context re-write after every >5 m break (no overhead; 1 h available via flag)'),('openclaw','OpenClaw',f'full-context re-write at 1.25× after every >5 m break (+{ovh["openclaw"]} tk/turn; 1 h available via cacheRetention)'),('hermes','Hermes','full-context re-write at 1.25× after every >5 m break (1 h available via cache_ttl)')]]+[['Claude Code',f'{monthly["claude"][2]-monthly["agnt-claude"][2]:,.2f}',f'{12*(monthly["claude"][2]-monthly["agnt-claude"][2]):,.2f}',f'{ovh["claude"]} tokens/turn residual overhead at 1 h premium'],['No caching',f'{tax["none"]:,.0f}',f'{12*tax["none"]:,.0f}','all tokens at list price']],
 f'Cache tax vs. AGNT {V["agnt"]}, heavy profile (8 h/day, 22 workdays).',left=(0,3),widths={3:.5})
sub('Team scale')
para(f"Annualized over a five-seat team at the heavy profile: AGNT ${team['agnt-claude']:,.0f}; Claude Code ${team['claude']:,.0f}; OMP (default) ${team['omp-r2']:,.0f}; Hermes ${team['hermes']:,.0f}; OpenClaw ${team['openclaw']:,.0f}; uncached ${team['none']:,.0f}. The OpenClaw-vs-AGNT delta alone is ≈${team['openclaw']-team['agnt-claude']:,.0f}/year for byte-identical work — the cost of one configuration bit, not a model upgrade.")
para('Three properties of this extrapolation bias it against the headline gap rather than for it: (i) the session model assumes breaks only every ≈12 minutes, more frequent pauses would add re-write events to the 5 m scenario but were not measured here; (ii) the identical-content prefix is held at the measured first-turn size, though real agent contexts grow with tool results and file contents, scaling every re-write linearly; and (iii) the model charges the 5 m harnesses nothing for intra-burst expiry. These are scenario sensitivities, not proved lower bounds for real workloads.')
sub('The agentic amplifier: tool latency as involuntary pause')
para('The pause test models the idle gap as a human pause, but nothing in the mechanism requires a human. An agent turn is a chain of model calls separated by tool executions, and any tool that runs longer than the marker TTL expires the cache mid-turn: build systems and test suites (2–20 min), media-generation polling (5–10 min per asset), rate-limit backoff, CI and deployment waits, sub-agent delegation, and approval gates all routinely exceed five minutes with no user absent. On a 5 m-TTL harness, every such tool forces a full-context re-write at 1.25× for the next model call in the same turn.')
para(f"The per-event cost scales with working-context size. At a realistic agentic context of 100k tokens (Sonnet 5 list, ${U_:.0f}/M input), one post-expiry re-write bills 100k × ${U_:.0f}/M × 1.25 = {usd2(amp_event)}. An autonomous workload executing 30 long-running tools per day on a 5 m-TTL harness therefore pays ≈{usd2(amp_day)}/day — ≈${amp_month:,.0f}/month per seat — in gross post-expiry input charges; under the stated sub-hour gaps a retained one-hour read would be charged at the read rate instead, in addition to the human-pause modeling above, and growing linearly with context length. The exposure is thus largest precisely where agent harnesses do their most valuable work: long-context, tool-heavy autonomous sessions. The inversion noted in [1] stands: the harnesses marketed most explicitly for autonomous multi-step operation (OpenClaw, Hermes) still ship the TTL least compatible with it.")
sec('Ancillary Finding: Subscription Access')
para(f"In [1] we observed Anthropic reject a third-party harness presenting a valid Claude-subscription OAuth token (HTTP 400, “Third-party apps now draw from your extra usage, not your plan limits”), and every third-party arm had to be run on a funded API key. In this study every arm ran on a subscription credential: the Claude subscription for AGNT, OMP, Claude Code, OpenClaw and Hermes, and the ChatGPT subscription via Codex for AGNT, Codex CLI, OpenClaw, OMP and Hermes. The 65 accepted requests completed successfully on their subscription paths. The receipts establish successful service, not which plan allowance or any overage bucket funded it. The receipts do not reveal which allowance the usage was drawn from, and subscription usage is metered against plan limits rather than invoiced per token — Claude Code's own documentation states that its session dollar figure is an estimate, not the bill for included plan usage [11]. The dollar figures in §§5–7 are therefore API-list equivalents of the measured counters: what the same work costs at metered rates, not a measured conversion to subscription allowance. The caching results themselves are independent of the billing mode.")
sec('Threats to Validity')
defs([('Single trial per cell.','Anthropic TTL expiry is contractual and the observed partition follows it exactly, but each Codex-path survival is one sample of a load-dependent, unguaranteed mechanism, and OMP\'s Codex turn-4 miss is a single unexplained event.'),
 ('Single idle duration.','≥390 s cleanly separates the two TTLs; gaps > 1 h would defeat every harness tested absent keep-warm traffic.'),
 ('Synthetic payloads.','Deterministic filler standing in for real work. Real sessions grow faster (tool results) and pause more often; both effects increase the measured gaps\' magnitude, not their direction.'),
 ('Footprint heterogeneity.',f'Absolute token counts are incomparable across harnesses by construction — AGNT carried its full resident instruction context and tool schemas ({k(c1)} first-turn prefix), the other harnesses isolated reduced-tool configurations ({k(min(I(i,1)-C(i,1) for i in ["omp-r2","openclaw","hermes","claude"]))}–{k(max(I(i,1)-C(i,1) for i in ["omp-r2","openclaw","hermes","claude"]))}); all cross-harness claims use ratios, payload-normalized overhead, or the identical-content model (§4.4).'),
 ('Warm first turns.',f'Provider caches cannot be flushed; AGNT and Claude Code read {num(C("agnt-claude",1))} and {num(C("claude",1))} shared-prefix tokens on turn 1, and Codex CLI {num(C("codex-r2",1))}. The pause test is unaffected (it compares turn 5 to turn 4), and the cost model starts every session cold.'),
 ('Extrapolation assumptions.','Fixed break schedule and payload size; the model is linear in measured per-turn parameters and stated in full (§6) so readers may re-parameterize.'),
 ('Monthly extrapolation.','§7 composes single-session measurements linearly across fixed daily profiles; real months vary payload size, schedule, and break structure. The measurements do not establish the distribution of real pause lengths, prompt growth or task quality; the monthly values are conditional projections.'),
 ('Subscription telemetry.','Dollar figures are list-price equivalents of counters obtained on subscription paths, not invoices (§8); ratios and token counts are unaffected.'),
 ('Version pinning.','Results describe the versions in Table 1 as of 2026-09-08; harness caching behavior can change across releases — as the disappearance of both pathologies of [1] demonstrates.')])
sec('Reproducibility')
blocks.append(('raw',(r'All measurements derive from the published artifact set (\url{https://agnt.gg/whitepapers/cache-wars-2-artifacts/artifacts/index.html}\par \url{https://agnt.gg/whitepapers/cache-wars-2-artifacts/SHA256SUMS.txt}):','All measurements derive from the published artifact set (<a href="artifacts/index.html">browse</a> · <a href="SHA256SUMS.txt">checksums</a>):')))
table(['File','Contents'],[
 ['artifacts/data/measurements.json','All 13 configurations × 5 turns: normalized counters, prompt digests, gaps, per-turn API-equivalent cost'],
 ['artifacts/receipts/<arm>-turn<t>.json','65 receipts: the original harness/provider usage object, reply, model, session gap and prompt SHA-256 for every accepted request'],
 ['artifacts/data/workload-{claude,codex}.json','The five per-turn prompts of each track with byte counts and SHA-256 digests'],
 ['artifacts/environment/versions.json, package-lock.json, hermes-uv.lock','Pinned harness versions, runtimes and dependency locks; AGNT source fingerprints'],
 ['artifacts/figures/figure-{1..7}.{svg,pdf}, figure-c{1..6}.{svg,pdf}, figure-data.csv, scenario.json, codex-scenario.json','Original chart set plus six Codex charts; measured rows and complete Claude/Codex model quantities'],
 ['artifacts/scripts/build.py','Regenerates the figures, tables, HTML and LaTeX of this paper from measurements.json'],
 ['artifacts/scripts/verify.py','Offline verifier: manifest, raw-usage reconciliation, guards, cost model and published numbers'],
 ['artifacts/scripts/live_protocol.py','Live driver: per-harness invocation, guards G1–G3, 390 s idle, receipt capture'],
 ['SHA256SUMS.txt','Integrity manifest over every released file except itself']],'Artifact set.',left=(0,1),widths={0:.4,1:.55})
para(f"Replication procedure: (1) install the six harnesses at the pinned versions; (2) authenticate each on its subscription path in an isolated home directory (Claude Code via {tt('CLAUDE_CONFIG_DIR')}, Codex via {tt('CODEX_HOME')}, OMP via {tt('PI_CODING_AGENT_DIR')}, OpenClaw via {tt('OPENCLAW_HOME')}, Hermes via {tt('HERMES_HOME')}; AGNT on a separately provisioned isolated host with its own data directory — never a production database); (3) run {tt('live_protocol.py --harness … --track … [--retention long]')}, which issues four turns of Listing-1 payloads, sleeps 390 s, issues turn 5, and aborts on any guard violation; (4) run {tt('verify.py')} to re-derive every counter in Tables 2–6 from the receipts' raw usage objects and every cost-model quantity in Tables 7–11 and C5–C8 from §6–7; (5) run {tt('build.py --output NEW_DIRECTORY')} to regenerate this manuscript and its figures from the same data. Every number in this paper is either a raw counter from these files or an arithmetic combination shown in the text.")
sec('Conclusion')
para(f"Prompt-cache efficiency in deployed agent harnesses is determined by client-side engineering, and shipped behavior still diverges from what documentation implies — though less than it did two months ago. A single configuration bit — cache TTL — separates harnesses that resume an interrupted session at 10% of list price from harnesses that silently re-purchase their entire context after every human-scale pause. The two pathologies layered on top of it in [1] have been engineered away: Claude Code's per-turn scaffolding has fallen from 6,817 tokens to {ovh['claude']}, and Hermes' marker placement now extends the cached prefix over history. What has not changed is the default: OMP, OpenClaw and Hermes still ship the 5-minute TTL that forfeits the pause, and each still requires a documented opt-in to survive it. AGNT {V['agnt']} combines default one-hour Claude markers, Claude writes equal to the measured input increment, and successful Codex reuse — posting the highest post-pause cache-hit ratio on the Claude path ({pct2(h5('agnt-claude'))}) and the Codex path ({pct2(h5('agnt-codex'))}) in these trials. Under the stated Claude default-policy projection AGNT attains the lowest cost, tied with other zero-extra-growth one-hour configurations when those options are enabled. Codex cost projections are conditional on prefix availability; equal content and equal availability imply equal price, not an exclusive harness discount. Composed over a working month, the surviving mechanism compounds into a per-seat cache tax of {rng(tax[i] for i in five)}/month ({rng(12*tax[i] for i in five)}/year) for the 5-minute-default harnesses, and ≈${team['openclaw']-team['agnt-claude']:,.0f}/year for a five-seat team in the worst measured case — recurring spend separable from model quality entirely, and recoverable by client-side engineering alone.")
refs=[('The Cache Wars: An Empirical Study of Prompt-Cache Efficiency in Six LLM Agent Harnesses.','Technical Report v2.0, AGNT Labs, July 7, 2026.',ORIG),
 ('Anthropic. Prompt caching; Model and cache pricing.','Claude Platform Documentation (accessed 2026-09-08). Sonnet 5: input $2.00/M; cache read $0.20 (0.1×); 5-minute write $2.50 (1.25×); 1-hour write $4.00 (2×).','https://platform.claude.com/docs/en/build-with-claude/prompt-caching'),
 ('OpenAI. API pricing.','OpenAI Developer Documentation (accessed 2026-09-08). gpt-6-astra standard short-context: input $10.00/M; cached input $1.00; cache write $12.50; output $50.00. Automatic prefix caching; model-specific retention controls are documented separately [12].','https://developers.openai.com/api/docs/pricing'),
 ('OpenAI. Using Codex with your ChatGPT plan.','OpenAI Help Center (accessed 2026-09-08).','https://help.openai.com/en/articles/11369540-using-codex-with-your-chatgpt-plan'),
 ('OpenClaw. Anthropic provider; Model providers and runtime selection.','Technical reference (accessed 2026-09-08). cacheRetention: none|short|long; “short” (5 m) seeded by default for Anthropic.','https://docs.openclaw.ai/providers/anthropic'),
 ('Nous Research. Hermes Agent release v2026.9.7 (0.21.1).','GitHub release (accessed 2026-09-08).','https://github.com/NousResearch/hermes-agent/releases/tag/v2026.9.7'),
 ('oh-my-pi. @oh-my-pi/pi-coding-agent 18.1.14.','npm registry record (accessed 2026-09-08).','https://registry.npmjs.org/@oh-my-pi/pi-coding-agent/18.1.14'),
 ('Anthropic. @anthropic-ai/claude-code 2.1.263.','npm registry record (accessed 2026-09-08).','https://registry.npmjs.org/@anthropic-ai/claude-code/2.1.263'),
 ('OpenAI. @openai/codex 0.153.4.','npm registry record (accessed 2026-09-08).','https://registry.npmjs.org/@openai/codex/0.153.4'),
 ('OpenClaw. openclaw 2026.9.2.','npm registry record (accessed 2026-09-08).','https://registry.npmjs.org/openclaw/2026.9.2'),
 ('Anthropic. Claude Code: manage costs effectively.','Claude Code documentation (accessed 2026-09-08). The session cost display is an estimate and does not reflect subscription billing.','https://code.claude.com/docs/en/costs')]
blocks.append(('references',refs))
FOOT=f"Technical Report · AGNT Labs · Benchmark executed 2026-09-08 · Models: claude-sonnet-5 (Claude subscription) + gpt-6-astra (ChatGPT subscription via Codex) · Idle ≥390 s all arms · Payload ≈{m:,} Sonnet 5 tokens/turn all Anthropic-path arms · Correspondence: AGNT project."

exec(compile((ROOT/'artifacts/scripts/codex_analysis.py').read_text(encoding='utf8'), 'codex_analysis.py', 'exec'))

# ------------------------------------------------------------------ emitters
def texescape(s):
    trans={'\\':r'\textbackslash{}','&':r'\&','%':r'\%','$':r'\$','#':r'\#','_':r'\_','{':r'\{','}':r'\}','~':r'\textasciitilde{}','^':r'\textasciicircum{}'}
    out=[];code=False
    for ch in str(s):
        if ch=='\u2063':out.append('\\texttt{' if not code else '}');code=not code;continue
        out.append(trans.get(ch,ch)+(r'\allowbreak{}' if code and ch in '_./,=:' else ''))
    s=''.join(out)
    for a,b in [('Δ',r'$\Delta$'),('—','---'),('–','--'),('−','-'),('’',"'"),('‘','`'),('“','``'),('”',"''"),('·',r'\textperiodcentered{}'),('×',r'$\times$'),('≥',r'$\geq$'),('≤',r'$\leq$'),('≪',r'$\ll$'),('≈',r'$\approx$'),('→',r'$\rightarrow$'),('⁶',r'$^{6}$'),('✓',r'$\checkmark$'),('⟨',r'$\langle$'),('⟩',r'$\rangle$'),('ᵤ',r'$_u$'),('₁ₕ',r'$_{1h}$'),('₅ₘ',r'$_{5m}$'),('₁','$_1$'),('₂','$_2$'),('₃','$_3$'),('₄','$_4$'),('₅','$_5$'),('ₜ','$_t$'),('Σ',r'$\Sigma$')]:s=s.replace(a,b)
    return s
def H(s):
    s=html.escape(str(s));parts=s.split('\u2063');return ''.join(p if i%2==0 else '<code>'+p+'</code>' for i,p in enumerate(parts))
T=texescape
preamble=r'''\documentclass[11pt]{article}
\usepackage[margin=1in,letterpaper]{geometry}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage{amsmath,amssymb,booktabs,array,longtable,graphicx,times}
\usepackage[hidelinks]{hyperref}
\usepackage{caption}
\captionsetup{font=small,labelfont=bf}
\setlength{\parskip}{2pt}
\setlength{\emergencystretch}{2em}
\Urlmuskip=0mu plus 2mu\relax
\hypersetup{pdftitle={The Cache Wars 2: Prompt-Cache Efficiency in Six LLM Agent Harnesses},pdfauthor={Annie, AGNT Labs}}
\title{\textbf{The Cache Wars 2: An Empirical Study of Prompt-Cache Efficiency\\in Six LLM Agent Harnesses}}
\author{Annie\\\textit{AGNT Labs}}
\date{Technical Report --- '''+DATE+r'''}
\begin{document}
\maketitle
'''
tex=[preamble,r'\begin{abstract}',T(abstract),r'\end{abstract}',r'\noindent\textbf{Keywords:} '+T(KEYWORDS)+'.']
style=(ROOT/'artifacts/scripts/original-paper.css').read_text(encoding='utf8')+'''\nbody{overflow-wrap:anywhere}figure svg{max-width:100%;height:auto} pre{overflow-x:auto} .tablebox{overflow-x:auto;max-width:100%} .eq{overflow-x:auto;font-style:normal} .artifact-link{text-align:center;font-size:9pt} p.def{margin-bottom:4pt} @media(max-width:700px){body{padding:16px;box-sizing:border-box}.abstract{margin:14pt 0}table{min-width:510px}.keyword{margin:0 0 10pt}}'''
web=['<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>The Cache Wars 2: An Empirical Study of Prompt-Cache Efficiency in Six LLM Agent Harnesses</title><meta name="description" content="A controlled measurement of prompt-cache efficiency across six production LLM agent harnesses on subscription-authenticated Claude and Codex paths, with a parametric cost model and complete artifacts."><style>'+style+'</style></head><body><main><div class="titleblock"><h1 class="title">The Cache Wars 2: An Empirical Study of Prompt-Cache Efficiency<br>in Six LLM Agent Harnesses</h1><div class="authors">Annie</div><div class="affil">AGNT Labs</div><div class="date">Technical Report · <time datetime="2026-09-08">'+DATE+'</time></div></div><div class="abstract"><div class="ah">Abstract</div><p>'+H(abstract)+'</p></div><p class="keyword"><b>Keywords:</b> '+H(KEYWORDS)+'</p><p class="artifact-link"><a href="cache-wars-2.pdf">PDF version</a> · <a href="artifacts/index.html">Artifacts &amp; raw counters</a> · <a href="'+ORIG+'">Original paper [1]</a></p>']
sn=0;subn=0;tn=0;en=0
for kind,value in blocks:
    if kind=='section':sn+=1;subn=0;tex.append(r'\section{'+T(value)+'}');web.append(f'<h2 id="s{sn}">{sn}&nbsp;&nbsp;{H(value)}</h2>')
    elif kind=='subsection':subn+=1;tex.append(r'\subsection{'+T(value)+'}');web.append(f'<h3>{sn}.{subn}&nbsp;&nbsp;{H(value)}</h3>')
    elif kind=='p':tex.append(T(value)+'\n');web.append('<p>'+H(value)+'</p>')
    elif kind=='raw':tex.append(value[0]+'\n');web.append('<p>'+value[1]+'</p>')
    elif kind=='ul':tex.append(r'\begin{itemize}\setlength{\itemsep}{1pt}'+'\n'+'\n'.join(r'\item '+T(x) for x in value)+'\n'+r'\end{itemize}');web.append('<ul>'+''.join('<li>'+H(x)+'</li>' for x in value)+'</ul>')
    elif kind=='pre':title,code=value;tex.append(r'\noindent\textit{'+T(title)+'}'+'\n'+r'\begin{footnotesize}\begin{verbatim}'+'\n'+code+'\n'+r'\end{verbatim}\end{footnotesize}');web.append('<pre>'+html.escape(title)+'\n'+html.escape(code)+'</pre>')
    elif kind=='defs':
        for term,body in value:tex.append(r'\noindent\textbf{'+T(term)+'} '+T(body)+'\n');web.append('<p class="def"><b>'+H(term)+'</b> '+H(body)+'</p>')
    elif kind=='equation':en+=1;tex.append(r'\begin{equation}'+value[0]+r'\end{equation}');web.append('<div class="eq">'+H(value[1])+f' &nbsp; ({en})</div>')
    elif kind in ['figure','xfigure']:
        extra=kind=='xfigure';cap=next(c for n,c in (xfigures if extra else figures) if n==value);number=('C' if extra else '')+str(value);tex.append(r'\begin{center}\begin{minipage}{0.98\linewidth}\centering\includegraphics[width=\linewidth]{artifacts/figures/figure-'+(('c' if extra else '')+str(value))+r'.pdf}'+(r'\captionof*{figure}{\textbf{Figure C'+str(value)+'.} ' if extra else r'\captionof{figure}{')+T(cap)+r'}\end{minipage}\end{center}')
        svg=(FIG/f"figure-{'c' if extra else ''}{value}.svg").read_text(encoding='utf8');svg=svg[svg.index('<svg'):];web.append(f'<figure class="fig" id="fig{number}">'+svg+f'<figcaption><b>Figure {number}.</b> '+H(cap)+'</figcaption></figure>')
    elif kind in ['table','xtable']:
        extra=kind=='xtable'
        if extra:
            label,headers,rows,cap,left,widths=value;number='C'+str(label)
        else:
            tn+=1;headers,rows,cap,left,widths=value;number=str(tn)
        columns=''.join((r'>{\raggedright\arraybackslash}p{%.3f\linewidth}'%widths[j]) if widths and j in widths else 'l' if j in left else 'r' for j in range(len(headers)))
        size=r'\footnotesize' if len(headers)>=9 else r'\small';sep='2.2pt' if len(headers)>=9 else '3.5pt'
        tex.append(r'\begingroup'+size+r'\setlength{\tabcolsep}{'+sep+r'}\begin{longtable}{'+columns+'}\n'+(r'\caption*{\textbf{Table '+number+'.} ' if extra else r'\caption{')+T(cap)+r'}\\\toprule'+'\n'+' & '.join(T(x) for x in headers)+r'\\\midrule\endfirsthead'+'\n'+' & '.join(T(x) for x in headers)+r'\\\midrule\endhead'+'\n'+r'\bottomrule\endfoot'+'\n'+'\n'.join(' & '.join(T(x) for x in row)+r'\\' for row in rows)+'\n'+r'\end{longtable}'+(r'\addtocounter{table}{-1}' if extra else '')+r'\endgroup')
        cls=lambda j:' class="l"' if j in left else ''
        web.append(f'<div class="tablebox"><table><caption><b>Table {number}.</b> '+H(cap)+'</caption><thead><tr>'+''.join(f'<th{cls(j)}>'+H(x)+'</th>' for j,x in enumerate(headers))+'</tr></thead><tbody>'+''.join('<tr>'+''.join(f'<td{cls(j)}>'+H(x)+'</td>' for j,x in enumerate(row))+'</tr>' for row in rows)+'</tbody></table></div>')
    elif kind=='references':
        tex.append(r'\begin{thebibliography}{99}');web.append('<h2>References</h2><ol class="refs">')
        for i,(title,detail,url) in enumerate(value,1):tex.append(r'\bibitem{r'+str(i)+'} '+T(title)+' '+T(detail)+r' \url{'+url+'}');web.append('<li><i>'+H(title)+'</i> '+H(detail)+' <a href="'+H(url)+'">'+H(url.replace('https://',''))+'</a></li>')
        tex.append(r'\end{thebibliography}');web.append('</ol>')
tex.append(r'\vspace{6pt}\noindent\footnotesize{'+T(FOOT)+'}');tex.append(r'\end{document}')
web.append('<div class="foot">'+H(FOOT)+' · <a href="artifacts/index.html">Artifacts &amp; raw counters</a></div></main></body></html>')
(OUT/'cache-wars-2.tex').write_text('\n\n'.join(tex),encoding='utf8');(OUT/'cache-wars-2.html').write_text('\n'.join(web),encoding='utf8')
(OUT/'manuscript.json').write_text(json.dumps({'abstract':abstract,'keywords':KEYWORDS,'blocks':blocks,'figures':figures,'codexFigures':xfigures,'foot':FOOT},indent=2,ensure_ascii=False),encoding='utf8')
assert tn==12 and en==4 and sn==11 and len(figures)==7 and len(xfigures)==6,(tn,en,sn,len(figures))
print(json.dumps({'output':str(OUT),'figures':len(figures)+len(xfigures),'tables':tn+8,'sections':sn,'equations':en,'htmlBytes':(OUT/'cache-wars-2.html').stat().st_size,'latexBytes':(OUT/'cache-wars-2.tex').stat().st_size,'identicalContent':{'c1':c1,'m':m},'oneHour':{i:round(proj[i][2],4) for i in proj},'control20':round(ctl20[1],4),'multipliers':{i:[round(v,3) for v in mult[i]] for i in mult},'monthly':{i:[round(v,2) for v in monthly[i]] for i in monthly},'taxHeavy':{i:round(v,2) for i,v in tax.items()},'team':{i:round(v,0) for i,v in team.items()},'divergence':[round(div_lo,2),round(div_hi,2)]},indent=2))
