self-evolve-framework 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/package.json +1 -1
  2. package/template/skills/skillopt-sleep/SKILL.md +42 -0
  3. package/template/skills/skillopt-sleep/configs/_base_/default.yaml +103 -0
  4. package/template/skills/skillopt-sleep/configs/alfworld/default.yaml +29 -0
  5. package/template/skills/skillopt-sleep/configs/docvqa/default.yaml +28 -0
  6. package/template/skills/skillopt-sleep/configs/features/soft_gate.yaml +47 -0
  7. package/template/skills/skillopt-sleep/configs/livemathematicianbench/default.yaml +22 -0
  8. package/template/skills/skillopt-sleep/configs/officeqa/default.yaml +34 -0
  9. package/template/skills/skillopt-sleep/configs/searchqa/default.yaml +32 -0
  10. package/template/skills/skillopt-sleep/configs/spreadsheetbench/default.yaml +34 -0
  11. package/template/skills/skillopt-sleep/scripts/framework/skillopt/__init__.py +28 -0
  12. package/template/skills/skillopt-sleep/scripts/framework/skillopt/config.py +282 -0
  13. package/template/skills/skillopt-sleep/scripts/framework/skillopt/datasets/__init__.py +7 -0
  14. package/template/skills/skillopt-sleep/scripts/framework/skillopt/datasets/base.py +512 -0
  15. package/template/skills/skillopt-sleep/scripts/framework/skillopt/engine/__init__.py +9 -0
  16. package/template/skills/skillopt-sleep/scripts/framework/skillopt/engine/trainer.py +2379 -0
  17. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/__init__.py +1 -0
  18. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/_template/README.md +43 -0
  19. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/_template/config_template.yaml +55 -0
  20. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/_template/env_template.py +151 -0
  21. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/_template/loader_template.py +87 -0
  22. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/__init__.py +5 -0
  23. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/adapter.py +428 -0
  24. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/dataloader.py +123 -0
  25. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/prompts/analyst_error.md +55 -0
  26. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/prompts/analyst_success.md +33 -0
  27. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/prompts/rollout_no_history.md +8 -0
  28. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/prompts/rollout_with_history.md +9 -0
  29. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/prompts/rollout_with_memory.md +16 -0
  30. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/reflect.py +4 -0
  31. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/rollout.py +366 -0
  32. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/skills/initial.md +45 -0
  33. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/__init__.py +9 -0
  34. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/alfworld_envs.py +221 -0
  35. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/alfworld_projection.py +60 -0
  36. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/alfworld_prompts.py +8 -0
  37. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/config_tw.yaml +145 -0
  38. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/env_base.py +84 -0
  39. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/env_manager.py +139 -0
  40. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/memory.py +87 -0
  41. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/base.py +329 -0
  42. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/__init__.py +1 -0
  43. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/adapter.py +90 -0
  44. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/dataloader.py +61 -0
  45. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/evaluator.py +113 -0
  46. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/prompts/analyst_error.md +35 -0
  47. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/prompts/analyst_success.md +24 -0
  48. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/prompts/rollout_system.md +12 -0
  49. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/rollout.py +391 -0
  50. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/skills/initial.md +11 -0
  51. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/__init__.py +1 -0
  52. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/adapter.py +129 -0
  53. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/dataloader.py +308 -0
  54. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/evaluator.py +62 -0
  55. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/prompts/analyst_error.md +37 -0
  56. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/prompts/analyst_success.md +25 -0
  57. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/prompts/rollout_system.md +12 -0
  58. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/reflect.py +4 -0
  59. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/rollout.py +434 -0
  60. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/skills/initial.md +16 -0
  61. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/__init__.py +1 -0
  62. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/adapter.py +112 -0
  63. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/dataloader.py +71 -0
  64. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/evaluator.py +46 -0
  65. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/prompts/analyst_error.md +37 -0
  66. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/prompts/analyst_success.md +25 -0
  67. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/prompts/rollout_system.md +15 -0
  68. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/rollout.py +799 -0
  69. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/skills/initial.md +15 -0
  70. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/tool_runtime.py +552 -0
  71. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/__init__.py +1 -0
  72. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/adapter.py +96 -0
  73. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/dataloader.py +42 -0
  74. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/evaluator.py +100 -0
  75. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/prompts/analyst_error.md +46 -0
  76. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/prompts/analyst_success.md +32 -0
  77. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/prompts/rollout_system.md +13 -0
  78. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/reflect.py +4 -0
  79. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/rollout.py +494 -0
  80. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/skills/initial.md +3 -0
  81. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/__init__.py +5 -0
  82. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/adapter.py +159 -0
  83. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/codegen_agent.py +731 -0
  84. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/dataloader.py +37 -0
  85. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/evaluator.py +158 -0
  86. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/executor.py +67 -0
  87. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/prompts/analyst_error.md +46 -0
  88. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/prompts/analyst_success.md +32 -0
  89. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/prompts/codegen_system.md +1 -0
  90. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/prompts/critical_rules.md +9 -0
  91. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/prompts/react_system.md +21 -0
  92. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/react_agent.py +395 -0
  93. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/reflect.py +4 -0
  94. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/rollout.py +979 -0
  95. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/skills/initial.md +56 -0
  96. package/template/skills/skillopt-sleep/scripts/framework/skillopt/evaluation/__init__.py +13 -0
  97. package/template/skills/skillopt-sleep/scripts/framework/skillopt/evaluation/gate.py +148 -0
  98. package/template/skills/skillopt-sleep/scripts/framework/skillopt/gradient/__init__.py +15 -0
  99. package/template/skills/skillopt-sleep/scripts/framework/skillopt/gradient/aggregate.py +253 -0
  100. package/template/skills/skillopt-sleep/scripts/framework/skillopt/gradient/reflect.py +635 -0
  101. package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/__init__.py +514 -0
  102. package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/azure_openai.py +915 -0
  103. package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/backend_config.py +185 -0
  104. package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/claude_backend.py +371 -0
  105. package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/codex_backend.py +666 -0
  106. package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/codex_harness.py +1057 -0
  107. package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/common.py +229 -0
  108. package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/minimax_backend.py +277 -0
  109. package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/qwen_backend.py +456 -0
  110. package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/router.py +236 -0
  111. package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/__init__.py +15 -0
  112. package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/appendix.py +156 -0
  113. package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/clip.py +109 -0
  114. package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/lr_autonomous.py +108 -0
  115. package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/meta_skill.py +79 -0
  116. package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/rewrite.py +59 -0
  117. package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/scheduler.py +127 -0
  118. package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/select.py +4 -0
  119. package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/skill.py +201 -0
  120. package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/skill_aware.py +206 -0
  121. package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/slow_update.py +396 -0
  122. package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/update_modes.py +135 -0
  123. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/__init__.py +63 -0
  124. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_error.md +41 -0
  125. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_error_full_rewrite.md +32 -0
  126. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_error_rewrite.md +44 -0
  127. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_success.md +36 -0
  128. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_success_full_rewrite.md +30 -0
  129. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_success_rewrite.md +33 -0
  130. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/lr_autonomous.md +20 -0
  131. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_failure.md +30 -0
  132. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_failure_full_rewrite.md +28 -0
  133. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_failure_rewrite.md +26 -0
  134. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_final.md +33 -0
  135. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_final_full_rewrite.md +28 -0
  136. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_final_rewrite.md +25 -0
  137. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_success.md +28 -0
  138. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_success_full_rewrite.md +28 -0
  139. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_success_rewrite.md +25 -0
  140. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/meta_skill.md +40 -0
  141. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/ranking.md +20 -0
  142. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/ranking_rewrite.md +15 -0
  143. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/rewrite_skill.md +25 -0
  144. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/slow_update.md +60 -0
  145. package/template/skills/skillopt-sleep/scripts/framework/skillopt/scheduler/__init__.py +8 -0
  146. package/template/skills/skillopt-sleep/scripts/framework/skillopt/types.py +306 -0
  147. package/template/skills/skillopt-sleep/scripts/framework/skillopt/utils/__init__.py +4 -0
  148. package/template/skills/skillopt-sleep/scripts/framework/skillopt/utils/json_utils.py +172 -0
  149. package/template/skills/skillopt-sleep/scripts/framework/skillopt/utils/scoring.py +28 -0
@@ -0,0 +1,56 @@
1
+ # Spreadsheet Manipulation Skill (xlsx)
2
+
3
+ ## Overview
4
+ This skill guides agents in manipulating Excel (.xlsx) spreadsheets using Python.
5
+
6
+ **Primary libraries**: `openpyxl` (structure-preserving read/write), `pandas` (data transformation).
7
+ Never use any other third-party libraries.
8
+
9
+ ---
10
+
11
+ ## Common Workflow
12
+
13
+ 1. **Explore** the input file: list sheets, inspect headers, check dimensions.
14
+ 2. **Write `solution.py`** with `INPUT_PATH` and `OUTPUT_PATH` defined at the top.
15
+ 3. **Execute** `python solution.py` and verify the output file was created.
16
+ 4. **Confirm** the target cells/range contain the expected values.
17
+
18
+ ---
19
+
20
+ ## Library Selection
21
+
22
+ | Use case | Library |
23
+ |----------|---------|
24
+ | Preserve formulas, formatting, named ranges | `openpyxl` |
25
+ | Bulk data transformation, aggregation, sorting | `pandas` → write back with `openpyxl` |
26
+ | Simple cell read/write | `openpyxl` |
27
+
28
+ **Warning**: `pandas.to_excel()` silently destroys existing formulas and named ranges.
29
+ When writing back to a spreadsheet that contains formulas, always use `openpyxl.save()`.
30
+
31
+ ---
32
+
33
+ ## solution.py Template
34
+
35
+ ```python
36
+ import openpyxl
37
+ import pandas as pd
38
+
39
+ INPUT_PATH = "..." # set to the actual input path
40
+ OUTPUT_PATH = "..." # set to the actual output path
41
+
42
+ wb = openpyxl.load_workbook(INPUT_PATH)
43
+ ws = wb.active # or wb["SheetName"]
44
+
45
+ # --- perform manipulation ---
46
+
47
+ wb.save(OUTPUT_PATH)
48
+ ```
49
+
50
+ ---
51
+
52
+ ## Output Requirements
53
+
54
+ - Save the result to `OUTPUT_PATH`.
55
+ - Do not hardcode row counts or column letters — iterate over actual rows in the workbook.
56
+ - Preserve sheets and cells not mentioned in the instruction.
@@ -0,0 +1,13 @@
1
+ """ReflACT Evaluation -- candidate skill validation and model selection.
2
+
3
+ Analogous to validation-based early stopping and model selection in neural
4
+ network training: evaluates candidate skills on held-out selection sets and
5
+ decides whether to accept or reject proposed updates.
6
+ """
7
+ from skillopt.evaluation.gate import ( # noqa: F401
8
+ GateAction,
9
+ GateMetric,
10
+ GateResult,
11
+ evaluate_gate,
12
+ select_gate_score,
13
+ )
@@ -0,0 +1,148 @@
1
+ """Validation gate — accept / reject candidate skills.
2
+
3
+ Analogous to validation-based early stopping and model selection in neural
4
+ network training: compares the candidate's score against the current and
5
+ best scores, then returns an accept/reject decision.
6
+
7
+ The trainer owns side-effects (cache lookup, rollout, printing, state
8
+ mutation). This module is the pure decision function.
9
+
10
+ Metric selection
11
+ ----------------
12
+ Three gate metrics are supported:
13
+
14
+ * ``"hard"`` (default, backward-compatible):
15
+ Compare candidate vs current/best using *hard* exact-match accuracy.
16
+ * ``"soft"``:
17
+ Compare using *soft* per-item score (F1 / partial credit / etc.).
18
+ Use this when a small held-out selection set has too few items for
19
+ hard accuracy to be sensitive to incremental skill improvements.
20
+ * ``"mixed"``:
21
+ Compare using a weighted average ``(1 - w) * hard + w * soft``.
22
+ ``w`` is configurable via ``mixed_weight`` (default ``0.5``).
23
+ """
24
+ from __future__ import annotations
25
+
26
+ from dataclasses import dataclass
27
+ from typing import Literal
28
+
29
+
30
+ GateAction = Literal["accept_new_best", "accept", "reject"]
31
+ GateMetric = Literal["hard", "soft", "mixed"]
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class GateResult:
36
+ """Immutable outcome of the validation gate."""
37
+
38
+ action: GateAction
39
+ current_skill: str
40
+ current_score: float
41
+ best_skill: str
42
+ best_score: float
43
+ best_step: int
44
+
45
+
46
+ def select_gate_score(
47
+ hard: float,
48
+ soft: float,
49
+ metric: GateMetric = "hard",
50
+ mixed_weight: float = 0.5,
51
+ ) -> float:
52
+ """Project (hard, soft) onto a single comparison metric.
53
+
54
+ Parameters
55
+ ----------
56
+ hard, soft
57
+ Aggregate hard / soft scores from a rollout batch (both 0..1).
58
+ metric
59
+ Which metric to compare on.
60
+ mixed_weight
61
+ For ``"mixed"``: weight given to ``soft``. Must be in ``[0, 1]``.
62
+ Ignored for ``"hard"`` / ``"soft"``.
63
+ """
64
+ if metric == "hard":
65
+ return float(hard)
66
+ if metric == "soft":
67
+ return float(soft)
68
+ if metric == "mixed":
69
+ w = max(0.0, min(1.0, float(mixed_weight)))
70
+ return (1.0 - w) * float(hard) + w * float(soft)
71
+ raise ValueError(
72
+ f"unknown gate metric {metric!r}; expected 'hard', 'soft', or 'mixed'"
73
+ )
74
+
75
+
76
+ def evaluate_gate(
77
+ candidate_skill: str,
78
+ cand_hard: float,
79
+ current_skill: str,
80
+ current_score: float,
81
+ best_skill: str,
82
+ best_score: float,
83
+ best_step: int,
84
+ global_step: int,
85
+ *,
86
+ cand_soft: float = 0.0,
87
+ metric: GateMetric = "hard",
88
+ mixed_weight: float = 0.5,
89
+ ) -> GateResult:
90
+ """Pure gate decision: compare candidate score to current/best.
91
+
92
+ Parameters
93
+ ----------
94
+ candidate_skill
95
+ The candidate skill content being evaluated.
96
+ cand_hard, cand_soft
97
+ Aggregate hard / soft scores of the candidate on the selection set.
98
+ current_skill, current_score
99
+ The currently-active skill and its *metric-space* score.
100
+ best_skill, best_score, best_step
101
+ The best-so-far skill, its *metric-space* score, and the step
102
+ at which it was accepted.
103
+ global_step
104
+ Current global training step (recorded if a new best is accepted).
105
+ cand_soft
106
+ Soft score of the candidate; only consulted when ``metric != "hard"``.
107
+ Defaults to ``0.0`` for backward compatibility with callers that
108
+ previously passed only ``cand_hard``.
109
+ metric
110
+ Which metric to compare on. Defaults to ``"hard"`` to preserve
111
+ the original gate behavior.
112
+ mixed_weight
113
+ Weight on ``soft`` when ``metric == "mixed"``.
114
+
115
+ Returns
116
+ -------
117
+ GateResult
118
+ Updated state; the caller decides what to do with it (print,
119
+ mutate trainer state, log, etc.).
120
+ """
121
+ cand_score = select_gate_score(cand_hard, cand_soft, metric, mixed_weight)
122
+
123
+ if cand_score > current_score:
124
+ if cand_score > best_score:
125
+ return GateResult(
126
+ action="accept_new_best",
127
+ current_skill=candidate_skill,
128
+ current_score=cand_score,
129
+ best_skill=candidate_skill,
130
+ best_score=cand_score,
131
+ best_step=global_step,
132
+ )
133
+ return GateResult(
134
+ action="accept",
135
+ current_skill=candidate_skill,
136
+ current_score=cand_score,
137
+ best_skill=best_skill,
138
+ best_score=best_score,
139
+ best_step=best_step,
140
+ )
141
+ return GateResult(
142
+ action="reject",
143
+ current_skill=current_skill,
144
+ current_score=current_score,
145
+ best_skill=best_skill,
146
+ best_score=best_score,
147
+ best_step=best_step,
148
+ )
@@ -0,0 +1,15 @@
1
+ """SkillOpt Gradient -- trajectory analysis and patch generation.
2
+
3
+ Analogous to gradient computation in neural network training: analyzes
4
+ minibatch rollout trajectories to produce skill-edit patches (the "gradient"
5
+ that drives skill updates).
6
+
7
+ Modules
8
+ -------
9
+ - reflect: minibatch trajectory analysis (gradient computation)
10
+ - aggregate: hierarchical patch merging (gradient aggregation)
11
+ """
12
+ from skillopt.gradient.reflect import ( # noqa: F401
13
+ run_minibatch_reflect,
14
+ )
15
+ from skillopt.gradient.aggregate import merge_patches # noqa: F401
@@ -0,0 +1,253 @@
1
+ """ReflACT Aggregate stage — hierarchical patch merging.
2
+
3
+ The Aggregate stage takes independently-generated patches from the Reflect
4
+ stage and merges them into a single coherent patch via hierarchical LLM calls.
5
+ Failure-driven patches take priority over success-driven ones.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ from concurrent.futures import ThreadPoolExecutor, as_completed
11
+
12
+ from skillopt.model import chat_optimizer
13
+ from skillopt.optimizer.meta_skill import format_meta_skill_context
14
+ from skillopt.optimizer.update_modes import (
15
+ get_payload_items,
16
+ is_full_rewrite_minibatch_mode,
17
+ is_rewrite_mode,
18
+ normalize_update_mode,
19
+ payload_key,
20
+ payload_label,
21
+ )
22
+ from skillopt.prompts import load_prompt
23
+ from skillopt.utils import extract_json
24
+
25
+
26
+ # ── Internal helpers ──────────────────────────────────────────────────────────
27
+
28
+ def _merge_batch(
29
+ skill_content: str,
30
+ patches: list[dict],
31
+ system_prompt: str,
32
+ update_mode: str,
33
+ meta_skill_context: str = "",
34
+ level: int = 1,
35
+ ) -> dict:
36
+ """Call optimizer LLM to merge a batch of patches into one."""
37
+ patches_text = json.dumps(patches, ensure_ascii=False, indent=2)
38
+ user = (
39
+ f"## Current Skill\n{skill_content}\n\n"
40
+ f"## Patches to merge ({len(patches)} total, merge level {level})\n{patches_text}"
41
+ )
42
+ optimizer_ctx = format_meta_skill_context(meta_skill_context)
43
+ if optimizer_ctx:
44
+ user = f"{optimizer_ctx}\n\n{user}"
45
+ try:
46
+ response, _ = chat_optimizer(
47
+ system=system_prompt,
48
+ user=user,
49
+ max_completion_tokens=64000 if is_full_rewrite_minibatch_mode(update_mode) else 16384,
50
+ retries=3,
51
+ stage="merge",
52
+ )
53
+ merged = extract_json(response)
54
+ key = payload_key(update_mode)
55
+ if merged and key in merged:
56
+ for e in merged.get(key, []):
57
+ e["merge_level"] = level
58
+ return merged
59
+ except Exception: # noqa: BLE001
60
+ pass
61
+ # Fallback: concatenate all edits
62
+ all_edits = []
63
+ for p in patches:
64
+ for e in get_payload_items(p, update_mode):
65
+ e.setdefault("merge_level", level)
66
+ all_edits.append(e)
67
+ return {"reasoning": "fallback concatenation", payload_key(update_mode): all_edits}
68
+
69
+
70
+ def _hierarchical_merge(
71
+ skill_content: str,
72
+ patches: list[dict],
73
+ system_prompt: str,
74
+ update_mode: str,
75
+ batch_size: int,
76
+ verbose: bool,
77
+ label: str = "",
78
+ workers: int = 16,
79
+ meta_skill_context: str = "",
80
+ ) -> dict:
81
+ """Hierarchically merge N patches using the given system prompt.
82
+
83
+ Same-level batches are executed in PARALLEL via ThreadPoolExecutor.
84
+ """
85
+ if not patches:
86
+ return {"reasoning": "no patches", payload_key(update_mode): []}
87
+ if len(patches) == 1:
88
+ return patches[0]
89
+
90
+ current = list(patches)
91
+ level = 0
92
+ while len(current) > 1:
93
+ level += 1
94
+ batches: list[tuple[int, list[dict]]] = []
95
+ for i in range(0, len(current), batch_size):
96
+ batch = current[i : i + batch_size]
97
+ batches.append((i, batch))
98
+
99
+ if verbose:
100
+ print(
101
+ f" [aggregate {label}] level={level} "
102
+ f"{len(current)} patches → {len(batches)} batches "
103
+ f"(parallel, batch_size={batch_size})"
104
+ )
105
+
106
+ next_level: list[dict | None] = [None] * len(batches)
107
+
108
+ to_merge: list[tuple[int, list[dict]]] = []
109
+ for idx, (i, batch) in enumerate(batches):
110
+ if len(batch) == 1:
111
+ next_level[idx] = batch[0]
112
+ else:
113
+ to_merge.append((idx, batch))
114
+
115
+ if to_merge:
116
+ with ThreadPoolExecutor(max_workers=workers) as ex:
117
+ futs = {
118
+ ex.submit(
119
+ _merge_batch, skill_content, batch, system_prompt, update_mode,
120
+ meta_skill_context, level,
121
+ ): idx
122
+ for idx, batch in to_merge
123
+ }
124
+ for fut in as_completed(futs):
125
+ idx = futs[fut]
126
+ next_level[idx] = fut.result()
127
+ if verbose:
128
+ batch_i, batch_data = batches[idx]
129
+ n_edits = len(get_payload_items(next_level[idx], update_mode))
130
+ print(
131
+ f" [aggregate {label}] level={level} "
132
+ f"batch [{batch_i}:{batch_i+len(batch_data)}] "
133
+ f"→ 1 patch ({n_edits} {payload_label(update_mode)})"
134
+ )
135
+
136
+ current = [x for x in next_level if x is not None]
137
+
138
+ return current[0]
139
+
140
+
141
+ # ── Public API ────────────────────────────────────────────────────────────────
142
+
143
+ def merge_patches(
144
+ skill_content: str,
145
+ failure_patches: list[dict],
146
+ success_patches: list[dict],
147
+ batch_size: int = 8,
148
+ verbose: bool = True,
149
+ workers: int = 16,
150
+ update_mode: str = "patch",
151
+ meta_skill_context: str = "",
152
+ ) -> dict:
153
+ """Failure-first hierarchical merge with support count tracking.
154
+
155
+ 1. Merge failure patches independently (parallel)
156
+ 2. Merge success patches independently (parallel)
157
+ 3. Final merge: combine both groups with failure priority
158
+
159
+ Returns a merged :class:`~skillopt.types.Patch` dict (``edits`` + ``reasoning``).
160
+ """
161
+ if verbose:
162
+ print(
163
+ f" [3/6 AGGREGATE] "
164
+ f"failure={len(failure_patches)} success={len(success_patches)} "
165
+ f"(parallel, workers={workers})"
166
+ )
167
+
168
+ update_mode = normalize_update_mode(update_mode)
169
+ if is_full_rewrite_minibatch_mode(update_mode):
170
+ merge_failure_prompt = load_prompt("merge_failure_full_rewrite")
171
+ merge_success_prompt = load_prompt("merge_success_full_rewrite")
172
+ merge_final_prompt = load_prompt("merge_final_full_rewrite")
173
+ elif is_rewrite_mode(update_mode):
174
+ merge_failure_prompt = load_prompt("merge_failure_rewrite")
175
+ merge_success_prompt = load_prompt("merge_success_rewrite")
176
+ merge_final_prompt = load_prompt("merge_final_rewrite")
177
+ else:
178
+ merge_failure_prompt = load_prompt("merge_failure")
179
+ merge_success_prompt = load_prompt("merge_success")
180
+ merge_final_prompt = load_prompt("merge_final")
181
+
182
+ failure_merged = _hierarchical_merge(
183
+ skill_content, failure_patches, merge_failure_prompt, update_mode,
184
+ batch_size, verbose, label="failure", workers=workers,
185
+ meta_skill_context=meta_skill_context,
186
+ )
187
+
188
+ success_merged = _hierarchical_merge(
189
+ skill_content, success_patches, merge_success_prompt, update_mode,
190
+ batch_size, verbose, label="success", workers=workers,
191
+ meta_skill_context=meta_skill_context,
192
+ )
193
+
194
+ f_edits = get_payload_items(failure_merged, update_mode)
195
+ s_edits = get_payload_items(success_merged, update_mode)
196
+
197
+ if not f_edits and not s_edits:
198
+ return {"reasoning": "no updates from either group", payload_key(update_mode): []}
199
+ if not s_edits:
200
+ return failure_merged
201
+ if not f_edits:
202
+ return success_merged
203
+
204
+ combined_patches = [failure_merged, success_merged]
205
+ combined_text = json.dumps(combined_patches, ensure_ascii=False, indent=2)
206
+ if is_full_rewrite_minibatch_mode(update_mode):
207
+ item_label = payload_label(update_mode)
208
+ user = (
209
+ f"## Current Skill\n{skill_content}\n\n"
210
+ f"## Two pre-merged candidate groups to combine\n"
211
+ f"Group 1 (from failed trajectories): "
212
+ f"{len(f_edits)} {item_label}\n"
213
+ f"Group 2 (from successful trajectories): "
214
+ f"{len(s_edits)} {item_label}\n\n"
215
+ f"{combined_text}"
216
+ )
217
+ else:
218
+ user = (
219
+ f"## Current Skill\n{skill_content}\n\n"
220
+ f"## Two pre-merged patch groups to combine\n"
221
+ f"Group 1 (failure-driven, HIGH priority): "
222
+ f"{len(f_edits)} edits\n"
223
+ f"Group 2 (success-driven, lower priority): "
224
+ f"{len(s_edits)} edits\n\n"
225
+ f"{combined_text}"
226
+ )
227
+ optimizer_ctx = format_meta_skill_context(meta_skill_context)
228
+ if optimizer_ctx:
229
+ user = f"{optimizer_ctx}\n\n{user}"
230
+ try:
231
+ response, _ = chat_optimizer(
232
+ system=merge_final_prompt,
233
+ user=user,
234
+ max_completion_tokens=64000 if is_full_rewrite_minibatch_mode(update_mode) else 16384,
235
+ retries=3,
236
+ stage="merge",
237
+ )
238
+ final = extract_json(response)
239
+ key = payload_key(update_mode)
240
+ if final and key in final:
241
+ if verbose:
242
+ print(
243
+ f" [aggregate final] "
244
+ f"{len(f_edits)}+{len(s_edits)} → {len(final[key])} {payload_label(update_mode)}"
245
+ )
246
+ return final
247
+ except Exception: # noqa: BLE001
248
+ pass
249
+
250
+ return {
251
+ "reasoning": "fallback: failure first, then success",
252
+ payload_key(update_mode): f_edits + s_edits,
253
+ }