okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
3d822db
main
1// What a Claude request's longest-lived cache marks hold is never reported 2// by a call that reads them whole: the fade finds it in the request ledger, 3// from whichever session last wrote that head, and the E2E fixture provider 4// speaks no Claude protocol that could report the TTL-split writes. 5import albedo/daemon/conversation 6import albedo/daemon/requests 7import albedo/harness/cache_fade .{ Fade , Step } 8import albedo/harness/cache_ttl 9import albedo/harness/runtime 10import albedo/openai_api/types 11import gleam/option .{ type Option , None , Some } 12import gleeunit/should 13 14@external ( erlang , "albedo_runtime_test_support" , "temporary_database" ) 15fn temporary_database () -> String 16 17@external ( erlang , "albedo_runtime_test_support" , "cleanup" ) 18fn cleanup ( path : String ) -> Nil 19 20/// The marks Claude requests carry: tools, system prompt, and moving tail 21/// all use the one-hour TTL. 22const marks = [ 23types . CacheMark ( types . ToolsSpan , 3600 ), 24types . CacheMark ( types . SystemSpan , 3600 ), 25types . CacheMark ( types . InputSpan ( 3 ), 3600 ), 26] 27 28fn claude () -> cache_ttl . Entry { 29cache_ttl . Entry ( 30id : "claude" , 31layer : "default" , 32match : cache_ttl . Match ( None , None , None ), 33policy : cache_ttl . Refresh , 34clock : cache_ttl . Request , 35tiers : Some ([ 36cache_ttl . Tier ( 300 , Some ( 1.25 )), 37cache_ttl . Tier ( 3600 , Some ( 2.0 )), 38]), 39read : Some ( 0.1 ), 40survival : None , 41evidence : cache_ttl . Documented , 42source : "" , 43checked : "" , 44note : "" , 45) 46} 47 48fn usage ( read : Int , write_5m : Int , write_1h : Int ) -> types . Usage { 49types . Usage ( 50input_tokens : read + write_5m + write_1h + 10 , 51output_tokens : 4 , 52cached_input_tokens : Some ( read ), 53cache_creation_tokens : Some ( write_5m + write_1h ), 54cache_write_5m_tokens : Some ( write_5m ), 55cache_write_1h_tokens : Some ( write_1h ), 56reasoning_tokens : None , 57) 58} 59 60fn fade ( 61usage : types . Usage , 62measured : fn () -> Option ( Int ), 63) -> Option ( cache_fade . Fade ) { 64cache_fade . fade ( Some ( claude ()), marks , Some ( usage ), measured , 1000 , 9000 ) 65} 66 67pub fn the_tail_expires_into_the_measured_head_test () -> Nil { 68// The head is read whole with a cached tail after it, as on a fork or a 69// rewound branch: after the one-hour tail expires, only the head the 70// ledger measured is left to read, counted from the send. 71fade ( usage ( 25_000 , 3000 , 0 ), fn () { Some ( 20_000 ) }) 72|> should . equal ( Some ( Fade ( 1000 , True , [ Step ( 3_600_000 , Some ( 0 ))]))) 73// No call has measured the head: what survives the tail is unknown. 74fade ( usage ( 25_000 , 3000 , 0 ), fn () { None }) 75|> should . equal ( Some ( Fade ( 1000 , True , [ Step ( 3_600_000 , Some ( 0 ))]))) 76} 77 78pub fn a_call_that_writes_the_head_keeps_all_it_read_test () -> Nil { 79// A changed system prompt: the tools were read, the rest of the head 80// written, so every token read survives the one-hour tail. 81fade ( usage ( 8000 , 5000 , 14_000 ), fn () { panic as "the call measured itself" }) 82|> should . equal ( Some ( Fade ( 1000 , True , [ Step ( 3_600_000 , Some ( 0 ))]))) 83} 84 85pub fn the_ledger_measures_a_head_across_sessions_test () -> Nil { 86let path = temporary_database () 87let assert Ok ( host ) = runtime . start ( path ) 88let ledger = runtime . ledger ( host ) 89let assert Ok ( _ ) = conversation . initialise ( ledger ) 90let record = fn ( session , profile , model , head , usage ) { 91let assert Ok ( _ ) = 92requests . record ( 93ledger , 94requests . Call ( 95session , 96requests . Turn , 97profile , 98"claude" , 99None , 100model , 1010 , 1021 , 103requests . Completed , 104Some ( usage ), 105requests . Prefix ( head , 1 , Some ( 0 ), None , None ), 106marks , 107session <> "-run" , 108), 109) 110Nil 111} 112let assert Ok ( _ ) = conversation . create ( ledger , info ( "parent" )) 113let assert Ok ( _ ) = conversation . create ( ledger , info ( "fork" )) 114// The parent wrote the head after reading its tools: 2k + 18k. 115record ( "parent" , "claude" , "opus" , "head" , usage ( 2000 , 500 , 18_000 )) 116// The fork reads it whole, tail and all, which measures nothing. 117record ( "fork" , "claude" , "opus" , "head" , usage ( 26_000 , 700 , 0 )) 118requests . head_tokens ( ledger , "claude" , "opus" , "head" ) 119|> should . equal ( Some ( 20_000 )) 120// A rewrite of the same head measures it again; the latest one counts. 121record ( "fork" , "claude" , "opus" , "head" , usage ( 0 , 900 , 20_500 )) 122requests . head_tokens ( ledger , "claude" , "opus" , "head" ) 123|> should . equal ( Some ( 20_500 )) 124// Another head, model, or profile has its own measure. 125requests . head_tokens ( ledger , "claude" , "opus" , "other head" ) 126|> should . equal ( None ) 127requests . head_tokens ( ledger , "claude" , "sonnet" , "head" ) 128|> should . equal ( None ) 129requests . head_tokens ( ledger , "anthropic-api" , "opus" , "head" ) 130|> should . equal ( None ) 131runtime . stop ( host ) 132cleanup ( path ) 133} 134 135fn info ( id : String ) -> conversation . Info { 136conversation . Info ( 137id , 138"new session" , 139"/tmp" , 140"claude" , 141"opus" , 142types . Responses , 143conversation . Idle , 144None , 145None , 146) 147} 148 149pub fn unmarked_providers_fade_by_their_table_entry_test () -> Nil { 150let unmeasured = fn () { None } 151let evict = 152cache_ttl . Entry ( 153.. claude (), 154policy : cache_ttl . Evict , 155clock : cache_ttl . Response , 156tiers : None , 157survival : Some ( cache_ttl . Survival ( 450 , Some ( 3600 ))), 158) 159// Unknown past typical survival, gone past the bound, from the response. 160cache_fade . fade ( 161Some ( evict ), 162[], 163Some ( usage ( 4096 , 0 , 0 )), 164unmeasured , 1651000 , 1669000 , 167) 168|> should . equal ( 169Some ( Fade ( 9000 , False , [ Step ( 450_000 , None ), Step ( 3_600_000 , Some ( 0 ))])), 170) 171// A clock a hit restarts: gone when it runs out. 172cache_fade . fade ( 173Some ( cache_ttl . Entry (.. claude (), tiers : Some ([ cache_ttl . Tier ( 1800 , None )]))), 174[], 175Some ( usage ( 4096 , 0 , 0 )), 176unmeasured , 1771000 , 1789000 , 179) 180|> should . equal ( Some ( Fade ( 1000 , True , [ Step ( 1_800_000 , Some ( 0 ))]))) 181// Nothing known: no fading at all. 182cache_fade . fade ( None , [], Some ( usage ( 4096 , 0 , 0 )), unmeasured , 1000 , 9000 ) 183|> should . equal ( None ) 184}