Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 46 additions & 31 deletions skillopt_sleep/cycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,41 @@ def _report_score(value: object) -> str:
return f"{score:.3f}" if math.isfinite(score) else "—"


def _render_gate_trials_md(trials: List[dict], *, heading_level: int = 3) -> List[str]:
"""Render held-out comparisons without changing their owning report."""
lines: List[str] = []
for trial in trials:
target = _markdown_table_text(trial.get("target", "candidate"))
accepted = bool(trial.get("accepted", False))
blocked = bool(trial.get("blocked_by_regression", False))
decision = "accepted" if accepted else "rejected"
if blocked:
decision += " (task regression)"
baseline = _report_score(trial.get("baseline_score"))
candidate = _report_score(trial.get("candidate_score"))
lines.append(
f"{'#' * heading_level} `{target}` candidate — {decision} "
f"({baseline} → {candidate})"
)
lines.append("")
lines.append("| Task | Tags | Baseline | Candidate | Change |")
lines.append("|---|---|---:|---:|---|")
for delta in trial.get("task_deltas", []):
task_id = _markdown_table_text(delta.get("task_id", ""))
tags = _markdown_table_text(
", ".join(str(tag) for tag in delta.get("tags", [])) or "—"
)
baseline_score = _report_score(delta.get("baseline_score"))
candidate_score = _report_score(delta.get("candidate_score"))
status = _markdown_table_text(delta.get("status", "unchanged"))
lines.append(
f"| `{task_id}` | {tags} | {baseline_score} "
f"| {candidate_score} | {status} |"
)
lines.append("")
return lines


def _render_report_md(report: SleepReport, cfg: SleepConfig) -> str:
project = _markdown_text(report.project)
backend = _markdown_text(cfg.get("backend"))
Expand Down Expand Up @@ -370,35 +405,7 @@ def _render_report_md(report: SleepReport, cfg: SleepConfig) -> str:
"they do not establish that a particular edit caused the change._"
)
lines.append("")
for trial in report.gate_trials:
target = _markdown_table_text(trial.get("target", "candidate"))
accepted = bool(trial.get("accepted", False))
blocked = bool(trial.get("blocked_by_regression", False))
decision = "accepted" if accepted else "rejected"
if blocked:
decision += " (task regression)"
baseline = _report_score(trial.get("baseline_score"))
candidate = _report_score(trial.get("candidate_score"))
lines.append(
f"### `{target}` candidate — {decision} "
f"({baseline} → {candidate})"
)
lines.append("")
lines.append("| Task | Tags | Baseline | Candidate | Change |")
lines.append("|---|---|---:|---:|---|")
for delta in trial.get("task_deltas", []):
task_id = _markdown_table_text(delta.get("task_id", ""))
tags = _markdown_table_text(
", ".join(str(tag) for tag in delta.get("tags", [])) or "—"
)
baseline_score = _report_score(delta.get("baseline_score"))
candidate_score = _report_score(delta.get("candidate_score"))
status = _markdown_table_text(delta.get("status", "unchanged"))
lines.append(
f"| `{task_id}` | {tags} | {baseline_score} "
f"| {candidate_score} | {status} |"
)
lines.append("")
lines.extend(_render_gate_trials_md(report.gate_trials))
if report.edits:
lines.append("## Accepted edits")
for e in report.edits:
Expand Down Expand Up @@ -478,6 +485,12 @@ def _render_report_md(report: SleepReport, cfg: SleepConfig) -> str:
f"| `{name}` | **{decision}**{reason} | {group_gate} "
f"| {g.n_tasks} | {scores} | {edits} |")
lines.append("")
for group in report.skill_groups:
if group.gate_trials:
name = _markdown_table_text(group.skill_name or "_(no skill name)_")
lines.append(f"### `{name}` held-out task changes")
lines.append("")
lines.extend(_render_gate_trials_md(group.gate_trials, heading_level=4))
if report.notes:
lines.append("## Notes")
for n in report.notes:
Expand Down Expand Up @@ -1064,13 +1077,14 @@ def budget_exhausted() -> bool:
if outcome is not None:
group_outcomes[name] = outcome
group_rows = skill_group_reports(group_outcomes)
# Skill hints and isolated backend failures are both untrusted
# free text. Scrub them before either report.json or report.md is
# Skill hints, backend failures, and trial task text are untrusted.
# Scrub them before either report.json or report.md is
# rendered so staging cannot turn a failed call into a credential
# leak.
for row in group_rows:
row.skill_name = str(redact_secrets(row.skill_name))
row.reason = str(redact_secrets(row.reason))
row.gate_trials = redact_secrets(row.gate_trials)
report.skill_groups = group_rows

report.tokens_used = backend.tokens_used()
Expand Down Expand Up @@ -1178,6 +1192,7 @@ def budget_exhausted() -> bool:
getattr(result, "holdout_detail", [])
),
"gate_trials": report.gate_trials,
"skill_groups": [row.to_dict() for row in report.skill_groups],
}),
_fh,
indent=2,
Expand Down
1 change: 1 addition & 0 deletions skillopt_sleep/multi_skill.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ def skill_group_reports(
row.candidate_score = result.candidate_score
row.n_applied_edits = len(result.applied_edits)
row.n_rejected_edits = len(result.rejected_edits)
row.gate_trials = list(result.gate_trials)
rows.append(row)
return rows

Expand Down
2 changes: 2 additions & 0 deletions skillopt_sleep/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ class SkillGroupReport:
n_applied_edits: int = 0
n_rejected_edits: int = 0
reason: str = ""
# Keep optional fields last for callers using positional construction.
gate_trials: List[Dict[str, Any]] = field(default_factory=list)

def to_dict(self) -> Dict[str, Any]:
return asdict(self)
Expand Down
Loading