feat(optimizer): skill-aware reflection (EmbodiSkill S_app), config-controlled and env-independent

Split failure reflections into SKILL_DEFECT (body edit) vs EXECUTION_LAPSE
(protected appendix note that re-emphasizes an existing rule, never edited
by step-level analysts). Toggle: optimizer.use_skill_aware_reflection
(default false; baseline byte-identical when off).

- optimizer/appendix.py: protected APPENDIX region (inject/extract/append
  with dedup), mirrors the slow_update protected-field pattern
- optimizer/skill_aware.py: analyst prompt augmentation, appendix_notes
  parsing, threshold-gated LLM consolidation, and a process-wide runtime
  switch (configure_skill_aware_reflection) set once by the trainer
- gradient/reflect.py: augment error/success analyst prompts at runtime;
  None-sentinel kwargs resolve from the global switch, so env adapters
  need no per-benchmark wiring (works for all envs, present and future)
- optimizer/skill.py: generalize the protected-region check to
  (slow_update, appendix); edits inside any protected region are skipped
- engine/trainer.py: inject appendix at init, flush per-step
  EXECUTION_LAPSE notes after the gate settles, optional consolidation
- tests: regression suite incl. toggle-off byte-identical guarantee and
  env-independent global-switch resolution (6/6 passing + live smoke)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cuzyoung
2026-06-10 11:28:29 +00:00
parent ffe581098b
commit 0dc84162dc
9 changed files with 840 additions and 21 deletions
+57 -20
View File
@@ -14,25 +14,62 @@ if TYPE_CHECKING:
SLOW_UPDATE_START = "<!-- SLOW_UPDATE_START -->"
SLOW_UPDATE_END = "<!-- SLOW_UPDATE_END -->"
# Skill-aware reflection (EmbodiSkill S_app) appendix region. Like the slow
# update region, it is protected: step-level analyst edits must not modify it.
APPENDIX_START = "<!-- APPENDIX_START -->"
APPENDIX_END = "<!-- APPENDIX_END -->"
def _is_in_slow_update_region(skill: str, target: str) -> bool:
"""Check if *target* text falls within the protected slow update region."""
start_idx = skill.find(SLOW_UPDATE_START)
end_idx = skill.find(SLOW_UPDATE_END)
if start_idx == -1 or end_idx == -1:
# All protected (start, end) marker pairs. Step-level edits cannot target text
# inside any of these regions, and `append` / `insert_after`-fallback ops are
# inserted before the earliest-occurring region so protected blocks stay at the
# document tail. With only the slow-update region present, every helper reduces
# to the original slow-update-only behavior (byte-identical skill output).
_PROTECTED_REGIONS: tuple[tuple[str, str], ...] = (
(SLOW_UPDATE_START, SLOW_UPDATE_END),
(APPENDIX_START, APPENDIX_END),
)
def _earliest_protected_start(skill: str) -> int:
"""Index of the earliest protected-region start marker, or -1 if none."""
positions = [
idx
for idx in (skill.find(start) for start, _ in _PROTECTED_REGIONS)
if idx != -1
]
return min(positions) if positions else -1
def _is_in_protected_region(skill: str, target: str) -> bool:
"""Check if *target* text falls within any protected region."""
if not target:
return False
target_idx = skill.find(target)
if target_idx == -1:
return False
region_end = end_idx + len(SLOW_UPDATE_END)
return start_idx <= target_idx < region_end
for start_marker, end_marker in _PROTECTED_REGIONS:
start_idx = skill.find(start_marker)
end_idx = skill.find(end_marker)
if start_idx == -1 or end_idx == -1:
continue
region_end = end_idx + len(end_marker)
if start_idx <= target_idx < region_end:
return True
return False
def _is_in_slow_update_region(skill: str, target: str) -> bool:
"""Backward-compatible alias kept for any external callers/tests."""
return _is_in_protected_region(skill, target)
def _strip_slow_update_markers(text: str) -> str:
"""Remove any SLOW_UPDATE markers from edit content to prevent duplication."""
"""Remove any protected-region markers from edit content to prevent duplication."""
return (
text.replace(SLOW_UPDATE_START, "")
.replace(SLOW_UPDATE_END, "")
.replace(APPENDIX_START, "")
.replace(APPENDIX_END, "")
)
@@ -54,27 +91,27 @@ def _apply_edit_with_report(skill: str, edit: EditType | dict) -> tuple[str, dic
"status": "unknown",
}
if target and _is_in_slow_update_region(skill, target):
report["status"] = "skipped_protected_slow_update_region"
if target and _is_in_protected_region(skill, target):
report["status"] = "skipped_protected_region"
return skill, report
if op == "append":
su_start = skill.find(SLOW_UPDATE_START)
if su_start != -1:
before = skill[:su_start].rstrip()
after = skill[su_start:]
report["status"] = "applied_append_before_slow_update"
prot_start = _earliest_protected_start(skill)
if prot_start != -1:
before = skill[:prot_start].rstrip()
after = skill[prot_start:]
report["status"] = "applied_append_before_protected_region"
return before + "\n\n" + content + "\n\n" + after, report
report["status"] = "applied_append"
return skill.rstrip() + "\n\n" + content + "\n", report
if op == "insert_after":
if not target or target not in skill:
su_start = skill.find(SLOW_UPDATE_START)
if su_start != -1:
before = skill[:su_start].rstrip()
after = skill[su_start:]
report["status"] = "applied_insert_after_fallback_before_slow_update"
prot_start = _earliest_protected_start(skill)
if prot_start != -1:
before = skill[:prot_start].rstrip()
after = skill[prot_start:]
report["status"] = "applied_insert_after_fallback_before_protected_region"
return before + "\n\n" + content + "\n\n" + after, report
report["status"] = "applied_insert_after_fallback_append"
return skill.rstrip() + "\n\n" + content + "\n", report