okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
0a3eddd
main
1//// Worker call timeouts and helper cleanup require acknowledgment races and 2//// process monitors that the external E2E API cannot control. 3 4import albedo/daemon/conversation 5import albedo/daemon/events 6import albedo/daemon/session_run 7import albedo/daemon/turn 8import albedo/daemon/usage 9import albedo/harness/extension 10import albedo/openai_api/types 11import gleam/erlang/process .{ type Subject } 12import gleam/list 13import gleam/option .{ None } 14import gleam/otp/actor 15import gleeunit/should 16 17fn server ( mode : fn ( message) -> actor . Next ( Nil , message)) -> Subject ( message) { 18let assert Ok ( started ) = 19actor . new ( Nil ) 20|> actor . on_message ( fn ( _ , message ) { mode ( message ) }) 21|> actor . start 22started . data 23} 24 25/// A stand-in for the session actor's protocol. 26type Owner { 27Publish ( reply : Subject ( Bool )) 28Progress ( reply : Subject ( Bool )) 29Commit ( reply : Subject ( Result (#( Int , option . Option ( Int )), String ))) 30Commits ( reply : Subject ( Int )) 31Unused 32} 33 34fn messages () -> session_run . Messages ( Owner ) { 35session_run . Messages ( 36publish : fn ( _ , _ , reply ) { Publish ( reply ) }, 37tool_progress_delta : fn ( _ , _ , _ , _ , _ , _ , reply ) { Progress ( reply ) }, 38tool_progress_running : fn ( _ , _ , _ , _ , _ , _ , reply ) { Progress ( reply ) }, 39tool_progress_reset : fn ( _ , _ , _ ) { Unused }, 40tool_progress_finish : fn ( _ , _ , _ ) { Unused }, 41commit : fn ( _ , _ , _ , _ , reply ) { Commit ( reply ) }, 42fits : fn ( _ , _ , _ ) { Unused }, 43context : fn ( _ , _ , _ , _ ) { Unused }, 44usage : fn ( _ , _ , _ ) { Unused }, 45drain : fn ( _ , _ ) { Unused }, 46pin : fn ( _ , _ , _ ) { Unused }, 47sent : fn ( _ , _ , _ ) { Unused }, 48finished : fn ( _ , _ ) { Unused }, 49collect : Unused , 50) 51} 52 53fn publish ( owner : Subject ( Owner )) -> Bool { 54publish_after ( owner , turn . latch ()) 55} 56 57fn publish_after ( owner : Subject ( Owner ), stop : turn . Latch ) -> Bool { 58session_run . publish_fn ( owner , "run" , messages (), stop , waiting : 20 )( 59events . Checkpoint , 60) 61} 62 63pub fn publish_passes_the_owner_answer_through_test () -> Nil { 64let answering = fn ( keep_going ) { 65server ( fn ( message ) { 66case message { 67Publish ( reply ) -> process . send ( reply , keep_going ) 68_ -> Nil 69} 70actor . continue ( Nil ) 71}) 72} 73list . each ([ True , False ], fn ( keep_going ) { 74session_run . publish_fn ( 75answering ( keep_going ), 76"run" , 77messages (), 78turn . latch (), 79waiting : 2000 , 80)( events . Checkpoint ) 81|> should . equal ( keep_going ) 82}) 83} 84 85pub fn publish_keeps_streaming_through_a_stalled_owner_test () -> Nil { 86server ( fn ( _ ) { actor . continue ( Nil ) }) 87|> publish 88|> should . be_true 89} 90 91/// A cancelled session actor that stalls must not let a tool gate open: the 92/// kernel interrupt cannot stop a tool outside the kernel. 93pub fn publish_fails_closed_on_a_stall_once_cancelled_test () -> Nil { 94let pid = process . spawn ( fn () { Nil }) 95let assert turn . Running ( run ) = 96turn . Running ( turn . Run ( 97"run" , 98pid , 99process . monitor ( pid ), 100False , 101turn . latch (), 102turn . Turn ( None ), 103)) 104|> turn . cancel 105server ( fn ( _ ) { actor . continue ( Nil ) }) 106|> publish_after ( run . stop ) 107|> should . be_false 108} 109 110pub fn publish_stops_the_stream_when_the_owner_dies_test () -> Nil { 111server ( fn ( _ ) { actor . stop () }) 112|> publish 113|> should . be_false 114} 115 116fn commit ( owner : Subject ( Owner )) -> Result (#( Int , option . Option ( Int )), String ) { 117session_run . commit_fn ( owner , "run" , messages (), waiting : 20 )( 118[], 119conversation . Tool , 120None , 121) 122} 123 124pub fn a_dead_owner_ends_the_turn_instead_of_the_worker_test () -> Nil { 125let dies = fn () { server ( fn ( _ ) { actor . stop () }) } 126commit ( dies ()) 127|> should . equal ( Error ( "commit unconfirmed: the session stopped" )) 128session_run . usage_fn ( dies (), "run" , messages (), waiting : 20 )( usage . Metadata ( 129"model" , 1300 , 131None , 132None , 133None , 134)) 135|> should . equal ( Error ( "usage unconfirmed: the session stopped" )) 136session_run . drain_fn ( dies (), "run" , messages (), waiting : 20 )() 137|> should . equal ( Error ( "steering unconfirmed: the session stopped" )) 138} 139 140/// A stalled commit may still land, so it must be sent exactly once. 141pub fn a_stalled_commit_ends_the_turn_without_a_retry_test () -> Nil { 142let assert Ok ( started ) = 143actor . new ( 0 ) 144|> actor . on_message ( fn ( commits , message ) { 145case message { 146Commit ( _ ) -> actor . continue ( commits + 1 ) 147Commits ( reply ) -> { 148process . send ( reply , commits ) 149actor . continue ( commits ) 150} 151_ -> actor . continue ( commits ) 152} 153}) 154|> actor . start 155commit ( started . data ) 156|> should . equal ( Error ( 157"commit unconfirmed after a session stall; it may still be saved" , 158)) 159actor . call ( started . data , 1000 , Commits ) |> should . equal ( 1 ) 160} 161 162fn cancel ( stop : turn . Latch ) -> Nil { 163let pid = process . self () 164let monitor = process . monitor ( pid ) 165let _ = 166turn . cancel ( 167turn . Running ( turn . Run ( "run" , pid , monitor , False , stop , turn . Turn ( None ))), 168) 169process . demonitor_process ( monitor ) 170} 171 172pub fn progress_timeout_stops_delivery_at_the_first_fragment_test () -> Nil { 173let assert Ok ( started ) = 174actor . new ( 0 ) 175|> actor . on_message ( fn ( count , message ) { 176case message { 177Progress ( _ ) -> actor . continue ( count + 1 ) 178Commits ( reply ) -> { 179process . send ( reply , count ) 180actor . continue ( count ) 181} 182_ -> actor . continue ( count ) 183} 184}) 185|> actor . start 186let progress = 187session_run . tool_progress_delta_fn ( 188started . data , 189"run" , 190messages (), 191turn . latch (), 192waiting : 20 , 193) 194// This is the transport's continuation rule: a refusal stops delivery. 195let continued = case progress ( 0 , 1 , 0 , "python" , "first" ) { 196True -> progress ( 0 , 1 , 0 , "python" , "second" ) 197False -> False 198} 199continued |> should . be_false 200actor . call ( started . data , 1000 , Commits ) |> should . equal ( 1 ) 201} 202 203pub fn running_timeout_refuses_continuation_test () -> Nil { 204let owner = server ( fn ( _ ) { actor . continue ( Nil ) }) 205session_run . tool_progress_running_fn ( 206owner , 207"run" , 208messages (), 209turn . latch (), 210waiting : 20 , 211)( 0 , 1 , 0 , "tool" , "python" ) 212|> should . be_false 213} 214 215pub fn cancelled_progress_sends_no_request_test () -> Nil { 216let assert Ok ( started ) = 217actor . new ( 0 ) 218|> actor . on_message ( fn ( count , message ) { 219case message { 220Progress ( reply ) -> { 221process . send ( reply , True ) 222actor . continue ( count + 1 ) 223} 224Commits ( reply ) -> { 225process . send ( reply , count ) 226actor . continue ( count ) 227} 228_ -> actor . continue ( count ) 229} 230}) 231|> actor . start 232let owner = started . data 233let stop = turn . latch () 234cancel ( stop ) 235session_run . tool_progress_delta_fn ( 236owner , 237"run" , 238messages (), 239stop , 240waiting : 20 , 241)( 0 , 1 , 0 , "python" , "fragment" ) 242|> should . be_false 243session_run . tool_progress_running_fn ( 244owner , 245"run" , 246messages (), 247stop , 248waiting : 20 , 249)( 0 , 1 , 0 , "tool" , "python" ) 250|> should . be_false 251// The reply follows every request already sent by this process. 252actor . call ( owner , 1000 , Commits ) |> should . equal ( 0 ) 253} 254 255pub fn cancellation_before_progress_acknowledgment_refuses_continuation_test () -> Nil { 256let stop = turn . latch () 257let owner = 258server ( fn ( message ) { 259case message { 260Progress ( reply ) -> { 261cancel ( stop ) 262process . send ( reply , True ) 263} 264_ -> Nil 265} 266actor . continue ( Nil ) 267}) 268session_run . tool_progress_delta_fn ( 269owner , 270"run" , 271messages (), 272stop , 273waiting : 1000 , 274)( 0 , 1 , 0 , "python" , "fragment" ) 275|> should . be_false 276let other_stop = turn . latch () 277let other_owner = 278server ( fn ( message ) { 279case message { 280Progress ( reply ) -> { 281cancel ( other_stop ) 282process . send ( reply , True ) 283} 284_ -> Nil 285} 286actor . continue ( Nil ) 287}) 288session_run . tool_progress_running_fn ( 289other_owner , 290"run" , 291messages (), 292other_stop , 293waiting : 1000 , 294)( 0 , 1 , 0 , "tool" , "python" ) 295|> should . be_false 296} 297 298fn idle_provider ( started : Subject ( process . Pid )) -> extension . Upstream { 299extension . Upstream ( 300endpoint : "test" , 301protocol : types . Responses , 302stream : fn ( _ , _ ) { 303let never = process . new_subject () 304process . send ( started , process . self ()) 305let _ : Nil = process . receive_forever ( never ) 306Error ( types . Cancelled ) 307}, 308explain : fn ( _ ) { None }, 309account : fn () { None }, 310cache_marks : fn ( _ ) { [] }, 311images : types . any_images , 312) 313} 314 315fn idle_provider_cleanup ( owner_dies : Bool ) -> Nil { 316let owner = server ( fn ( _ ) { actor . continue ( Nil ) }) 317let started = process . new_subject () 318let finished = process . new_subject () 319let stop = turn . latch () 320let upstream = session_run . stoppable ( idle_provider ( started ), owner , stop ) 321let worker = 322process . spawn_unlinked ( fn () { 323process . send ( 324finished , 325upstream . stream ( 326types . Request ( "test" , None , [], [], None , types . defaults ), 327fn ( _ ) { types . Continue }, 328), 329) 330}) 331let worker_monitor = process . monitor ( worker ) 332let assert Ok ( helper ) = process . receive ( started , 1000 ) 333let helper_monitor = process . monitor ( helper ) 334case owner_dies { 335True -> { 336let assert Ok ( owner_pid ) = process . subject_owner ( owner ) 337process . unlink ( owner_pid ) 338process . kill ( owner_pid ) 339} 340False -> cancel ( stop ) 341} 342process . receive ( finished , 1000 ) |> should . equal ( Ok ( Error ( types . Cancelled ))) 343process . new_selector () 344|> process . select_specific_monitor ( helper_monitor , fn ( _ ) { Nil }) 345|> process . selector_receive ( 1000 ) 346|> should . equal ( Ok ( Nil )) 347process . new_selector () 348|> process . select_specific_monitor ( worker_monitor , fn ( _ ) { Nil }) 349|> process . selector_receive ( 1000 ) 350|> should . equal ( Ok ( Nil )) 351process . demonitor_process ( helper_monitor ) 352process . demonitor_process ( worker_monitor ) 353case owner_dies { 354True -> Nil 355False -> { 356let assert Ok ( owner_pid ) = process . subject_owner ( owner ) 357process . unlink ( owner_pid ) 358process . kill ( owner_pid ) 359} 360} 361} 362 363pub fn owner_death_cleans_up_an_idle_provider_helper_test () -> Nil { 364idle_provider_cleanup ( True ) 365} 366 367pub fn cancellation_cleans_up_an_idle_provider_helper_test () -> Nil { 368idle_provider_cleanup ( False ) 369}