@@ -282,6 +282,19 @@ class PromptConfig:
282282 include_artifacts : bool = True
283283 max_artifact_bytes : int = 20 * 1024 # 20KB in prompt
284284 artifact_security_filter : bool = True
285+ # Optional ordered subset of evaluator artifacts rendered in prompts.
286+ # Stored artifacts remain complete.
287+ artifact_include_names : Optional [List [str ]] = None
288+ # Optional evaluator artifact collected across the complete live archive
289+ # and rendered as one compact deterministic prompt artifact.
290+ archive_context_artifact : Optional [str ] = None
291+ archive_context_max_items : int = 64
292+ # Optional evaluator-produced neighborhood. The artifact must be schema
293+ # version 1 JSON with an identity_artifact name and ordered options carrying
294+ # identity fields. The controller removes identities already retained in the
295+ # live archive and renders the remainder as proposal-options.json.
296+ proposal_neighborhood_artifact : Optional [str ] = None
297+ proposal_options_max_items : int = 32
285298
286299 # Feature extraction and program labeling
287300 suggest_simplification_after_chars : Optional [int ] = (
@@ -312,13 +325,33 @@ class ControllerSchedulerConfig:
312325 """Optional observed-result island allocator for bounded live comparisons."""
313326
314327 enabled : bool = False
328+ score_metric : str = "combined_score"
329+ # Optional evaluator artifact used to count distinct measured phenotypes.
330+ # When absent, source-code hashes preserve the legacy definition.
331+ diversity_artifact : Optional [str ] = None
315332 exploitation_weight : float = 0.0
316333 underexplored_weight : float = 0.0
317334 validity_weight : float = 0.0
318335 diversity_weight : float = 0.0
319336 token_efficiency_weight : float = 0.0
320337 rejection_penalty : float = 0.0
321338 minimum_calls : int = 1
339+ # Optional post-warmup quality band. When set, allocate only among islands
340+ # whose retained best score is within this absolute distance of the global
341+ # retained leader. None preserves unrestricted legacy allocation.
342+ leader_score_band : Optional [float ] = None
343+ # Optional post-warmup parent band within the selected island. When set,
344+ # parent sampling is limited to retained programs whose score is within
345+ # this absolute distance of that island's retained leader.
346+ parent_score_band : Optional [float ] = None
347+ # Keep this many islands outside the active worker frontier so a completed
348+ # call leaves the allocator a real choice. This does not reduce concurrency
349+ # when num_islands > parallel_evaluations.
350+ reserve_islands : int = 1
351+ # Optional lower in-flight limit after the balanced warmup proposal count
352+ # has been submitted. This trades some throughput for fresher archive
353+ # context during quality-focused adaptive search.
354+ adaptive_parallelism : Optional [int ] = None
322355
323356
324357@dataclass
@@ -461,6 +494,14 @@ class Config:
461494 strict_diff_application : bool = False
462495 enforce_evolve_blocks : bool = False
463496 max_diff_blocks : int = 32
497+ # Optional evaluator artifact whose exact value identifies the candidate's
498+ # behaviorally meaningful contents. When configured, a child duplicating
499+ # any program already known to the live archive is rejected.
500+ program_identity_artifact : Optional [str ] = None
501+ # Optional second evaluator identity for exact measured behavior. This is
502+ # checked only after evaluation, so structurally different programs that
503+ # produce an already archived phenotype do not bloat parent selection.
504+ phenotype_identity_artifact : Optional [str ] = None
464505
465506 # Early stopping settings
466507 early_stopping_patience : Optional [int ] = None
@@ -544,9 +585,92 @@ def validate(self) -> None:
544585 )
545586 if self .max_diff_blocks < 1 :
546587 raise ValueError ("max_diff_blocks must be at least 1" )
588+ if self .prompt .archive_context_artifact is not None and (
589+ not isinstance (self .prompt .archive_context_artifact , str )
590+ or not self .prompt .archive_context_artifact .strip ()
591+ ):
592+ raise ValueError (
593+ "prompt.archive_context_artifact must be a non-empty string or None"
594+ )
595+ if self .prompt .proposal_neighborhood_artifact is not None and (
596+ not isinstance (self .prompt .proposal_neighborhood_artifact , str )
597+ or not self .prompt .proposal_neighborhood_artifact .strip ()
598+ ):
599+ raise ValueError (
600+ "prompt.proposal_neighborhood_artifact must be a non-empty "
601+ "string or None"
602+ )
603+ artifact_names = self .prompt .artifact_include_names
604+ if artifact_names is not None :
605+ if not isinstance (artifact_names , list ) or any (
606+ not isinstance (name , str ) or not name .strip ()
607+ for name in artifact_names
608+ ):
609+ raise ValueError (
610+ "prompt.artifact_include_names must be a list of non-empty strings or None"
611+ )
612+ if len (set (artifact_names )) != len (artifact_names ):
613+ raise ValueError (
614+ "prompt.artifact_include_names must not contain duplicates"
615+ )
616+ if (
617+ self .prompt .archive_context_artifact is not None
618+ and "archive-context.json" not in artifact_names
619+ ):
620+ raise ValueError (
621+ "prompt.artifact_include_names must include archive-context.json "
622+ "when archive_context_artifact is configured"
623+ )
624+ if (
625+ self .prompt .proposal_neighborhood_artifact is not None
626+ and "proposal-options.json" not in artifact_names
627+ ):
628+ raise ValueError (
629+ "prompt.artifact_include_names must include "
630+ "proposal-options.json when proposal_neighborhood_artifact "
631+ "is configured"
632+ )
633+ if (
634+ isinstance (self .prompt .archive_context_max_items , bool )
635+ or not isinstance (self .prompt .archive_context_max_items , int )
636+ or self .prompt .archive_context_max_items < 1
637+ ):
638+ raise ValueError (
639+ "prompt.archive_context_max_items must be a positive integer"
640+ )
641+ if (
642+ isinstance (self .prompt .proposal_options_max_items , bool )
643+ or not isinstance (self .prompt .proposal_options_max_items , int )
644+ or self .prompt .proposal_options_max_items < 1
645+ ):
646+ raise ValueError (
647+ "prompt.proposal_options_max_items must be a positive integer"
648+ )
649+ for field_name in (
650+ "program_identity_artifact" ,
651+ "phenotype_identity_artifact" ,
652+ ):
653+ value = getattr (self , field_name )
654+ if value is not None and (
655+ not isinstance (value , str ) or not value .strip ()
656+ ):
657+ raise ValueError (f"{ field_name } must be a non-empty string or None" )
547658 scheduler = self .database .controller_scheduler
548659 if not isinstance (scheduler .enabled , bool ):
549660 raise ValueError ("database.controller_scheduler.enabled must be boolean" )
661+ if not isinstance (scheduler .score_metric , str ) or not scheduler .score_metric .strip ():
662+ raise ValueError (
663+ "database.controller_scheduler.score_metric "
664+ "must be a non-empty string"
665+ )
666+ if scheduler .diversity_artifact is not None and (
667+ not isinstance (scheduler .diversity_artifact , str )
668+ or not scheduler .diversity_artifact .strip ()
669+ ):
670+ raise ValueError (
671+ "database.controller_scheduler.diversity_artifact "
672+ "must be a non-empty string or None"
673+ )
550674 for field_name in (
551675 "exploitation_weight" ,
552676 "underexplored_weight" ,
@@ -566,6 +690,18 @@ def validate(self) -> None:
566690 f"database.controller_scheduler.{ field_name } "
567691 "must be finite and nonnegative"
568692 )
693+ for field_name in ("leader_score_band" , "parent_score_band" ):
694+ value = getattr (scheduler , field_name )
695+ if value is not None and (
696+ isinstance (value , bool )
697+ or not isinstance (value , (int , float ))
698+ or not math .isfinite (float (value ))
699+ or float (value ) < 0.0
700+ ):
701+ raise ValueError (
702+ f"database.controller_scheduler.{ field_name } "
703+ "must be finite and nonnegative when provided"
704+ )
569705 if (
570706 isinstance (scheduler .minimum_calls , bool )
571707 or not isinstance (scheduler .minimum_calls , int )
@@ -575,6 +711,36 @@ def validate(self) -> None:
575711 "database.controller_scheduler.minimum_calls "
576712 "must be a positive integer"
577713 )
714+ if (
715+ isinstance (scheduler .reserve_islands , bool )
716+ or not isinstance (scheduler .reserve_islands , int )
717+ or scheduler .reserve_islands < 0
718+ or (
719+ scheduler .enabled
720+ and scheduler .reserve_islands >= self .database .num_islands
721+ )
722+ ):
723+ raise ValueError (
724+ "database.controller_scheduler.reserve_islands must be an integer "
725+ "in [0, database.num_islands) when the scheduler is enabled"
726+ )
727+ if scheduler .adaptive_parallelism is not None and (
728+ isinstance (scheduler .adaptive_parallelism , bool )
729+ or not isinstance (scheduler .adaptive_parallelism , int )
730+ or scheduler .adaptive_parallelism < 1
731+ or scheduler .adaptive_parallelism
732+ > self .evaluator .parallel_evaluations
733+ or (
734+ scheduler .enabled
735+ and scheduler .adaptive_parallelism
736+ > self .database .num_islands - scheduler .reserve_islands
737+ )
738+ ):
739+ raise ValueError (
740+ "database.controller_scheduler.adaptive_parallelism must be a "
741+ "positive integer no greater than the evaluator worker count or "
742+ "selectable island count"
743+ )
578744
579745 def to_dict (self ) -> Dict [str , Any ]:
580746 return asdict (self )
0 commit comments