@@ -367,6 +367,9 @@ def parse_research_verdict(output: str, candidate_id: str) -> dict:
367367 "outcome" : fields ["outcome" ],
368368 "evidence" : fields ["evidence" ],
369369 "new_frontier" : fields ["new_frontier" ],
370+ "created_obligation_ids" : list (
371+ fields .get ("created_obligation_ids" , []),
372+ ),
370373 }
371374 matches = list (re .finditer (
372375 r"^(?:critic>\s*)?### AUTORESEARCH_VERDICT\s*$"
@@ -397,6 +400,7 @@ def parse_research_verdict(output: str, candidate_id: str) -> dict:
397400 "outcome" : fields ["Outcome" ],
398401 "evidence" : fields ["Evidence" ],
399402 "new_frontier" : fields ["New frontier" ],
403+ "created_obligation_ids" : [],
400404 }
401405
402406
@@ -626,7 +630,7 @@ def propose_candidate(
626630 current : dict ,
627631 results_text : str ,
628632 ledger : dict ,
629- max_prefill_tokens : int = 8192 ,
633+ max_prefill_tokens : int = 8448 ,
630634) -> dict :
631635 from kakeya import Client
632636 from transformers import AutoTokenizer
@@ -826,14 +830,102 @@ def best_kept(results: list[dict]) -> dict | None:
826830 )
827831
828832
833+ def _created_ids (row : dict ) -> list [str ]:
834+ raw = row .get ("created_obligation_ids" , "" )
835+ if isinstance (raw , list ):
836+ return [str (item ) for item in raw if item ]
837+ if not raw :
838+ return []
839+ try :
840+ value = json .loads (raw )
841+ except (TypeError , json .JSONDecodeError ):
842+ return []
843+ return [str (item ) for item in value if item ] if isinstance (value , list ) else []
844+
845+
846+ def _row_made_progress (row : dict ) -> bool :
847+ if row .get ("kept" ) not in {True , "True" }:
848+ return False
849+ outcome = row .get ("research_outcome" )
850+ if outcome in {"SUPPORTED" , "FALSIFIED" }:
851+ return True
852+ if outcome == "DECOMPOSED" :
853+ # Legacy rows predate created_obligation_ids but were already admitted
854+ # by the host's child-creation gate.
855+ return bool (_created_ids (row )) or not row .get (
856+ "created_obligation_ids" ,
857+ )
858+ return False
859+
860+
861+ def strategy_trigger_reason (
862+ results : list [dict ],
863+ * ,
864+ stagnation_rounds : int ,
865+ force : bool = False ,
866+ trigger_file : Path | None = None ,
867+ ) -> str :
868+ if force :
869+ return "manual-cli"
870+ if trigger_file is not None and trigger_file .exists ():
871+ return "manual-trigger-file"
872+ if results and results [- 1 ].get ("research_outcome" ) == "FALSIFIED" :
873+ return "branch-falsified"
874+ stagnant = 0
875+ for row in reversed (results ):
876+ if _row_made_progress (row ):
877+ break
878+ stagnant += 1
879+ if stagnant >= stagnation_rounds :
880+ return f"stagnation-{ stagnant } "
881+ return ""
882+
883+
884+ def build_host_candidate (current : dict , ledger : dict ) -> dict :
885+ target_id = _select_repair_target (current , ledger )
886+ target = next (
887+ item
888+ for item in ledger .get ("obligations" , [])
889+ if item .get ("obligation_id" ) == target_id
890+ )
891+ statement = str (target .get ("statement" , "" )).strip ()
892+ evidence = str (target .get ("last_evidence" , "" )).strip ()
893+ digest = hashlib .sha256 (target_id .encode ()).hexdigest ()[:12 ]
894+ candidate = {
895+ "candidate_id" : f"host-leaf-{ digest } " ,
896+ "target_obligation_id" : target_id ,
897+ "hypothesis" : statement ,
898+ "generator_directive" : (
899+ f"Resolve or falsify the exact target leaf { target_id } : "
900+ f"{ statement } Previous Critic evidence: { evidence or '(none)' } . "
901+ "Provide an explicit derivation or counterexample; do not rename "
902+ "the same gap as a new lemma."
903+ ),
904+ "critic_directive" : (
905+ f"Adversarially test target leaf { target_id } . Reject unsupported "
906+ "existence claims and semantic restatements. Mark PROVED or "
907+ "DISPROVED only with explicit evidence; otherwise identify one "
908+ "strictly smaller, falsifiable missing obligation."
909+ ),
910+ "prefill_compute_chunk_tokens" : int (
911+ current ["prefill_compute_chunk_tokens" ],
912+ ),
913+ "snapshot_mode" : "final_only" ,
914+ "max_segment_seconds" : 300.0 ,
915+ "require_full_context" : True ,
916+ "allow_fallback" : False ,
917+ }
918+ validate_candidate (candidate )
919+ return candidate
920+
921+
829922def should_keep (result : dict , baseline : dict | None ) -> bool :
830923 if not result ["accepted" ]:
831924 return False
832- if result .get ("research_outcome" ) not in {
833- "SUPPORTED" , "FALSIFIED" , "DECOMPOSED" ,
834- }:
925+ outcome = result .get ("research_outcome" )
926+ if outcome not in {"SUPPORTED" , "FALSIFIED" , "DECOMPOSED" }:
835927 return False
836- if not result .get ("hypothesis_novel" , False ):
928+ if outcome == "DECOMPOSED" and not result .get ("created_obligation_ids" ):
837929 return False
838930 if baseline is None :
839931 return True
@@ -850,7 +942,8 @@ def should_keep(result: dict, baseline: dict | None) -> bool:
850942 "proof_obligations_unresolved" , "compute_chunk_tokens" ,
851943 "candidate_sha256" , "report_path" ,
852944 "hypothesis_sha256" , "research_outcome" , "research_evidence" ,
853- "new_frontier" , "transcript_path" , "error" ,
945+ "new_frontier" , "created_obligation_ids" , "strategy_mode" ,
946+ "transcript_path" , "error" ,
854947)
855948
856949
@@ -910,6 +1003,7 @@ def run_iteration(args, iteration: int) -> dict:
9101003 transcript_path = reports_dir / "not-started.log"
9111004 hypothesis_sha256 = ""
9121005 candidate_sha256 = hashlib .sha256 (previous_candidate ).hexdigest ()
1006+ strategy_mode = "baseline"
9131007 try :
9141008 print (
9151009 f"[autoresearch] iteration={ iteration } "
@@ -926,17 +1020,26 @@ def run_iteration(args, iteration: int) -> dict:
9261020 f"kv_hit_rate={ health .get ('kv_hit_rate' , 0 ):.1%} " ,
9271021 flush = True ,
9281022 )
1023+ ledger_data = json .loads (ledger_path .read_text ())
1024+ trigger_file = Path (args .strategy_trigger_file ).expanduser ()
1025+ trigger_reason = strategy_trigger_reason (
1026+ results ,
1027+ stagnation_rounds = args .strategy_stagnation_rounds ,
1028+ force = args .force_strategy and iteration == 0 ,
1029+ trigger_file = trigger_file ,
1030+ )
9291031 if baseline is None and iteration == 0 :
9301032 print (
9311033 "[autoresearch] phase=baseline using current candidate" ,
9321034 flush = True ,
9331035 )
934- else :
1036+ elif trigger_reason :
1037+ strategy_mode = "gemma"
9351038 print (
936- "[autoresearch] phase=strategy-proposal real-gemma" ,
1039+ "[autoresearch] phase=strategy-proposal "
1040+ f"mode=gemma trigger={ trigger_reason } " ,
9371041 flush = True ,
9381042 )
939- ledger_data = json .loads (ledger_path .read_text ())
9401043 proposed = propose_candidate (
9411044 address = args .address ,
9421045 tokenizer_id = args .tokenizer_id ,
@@ -954,13 +1057,24 @@ def run_iteration(args, iteration: int) -> dict:
9541057 raise ValueError (
9551058 "strategy agent targeted a non-leaf proof obligation" ,
9561059 )
957- candidate_path .write_text (render_candidate (proposed ))
1060+ if trigger_reason == "manual-trigger-file" :
1061+ trigger_file .unlink (missing_ok = True )
1062+ else :
1063+ strategy_mode = "host"
1064+ proposed = build_host_candidate (current , ledger_data )
9581065 print (
959- f"[autoresearch] phase=candidate-written "
960- f"candidate={ proposed ['candidate_id' ]} "
1066+ "[autoresearch] phase=deterministic-candidate "
9611067 f"target={ proposed ['target_obligation_id' ]} " ,
9621068 flush = True ,
9631069 )
1070+ candidate_path .write_text (render_candidate (proposed ))
1071+ print (
1072+ f"[autoresearch] phase=candidate-written "
1073+ f"candidate={ proposed ['candidate_id' ]} "
1074+ f"target={ proposed ['target_obligation_id' ]} "
1075+ f"mode={ strategy_mode } " ,
1076+ flush = True ,
1077+ )
9641078 validate_candidate (proposed )
9651079 hypothesis_sha256 = hashlib .sha256 (
9661080 proposed ["hypothesis" ].strip ().lower ().encode (),
@@ -970,7 +1084,8 @@ def run_iteration(args, iteration: int) -> dict:
9701084 for row in results
9711085 if row .get ("hypothesis_sha256" )
9721086 }
973- if hypothesis_sha256 in seen_hypotheses :
1087+ hypothesis_novel = hypothesis_sha256 not in seen_hypotheses
1088+ if strategy_mode == "gemma" and not hypothesis_novel :
9741089 raise ValueError ("strategy agent repeated a previous hypothesis" )
9751090 candidate_sha256 = hashlib .sha256 (
9761091 candidate_path .read_bytes (),
@@ -1011,8 +1126,9 @@ def run_iteration(args, iteration: int) -> dict:
10111126 "research_outcome" : verdict ["outcome" ],
10121127 "research_evidence" : verdict ["evidence" ],
10131128 "new_frontier" : verdict ["new_frontier" ],
1129+ "created_obligation_ids" : verdict ["created_obligation_ids" ],
10141130 "transcript_path" : str (transcript_path ),
1015- "hypothesis_novel" : True ,
1131+ "hypothesis_novel" : hypothesis_novel ,
10161132 })
10171133 keep = should_keep (result , baseline )
10181134 print (
@@ -1050,6 +1166,10 @@ def run_iteration(args, iteration: int) -> dict:
10501166 "research_outcome" : verdict ["outcome" ],
10511167 "research_evidence" : verdict ["evidence" ],
10521168 "new_frontier" : verdict ["new_frontier" ],
1169+ "created_obligation_ids" : json .dumps (
1170+ verdict ["created_obligation_ids" ],
1171+ ),
1172+ "strategy_mode" : strategy_mode ,
10531173 "transcript_path" : str (transcript_path ),
10541174 }
10551175 append_result (results_path , row )
@@ -1094,6 +1214,7 @@ def run_iteration(args, iteration: int) -> dict:
10941214 "report_path" : str (report_path ),
10951215 "hypothesis_sha256" : hypothesis_sha256 ,
10961216 "research_outcome" : "EVALUATION_FAILED" ,
1217+ "strategy_mode" : strategy_mode ,
10971218 "transcript_path" : str (transcript_path ),
10981219 "error" : f"{ type (exc ).__name__ } : { exc } " ,
10991220 }
@@ -1117,7 +1238,20 @@ def main() -> int:
11171238 parser .add_argument (
11181239 "--strategy-max-prefill-tokens" ,
11191240 type = int ,
1120- default = 8192 ,
1241+ default = 8448 ,
1242+ )
1243+ parser .add_argument (
1244+ "--strategy-stagnation-rounds" ,
1245+ type = int ,
1246+ default = 3 ,
1247+ )
1248+ parser .add_argument ("--force-strategy" , action = "store_true" )
1249+ parser .add_argument (
1250+ "--strategy-trigger-file" ,
1251+ default = str (
1252+ Path .home ()
1253+ / ".kakeya/autoresearch/request_strategy"
1254+ ),
11211255 )
11221256 parser .add_argument (
11231257 "--tokenizer-id" ,
@@ -1148,6 +1282,8 @@ def main() -> int:
11481282 raise SystemExit ("iterations must be > 0" )
11491283 if args .strategy_max_prefill_tokens <= 0 :
11501284 raise SystemExit ("strategy-max-prefill-tokens must be > 0" )
1285+ if args .strategy_stagnation_rounds <= 0 :
1286+ raise SystemExit ("strategy-stagnation-rounds must be > 0" )
11511287 for iteration in range (args .iterations ):
11521288 row = run_iteration (args , iteration )
11531289 print (json .dumps (row , indent = 2 , sort_keys = True ))
0 commit comments